-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
25 changed files
with
1,421 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// | ||
// BGUploader.h | ||
// BGUploader | ||
// | ||
// Created by Ruben Nine on 20/10/21. | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
//! Project version number for BGUploader. | ||
FOUNDATION_EXPORT double BGUploaderVersionNumber; | ||
|
||
//! Project version string for BGUploader. | ||
FOUNDATION_EXPORT const unsigned char BGUploaderVersionString[]; | ||
|
||
// In this header, you should import all the public headers of your framework using statements like #import <BGUploader/PublicHeader.h> | ||
|
||
|
13 changes: 13 additions & 0 deletions
13
Examples/BGUploaderDemo/BGUploader/Sources/Internal/Extension/UserDefaults+Settings.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// | ||
// UserDefaults+Settings.swift | ||
// BGUploader | ||
// | ||
// Created by Ruben Nine on 20/10/21. | ||
// | ||
|
||
import Foundation | ||
|
||
extension UserDefaults { | ||
@UserDefault(key: "backgroundUploadProcess", defaultValue: BackgroundUploadProcess()) | ||
static var backgroundUploadProcess: BackgroundUploadProcess | ||
} |
42 changes: 42 additions & 0 deletions
42
Examples/BGUploaderDemo/BGUploader/Sources/Internal/Property Wrappers/UserDefault.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// | ||
// UserDefault.swift | ||
// Doorkeeper | ||
// | ||
// Created by Ruben Nine on 6/10/21. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Allows to match for optionals with generics that are defined as non-optional. | ||
protocol AnyOptional { | ||
/// Returns `true` if `nil`, otherwise `false`. | ||
var isNil: Bool { get } | ||
} | ||
|
||
extension Optional: AnyOptional { | ||
public var isNil: Bool { self == nil } | ||
} | ||
|
||
@propertyWrapper | ||
struct UserDefault<Value: Codable> { | ||
let key: String | ||
let defaultValue: Value | ||
var container: UserDefaults = .standard | ||
|
||
var wrappedValue: Value { | ||
get { | ||
if let data = container.object(forKey: key) as? Data, | ||
let user = try? JSONDecoder().decode(Value.self, from: data) { | ||
return user | ||
} | ||
|
||
return defaultValue | ||
} | ||
|
||
set { | ||
if let encoded = try? JSONEncoder().encode(newValue) { | ||
container.set(encoded, forKey: key) | ||
} | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
Examples/BGUploaderDemo/BGUploader/Sources/Public/Models/BackgroundUploadProcess.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// | ||
// BackgroundUploadProcess.swift | ||
// BGUploader | ||
// | ||
// Created by Ruben Nine on 20/10/21. | ||
// | ||
|
||
import Foundation | ||
|
||
public class BackgroundUploadProcess: Codable { | ||
/// Contains the upload tasks currently in progress. | ||
public var tasks: [Int: BackgroundUploadTaskResult] = [:] | ||
} |
31 changes: 31 additions & 0 deletions
31
Examples/BGUploaderDemo/BGUploader/Sources/Public/Models/BackgroundUploadTaskResult.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// | ||
// BackgroundUploadTaskResult.swift | ||
// BGUploader | ||
// | ||
// Created by Ruben Nine on 20/10/21. | ||
// | ||
|
||
import Foundation | ||
|
||
public class BackgroundUploadTaskResult: Codable { | ||
/// The `URL` that is to be uploaded. | ||
public let url: URL | ||
|
||
/// The current status for this task. | ||
public internal(set) var status: Status = .started | ||
|
||
/// Default initializer. | ||
/// | ||
/// - Parameter url: The `URL` that is going to be uploaded. | ||
init(url: URL) { | ||
self.url = url | ||
} | ||
} | ||
|
||
extension BackgroundUploadTaskResult { | ||
public enum Status: Equatable, Codable { | ||
case started | ||
case completed(response: StoreResponse) | ||
case failed(error: BGUploadService.Error) | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
Examples/BGUploaderDemo/BGUploader/Sources/Public/Models/StoreResponse.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// | ||
// StoreResponse.swift | ||
// BGUploader | ||
// | ||
// Created by Ruben Nine on 20/10/21. | ||
// | ||
|
||
import Foundation | ||
|
||
/// `StoreResponse` represents the expected JSON object response when doing a POST against /api/store/S3. | ||
public struct StoreResponse: Codable, Equatable { | ||
/// Filestack Handle (derived from `url`) | ||
public var handle: String { url.lastPathComponent } | ||
|
||
/// Filestack Handle URL. | ||
public let url: URL | ||
|
||
/// S3 container. | ||
public let container: String | ||
|
||
/// Filename (e.g. "pic1.jpg") | ||
public let filename: String | ||
|
||
/// Key used in S3 storage. | ||
public let key: String | ||
|
||
/// Mimetype (e.g. "image/jpeg") | ||
public let type: String | ||
|
||
/// Filesize (e.g. 5520262) | ||
public let size: Int | ||
} |
161 changes: 161 additions & 0 deletions
161
Examples/BGUploaderDemo/BGUploader/Sources/Public/Services/BGUploadService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// | ||
// BGUploadService.swift | ||
// BGUploader | ||
// | ||
// Created by Ruben Nine on 20/10/21. | ||
// | ||
|
||
import Foundation | ||
import FilestackSDK | ||
|
||
public protocol BGUploadServiceDelegate: AnyObject { | ||
/// Called after an upload task completes (either successfully or failing.) | ||
/// | ||
/// You may query `url` to determine the file `URL` that was uploaded and `status` to determine completion status | ||
/// on the returned `BackgroundUploadTaskResult` object. | ||
func uploadService(_ uploadService: BGUploadService, didCompleteWith result: BackgroundUploadTaskResult) | ||
} | ||
|
||
public class BGUploadService: NSObject { | ||
// MARK: - Public Properties | ||
|
||
public let backgroundIdentifer = "com.filestack.BGUploader" | ||
public weak var delegate: BGUploadServiceDelegate? | ||
|
||
// MARK: - Private Properties | ||
|
||
private let storeURL = URL(string: "https://www.filestackapi.com/api/store/S3")! | ||
private var transitorySessionData = [URLSessionTask: Data]() | ||
private let fsClient: Client | ||
|
||
private lazy var session: URLSession = { | ||
let configuration: URLSessionConfiguration | ||
|
||
configuration = .background(withIdentifier: backgroundIdentifer) | ||
configuration.isDiscretionary = false | ||
configuration.waitsForConnectivity = true | ||
|
||
return URLSession(configuration: configuration, delegate: self, delegateQueue: .main) | ||
}() | ||
|
||
// MARK: - Lifecycle | ||
|
||
public init(fsClient: Client) { | ||
self.fsClient = fsClient | ||
} | ||
} | ||
|
||
// MARK: - URLSessionDataDelegate Protocol Implementation | ||
|
||
extension BGUploadService: URLSessionDataDelegate { | ||
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { | ||
transitorySessionData[dataTask] = data | ||
} | ||
} | ||
|
||
// MARK: - URLSessionTaskDelegate Protocol Implementation | ||
|
||
extension BGUploadService: URLSessionTaskDelegate { | ||
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Swift.Error?) { | ||
guard let result = UserDefaults.backgroundUploadProcess.tasks[task.taskIdentifier] else { return } | ||
|
||
if task.state == .completed, let responseData = transitorySessionData[task] { | ||
transitorySessionData.removeValue(forKey: task) | ||
|
||
do { | ||
let storeResponse = try JSONDecoder().decode(StoreResponse.self, from: responseData) | ||
result.status = .completed(response: storeResponse) | ||
} catch { | ||
result.status = .failed(error: .undecodableJSONResponse) | ||
} | ||
} else if let error = error { | ||
result.status = .failed(error: .other(description: error.localizedDescription)) | ||
} else { | ||
result.status = .failed(error: .unknown) | ||
} | ||
|
||
delegate?.uploadService(self, didCompleteWith: result) | ||
removeTaskResult(with: task.taskIdentifier) | ||
} | ||
} | ||
|
||
// MARK: - BGUploadService Error | ||
|
||
public extension BGUploadService { | ||
enum Error: Swift.Error, Equatable, Codable { | ||
case undecodableJSONResponse | ||
case other(description: String) | ||
case unknown | ||
} | ||
} | ||
|
||
// MARK: - Public Functions | ||
|
||
public extension BGUploadService { | ||
/// Uploads an `URL` to Filestack using a background `URLSession`. | ||
@discardableResult | ||
func upload(url: URL) -> URLSessionUploadTask? { | ||
let task = session.uploadTask(with: storeRequest(for: url), fromFile: url) | ||
|
||
addTaskResult(with: task.taskIdentifier, for: url) | ||
|
||
task.resume() | ||
|
||
return task | ||
} | ||
|
||
/// Resumes any pending background uploads. | ||
/// | ||
/// Call this function on your `AppDelegate.application(_:,handleEventsForBackgroundURLSession:,completionHandler:)` | ||
func resumePendingUploads(completionHandler: @escaping () -> Void) { | ||
session.getAllTasks { tasks in | ||
for task in tasks { | ||
task.resume() | ||
} | ||
|
||
completionHandler() | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Private Functions | ||
|
||
private extension BGUploadService { | ||
/// Returns an `URLRequest` setup for uploading a file using | ||
/// Filestack's [Basic Uploads](https://www.filestack.com/docs/uploads/uploading/#basic-uploads) API. | ||
func storeRequest(for url: URL) -> URLRequest { | ||
var components = URLComponents(url: storeURL, resolvingAgainstBaseURL: false)! | ||
var queryItems: [URLQueryItem] = [] | ||
|
||
queryItems = [ | ||
URLQueryItem(name: "key", value: fsClient.apiKey), | ||
URLQueryItem(name: "filename", value: url.filename) | ||
] | ||
|
||
if let security = fsClient.security { | ||
queryItems.append(URLQueryItem(name: "policy", value: security.encodedPolicy)) | ||
queryItems.append(URLQueryItem(name: "signature", value: security.signature)) | ||
} | ||
|
||
components.queryItems = queryItems | ||
|
||
var request = URLRequest(url: components.url!) | ||
|
||
request.addValue(url.mimeType ?? "text/plain", forHTTPHeaderField: "Content-Type") | ||
request.httpMethod = "POST" | ||
|
||
return request | ||
} | ||
|
||
@discardableResult | ||
func addTaskResult(with taskIdentifier: Int, for url: URL) -> BackgroundUploadTaskResult { | ||
let taskResult = BackgroundUploadTaskResult(url: url) | ||
UserDefaults.backgroundUploadProcess.tasks[taskIdentifier] = taskResult | ||
return taskResult | ||
} | ||
|
||
@discardableResult | ||
private func removeTaskResult(with taskIdentifier: Int) -> BackgroundUploadTaskResult? { | ||
return UserDefaults.backgroundUploadProcess.tasks.removeValue(forKey: taskIdentifier) | ||
} | ||
} |
Oops, something went wrong.