GithubHelp home page GithubHelp logo

mredigital / textfetcher Goto Github PK

View Code? Open in Web Editor NEW
1.0 2.0 1.0 220 KB

TextFetcher is a library for retrieving, caching, and returning versionable bundled or remote text files.

License: MIT License

Ruby 0.46% Swift 99.54%
cocoapods swift swift5 ios macos watchos appletv text versioned

textfetcher's Introduction

TextFetcher

language Version CI Status Coverage Status Platform License Twitter

Hoping to spend all your time focusing on the intended purpose of that app you're building, and not how to manage licenses, policies, or other various texts? Well boy are you in luck! :)

TextFetcher is a simple library for fetching, caching, and retrieving versionable bundled or remote text files with ease!

Description

TextFetcher maintains a file cache in your apps's documents directory which it populates with text files and version json, both local and remote. Which text files are pulled and cached depend on the versions retrieved from an optional VersionSource. The latest versions of text are always prefered, but older versions are considered a reasonable fallback.

Installation

TextFetcher is available through CocoaPods. To install, simply add the following line to your Podfile:

pod "TextFetcher"

And run:

$ pod install

Usage

Initialization

TextFetcher's primary class provides a single static default instance which should accomodate nearly every use case. However, it maintains a distinct local cache per Session ID. So in the case where multiple distinct and separate caches are necessary, a public initializer is provided as well.

Interface(s):

public static let `default`: TextFetcher = .init()
public init(withSessionID sessionID: String)

Example(s):

TextFetcher.default.doSomething(...)

// Or in the case that multiple distinct and separate caches are necessary, you might do this instead:

let textFetcherA = TextFetcher(withSessionID: "SomeIdentifierA")
textFetcherA.doSomething(...)

let textFetcherB = TextFetcher(withSessionID: "SomeIdentifierB")
textFetcherB.doSomething(...)

Configuration

TextFetcher provides two public configuration calls. One for Resource Bundle, and one for Resource Timeout. The Resource Bundle setting specifies an App Bundle in which to search for any BundleFiles resulting from a VersionSource or a TextSource. (see: Versioning/Text Registration) The Resource Timeout setting specifies the amount of time before a text request which is awaiting a remote fetch will be allowed before failing and falling back to a more local version. (see: Text Retrieval)

Interface(s):

public func setResourceBundle(to bundle: Bundle) 
public func setResourceTimeout(to timeout: TimeInterval)

Example(s):

TextFetcher.default.setResourceBundle(to: Bundle(for: MyViewController.self))
TextFetcher.default.setResourceTimeout(to: 1)

Versioning

To enable versioning in TextFetcher, you first create a VersionSource. To create a VersionSource you must provide a RemoteFile specification, and optionally, a BundleFile specification.

Interface(s):

public struct VersionSource {
    public init(bundleFile: BundleFile? = nil, remoteFile: RemoteFile)
}
public struct BundleFile {   
    public init(fileName: String, fileExtension: String)
}
public struct RemoteFile {
    public init(urlString: String)
}

Example(s):

let bundleFile: BundleFile = .init(fileName: "Versions", fileExtension: "json")
let remoteFile: RemoteFile = .init(urlString: "http://www.domain.tld/path/file.extension")
let versionSource: VersionSource = .init(bundleFile: bundleFile, remoteFile: remoteFile))

Once created, you set it into the TextFetcher instance usig the following method:

Interface(s):

public func setVersionSource(to versionSource: VersionSource, withCompletion completion: (()->Void)? = nil)

Example(s):

TextFetcher.default.setVersionSource(to: versionSource)

Once set, the library will attempt to retrieve and decode each corresponding file. The resulting pairings are then used to determine the versions of any registered TextSources by using their identifiers as a key to retrieve their corresponding versions. The json format for a Version file might thereby look something like this:

{
    "Text1": "1.2.3",
    "Text2": "123.456.789",
    "Text3": "0.0.1",
}

Where "Text1", "Text2", and "Text3" would correspond with the identifiers of three different registered TextSources.

Text Registration

For each text you would like fetched, cached, and at some point, retrieved, you start by creating a TextSource for each. To create a TextSource you must provide a BundleFile specification and/or a RemoteFile specification, as well as a String identifier which can be used for later retrieval, and associated with a Version as its key value (see: Versioning).

Interface(s):

public struct TextSource {
    public init(identifier: String, bundleFile: BundleFile?, remoteFile: RemoteFile?)
}
public struct BundleFile {   
    public init(fileName: String, fileExtension: String)
}
public struct RemoteFile {
    public init(urlString: String)
}

Example(s):

let eulaBundleFile: BundleFile = .init(fileName: "EULA", fileExtension: "txt")
let eulaRemoteFile: RemoteFile = .init(urlString: "http://www.domain.tld/path/file.extension")
let eulaTextSource: TextSource = .init(identifier: "eula", bundleFile: eulaBundleFile, remoteFile: eulaRemoteFile)

Once created, you register the TextSource with TextFetcher using one of the two following methods:

Interface(s):

public func registerTextSource(_ textSource: TextSource)

// convenience accessor(s):
public func registerTextSource(fromProvider textSourceProvider: TextSourceProvider)

Example(s):

TextFetcher.default.registerTextSource(eulaTextSource)

Once registered, the library will attempt to retrieve and cache the corresponding text, starting with the latest version. Should that fail, it will fall back to the next version. If there are more than one latest version, it will always prefer the most local version. If no versions exist for the source, it will always load the most local file specification, falling back as needed.

Text Retrieval

To retrieve text from TextFetcher, assuming a TextSource has been registered with valid File specifications, you can simply call one of the following functions, which will immediately return whichever value had been cached. There may be times when a text has not finished being fetched, however, and you are willing to await its retrieval. In such cases setting awaitRemoteFetchIfNeeded to true will wait for that process to finish (or attempt it again if needed), before returning the resulting value. If the latest version has already been cached, however, setting it to true will have no effect.

Interface(s):

public func text(for resourceID: String, awaitRemoteFetchIfNeeded: Bool = false, completion: @escaping TextRequestCompletion)

// convenience accessor(s):
public func text(for textSource: TextSource, awaitRemoteFetchIfNeeded: Bool = false, completion: @escaping TextRequestCompletion)
public func text(for textSourceProvider: TextSourceProvider, awaitRemoteFetchIfNeeded: Bool = false, completion: @escaping TextRequestCompletion)

// typealias:
public typealias TextRequestCompletion = (_ text: String?, _ version: Version?) -> Void

Example(s):

TextFetcher.default.text(for: "Text1", awaitRemoteFetchIfNeeded: true) { text, version in
    print("Text retrieved: \(text ?? "None"), with Version: \(version?.stringValue ?? "No Version specified")")
}

Notifications

There may be times when it is useful to be informed when a TextSource's version has been increased. For example, if your end user license agreement has incremented, you could block the UI until it is accepted. To receive such notifications, you first need to conform to the TextFetcherNotificationReceiver protocol:

Interface(s)

public protocol TextFetcherNotificationReceiver: class {
    func versionIncreased(to version: Version, for textSource: TextSource)
}

Example(s)

class MyViewController: UIViewController, TextFetcherNotificationReceiver {
    ...
  
    func versionIncreased(to version: Version, for textSource: TextSource) {
        if textSource.identifier == "eula" {
            setNeedsToAcceptEULA(to: true)
            presentEula()
        }
    }
}

Next, you manage the class's notification registration with TextFetcher via the following method:

Interface(s)

public func addNotificationReceiver(_ notificationReceiver: TextFetcherNotificationReceiver)
public func removeNotificationReceiver(_ notificationReceiver: TextFetcherNotificationReceiver)

Example(s)

TextFetcher.default.addNotificationReceiver(self)

Once this is done, any version increases will trigger the versionIncreased(to:for:) call in your registered class. Note that receiver references are weakly retained, so the removeNotificationReceiver is really only offered for special cases where a class may simply wish to stop listening for such things.

Conveniences

You may notice various alternate methods throughout which receive a TextSourceProvider where a TextSource would usually be required. TextSourceProvider is a public protocol whose only requirement is that an object must contain a TextSource getter.

Interface(s)

public protocol TextSourceProvider {
    var textSource: TextSource { get }
}

The reason this has been included is because I wanted it to be easy to manage TextSources via Enum values (or some other structured approach), such as:

Example(s)

enum TextType: String, TextSourceProvider, CaseIterable {
    
    case text1 = "Text1"
    case text2 = "Text2"
    
    static var versionSource: VersionSource { ... }    
    var textSource: TextSource { ... }
}

With behavior such as:

Example(s)

// Registration
TextType.allCases.forEach {
    TextFetcher.default.registerTextSource(fromProvider: $0)
}

// Retrieval
TextFetcher.default.text(for: TextType.text1) { text, version in
    print("Text retrieved: \(text ?? "None"), with Version: \(version?.stringValue ?? "No Version specified")")
}

Potential Improvements

High Priority

Low Priority

  • Re-fetch remote Versions if they did not initially return. (Currently requires a re-set of the VersionSource)
  • Versions do not currently support values which begin with any number of zeros. (Eg: “1.0.001” would collapse into “1.0.1”)

License

This project is available under The MIT License.
Copyright © 2020, Eric Reedy. See LICENSE file.

textfetcher's People

Contributors

bainfu avatar mredigital avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Forkers

bainfu

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.