-
-
Save Idomo/ece6683348372b54e470fd630b1fb76e to your computer and use it in GitHub Desktop.
Files manager that allow sending background POST requests and retry if the user has been force-quitting the app
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
// | |
// FilesManager.swift | |
// | |
// Created by Ido Monzon on 26.6.2018. | |
// Copyright © 2018 Ido Monzon. All rights reserved. | |
// | |
import UIKit | |
class FilesManager: NSObject { | |
static let uploadUrl = URL(string: "https://...")! | |
static func fullPath(for path: String?) -> String { | |
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] | |
return "\(documentsPath)/\(path ?? "fileName.jpg.multipart")" | |
} | |
static let multipartFileNameSuffix = ".multipart" | |
static let urlSessionIdentifierPrefix = "\(Bundle.main.bundleIdentifier ?? "com.app.bundle").uploadfile." | |
static func urlSessionConfiguration(for id: String) -> URLSessionConfiguration { | |
let id = id.ltrim(text: FilesManager.urlSessionIdentifierPrefix) // Trim the identifier prefix if `id` containing it | |
let configuration = URLSessionConfiguration.background(withIdentifier: FilesManager.urlSessionIdentifierPrefix + id) | |
configuration.sharedContainerIdentifier = FilesManager.urlSessionIdentifierPrefix | |
return configuration | |
} | |
} | |
// MARK: - Utils | |
extension FilesManager { | |
/// Recreate URLSession with same configuration for file to initiate urlSession(_:task:didCompleteWithError:) | |
func recreateSession(id: String) { | |
let multipartFileName = id.ltrim(text: FilesManager.urlSessionIdentifierPrefix) | |
_ = URLSession(configuration: FilesManager.urlSessionConfiguration(for: multipartFileName), delegate: self, delegateQueue: nil) | |
} | |
func uploadFile(name fileName: String) { | |
var urlRequest = URLRequest(url: FilesManager.uploadUrl) | |
urlRequest.httpMethod = "POST" | |
let boundary = urlRequest.generateBoundary() | |
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") | |
let session = URLSession(configuration: FilesManager.urlSessionConfiguration(for: fileName), delegate: self, delegateQueue: nil) | |
let filePath = FilesManager.fullPath(for: fileName) | |
DispatchQueue.main.async { | |
let fileUrl = NSURL(fileURLWithPath: filePath) as URL | |
if FileManager.default.fileExists(atPath: fileUrl.path) { | |
session.uploadTask(with: urlRequest, fromFile: fileUrl).resume() | |
} | |
FilesManager.addFileUploadTask(id: session.configuration.identifier, fileName: fileName) | |
} | |
} | |
static func addFileUploadTask(id: String?, fileName: String) { | |
guard let sessionId = id else { return } | |
var filesUploadTasks = UserDefaults.standard.stringArray(forKey: UserDefaultsKeys.filesUploadTasks) ?? [] | |
if filesUploadTasks.firstIndex(of: sessionId) == nil { // Append only if doesn't already exists | |
filesUploadTasks.append(sessionId) | |
} | |
UserDefaults.standard.set(filesUploadTasks, forKey: UserDefaultsKeys.filesUploadTasks) | |
} | |
static func removeFileUploadTask(id: String?) { | |
guard let sessionId = id else { return } | |
var filesUploadTasks = UserDefaults.standard.stringArray(forKey: UserDefaultsKeys.filesUploadTasks) | |
if let index = filesUploadTasks?.firstIndex(of: sessionId) { | |
filesUploadTasks?.remove(at: index) | |
} | |
UserDefaults.standard.set(filesUploadTasks, forKey: UserDefaultsKeys.filesUploadTasks) | |
} | |
static func taskFinishedCompletion(id: String?) { | |
do { | |
let fileName = id?.ltrim(text: FilesManager.urlSessionIdentifierPrefix) | |
let filePath = FilesManager.fullPath(for: fileName) | |
FilesManager.removeFileUploadTask(id: id) | |
let fileManager = FileManager.default | |
if fileManager.fileExists(atPath: filePath) { | |
try fileManager.removeItem(atPath: filePath) | |
} | |
} catch _ { | |
} | |
} | |
} | |
// MARK: - URLSessionTaskDelegate | |
extension FilesManager: URLSessionDataDelegate, URLSessionDelegate { | |
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { | |
guard let multipartFileName = session.configuration.identifier?.ltrim(text: FilesManager.urlSessionIdentifierPrefix) else { return } | |
if let error = error { | |
let nsError = (error as NSError) | |
guard nsError.userInfo[NSURLErrorBackgroundTaskCancelledReasonKey] as? Int == NSURLErrorCancelledReasonUserForceQuitApplication else { return } | |
// File didn't finished upload because of force quit | |
// Try uploading again from saved file | |
uploadFile(name: multipartFileName) | |
} else { | |
FilesManager.taskFinishedCompletion(id: session.configuration.identifier) | |
} | |
} | |
} |
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
import UIKit | |
extension String { | |
/// trim spaces & enters from both sides of the string | |
func trim() -> String { | |
return trimmingCharacters(in: .whitespaces).trimmingCharacters(in: .newlines) | |
} | |
/// trim specific text from the beginning | |
func ltrim(text: String) -> String { | |
return replacingOccurrences(of: text, with: "", options: .anchored) | |
} | |
/// trim specific text from the ending | |
func rtrim(text: String) -> String { | |
return replacingOccurrences(of: text, with: "", options: [.anchored, .backwards]) | |
} | |
} | |
extension URLRequest { | |
func generateBoundary() -> String { | |
return "Boundary-\(userId)" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment