From 0f988f528cff57cd74704d2621c4aa17c3e41089 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 24 Sep 2018 13:02:45 +0200 Subject: [PATCH 01/43] Update main project for Swift 4.2 --- WeTransfer.xcodeproj/project.pbxproj | 58 ++++++++++++++----- .../Transfer/SimpleTransferTests.swift | 2 +- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index a7fbc20..d575019 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 3E3B9D3C20F7ADDC0064915C /* MediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3B9D3B20F7ADDC0064915C /* MediaPicker.swift */; }; 3E5E514F20EA816600485FA3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5E514720EA816600485FA3 /* MainViewController.swift */; }; 3E5E515320EA816600485FA3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5E514D20EA816600485FA3 /* AppDelegate.swift */; }; - 3E71177720FC9C2E00115E55 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; 3E80E0A72091D0E700114711 /* WeTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E80E0A62091D0E700114711 /* WeTransfer.swift */; }; 3E80E0AA2091D12B00114711 /* Transfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E80E0A92091D12B00114711 /* Transfer.swift */; }; 3E80E0AE2091D16E00114711 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E80E0AD2091D16E00114711 /* Result.swift */; }; @@ -85,6 +84,10 @@ 3E9915DE211201150071FDF7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E5E514820EA816600485FA3 /* Assets.xcassets */; }; 3EA0EFBA20BD4D90009E4BB1 /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA0EFB920BD4D90009E4BB1 /* AsynchronousOperation.swift */; }; 3EA0EFCE20BD7743009E4BB1 /* AsynchronousResultOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA0EFCD20BD7743009E4BB1 /* AsynchronousResultOperation.swift */; }; + 3EB1E1872158F72900E1E4EE /* WeTransfer_Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = 3EE259662091CF8800DC5A97 /* WeTransfer_Swift.h */; }; + 3EB1E1882158F72A00E1E4EE /* WeTransfer_Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = 3EE259662091CF8800DC5A97 /* WeTransfer_Swift.h */; }; + 3EB1E18C2158F9D000E1E4EE /* WeTransfer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E7116DC20FC85D400115E55 /* WeTransfer.framework */; }; + 3EB1E18D2158F9D000E1E4EE /* WeTransfer.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3E7116DC20FC85D400115E55 /* WeTransfer.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3ED6ED4020EA505800130261 /* APIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED6ED3F20EA505800130261 /* APIEndpoint.swift */; }; 3EFDC98021012FD70091CF85 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EFDC97F21012FD70091CF85 /* RoundedButton.swift */; }; 3F5E8A722111F80600F750EC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3E5E514B20EA816600485FA3 /* Main.storyboard */; }; @@ -106,8 +109,29 @@ remoteGlobalIDString = 3E7116DB20FC85D400115E55; remoteInfo = "WeTransfer iOS"; }; + 3EB1E1892158F76E00E1E4EE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3EE2595A2091CF8800DC5A97 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3E7116DB20FC85D400115E55; + remoteInfo = "WeTransfer iOS"; + }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 3EB1E18E2158F9D000E1E4EE /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3EB1E18D2158F9D000E1E4EE /* WeTransfer.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 3E0EEBA720BEE918009E6B2D /* AsynchronousDependencyResultOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousDependencyResultOperation.swift; sourceTree = ""; }; 3E0EEBAF20C1444C009E6B2D /* ChainedAsynchronousResultOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainedAsynchronousResultOperation.swift; sourceTree = ""; }; @@ -169,7 +193,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3E71177720FC9C2E00115E55 /* (null) in Frameworks */, + 3EB1E18C2158F9D000E1E4EE /* WeTransfer.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -390,6 +414,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 3EB1E1872158F72900E1E4EE /* WeTransfer_Swift.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -397,6 +422,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 3EB1E1882158F72A00E1E4EE /* WeTransfer_Swift.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -410,6 +436,7 @@ 3E5E513020EA813D00485FA3 /* Sources */, 3E5E513120EA813D00485FA3 /* Frameworks */, 3E5E513220EA813D00485FA3 /* Resources */, + 3EB1E18E2158F9D000E1E4EE /* Embed Frameworks */, ); buildRules = ( ); @@ -452,6 +479,7 @@ buildRules = ( ); dependencies = ( + 3EB1E18A2158F76E00E1E4EE /* PBXTargetDependency */, ); name = "WeTransfer iOSTests"; productName = "WeTransfer iOSTests"; @@ -508,12 +536,15 @@ TargetAttributes = { 3E5E513320EA813D00485FA3 = { CreatedOnToolsVersion = 9.4; + LastSwiftMigration = 1000; }; 3E7116DB20FC85D400115E55 = { CreatedOnToolsVersion = 9.4.1; + LastSwiftMigration = 1000; }; 3E7116E320FC85D400115E55 = { CreatedOnToolsVersion = 9.4.1; + LastSwiftMigration = 1000; }; 3E7116F720FC85F100115E55 = { CreatedOnToolsVersion = 9.4.1; @@ -603,7 +634,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "filename=\"Secrets.plist\"\nfile=\"${PROJECT_DIR}/${PROJECT_NAME}Tests/$filename\"\nif [ -f \"$file\" ]; then\ncp -r \"$file\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.xctest/$filename\"\nfi"; + shellScript = "filename=\"Secrets.plist\"\nfile=\"${PROJECT_DIR}/${PROJECT_NAME}Tests/$filename\"\nif [ -f \"$file\" ]; then\ncp -r \"$file\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.xctest/$filename\"\nfi\n"; }; 3E71177320FC956C00115E55 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; @@ -772,6 +803,11 @@ target = 3E7116DB20FC85D400115E55 /* WeTransfer iOS */; targetProxy = 3E71176D20FC889300115E55 /* PBXContainerItemProxy */; }; + 3EB1E18A2158F76E00E1E4EE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3E7116DB20FC85D400115E55 /* WeTransfer iOS */; + targetProxy = 3EB1E1892158F76E00E1E4EE /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -810,10 +846,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.wetransfer.WeTransfer-Sample-Project"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 armv7 armv7s"; }; name = Debug; }; @@ -833,10 +867,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.wetransfer.WeTransfer-Sample-Project"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 armv7 armv7s"; }; name = Release; }; @@ -862,7 +894,7 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -889,7 +921,7 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -909,7 +941,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.wetransfer.WeTransfer-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -929,7 +961,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.wetransfer.WeTransfer-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/WeTransferTests/Transfer/SimpleTransferTests.swift b/WeTransferTests/Transfer/SimpleTransferTests.swift index b3488ed..55c39ea 100644 --- a/WeTransferTests/Transfer/SimpleTransferTests.swift +++ b/WeTransferTests/Transfer/SimpleTransferTests.swift @@ -41,7 +41,7 @@ final class SimpleTransferTests: XCTestCase { timer = Timer(timeInterval: 1 / 30, repeats: true, block: { _ in print("Progress: \(progress.fractionCompleted)") }) - RunLoop.main.add(timer!, forMode: .commonModes) + RunLoop.main.add(timer!, forMode: RunLoop.Mode.common) case .completed(let transfer): timer?.invalidate() timer = nil From e9e096207712a1e435eda404116c414fb339e5ed Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 24 Sep 2018 13:03:06 +0200 Subject: [PATCH 02/43] Updated sample project for Swift 4.2 --- WeTransfer Sample Project/MainViewController.swift | 2 +- WeTransfer Sample Project/MediaPicker.swift | 6 +++--- .../Supporting Files/AppDelegate.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WeTransfer Sample Project/MainViewController.swift b/WeTransfer Sample Project/MainViewController.swift index e804d2d..6ff1672 100644 --- a/WeTransfer Sample Project/MainViewController.swift +++ b/WeTransfer Sample Project/MainViewController.swift @@ -184,7 +184,7 @@ extension MainViewController { case .transferCompleted(let shortURL): titleLabel.text = "Transfer completed" bodyLabel.text = nil - let attributes: [NSAttributedStringKey: Any] = [.underlineStyle: NSUnderlineStyle.styleSingle.rawValue, + let attributes: [NSAttributedString.Key: Any] = [.underlineStyle: NSUnderlineStyle.single.rawValue, .foregroundColor: urlButton.currentTitleColor] let attributedURLText = NSAttributedString(string: shortURL.absoluteString, attributes: attributes) urlButton.setAttributedTitle(attributedURLText, for: .normal) diff --git a/WeTransfer Sample Project/MediaPicker.swift b/WeTransfer Sample Project/MediaPicker.swift index 99a8896..40780a2 100644 --- a/WeTransfer Sample Project/MediaPicker.swift +++ b/WeTransfer Sample Project/MediaPicker.swift @@ -77,7 +77,7 @@ final class MediaPicker: NSObject { // Get first frame if video let imageGenerator = AVAssetImageGenerator(asset: asset) imageGenerator.appliesPreferredTrackTransform = true - if let image = try? imageGenerator.copyCGImage(at: kCMTimeZero, actualTime: nil) { + if let image = try? imageGenerator.copyCGImage(at: CMTime.zero, actualTime: nil) { pickedMedia = Media(url: url, previewImage: UIImage(cgImage: image)) } } else { @@ -104,8 +104,8 @@ extension MediaPicker: UIImagePickerControllerDelegate, UINavigationControllerDe finish(with: nil) } - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) { - guard let url = info[UIImagePickerControllerImageURL] as? URL ?? info[UIImagePickerControllerMediaURL] as? URL else { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + guard let url = info[UIImagePickerController.InfoKey.imageURL] as? URL ?? info[UIImagePickerController.InfoKey.mediaURL] as? URL else { finish(with: nil) return } diff --git a/WeTransfer Sample Project/Supporting Files/AppDelegate.swift b/WeTransfer Sample Project/Supporting Files/AppDelegate.swift index e58241c..3462b17 100644 --- a/WeTransfer Sample Project/Supporting Files/AppDelegate.swift +++ b/WeTransfer Sample Project/Supporting Files/AppDelegate.swift @@ -13,7 +13,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true } } From cc036f8a5e1b9df25d1adef2abe9a788532674ee Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 24 Sep 2018 13:09:50 +0200 Subject: [PATCH 03/43] Update to version 2.0 --- WeTransfer Sample Project/Info.plist | 4 ++-- WeTransfer-Swift-SDK.podspec | 4 ++-- WeTransfer/Info.plist | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WeTransfer Sample Project/Info.plist b/WeTransfer Sample Project/Info.plist index 16b9080..6370be7 100644 --- a/WeTransfer Sample Project/Info.plist +++ b/WeTransfer Sample Project/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 2.0 CFBundleVersion - 1 + 2 LSRequiresIPhoneOS NSAppleMusicUsageDescription diff --git a/WeTransfer-Swift-SDK.podspec b/WeTransfer-Swift-SDK.podspec index 7eb73e1..cc9bcfe 100644 --- a/WeTransfer-Swift-SDK.podspec +++ b/WeTransfer-Swift-SDK.podspec @@ -1,13 +1,13 @@ Pod::Spec.new do |s| s.name = "WeTransfer-Swift-SDK" - s.version = "1.0" + s.version = "2.0" s.summary = "A Swift SDK for WeTransfer’s public API" s.homepage = "https://github.com/WeTransfer/WeTransfer-Swift-SDK" s.license = "MIT" s.author = { "Pim Coumans" => "pim@pixelrock.nl" } s.source = { :git => "https://github.com/WeTransfer/WeTransfer-Swift-SDK.git", :tag => "v#{s.version}" } - s.swift_version = "4.1" + s.swift_version = "4.2" s.ios.deployment_target = "9.0" s.osx.deployment_target = "10.10" diff --git a/WeTransfer/Info.plist b/WeTransfer/Info.plist index 1007fd9..53b23a1 100644 --- a/WeTransfer/Info.plist +++ b/WeTransfer/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass From ceb75a1245a844d3fa2538c313134e71dbd62480 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 24 Sep 2018 13:12:28 +0200 Subject: [PATCH 04/43] Use Xcode 10 for CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 57b81de..4ab413d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode9.4 +osx_image: xcode10 gemfile: Gemfile bundler_args: "--without documentation --path bundle" cache: From d1fa5b403829865007e0901f4cf8c49d4c6bc2e9 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Thu, 4 Oct 2018 11:16:08 +0200 Subject: [PATCH 05/43] [WIP] step one of new Transfer model --- WeTransfer/Models/Transfer.swift | 31 ++----- .../Server/Methods/CreateTransfer.swift | 19 ++-- .../Operations/CreateTransferOperation.swift | 33 ++++--- .../Operations/UploadFilesOperation.swift | 4 - WeTransfer/WeTransfer.swift | 31 +------ .../Transfer/CreateTransferTests.swift | 90 ++++--------------- .../Transfer/SimpleTransferTests.swift | 4 +- WeTransferTests/Transfer/UploadTests.swift | 57 ++++++------ 8 files changed, 94 insertions(+), 175 deletions(-) diff --git a/WeTransfer/Models/Transfer.swift b/WeTransfer/Models/Transfer.swift index 9cc5c99..b05d569 100644 --- a/WeTransfer/Models/Transfer.swift +++ b/WeTransfer/Models/Transfer.swift @@ -9,24 +9,22 @@ import Foundation /// Desribes a single transfer to be created, updated and sent. Used as an identifier between each request to be made and a local representation of the server-side transfer. -/// Can be initialized with files or these can be added later through the add files function +/// Can only be initialized from the create transfer request public final class Transfer { - public private(set) var identifier: String? + public let identifier: String /// The name of the transfer. This name will be shown when viewing the transfer on wetransfer.com - public let name: String - /// Optional description of the transfer. This will be shown when viewing the transfer on wetransfer.com - public let description: String? + public let message: String - /// References to all the files added to the transfer. Add other files with the public method on the WeTransfer struct or add them directly when initializing the transfer object - public private(set) var files: [File] = [] + /// References to all the files added to the transfer + public let files: [File] /// Available when the transfer is created on the server public private(set) var shortURL: URL? - public init(name: String, description: String?, files: [File] = []) { - self.name = name - self.description = description + init(identifier: String, message: String, files: [File] = []) { + self.identifier = identifier + self.message = message self.files = files } } @@ -37,19 +35,8 @@ extension Transfer { /// Updates the transfer with server-side information /// /// - Parameters: - /// - identifier: Identifier to point to global transfer /// - shortURL: URL of where the transfer can be found online - func update(with identifier: String, shortURL: URL) { - self.identifier = identifier + func update(with shortURL: URL) { self.shortURL = shortURL } - - /// Adds provided files to the transfer locally - /// - /// - Parameter files: Files to be added to the transfer - func add(_ files: [File]) { - for file in files where !self.files.contains(file) { - self.files.append(file) - } - } } diff --git a/WeTransfer/Server/Methods/CreateTransfer.swift b/WeTransfer/Server/Methods/CreateTransfer.swift index 0b08618..814c965 100644 --- a/WeTransfer/Server/Methods/CreateTransfer.swift +++ b/WeTransfer/Server/Methods/CreateTransfer.swift @@ -14,10 +14,12 @@ extension WeTransfer { /// If the transfer object was initialized with files, the files will be added on the server as well and updated with the appropriate data /// /// - Parameters: + /// - message: Message to add to transfer + /// - fileURLs: URLs pointing to local files /// - transfer: Transfer object that should be created on the server as well /// - completion: Closure that will be executed when the request or requests have finished /// - result: Result with either the updated transfer object or an error when something went wrong - public static func createTransfer(with transfer: Transfer, completion: @escaping (_ result: Result) -> Void) { + public static func createTransfer(saying message: String, fileURLs: [URL], completion: @escaping (_ result: Result) -> Void) { let callCompletion = { result in DispatchQueue.main.async { @@ -25,18 +27,9 @@ extension WeTransfer { } } - let creationOperation = CreateTransferOperation(transfer: transfer) + let creationOperation = CreateTransferOperation(message: message, fileURLs: fileURLs) - guard !transfer.files.isEmpty else { - creationOperation.onResult = callCompletion - client.operationQueue.addOperation(creationOperation) - return - } - - let addFilesOperation = AddFilesOperation() - addFilesOperation.onResult = callCompletion - let operations = [creationOperation, addFilesOperation].chained() - - client.operationQueue.addOperations(operations, waitUntilFinished: false) + creationOperation.onResult = callCompletion + client.operationQueue.addOperation(creationOperation) } } diff --git a/WeTransfer/Server/Operations/CreateTransferOperation.swift b/WeTransfer/Server/Operations/CreateTransferOperation.swift index 0b34989..f2cd14e 100644 --- a/WeTransfer/Server/Operations/CreateTransferOperation.swift +++ b/WeTransfer/Server/Operations/CreateTransferOperation.swift @@ -12,33 +12,44 @@ import Foundation /// This operation does not handle the requests necessary to add files to the server side transfer, which `AddFilesOperation` is responsible for final class CreateTransferOperation: AsynchronousResultOperation { - private let transfer: Transfer + let message: String + let fileURLs: [URL] - /// Initalized the operation with a transfer object + /// Initalized the operation with the necessary parameters for a transfer /// /// - Parameter transfer: Transfer object with optionally some files already added - required init(transfer: Transfer) { - self.transfer = transfer + required init(message: String, fileURLs: [URL]) { + self.message = message + self.fileURLs = fileURLs super.init() } override func execute() { - guard transfer.identifier == nil else { - self.finish(with: .failure(WeTransfer.Error.transferAlreadyCreated)) + let files: [File] + do { + files = try fileURLs.map({ try File(url: $0) }) + } catch { + // Fail when any of the files failed to create + finish(with: .failure(error)) return } - let parameters = CreateTransferParameters(with: transfer) + let parameters = CreateTransferParameters(message: message, files: files) WeTransfer.request(.createTransfer(), parameters: parameters) { [weak self] result in - guard let strongSelf = self else { + guard let self = self else { return } switch result { case .success(let response): - strongSelf.transfer.update(with: response.id, shortURL: response.shortenedUrl) - strongSelf.finish(with: .success(strongSelf.transfer)) + let updatedFiles: [File] = zip(response.files, files).map({ (files) in + let (responseFile, file) = files + file.update(with: response.id, numberOfChunks: responseFile.multipart.partNumbers, multipartUploadIdentifier: nil) + return file + }) + let transfer = Transfer(identifier: response.id, message: parameters.message, files: updatedFiles) + self.finish(with: .success(transfer)) case .failure(let error): - strongSelf.finish(with: .failure(error)) + self.finish(with: .failure(error)) } } } diff --git a/WeTransfer/Server/Operations/UploadFilesOperation.swift b/WeTransfer/Server/Operations/UploadFilesOperation.swift index 9b7778b..919dd3f 100644 --- a/WeTransfer/Server/Operations/UploadFilesOperation.swift +++ b/WeTransfer/Server/Operations/UploadFilesOperation.swift @@ -27,10 +27,6 @@ final class UploadFilesOperation: ChainedAsynchronousResultOperation Void) -> Transfer? { + public static func uploadTransfer(saying message: String, containing fileURLS: [URL], stateChanged: @escaping (_ state: State) -> Void) { // Make sure stateChanges closure is called on the main thread let changeState = { state in @@ -97,21 +96,8 @@ extension WeTransfer { } } - // Create the transfer model - let files: [File] - do { - files = try fileURLS.compactMap { url in try File(url: url) } - } catch { - changeState(.failed(error)) - return nil - } - let transfer = Transfer(name: name, description: nil, files: files) - // Create transfer on server - let creationOperation = CreateTransferOperation(transfer: transfer) - - // Add files to the transfer - let addFilesOperation = AddFilesOperation() + let creationOperation = CreateTransferOperation(message: message, fileURLs: fileURLS) // Upload all files from the chunks let uploadFilesOperation = UploadFilesOperation() @@ -123,15 +109,8 @@ extension WeTransfer { } } - // When all files are ready for upload - addFilesOperation.onResult = { result in - if case .success = result { - changeState(.uploading(uploadFilesOperation.progress)) - } - } - // Perform all operations in a chain - let operations = [creationOperation, addFilesOperation, uploadFilesOperation].chained() + let operations = [creationOperation, uploadFilesOperation].chained() client.operationQueue.addOperations(operations, waitUntilFinished: false) // Handle the result of the very last operation that's executed @@ -143,7 +122,5 @@ extension WeTransfer { changeState(.completed(transfer)) } } - - return transfer } } diff --git a/WeTransferTests/Transfer/CreateTransferTests.swift b/WeTransferTests/Transfer/CreateTransferTests.swift index a14b39c..0c35e5a 100644 --- a/WeTransferTests/Transfer/CreateTransferTests.swift +++ b/WeTransferTests/Transfer/CreateTransferTests.swift @@ -21,84 +21,32 @@ final class CreateTransferTests: XCTestCase { TestConfiguration.resetConfiguration() } - func testTransferModel() { - let transfer = Transfer(name: "Test Transfer", description: nil) - XCTAssertNil(transfer.identifier) - XCTAssertNil(transfer.description) - XCTAssert(transfer.files.isEmpty) - XCTAssertNil(transfer.shortURL) - } - func testCreateTransferRequest() { - let transfer = Transfer(name: "Test Transfer", description: nil) - let createdTransferExpectation = expectation(description: "Transfer is created") - WeTransfer.createTransfer(with: transfer) { (result) in - if case .failure(let error) = result { - XCTFail(error.localizedDescription) - return - } - createdTransferExpectation.fulfill() - } - waitForExpectations(timeout: 10) { _ in - XCTAssertNotNil(transfer.identifier) - XCTAssertNotNil(transfer.shortURL) - } - } - - func testTransferCreationWithFiles() { - guard let file = TestConfiguration.fileModel else { + guard let fileURL = TestConfiguration.imageFileURL else { XCTFail("File not available") return } - - let createdTransferExpectation = expectation(description: "Transfer is created with files") - - let transfer = Transfer(name: "Test Transfer", description: nil, files: [file]) - WeTransfer.createTransfer(with: transfer, completion: { (result) in - if case .failure(let error) = result { - XCTFail("Create transfer failed: \(error)") - } - createdTransferExpectation.fulfill() - }) - - waitForExpectations(timeout: 20) { _ in - XCTAssertNotNil(transfer.identifier) - XCTAssertNotNil(transfer.shortURL) - XCTAssertFalse(transfer.files.isEmpty) - - for file in transfer.files { - XCTAssertNotNil(file.identifier) - XCTAssertFalse(file.isUploaded) - XCTAssertNotNil(file.numberOfChunks) - } - } - } - - func testTransferAlreadyCreatedError() { - guard let file = TestConfiguration.fileModel else { - XCTFail("File not available") - return - } - - let createdTransferExpectation = expectation(description: "Transfer is created with files") - let transfer = Transfer(name: "Test Transfer", description: nil, files: [file]) - WeTransfer.createTransfer(with: transfer, completion: { (result) in - if case .failure(let error) = result { - XCTFail("Create transfer failed: \(error)") + let createdTransferExpectation = expectation(description: "Transfer is created") + var transferResult: Transfer? + WeTransfer.createTransfer(saying: "Test transfer", fileURLs: [fileURL]) { result in + switch result { + case .success(let transfer): + transferResult = transfer + case .failure(let error): + XCTFail("\(error.localizedDescription)") } createdTransferExpectation.fulfill() - }) - - waitForExpectations(timeout: 20) { _ in - XCTAssertNotNil(transfer.identifier) - XCTAssertNotNil(transfer.shortURL) - XCTAssertFalse(transfer.files.isEmpty) - - for file in transfer.files { - XCTAssertNotNil(file.identifier) - XCTAssertFalse(file.isUploaded) - XCTAssertNotNil(file.numberOfChunks) + } + waitForExpectations(timeout: 10) { _ in + XCTAssertNotNil(transferResult) + if let transfer = transferResult { + XCTAssertFalse(transfer.files.isEmpty) + for file in transfer.files { + XCTAssertNotNil(file.identifier) + XCTAssertFalse(file.isUploaded) + XCTAssertNotNil(file.numberOfChunks) + } } } } diff --git a/WeTransferTests/Transfer/SimpleTransferTests.swift b/WeTransferTests/Transfer/SimpleTransferTests.swift index 55c39ea..4ba9c2b 100644 --- a/WeTransferTests/Transfer/SimpleTransferTests.swift +++ b/WeTransferTests/Transfer/SimpleTransferTests.swift @@ -32,7 +32,7 @@ final class SimpleTransferTests: XCTestCase { var updatedTransfer: Transfer? var timer: Timer? - WeTransfer.uploadTransfer(named: "Test Transfer", containing: [fileURL]) { state in + WeTransfer.uploadTransfer(saying: "Test transfer", containing: [fileURL]) { state in switch state { case .created(let transfer): print("Transfer created: \(transfer)") @@ -41,7 +41,7 @@ final class SimpleTransferTests: XCTestCase { timer = Timer(timeInterval: 1 / 30, repeats: true, block: { _ in print("Progress: \(progress.fractionCompleted)") }) - RunLoop.main.add(timer!, forMode: RunLoop.Mode.common) + RunLoop.main.add(timer!, forMode: RunLoopMode.commonModes) case .completed(let transfer): timer?.invalidate() timer = nil diff --git a/WeTransferTests/Transfer/UploadTests.swift b/WeTransferTests/Transfer/UploadTests.swift index 2e9fb3f..dae88db 100644 --- a/WeTransferTests/Transfer/UploadTests.swift +++ b/WeTransferTests/Transfer/UploadTests.swift @@ -31,38 +31,45 @@ final class UploadTests: XCTestCase { } let transferSentExpectation = expectation(description: "Transfer is sent") - let transfer = Transfer(name: "Test transfer", description: nil, files: [file]) + var resultTransfer: Transfer? - WeTransfer.createTransfer(with: transfer, completion: { (result) in - if case .failure(let error) = result { + WeTransfer.createTransfer(saying: "TestTransfer", fileURLs: [file.url]) { result in + switch result { + case .failure(let error): XCTFail("Transfer creation failed: \(error)") transferSentExpectation.fulfill() return + case .success(let transfer): + resultTransfer = transfer + WeTransfer.upload(transfer, stateChanged: { (state) in + switch state { + case .created(let transfer): + print("Transfer created: \(String(describing: transfer.identifier))") + case .uploading(let progress): + print("Upload started") + var percentage = 0.0 + self.observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, _) in + let newPercentage = (progress.fractionCompleted * 100).rounded(FloatingPointRoundingRule.up) + if newPercentage != percentage { + percentage = newPercentage + print("PROGRESS: \(newPercentage)% (\(progress.completedUnitCount) bytes)") + } + }) + case .failed(let error): + XCTFail("Sending transfer failed: \(error)") + transferSentExpectation.fulfill() + case .completed: + transferSentExpectation.fulfill() + } + }) } - WeTransfer.upload(transfer, stateChanged: { (state) in - switch state { - case .created(let transfer): - print("Transfer created: \(String(describing: transfer.identifier))") - case .uploading(let progress): - print("Upload started") - var percentage = 0.0 - self.observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, _) in - let newPercentage = (progress.fractionCompleted * 100).rounded(FloatingPointRoundingRule.up) - if newPercentage != percentage { - percentage = newPercentage - print("PROGRESS: \(newPercentage)% (\(progress.completedUnitCount) bytes)") - } - }) - case .failed(let error): - XCTFail("Sending transfer failed: \(error)") - transferSentExpectation.fulfill() - case .completed: - transferSentExpectation.fulfill() - } - }) - }) + } waitForExpectations(timeout: 60) { _ in + guard let transfer = resultTransfer else { + XCTFail("No transfer created") + return + } self.observation = nil if let url = transfer.shortURL { print("Transfer uploaded: \(url)") From 43dd5102ed321cff51d24d2ad35c329976e1389d Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Thu, 4 Oct 2018 11:41:06 +0200 Subject: [PATCH 06/43] Added Boards --- WeTransfer.xcodeproj/project.pbxproj | 42 +++++++--- WeTransfer/Models/Board.swift | 54 +++++++++++++ WeTransfer/Models/File.swift | 11 +-- WeTransfer/Server/Endpoints/Endpoints.swift | 78 +++++++++++++++++-- WeTransfer/Server/Methods/AddFiles.swift | 9 ++- WeTransfer/Server/Methods/CreateBoard.swift | 32 ++++++++ WeTransfer/Server/Methods/Upload.swift | 7 +- .../Server/Operations/AddFilesOperation.swift | 24 +++--- .../Operations/CreateBoardOperation.swift | 45 +++++++++++ WeTransferTests/AuthorizationTests.swift | 4 +- .../{Transfer => Board}/AddFilesTests.swift | 42 +++++----- WeTransferTests/Board/CreateBoardTests.swift | 37 +++++++++ WeTransferTests/Transfer/ChunksTests.swift | 17 ++-- 13 files changed, 322 insertions(+), 80 deletions(-) create mode 100644 WeTransfer/Models/Board.swift create mode 100644 WeTransfer/Server/Methods/CreateBoard.swift create mode 100644 WeTransfer/Server/Operations/CreateBoardOperation.swift rename WeTransferTests/{Transfer => Board}/AddFilesTests.swift (73%) create mode 100644 WeTransferTests/Board/CreateBoardTests.swift diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index d575019..e9e54d5 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 3E0EEBBC20C19004009E6B2D /* UploadChunkOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0EEBBB20C19004009E6B2D /* UploadChunkOperation.swift */; }; 3E0EEBBE20C19037009E6B2D /* CompleteUploadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0EEBBD20C19037009E6B2D /* CompleteUploadOperation.swift */; }; 3E0EEBC020C19074009E6B2D /* UploadFileOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0EEBBF20C19074009E6B2D /* UploadFileOperation.swift */; }; + 3E3A87632162798800568775 /* CreateBoardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3A87622162798800568775 /* CreateBoardTests.swift */; }; + 3E3A87642162798800568775 /* CreateBoardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3A87622162798800568775 /* CreateBoardTests.swift */; }; 3E3B9D3C20F7ADDC0064915C /* MediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3B9D3B20F7ADDC0064915C /* MediaPicker.swift */; }; 3E5E514F20EA816600485FA3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5E514720EA816600485FA3 /* MainViewController.swift */; }; 3E5E515320EA816600485FA3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5E514D20EA816600485FA3 /* AppDelegate.swift */; }; @@ -84,10 +86,12 @@ 3E9915DE211201150071FDF7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E5E514820EA816600485FA3 /* Assets.xcassets */; }; 3EA0EFBA20BD4D90009E4BB1 /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA0EFB920BD4D90009E4BB1 /* AsynchronousOperation.swift */; }; 3EA0EFCE20BD7743009E4BB1 /* AsynchronousResultOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA0EFCD20BD7743009E4BB1 /* AsynchronousResultOperation.swift */; }; - 3EB1E1872158F72900E1E4EE /* WeTransfer_Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = 3EE259662091CF8800DC5A97 /* WeTransfer_Swift.h */; }; - 3EB1E1882158F72A00E1E4EE /* WeTransfer_Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = 3EE259662091CF8800DC5A97 /* WeTransfer_Swift.h */; }; - 3EB1E18C2158F9D000E1E4EE /* WeTransfer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E7116DC20FC85D400115E55 /* WeTransfer.framework */; }; - 3EB1E18D2158F9D000E1E4EE /* WeTransfer.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3E7116DC20FC85D400115E55 /* WeTransfer.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3EB1E192215916B000E1E4EE /* Board.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB1E191215916AF00E1E4EE /* Board.swift */; }; + 3EB1E193215916B000E1E4EE /* Board.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB1E191215916AF00E1E4EE /* Board.swift */; }; + 3EB1E1982159283600E1E4EE /* CreateBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB1E1972159283600E1E4EE /* CreateBoard.swift */; }; + 3EB1E1992159283600E1E4EE /* CreateBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB1E1972159283600E1E4EE /* CreateBoard.swift */; }; + 3EB1E19B215928BF00E1E4EE /* CreateBoardOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB1E19A215928BF00E1E4EE /* CreateBoardOperation.swift */; }; + 3EB1E19C215928BF00E1E4EE /* CreateBoardOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB1E19A215928BF00E1E4EE /* CreateBoardOperation.swift */; }; 3ED6ED4020EA505800130261 /* APIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED6ED3F20EA505800130261 /* APIEndpoint.swift */; }; 3EFDC98021012FD70091CF85 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EFDC97F21012FD70091CF85 /* RoundedButton.swift */; }; 3F5E8A722111F80600F750EC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3E5E514B20EA816600485FA3 /* Main.storyboard */; }; @@ -125,7 +129,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3EB1E18D2158F9D000E1E4EE /* WeTransfer.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -142,6 +145,7 @@ 3E0EEBBB20C19004009E6B2D /* UploadChunkOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadChunkOperation.swift; sourceTree = ""; }; 3E0EEBBD20C19037009E6B2D /* CompleteUploadOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteUploadOperation.swift; sourceTree = ""; }; 3E0EEBBF20C19074009E6B2D /* UploadFileOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadFileOperation.swift; sourceTree = ""; }; + 3E3A87622162798800568775 /* CreateBoardTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBoardTests.swift; sourceTree = ""; }; 3E3B9D2720F60F7B0064915C /* smallImage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = smallImage.jpg; sourceTree = ""; }; 3E3B9D3B20F7ADDC0064915C /* MediaPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPicker.swift; sourceTree = ""; }; 3E579B202092194F008DFFD2 /* image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = image.jpg; sourceTree = ""; }; @@ -177,6 +181,9 @@ 3E9413CD2099E3F5003A42D1 /* Chunk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chunk.swift; sourceTree = ""; }; 3EA0EFB920BD4D90009E4BB1 /* AsynchronousOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousOperation.swift; sourceTree = ""; }; 3EA0EFCD20BD7743009E4BB1 /* AsynchronousResultOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousResultOperation.swift; sourceTree = ""; }; + 3EB1E191215916AF00E1E4EE /* Board.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Board.swift; sourceTree = ""; }; + 3EB1E1972159283600E1E4EE /* CreateBoard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBoard.swift; sourceTree = ""; }; + 3EB1E19A215928BF00E1E4EE /* CreateBoardOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateBoardOperation.swift; sourceTree = ""; }; 3ED1C0A220B43E980045D8B3 /* CreateTransferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateTransferTests.swift; sourceTree = ""; }; 3ED1C0A420B442F70045D8B3 /* AddFilesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFilesTests.swift; sourceTree = ""; }; 3ED6ED3920EA2AD100130261 /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; @@ -193,7 +200,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3EB1E18C2158F9D000E1E4EE /* WeTransfer.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -239,6 +245,15 @@ path = Abstract; sourceTree = ""; }; + 3E3A87612162549F00568775 /* Board */ = { + isa = PBXGroup; + children = ( + 3E3A87622162798800568775 /* CreateBoardTests.swift */, + 3ED1C0A420B442F70045D8B3 /* AddFilesTests.swift */, + ); + path = Board; + sourceTree = ""; + }; 3E3B9D2C20F7644D0064915C /* Resources */ = { isa = PBXGroup; children = ( @@ -264,6 +279,7 @@ isa = PBXGroup; children = ( 3E80E0A92091D12B00114711 /* Transfer.swift */, + 3EB1E191215916AF00E1E4EE /* Board.swift */, 3E9413CB2099E390003A42D1 /* File.swift */, 3E9413CD2099E3F5003A42D1 /* Chunk.swift */, ); @@ -295,6 +311,7 @@ 3E9413C32099D78A003A42D1 /* Request.swift */, 3E9413C12098C132003A42D1 /* Authorize.swift */, 3E9413C52099D823003A42D1 /* CreateTransfer.swift */, + 3EB1E1972159283600E1E4EE /* CreateBoard.swift */, 3E9413C72099D89E003A42D1 /* AddFiles.swift */, 3E9413C92099DB05003A42D1 /* Upload.swift */, ); @@ -307,6 +324,7 @@ 3ECCF25F20C5838C00F2B59B /* Helpers */, 3E0EEBAE20C13F20009E6B2D /* Abstract */, 3E0EEBB120C17264009E6B2D /* CreateTransferOperation.swift */, + 3EB1E19A215928BF00E1E4EE /* CreateBoardOperation.swift */, 3E0EEBB320C175D0009E6B2D /* AddFilesOperation.swift */, 3E0EEBB920C18FA5009E6B2D /* UploadFilesOperation.swift */, 3E0EEBBF20C19074009E6B2D /* UploadFileOperation.swift */, @@ -330,7 +348,6 @@ children = ( 3E7348C120B41936009B41D0 /* SimpleTransferTests.swift */, 3ED1C0A220B43E980045D8B3 /* CreateTransferTests.swift */, - 3ED1C0A420B442F70045D8B3 /* AddFilesTests.swift */, 3E90734620BBEF3700AE3605 /* ChunksTests.swift */, 3E90734820BBFE5600AE3605 /* UploadTests.swift */, ); @@ -386,6 +403,7 @@ children = ( 3E3B9D2C20F7644D0064915C /* Resources */, 3ED1C0A120B43E780045D8B3 /* Transfer */, + 3E3A87612162549F00568775 /* Board */, 3E7348BD20B4040D009B41D0 /* TestConfiguration.swift */, 3E7348BF20B41895009B41D0 /* InitializationTests.swift */, 3E7348C520B41EC0009B41D0 /* RequestTests.swift */, @@ -414,7 +432,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3EB1E1872158F72900E1E4EE /* WeTransfer_Swift.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -422,7 +439,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3EB1E1882158F72A00E1E4EE /* WeTransfer_Swift.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -705,6 +721,7 @@ 3E80E0A72091D0E700114711 /* WeTransfer.swift in Sources */, 3E80E0AE2091D16E00114711 /* Result.swift in Sources */, 3E9413CA2099DB05003A42D1 /* Upload.swift in Sources */, + 3EB1E1982159283600E1E4EE /* CreateBoard.swift in Sources */, 3E0EEBC020C19074009E6B2D /* UploadFileOperation.swift in Sources */, 3E0EEBB620C177FE009E6B2D /* CreateChunkOperation.swift in Sources */, 3E9413C82099D89E003A42D1 /* AddFiles.swift in Sources */, @@ -717,7 +734,9 @@ 3E9413C42099D78A003A42D1 /* Request.swift in Sources */, 3E0EEBBE20C19037009E6B2D /* CompleteUploadOperation.swift in Sources */, 3E0EEBB020C1444C009E6B2D /* ChainedAsynchronousResultOperation.swift in Sources */, + 3EB1E19B215928BF00E1E4EE /* CreateBoardOperation.swift in Sources */, 3EA0EFBA20BD4D90009E4BB1 /* AsynchronousOperation.swift in Sources */, + 3EB1E192215916B000E1E4EE /* Board.swift in Sources */, 3E9413BD2098BC25003A42D1 /* APIClient.swift in Sources */, 3E9413CC2099E390003A42D1 /* File.swift in Sources */, 3ED6ED4020EA505800130261 /* APIEndpoint.swift in Sources */, @@ -734,6 +753,7 @@ 3E9915B32111B0C50071FDF7 /* RequestTests.swift in Sources */, 3E9915AC2111B0C50071FDF7 /* SimpleTransferTests.swift in Sources */, 3E9915B02111B0C50071FDF7 /* UploadTests.swift in Sources */, + 3E3A87642162798800568775 /* CreateBoardTests.swift in Sources */, 3E9915AE2111B0C50071FDF7 /* AddFilesTests.swift in Sources */, 3E9915B12111B0C50071FDF7 /* TestConfiguration.swift in Sources */, 3E9915AF2111B0C50071FDF7 /* ChunksTests.swift in Sources */, @@ -754,6 +774,7 @@ 3E9915C42111B1A30071FDF7 /* File.swift in Sources */, 3E9915C62111B1A30071FDF7 /* Request.swift in Sources */, 3E9915C72111B1A30071FDF7 /* Authorize.swift in Sources */, + 3EB1E1992159283600E1E4EE /* CreateBoard.swift in Sources */, 3E9915D42111B1A30071FDF7 /* UploadFilesOperation.swift in Sources */, 3E9915D22111B1A30071FDF7 /* CreateTransferOperation.swift in Sources */, 3E9915D92111B1A30071FDF7 /* APIClient.swift in Sources */, @@ -766,7 +787,9 @@ 3E9915D62111B1A30071FDF7 /* CreateChunkOperation.swift in Sources */, 3E9915C92111B1A30071FDF7 /* AddFiles.swift in Sources */, 3E9915C22111B1A30071FDF7 /* WeTransfer.swift in Sources */, + 3EB1E19C215928BF00E1E4EE /* CreateBoardOperation.swift in Sources */, 3E9915CF2111B1A30071FDF7 /* AsynchronousResultOperation.swift in Sources */, + 3EB1E193215916B000E1E4EE /* Board.swift in Sources */, 3E9915C52111B1A30071FDF7 /* Chunk.swift in Sources */, 3E9915CB2111B1A30071FDF7 /* APIEndpoint.swift in Sources */, 3E9915CC2111B1A30071FDF7 /* Endpoints.swift in Sources */, @@ -783,6 +806,7 @@ 3E9915BF2111B0C60071FDF7 /* RequestTests.swift in Sources */, 3E9915B82111B0C60071FDF7 /* SimpleTransferTests.swift in Sources */, 3E9915BC2111B0C60071FDF7 /* UploadTests.swift in Sources */, + 3E3A87632162798800568775 /* CreateBoardTests.swift in Sources */, 3E9915BA2111B0C60071FDF7 /* AddFilesTests.swift in Sources */, 3E9915BD2111B0C60071FDF7 /* TestConfiguration.swift in Sources */, 3E9915BB2111B0C60071FDF7 /* ChunksTests.swift in Sources */, diff --git a/WeTransfer/Models/Board.swift b/WeTransfer/Models/Board.swift new file mode 100644 index 0000000..77081ce --- /dev/null +++ b/WeTransfer/Models/Board.swift @@ -0,0 +1,54 @@ +// +// Transfer.swift +// WeTransfer Swift SDK +// +// Created by Pim Coumans on 26/04/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import Foundation + +/// Desribes a single board to be created, updated and sent. Used as an identifier between each request to be made and a local representation of the server-side transfer. +/// Can be initialized with files or these can be added later through the add files function +public final class Board { + public private(set) var identifier: String? + + /// The name of the transfer. This name will be shown when viewing the transfer on wetransfer.com + public let name: String + /// Optional description of the transfer. This will be shown when viewing the transfer on wetransfer.com + public let description: String? + + /// References to all the files added to the transfer. Add other files with the public method on the WeTransfer struct or add them directly when initializing the transfer object + public private(set) var files: [File] = [] + + /// Available when the transfer is created on the server + public private(set) var shortURL: URL? + + init(name: String, description: String?) { + self.name = name + self.description = description + } +} + +// MARK: - Private updating methods +extension Board { + + /// Updates the transfer with server-side information + /// + /// - Parameters: + /// - identifier: Identifier to point to global transfer + /// - shortURL: URL of where the transfer can be found online + func update(with identifier: String, shortURL: URL) { + self.identifier = identifier + self.shortURL = shortURL + } + + /// Adds provided files to the transfer locally + /// + /// - Parameter files: Files to be added to the transfer + func add(_ files: [File]) { + for file in files where !self.files.contains(file) { + self.files.append(file) + } + } +} diff --git a/WeTransfer/Models/File.swift b/WeTransfer/Models/File.swift index 86f76fd..679ab62 100644 --- a/WeTransfer/Models/File.swift +++ b/WeTransfer/Models/File.swift @@ -43,9 +43,6 @@ public final class File: Encodable { /// Size of the file in Bytes public let filesize: Bytes - - /// Unique identifier to keep track of files locally - let localIdentifier = UUID().uuidString public private(set) var numberOfChunks: Int? private(set) var multipartUploadIdentifier: String? @@ -62,15 +59,15 @@ public final class File: Encodable { } extension File: Equatable { - /// Only compares the url and localIdentifier of the File - /// Note: Disregards any state, so the `uploaded` property is ignored + /// Only compares the url and optional identifier of the file + // Note: Disregards any state, so the `uploaded` property is ignored public static func == (lhs: File, rhs: File) -> Bool { - return lhs.url == rhs.url && lhs.localIdentifier == rhs.localIdentifier + return lhs.url == rhs.url && lhs.identifier == rhs.identifier } } extension File { - func update(with identifier: String, numberOfChunks: Int, multipartUploadIdentifier: String) { + func update(with identifier: String, numberOfChunks: Int, multipartUploadIdentifier: String?) { self.identifier = identifier self.numberOfChunks = numberOfChunks self.multipartUploadIdentifier = multipartUploadIdentifier diff --git a/WeTransfer/Server/Endpoints/Endpoints.swift b/WeTransfer/Server/Endpoints/Endpoints.swift index 16507a9..1ce8b15 100644 --- a/WeTransfer/Server/Endpoints/Endpoints.swift +++ b/WeTransfer/Server/Endpoints/Endpoints.swift @@ -23,6 +23,13 @@ extension APIEndpoint { return APIEndpoint(method: .post, path: "transfers") } + /// Creates a new transfer + /// + /// - Returns: APIEndpoint with `POST` to `/transfer`, expecting a `CreateTransferResponse` as response + static func createBoard() -> APIEndpoint { + return APIEndpoint(method: .post, path: "boards") + } + /// Adds files to an existing transfer /// /// - Parameter transferIdentifier: Identifier of the transfer to add the files to @@ -66,6 +73,64 @@ struct AuthorizeResponse: Decodable { /// Parameters used for the create transfer request struct CreateTransferParameters: Encodable { + + struct FileParameters: Encodable { + let name: String + let size: UInt64 + } + + /// Message to go with the transfer + let message: String + /// Description of all the files to add + let files: [FileParameters] + + /// Initializes the parameters with a local transfer object + /// + /// - Parameter transfer: Transfer object to create on the server + init(message: String, files: [File]) { + self.message = message + + self.files = files.map({ file in + return FileParameters(name: file.filename, size: file.filesize) + }) + } +} + +/// Response from create transfer request +struct CreateTransferResponse: Decodable { + struct FileResponse: Decodable { + // swiftlint:disable nesting + /// Multipart upload information about each chunk + struct Multipart: Decodable { + /// Amount of chunks to be created + let partNumbers: Int + /// Default size for each chunk + let chunkSize: Bytes + } + + let id: String // swiftlint:disable:this identifier_name + /// Full name of file (e.g. "photo.jpg") + let name: String + /// Mulitpart information about each chunk + let multipart: Multipart + } + + /// Server side identifier of the transfer + let id: String // swiftlint:disable:this identifier_name + + /// Server side information about the files + let files: [FileResponse] +} + +// MARK: - Create board + +/// Parameters used for the create transfer request +struct CreateBoardParameters: Encodable { + + struct FileParameters: Encodable { + let name: String + let size: UInt64 + } /// Name of the transfer to create let name: String /// Description of the transfer to create @@ -74,18 +139,18 @@ struct CreateTransferParameters: Encodable { /// Initializes the parameters with a local transfer object /// /// - Parameter transfer: Transfer object to create on the server - init(with transfer: Transfer) { - name = transfer.name - description = transfer.description + init(with board: Board) { + name = board.name + description = board.description } } /// Response from create transfer request -struct CreateTransferResponse: Decodable { +struct CreateBoardResponse: Decodable { /// Server side identifier of the transfer let id: String // swiftlint:disable:this identifier_name /// The URL to where the transfer can be found online - let shortenedUrl: URL + let url: URL } // MARK: - Add files @@ -100,8 +165,6 @@ struct AddFilesParameters: Encodable { let filesize: UInt64 /// Type of content, currently always "file" let contentIdentifier: String = "file" - /// Identifier to uniquely identify file locally - let localIdentifier: String /// Initializes Item struct with a File struct /// @@ -109,7 +172,6 @@ struct AddFilesParameters: Encodable { init(with file: File) { filename = file.filename filesize = file.filesize - localIdentifier = file.localIdentifier } } diff --git a/WeTransfer/Server/Methods/AddFiles.swift b/WeTransfer/Server/Methods/AddFiles.swift index f8a741d..3207d7c 100644 --- a/WeTransfer/Server/Methods/AddFiles.swift +++ b/WeTransfer/Server/Methods/AddFiles.swift @@ -10,15 +10,16 @@ import Foundation extension WeTransfer { - /// Adds the given files to the provided transfer object and on the server side as well. When succeeded the files will be updated with the appropriate data like identifiers and information about the chunks + /// Adds the given files to the provided board object and on the server side as well. When succeeded the files will be updated with the appropriate data like identifiers and information about the chunks + /// Creates a remote instance of the board when that has not happended yet /// /// - Parameters: /// - files: File representations to be added to the transfer - /// - transfer: Transfer object to add the files to + /// - board: Board object to add the files to /// - completion: Closure to be executed when request has completed /// - result: Result with either the updated transfer object or an error when something went wrong - public static func add(_ files: [File], to transfer: Transfer, completion: @escaping (_ result: Result) -> Void) { - let operation = AddFilesOperation(transfer: transfer, files: files) + public static func add(_ files: [File], to board: Board, completion: @escaping (_ result: Result) -> Void) { + let operation = AddFilesOperation(board: board, files: files) operation.onResult = { result in DispatchQueue.main.async { completion(result) diff --git a/WeTransfer/Server/Methods/CreateBoard.swift b/WeTransfer/Server/Methods/CreateBoard.swift new file mode 100644 index 0000000..f964a91 --- /dev/null +++ b/WeTransfer/Server/Methods/CreateBoard.swift @@ -0,0 +1,32 @@ +// +// CreateTransfer.swift +// WeTransfer +// +// Created by Pim Coumans on 02/05/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import Foundation + +extension WeTransfer { + + /// Creates a board on the server and provides the given transfer object with an identifier and URL when succceeded. + /// + /// - Parameters: + /// - board: Local instance of the board to be created on the server + /// - completion: Closure that will be executed when the request or requests have finished + /// - result: Result with either the updated transfer object or an error when something went wrong + static func createExternalBoard(_ board: Board, completion: @escaping (_ result: Result) -> Void) { + + let callCompletion = { result in + DispatchQueue.main.async { + completion(result) + } + } + + let creationOperation = CreateBoardOperation(board: board) + + creationOperation.onResult = callCompletion + client.operationQueue.addOperation(creationOperation) + } +} diff --git a/WeTransfer/Server/Methods/Upload.swift b/WeTransfer/Server/Methods/Upload.swift index 6f3d347..1dcf072 100644 --- a/WeTransfer/Server/Methods/Upload.swift +++ b/WeTransfer/Server/Methods/Upload.swift @@ -36,10 +36,9 @@ extension WeTransfer { } let operation = UploadFilesOperation(transfer: transfer) - if transfer.identifier != nil { - changeState(.created(transfer)) - changeState(.uploading(operation.progress)) - } + changeState(.created(transfer)) + changeState(.uploading(operation.progress)) + operation.onResult = { result in switch result { case .failure(let error): diff --git a/WeTransfer/Server/Operations/AddFilesOperation.swift b/WeTransfer/Server/Operations/AddFilesOperation.swift index b10e1dd..8349256 100644 --- a/WeTransfer/Server/Operations/AddFilesOperation.swift +++ b/WeTransfer/Server/Operations/AddFilesOperation.swift @@ -8,9 +8,9 @@ import Foundation -/// Operation responsible for adding files to the provided transfer object and on the server as well. When succeeded the files will be updated with the appropriate data like identifiers and information about the chunks. -/// - Note: The files will be added to the provided transfer object when the operation has started executing -final class AddFilesOperation: ChainedAsynchronousResultOperation { +/// Operation responsible for adding files to the provided board object and on the server as well. When succeeded, the files will be updated with the appropriate data like identifiers and information about the chunks. +/// - Note: The files will be added to the provided board object when the operation has started executing +final class AddFilesOperation: ChainedAsynchronousResultOperation { /// The files to be added to the transfer if added during the initialization private var filesToAdd: [File]? @@ -20,19 +20,19 @@ final class AddFilesOperation: ChainedAsynchronousResultOperation { + + let board: Board + + /// Initalized the operation with the necessary parameters for a transfer + /// + /// - Parameter transfer: Transfer object with optionally some files already added + required init(board: Board) { + self.board = board + super.init() + } + + override func execute() { + guard board.identifier == nil else { + self.finish(with: .success(board)) + return + } + + let parameters = CreateBoardParameters(with: board) + WeTransfer.request(.createBoard(), parameters: parameters) { [weak self] result in + guard let self = self else { + return + } + switch result { + case .success(let response): + self.board.update(with: response.id, shortURL: response.url) + self.finish(with: .success(self.board)) + case .failure(let error): + self.finish(with: .failure(error)) + } + } + } +} diff --git a/WeTransferTests/AuthorizationTests.swift b/WeTransferTests/AuthorizationTests.swift index 89dc59f..37bc5fc 100644 --- a/WeTransferTests/AuthorizationTests.swift +++ b/WeTransferTests/AuthorizationTests.swift @@ -44,8 +44,8 @@ final class AuthorizationTests: XCTestCase { func testWrongJWTKey() { let authFailExpectation = expectation(description: "Request should failed") TestConfiguration.fakeAuthorize() - let transfer = Transfer(name: "Bad Transfer", description: nil) - WeTransfer.createTransfer(with: transfer) { result in + let board = Board(name: "Bad Transfer", description: nil) + WeTransfer.createExternalBoard(board) { result in if case .success = result { XCTFail("Request did not fail (like it should)") } diff --git a/WeTransferTests/Transfer/AddFilesTests.swift b/WeTransferTests/Board/AddFilesTests.swift similarity index 73% rename from WeTransferTests/Transfer/AddFilesTests.swift rename to WeTransferTests/Board/AddFilesTests.swift index 35cabd9..d6405a4 100644 --- a/WeTransferTests/Transfer/AddFilesTests.swift +++ b/WeTransferTests/Board/AddFilesTests.swift @@ -21,8 +21,9 @@ final class AddFilesTests: XCTestCase { TestConfiguration.resetConfiguration() } - func testAddingFilesToTransferModel() { - let transfer = Transfer(name: "Test Transfer", description: nil) + func testAddingFilesToBoardModel() { + + let transfer = Board(name: "Test Transfer", description: nil) guard let file = TestConfiguration.fileModel else { XCTFail("Could not create file model") return @@ -55,7 +56,7 @@ final class AddFilesTests: XCTestCase { } func testAddFilesRequest() { - let transfer = Transfer(name: "Test Transfer", description: nil) + let board = Board(name: "Test Transfer", description: nil) guard let file = TestConfiguration.fileModel else { XCTFail("File not available") @@ -63,25 +64,18 @@ final class AddFilesTests: XCTestCase { } let addedFilesExpectation = expectation(description: "Files are added") - - WeTransfer.createTransfer(with: transfer, completion: { (result) in + + WeTransfer.add([file], to: board, completion: { (result) in if case .failure(let error) = result { - XCTFail("Create transfer failed: \(error)") + XCTFail("Add files to transfer failed: \(error)") return } - - WeTransfer.add([file], to: transfer, completion: { (result) in - if case .failure(let error) = result { - XCTFail("Add files to transfer failed: \(error)") - return - } - addedFilesExpectation.fulfill() - }) + addedFilesExpectation.fulfill() }) waitForExpectations(timeout: 10) { _ in - XCTAssertFalse(transfer.files.isEmpty) - for file in transfer.files { + XCTAssertFalse(board.files.isEmpty) + for file in board.files { XCTAssertNotNil(file.identifier) XCTAssertFalse(file.isUploaded) } @@ -89,7 +83,7 @@ final class AddFilesTests: XCTestCase { } func testMulitpleFileRequests() { - let transfer = Transfer(name: "Test Transfer", description: nil) + let board = Board(name: "Test Transfer", description: nil) guard let file = TestConfiguration.fileModel, let smallFile = TestConfiguration.smallFileModel else { XCTFail("File not available") @@ -99,7 +93,7 @@ final class AddFilesTests: XCTestCase { let addedFirstFileExpectation = expectation(description: "First file was added") let addedSecondFileExpectation = expectation(description: "Second file was added") - WeTransfer.createTransfer(with: transfer, completion: { (result) in + WeTransfer.createExternalBoard(board) { result in if case .failure(let error) = result { XCTFail("Create transfer failed: \(error)") return @@ -107,7 +101,7 @@ final class AddFilesTests: XCTestCase { var firstFileCompleted = false - WeTransfer.add([file], to: transfer, completion: { (result) in + WeTransfer.add([file], to: board, completion: { (result) in if case .failure(let error) = result { XCTFail("Add files to transfer failed: \(error)") return @@ -117,20 +111,20 @@ final class AddFilesTests: XCTestCase { }) // Do the small file second and expect it to be completed after the first file completes - WeTransfer.add([smallFile], to: transfer, completion: { (result) in + WeTransfer.add([smallFile], to: board, completion: { (result) in if case .failure(let error) = result { XCTFail("Add files to transfer failed: \(error)") return } - XCTAssertEqual(transfer.files.count, 2) + XCTAssertEqual(board.files.count, 2) XCTAssertTrue(firstFileCompleted) addedSecondFileExpectation.fulfill() }) - }) + } waitForExpectations(timeout: 10) { _ in - XCTAssertFalse(transfer.files.isEmpty) - for file in transfer.files { + XCTAssertFalse(board.files.isEmpty) + for file in board.files { XCTAssertNotNil(file.identifier) XCTAssertFalse(file.isUploaded) } diff --git a/WeTransferTests/Board/CreateBoardTests.swift b/WeTransferTests/Board/CreateBoardTests.swift new file mode 100644 index 0000000..156826d --- /dev/null +++ b/WeTransferTests/Board/CreateBoardTests.swift @@ -0,0 +1,37 @@ +// +// CreateTransferTests.swift +// WeTransferTests +// +// Created by Pim Coumans on 22/05/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import XCTest +@testable import WeTransfer + +final class CreateBoardTests: XCTestCase { + + override func setUp() { + super.setUp() + TestConfiguration.configure(environment: .production) + } + + override func tearDown() { + super.tearDown() + TestConfiguration.resetConfiguration() + } + + func testCreateBoardRequest() { + let createdBoardExpectation = expectation(description: "Transfer is created") + let board = Board(name: "Test Transfer", description: nil) + WeTransfer.createExternalBoard(board, completion: { result in + if case .failure(let error) = result { + XCTFail("\(error.localizedDescription)") + } + createdBoardExpectation.fulfill() + }) + waitForExpectations(timeout: 10) { _ in + XCTAssertNotNil(board.identifier) + } + } +} diff --git a/WeTransferTests/Transfer/ChunksTests.swift b/WeTransferTests/Transfer/ChunksTests.swift index 16ded3f..95f4dff 100644 --- a/WeTransferTests/Transfer/ChunksTests.swift +++ b/WeTransferTests/Transfer/ChunksTests.swift @@ -22,9 +22,7 @@ final class ChunksTests: XCTestCase { } func testChunkCreationRequest() { - let transfer = Transfer(name: "Test Transfer", description: nil) - - guard let file = TestConfiguration.fileModel else { + guard let fileURL = TestConfiguration.imageFileURL else { XCTFail("File not available") return } @@ -34,14 +32,13 @@ final class ChunksTests: XCTestCase { let createdChunksExpectation = expectation(description: "Chunks are created") - WeTransfer.createTransfer(with: transfer, completion: { (result) in - if case .failure(let error) = result { + WeTransfer.createTransfer(saying: "Test Transfer", fileURLs: [fileURL]) { result in + switch result { + case .failure(let error): XCTFail("Creating transfer failed: \(error)") createdChunksExpectation.fulfill() return - } - - WeTransfer.add([file], to: transfer, completion: { (result) in + case .success(let transfer): if case .failure(let error) = result { XCTFail("Adding files failed: \(error)") createdChunksExpectation.fulfill() @@ -65,8 +62,8 @@ final class ChunksTests: XCTestCase { createdChunksExpectation.fulfill() } WeTransfer.client.operationQueue.addOperation(operation) - }) - }) + } + } waitForExpectations(timeout: 10) { _ in guard let file = updatedFile else { From 8a6c7f6f39ba7a24ea3419191a0c12d050bcc5a6 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:09:53 +0200 Subject: [PATCH 07/43] Added now required Content-Type header --- WeTransfer/Server/APIClient.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WeTransfer/Server/APIClient.swift b/WeTransfer/Server/APIClient.swift index c1149ed..17f40e5 100644 --- a/WeTransfer/Server/APIClient.swift +++ b/WeTransfer/Server/APIClient.swift @@ -56,6 +56,7 @@ extension APIClient { var request = URLRequest(endpoint: endpoint, baseURL: baseURL, apiKey: apiKey) request = authenticator.authenticatedRequest(from: request) + request.setValue("application/json", forHTTPHeaderField: "Content-Type") if let data = data { request.httpBody = data From 89114dc21377d8d1f26b2635a523ef1bb4466f3e Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:13:22 +0200 Subject: [PATCH 08/43] Seperated tests for transfers and boards --- WeTransfer.xcodeproj/project.pbxproj | 42 +++++-- WeTransfer/Server/Endpoints/Endpoints.swift | 112 ++++++++++++------ WeTransferTests/Board/BoardChunksTests.swift | 74 ++++++++++++ WeTransferTests/Board/BoardUploadTests.swift | 76 ++++++++++++ .../Board/SimpleBoardUploadTests.swift | 63 ++++++++++ WeTransferTests/RequestTests.swift | 42 +++++-- ...sTests.swift => TransferChunksTests.swift} | 18 ++- ...dTests.swift => TransferUploadTests.swift} | 2 +- 8 files changed, 359 insertions(+), 70 deletions(-) create mode 100644 WeTransferTests/Board/BoardChunksTests.swift create mode 100644 WeTransferTests/Board/BoardUploadTests.swift create mode 100644 WeTransferTests/Board/SimpleBoardUploadTests.swift rename WeTransferTests/Transfer/{ChunksTests.swift => TransferChunksTests.swift} (76%) rename WeTransferTests/Transfer/{UploadTests.swift => TransferUploadTests.swift} (97%) diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index e9e54d5..bd2fe5b 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -39,8 +39,8 @@ 3E9915AC2111B0C50071FDF7 /* SimpleTransferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7348C120B41936009B41D0 /* SimpleTransferTests.swift */; }; 3E9915AD2111B0C50071FDF7 /* CreateTransferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED1C0A220B43E980045D8B3 /* CreateTransferTests.swift */; }; 3E9915AE2111B0C50071FDF7 /* AddFilesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED1C0A420B442F70045D8B3 /* AddFilesTests.swift */; }; - 3E9915AF2111B0C50071FDF7 /* ChunksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E90734620BBEF3700AE3605 /* ChunksTests.swift */; }; - 3E9915B02111B0C50071FDF7 /* UploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E90734820BBFE5600AE3605 /* UploadTests.swift */; }; + 3E9915AF2111B0C50071FDF7 /* TransferChunksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E90734620BBEF3700AE3605 /* TransferChunksTests.swift */; }; + 3E9915B02111B0C50071FDF7 /* TransferUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E90734820BBFE5600AE3605 /* TransferUploadTests.swift */; }; 3E9915B12111B0C50071FDF7 /* TestConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7348BD20B4040D009B41D0 /* TestConfiguration.swift */; }; 3E9915B22111B0C50071FDF7 /* InitializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7348BF20B41895009B41D0 /* InitializationTests.swift */; }; 3E9915B32111B0C50071FDF7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7348C520B41EC0009B41D0 /* RequestTests.swift */; }; @@ -51,8 +51,8 @@ 3E9915B82111B0C60071FDF7 /* SimpleTransferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7348C120B41936009B41D0 /* SimpleTransferTests.swift */; }; 3E9915B92111B0C60071FDF7 /* CreateTransferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED1C0A220B43E980045D8B3 /* CreateTransferTests.swift */; }; 3E9915BA2111B0C60071FDF7 /* AddFilesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED1C0A420B442F70045D8B3 /* AddFilesTests.swift */; }; - 3E9915BB2111B0C60071FDF7 /* ChunksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E90734620BBEF3700AE3605 /* ChunksTests.swift */; }; - 3E9915BC2111B0C60071FDF7 /* UploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E90734820BBFE5600AE3605 /* UploadTests.swift */; }; + 3E9915BB2111B0C60071FDF7 /* TransferChunksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E90734620BBEF3700AE3605 /* TransferChunksTests.swift */; }; + 3E9915BC2111B0C60071FDF7 /* TransferUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E90734820BBFE5600AE3605 /* TransferUploadTests.swift */; }; 3E9915BD2111B0C60071FDF7 /* TestConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7348BD20B4040D009B41D0 /* TestConfiguration.swift */; }; 3E9915BE2111B0C60071FDF7 /* InitializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7348BF20B41895009B41D0 /* InitializationTests.swift */; }; 3E9915BF2111B0C60071FDF7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7348C520B41EC0009B41D0 /* RequestTests.swift */; }; @@ -84,6 +84,12 @@ 3E9915D92111B1A30071FDF7 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9413BC2098BC25003A42D1 /* APIClient.swift */; }; 3E9915DA2111B1A30071FDF7 /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8EB08B20E503E2002EB5AB /* Authenticator.swift */; }; 3E9915DE211201150071FDF7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E5E514820EA816600485FA3 /* Assets.xcassets */; }; + 3E9D48D32166535D00499760 /* SimpleBoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */; }; + 3E9D48D42166535D00499760 /* SimpleBoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */; }; + 3E9D48D62166537D00499760 /* BoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D52166537C00499760 /* BoardUploadTests.swift */; }; + 3E9D48D72166537D00499760 /* BoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D52166537C00499760 /* BoardUploadTests.swift */; }; + 3E9D48E1216B89CB00499760 /* BoardChunksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48E0216B89CB00499760 /* BoardChunksTests.swift */; }; + 3E9D48E2216B89CB00499760 /* BoardChunksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48E0216B89CB00499760 /* BoardChunksTests.swift */; }; 3EA0EFBA20BD4D90009E4BB1 /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA0EFB920BD4D90009E4BB1 /* AsynchronousOperation.swift */; }; 3EA0EFCE20BD7743009E4BB1 /* AsynchronousResultOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA0EFCD20BD7743009E4BB1 /* AsynchronousResultOperation.swift */; }; 3EB1E192215916B000E1E4EE /* Board.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB1E191215916AF00E1E4EE /* Board.swift */; }; @@ -168,8 +174,8 @@ 3E80E0A92091D12B00114711 /* Transfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transfer.swift; sourceTree = ""; }; 3E80E0AD2091D16E00114711 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 3E8EB08B20E503E2002EB5AB /* Authenticator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authenticator.swift; sourceTree = ""; }; - 3E90734620BBEF3700AE3605 /* ChunksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChunksTests.swift; sourceTree = ""; }; - 3E90734820BBFE5600AE3605 /* UploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadTests.swift; sourceTree = ""; }; + 3E90734620BBEF3700AE3605 /* TransferChunksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferChunksTests.swift; sourceTree = ""; }; + 3E90734820BBFE5600AE3605 /* TransferUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferUploadTests.swift; sourceTree = ""; }; 3E9413BC2098BC25003A42D1 /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = ""; }; 3E9413BE2098BF5A003A42D1 /* Endpoints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoints.swift; sourceTree = ""; }; 3E9413C12098C132003A42D1 /* Authorize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authorize.swift; sourceTree = ""; }; @@ -179,6 +185,9 @@ 3E9413C92099DB05003A42D1 /* Upload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Upload.swift; sourceTree = ""; }; 3E9413CB2099E390003A42D1 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; 3E9413CD2099E3F5003A42D1 /* Chunk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chunk.swift; sourceTree = ""; }; + 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleBoardUploadTests.swift; sourceTree = ""; }; + 3E9D48D52166537C00499760 /* BoardUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardUploadTests.swift; sourceTree = ""; }; + 3E9D48E0216B89CB00499760 /* BoardChunksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardChunksTests.swift; sourceTree = ""; }; 3EA0EFB920BD4D90009E4BB1 /* AsynchronousOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousOperation.swift; sourceTree = ""; }; 3EA0EFCD20BD7743009E4BB1 /* AsynchronousResultOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousResultOperation.swift; sourceTree = ""; }; 3EB1E191215916AF00E1E4EE /* Board.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Board.swift; sourceTree = ""; }; @@ -248,8 +257,11 @@ 3E3A87612162549F00568775 /* Board */ = { isa = PBXGroup; children = ( + 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */, 3E3A87622162798800568775 /* CreateBoardTests.swift */, 3ED1C0A420B442F70045D8B3 /* AddFilesTests.swift */, + 3E9D48E0216B89CB00499760 /* BoardChunksTests.swift */, + 3E9D48D52166537C00499760 /* BoardUploadTests.swift */, ); path = Board; sourceTree = ""; @@ -348,8 +360,8 @@ children = ( 3E7348C120B41936009B41D0 /* SimpleTransferTests.swift */, 3ED1C0A220B43E980045D8B3 /* CreateTransferTests.swift */, - 3E90734620BBEF3700AE3605 /* ChunksTests.swift */, - 3E90734820BBFE5600AE3605 /* UploadTests.swift */, + 3E90734620BBEF3700AE3605 /* TransferChunksTests.swift */, + 3E90734820BBFE5600AE3605 /* TransferUploadTests.swift */, ); path = Transfer; sourceTree = ""; @@ -748,15 +760,18 @@ buildActionMask = 2147483647; files = ( 3E9915AD2111B0C50071FDF7 /* CreateTransferTests.swift in Sources */, + 3E9D48E1216B89CB00499760 /* BoardChunksTests.swift in Sources */, 3E9915B52111B0C50071FDF7 /* Secrets.swift in Sources */, 3E9915B42111B0C50071FDF7 /* AuthorizationTests.swift in Sources */, 3E9915B32111B0C50071FDF7 /* RequestTests.swift in Sources */, 3E9915AC2111B0C50071FDF7 /* SimpleTransferTests.swift in Sources */, - 3E9915B02111B0C50071FDF7 /* UploadTests.swift in Sources */, + 3E9915B02111B0C50071FDF7 /* TransferUploadTests.swift in Sources */, + 3E9D48D32166535D00499760 /* SimpleBoardUploadTests.swift in Sources */, 3E3A87642162798800568775 /* CreateBoardTests.swift in Sources */, 3E9915AE2111B0C50071FDF7 /* AddFilesTests.swift in Sources */, 3E9915B12111B0C50071FDF7 /* TestConfiguration.swift in Sources */, - 3E9915AF2111B0C50071FDF7 /* ChunksTests.swift in Sources */, + 3E9915AF2111B0C50071FDF7 /* TransferChunksTests.swift in Sources */, + 3E9D48D62166537D00499760 /* BoardUploadTests.swift in Sources */, 3E9915B22111B0C50071FDF7 /* InitializationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -801,15 +816,18 @@ buildActionMask = 2147483647; files = ( 3E9915B92111B0C60071FDF7 /* CreateTransferTests.swift in Sources */, + 3E9D48E2216B89CB00499760 /* BoardChunksTests.swift in Sources */, 3E9915C12111B0C60071FDF7 /* Secrets.swift in Sources */, 3E9915C02111B0C60071FDF7 /* AuthorizationTests.swift in Sources */, 3E9915BF2111B0C60071FDF7 /* RequestTests.swift in Sources */, 3E9915B82111B0C60071FDF7 /* SimpleTransferTests.swift in Sources */, - 3E9915BC2111B0C60071FDF7 /* UploadTests.swift in Sources */, + 3E9915BC2111B0C60071FDF7 /* TransferUploadTests.swift in Sources */, + 3E9D48D42166535D00499760 /* SimpleBoardUploadTests.swift in Sources */, 3E3A87632162798800568775 /* CreateBoardTests.swift in Sources */, 3E9915BA2111B0C60071FDF7 /* AddFilesTests.swift in Sources */, 3E9915BD2111B0C60071FDF7 /* TestConfiguration.swift in Sources */, - 3E9915BB2111B0C60071FDF7 /* ChunksTests.swift in Sources */, + 3E9915BB2111B0C60071FDF7 /* TransferChunksTests.swift in Sources */, + 3E9D48D72166537D00499760 /* BoardUploadTests.swift in Sources */, 3E9915BE2111B0C60071FDF7 /* InitializationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/WeTransfer/Server/Endpoints/Endpoints.swift b/WeTransfer/Server/Endpoints/Endpoints.swift index 1ce8b15..39ef2a2 100644 --- a/WeTransfer/Server/Endpoints/Endpoints.swift +++ b/WeTransfer/Server/Endpoints/Endpoints.swift @@ -30,32 +30,65 @@ extension APIEndpoint { return APIEndpoint(method: .post, path: "boards") } - /// Adds files to an existing transfer + /// Adds files to an existing board /// - /// - Parameter transferIdentifier: Identifier of the transfer to add the files to - /// - Returns: APIEndpoint with `POST` to `/transfers/{id}/items`, expecting an array of `AddFilesResponse` structs as response - static func addItems(transferIdentifier: String) -> APIEndpoint<[AddFilesResponse]> { - return APIEndpoint<[AddFilesResponse]>(method: .post, path: "transfers/\(transferIdentifier)/items") + /// - Parameter boardIdentifier: Identifier of the board to add the files to + /// - Returns: APIEndpoint with `POST` to `/boards/{id}/items`, expecting an array of `AddFilesResponse` structs as response + static func addFiles(boardIdentifier: String) -> APIEndpoint<[AddFilesResponse]> { + return APIEndpoint<[AddFilesResponse]>(method: .post, path: "boards/\(boardIdentifier)/files") } /// Requests upload info of a chunk of a file to be uploaded /// /// - Parameters: + /// - transferIdentifier: Identifier of the transfer containing the file + /// - fileIdentifier: Identifier of the file to get the chunk info of + /// - chunkIndex: Index of the chunk + /// - Returns: APIEndpoint with `GET` to `/transfers/files/{file-id}/upload-url/{part-number}/ + static func requestTransferUploadURL(transferIdentifier: String, fileIdentifier: String, chunkIndex: Int) -> APIEndpoint { + let partNumber = chunkIndex + 1 + return APIEndpoint(method: .get, path: "transfers/\(transferIdentifier)/files/\(fileIdentifier)/upload-url/\(partNumber)") + } + + /// Requests upload info of a chunk of a file to be uploaded + /// + /// - Parameters: + /// - boardIndentifier: dentifier of the board containing the file /// - fileIdentifier: Identifier of the file to get the chunk info of /// - chunkIndex: Index of the chunk /// - multipartIdentifier: Multipart identifier of the file /// - Returns: APIEndpoint with `GET` to `/files/{file-id}/uploads/{chunk-number}/{multipart-id}` - static func requestUploadURL(fileIdentifier: String, chunkIndex: Int, multipartIdentifier: String) -> APIEndpoint { + static func requestBoardUploadURL(boardIdentifier: String, fileIdentifier: String, chunkIndex: Int, multipartIdentifier: String) -> APIEndpoint { let partNumber = chunkIndex + 1 - return APIEndpoint(method: .get, path: "files/\(fileIdentifier)/uploads/\(partNumber)/\(multipartIdentifier)") + return APIEndpoint(method: .get, path: "boards/\(boardIdentifier)/files/\(fileIdentifier)/upload-url/\(partNumber)/\(multipartIdentifier)") } /// Completes the upload of file, assuming all chunks have finished uploading /// - /// - Parameter fileIdentifier: Identifier of the file + /// - Parameters: + /// - transferIdentifier: Identifier for the containing transfer + /// - fileIdentifier: Identifier of the file /// - Returns: APIEndpoint with `POST` to `/files/{file-id}/uploads/complete` - static func completeUpload(fileIdentifier: String) -> APIEndpoint { - return APIEndpoint(method: .post, path: "files/\(fileIdentifier)/uploads/complete") + static func completeTransferFileUpload(transferIdentifier: String, fileIdentifier: String) -> APIEndpoint { + return APIEndpoint(method: .put, path: "transfers/\(transferIdentifier)/files/\(fileIdentifier)/uploads/complete") + } + + /// Completes the upload of file, assuming all chunks have finished uploading + /// + /// - Parameters: + /// - boardIdentifier: Identifier for the containing board + /// - fileIdentifier: Identifier of the file + /// - Returns: APIEndpoint with `POST` to `/files/{file-id}/uploads/complete` + static func completeBoardFileUpload(boardIdentifier: String, fileIdentifier: String) -> APIEndpoint { + return APIEndpoint(method: .put, path: "boards/\(boardIdentifier)/files/\(fileIdentifier)/upload-complete") + } + + /// Finilizes the transfer, resulting in an URL to be added to the transfer + /// + /// - Parameter transferIdentifier: Identifier for the transfer + /// - Returns: APIEndopint with `PUT` to `transfers/{transfer-id}/finalize` + static func finalizeTransfer(transferIdentifier: String) -> APIEndpoint { + return APIEndpoint(method: .put, path: "transfers/\(transferIdentifier)/finalize") } } @@ -157,33 +190,31 @@ struct CreateBoardResponse: Decodable { /// Parameters used for the add files request struct AddFilesParameters: Encodable { - /// Describes a file to be added to the transfer - struct Item: Encodable { + /// Describes a file to be added to a board + struct FileParameters: Encodable { /// Full name of file (e.g. "photo.jpg") - let filename: String + let name: String /// Filesize in bytes - let filesize: UInt64 - /// Type of content, currently always "file" - let contentIdentifier: String = "file" + let size: UInt64 /// Initializes Item struct with a File struct /// /// - Parameter file: File struct to initialize Item from init(with file: File) { - filename = file.filename - filesize = file.filesize + name = file.filename + size = file.filesize } } /// All items to be added to the transfer - let items: [Item] + let files: [FileParameters] /// Initalizes the parameters with an array of File structs /// /// - Parameter files: Array of File structs to be added to the transfer init(with files: [File]) { - items = files.map { file in - return Item(with: file) + self.files = files.map { file in + return FileParameters(with: file) } } } @@ -191,17 +222,16 @@ struct AddFilesParameters: Encodable { /// Response from the add files request struct AddFilesResponse: Decodable { /// Contains information about the chunks and the upload identifier - struct Meta: Decodable { - let multipartParts: Int - let multipartUploadId: String + struct Multipart: Decodable { + let id: String + let partNumbers: Int + let chunkSize: UInt64 } /// Identifier of the File on the server let id: String - /// Local identifier of the file to identify the local file with - let localIdentifier: String /// Number of multiparts (chunks) and upload identifier for uploading - let meta: Meta + let multipart: Multipart } // MARK: - Request upload URL @@ -209,19 +239,33 @@ struct AddFilesResponse: Decodable { /// Response from the add upload url request for chunks struct AddUploadURLResponse: Decodable { /// URL to upload the chunk to - let uploadUrl: URL - /// Number of the chunk - let partNumber: Int - /// Time interval when the upload URL is no longer valid and upload URL should be requested again - let uploadExpiresAt: TimeInterval + let url: URL } // MARK: - Complete upload +/// Parameters used for the complete file upload request +struct CompleteTransferFileUploadParameters: Encodable { + /// Number of chunks used for the file + let partNumbers: Int +} + /// Response from complete upload request -struct CompleteUploadResponse: Decodable { +struct CompleteTransferFileUploadResponse: Decodable { +} + +/// Response from complete upload request +struct CompleteBoardFileUploadResponse: Decodable { /// Whether the upload of all the chunks has succeeded - let ok: Bool // swiftlint:disable:this identifier_name + let success: Bool /// Message describing either success or failure of chunk uploads let message: String } + +// MARK: - Finalize transfer + +/// Response for the finalize transfer call +struct FinalizeTransferResponse: Decodable { + /// Public URL of the finalized transfer + let url: URL +} diff --git a/WeTransferTests/Board/BoardChunksTests.swift b/WeTransferTests/Board/BoardChunksTests.swift new file mode 100644 index 0000000..40adf15 --- /dev/null +++ b/WeTransferTests/Board/BoardChunksTests.swift @@ -0,0 +1,74 @@ +// +// BoardChunksTests.swift +// WeTransferTests +// +// Created by Pim Coumans on 28/05/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import XCTest +@testable import WeTransfer + +final class BoardChunksTests: XCTestCase { + + override func setUp() { + super.setUp() + TestConfiguration.configure(environment: .production) + } + + override func tearDown() { + super.tearDown() + TestConfiguration.resetConfiguration() + } + + func testChunkCreationRequest() { + guard let file = TestConfiguration.fileModel else { + XCTFail("File not available") + return + } + + var updatedFile: File? + var createdChunk: Chunk? + + let createdChunksExpectation = expectation(description: "Chunks are created") + + let board = Board(name: "Test board", description: nil) + WeTransfer.add([file], to: board) { (result) in + switch result { + case .failure(let error): + XCTFail("Creating board failed: \(error)") + createdChunksExpectation.fulfill() + return + case .success(let board): + updatedFile = board.files.first + guard let file = updatedFile else { + XCTFail("File not added to transfer") + createdChunksExpectation.fulfill() + return + } + let operation = CreateChunkOperation(container: board, file: file, chunkIndex: 0) + operation.onResult = { result in + switch result { + case .failure(let error): + XCTFail("Creating chunk failed: \(error)") + case .success(let chunk): + createdChunk = chunk + } + createdChunksExpectation.fulfill() + } + WeTransfer.client.operationQueue.addOperation(operation) + } + } + + waitForExpectations(timeout: 10) { _ in + guard let file = updatedFile else { + XCTFail("File not created") + return + } + XCTAssertNotNil(file.numberOfChunks, "File object doesn't have numberOfChunks") + XCTAssertNotNil(file.multipartUploadIdentifier, "File object doesn't have a multipart upload identifier") + XCTAssertEqual(file.numberOfChunks, Int(ceil(Double(file.filesize) / Double(file.chunkSize ?? Chunk.defaultChunkSize))), "File doesn't have correct number of chunks") + XCTAssertNotNil(createdChunk, "Chunk not created") + } + } +} diff --git a/WeTransferTests/Board/BoardUploadTests.swift b/WeTransferTests/Board/BoardUploadTests.swift new file mode 100644 index 0000000..7f6b8e3 --- /dev/null +++ b/WeTransferTests/Board/BoardUploadTests.swift @@ -0,0 +1,76 @@ +// +// ChunksTests.swift +// WeTransferTests +// +// Created by Pim Coumans on 28/05/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import XCTest +@testable import WeTransfer + +final class BoardUploadTests: XCTestCase { + + override func setUp() { + super.setUp() + TestConfiguration.configure(environment: .production) + } + + override func tearDown() { + super.tearDown() + TestConfiguration.resetConfiguration() + } + + var observation: NSKeyValueObservation? + + func testFileUpload() { + + guard let file = TestConfiguration.fileModel else { + XCTFail("File not available") + return + } + + let filesUploadedExpectation = expectation(description: "Files are uploaded") + + let board = Board(name: "Test Board", description: nil) + WeTransfer.add([file], to: board) { (result) in + if case .failure(let error) = result { + XCTFail("Adding files failed: \(error)") + filesUploadedExpectation.fulfill() + } + + WeTransfer.upload(board, stateChanged: { (state) in + switch state { + case .created(let board): + print("Transfer created: \(String(describing: board.identifier))") + case .uploading(let progress): + print("Upload started") + var percentage = 0.0 + self.observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, _) in + let newPercentage = (progress.fractionCompleted * 100).rounded(FloatingPointRoundingRule.up) + if newPercentage != percentage { + percentage = newPercentage + print("PROGRESS: \(newPercentage)% (\(progress.completedUnitCount) bytes)") + } + }) + case .failed(let error): + XCTFail("Sending transfer failed: \(error)") + filesUploadedExpectation.fulfill() + case .completed: + filesUploadedExpectation.fulfill() + } + }) + } + + waitForExpectations(timeout: 60) { _ in + self.observation = nil + if let url = board.shortURL { + print("Transfer uploaded: \(url)") + } + XCTAssertNotNil(board.shortURL) + for file in board.files { + XCTAssertTrue(file.isUploaded) + } + } + } +} diff --git a/WeTransferTests/Board/SimpleBoardUploadTests.swift b/WeTransferTests/Board/SimpleBoardUploadTests.swift new file mode 100644 index 0000000..6a5904b --- /dev/null +++ b/WeTransferTests/Board/SimpleBoardUploadTests.swift @@ -0,0 +1,63 @@ +// +// SimpleTransferTests.swift +// WeTransferTests +// +// Created by Pim Coumans on 22/05/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import XCTest +@testable import WeTransfer + +final class SimpleBoardUploadTests: XCTestCase { + + override func setUp() { + super.setUp() + TestConfiguration.configure(environment: .production) + } + + override func tearDown() { + super.tearDown() + TestConfiguration.resetConfiguration() + } + + func testSimpleUpload() { + + guard let fileURL = TestConfiguration.imageFileURL else { + XCTFail("Test image not found") + return + } + + let simpleBoardUploadExpectation = expectation(description: "All files are uploaded") + var updatedBoard: Board? + var timer: Timer? + + WeTransfer.uploadBoard(named: "Test board", description: nil, containing: [fileURL]) { state in + switch state { + case .created(let board): + print("Board created: \(board)") + case .uploading(let progress): + print("Uploading files...") + timer = Timer(timeInterval: 1 / 30, repeats: true, block: { _ in + print("Progress: \(progress.fractionCompleted)") + }) + RunLoop.main.add(timer!, forMode: RunLoopMode.commonModes) + case .completed(let board): + timer?.invalidate() + timer = nil + print("Files uploaded: \(String(describing: board.shortURL))") + updatedBoard = board + simpleBoardUploadExpectation.fulfill() + case .failed(let error): + timer?.invalidate() + timer = nil + XCTFail("Creation/upload failed: \(error)") + simpleBoardUploadExpectation.fulfill() + } + } + + waitForExpectations(timeout: 60) { _ in + XCTAssertNotNil(updatedBoard, "Board upload was not completed") + } + } +} diff --git a/WeTransferTests/RequestTests.swift b/WeTransferTests/RequestTests.swift index 43c3332..4c25dc8 100644 --- a/WeTransferTests/RequestTests.swift +++ b/WeTransferTests/RequestTests.swift @@ -17,7 +17,7 @@ final class RequestTests: XCTestCase { } func testEndpoints() { - let baseURLString = "https://dev.wetransfer.com/v1/" + let baseURLString = "https://dev.wetransfer.com/v2/" let baseURL = URL(string: baseURLString)! let authorizeEndpoint: APIEndpoint = .authorize() @@ -27,22 +27,40 @@ final class RequestTests: XCTestCase { let createTransferEndpoint: APIEndpoint = .createTransfer() XCTAssertEqual(createTransferEndpoint.method, .post) XCTAssertEqual(createTransferEndpoint.url(with: baseURL).absoluteString, baseURLString + "transfers") + + let createBoardEndpoint: APIEndpoint = .createBoard() + XCTAssertEqual(createBoardEndpoint.method, .post) + XCTAssertEqual(createBoardEndpoint.url(with: baseURL).absoluteString, baseURLString + "boards") - let itemIdentifier = "1234567890" - let addItemsTransferEndpoint: APIEndpoint = .addItems(transferIdentifier: itemIdentifier) - XCTAssertEqual(addItemsTransferEndpoint.method, .post) - XCTAssertEqual(addItemsTransferEndpoint.url(with: baseURL).absoluteString, baseURLString + "transfers/\(itemIdentifier)/items") - + let transferIdentifier = UUID().uuidString + let boardIdentifier = UUID().uuidString let fileIdentifier = UUID().uuidString let chunkIndex = 5 let multipartIdentifier = UUID().uuidString - let requestUploadURLEndpoint: APIEndpoint = .requestUploadURL(fileIdentifier: fileIdentifier, chunkIndex: chunkIndex, multipartIdentifier: multipartIdentifier) - XCTAssertEqual(requestUploadURLEndpoint.method, .get) - XCTAssertEqual(requestUploadURLEndpoint.url(with: baseURL).absoluteString, baseURLString + "files/\(fileIdentifier)/uploads/\(chunkIndex + 1)/\(multipartIdentifier)") + + let addFilesTransferEndpoint: APIEndpoint = .addFiles(boardIdentifier: boardIdentifier) + XCTAssertEqual(addFilesTransferEndpoint.method, .post) + XCTAssertEqual(addFilesTransferEndpoint.url(with: baseURL).absoluteString, baseURLString + "boards/\(boardIdentifier)/files") + + let requestTransferUploadURLEndpoint: APIEndpoint = .requestTransferUploadURL(transferIdentifier: transferIdentifier, fileIdentifier: fileIdentifier, chunkIndex: chunkIndex) + XCTAssertEqual(requestTransferUploadURLEndpoint.method, .get) + XCTAssertEqual(requestTransferUploadURLEndpoint.url(with: baseURL).absoluteString, baseURLString + "transfers/\(transferIdentifier)/files/\(fileIdentifier)/upload-url/\(chunkIndex + 1)") + + let requestBoardUploadURLEndpoint: APIEndpoint = .requestBoardUploadURL(boardIdentifier: boardIdentifier, fileIdentifier: fileIdentifier, chunkIndex: chunkIndex, multipartIdentifier: multipartIdentifier) + XCTAssertEqual(requestBoardUploadURLEndpoint.method, .get) + XCTAssertEqual(requestBoardUploadURLEndpoint.url(with: baseURL).absoluteString, baseURLString + "boards/\(boardIdentifier)/files/\(fileIdentifier)/upload-url/\(chunkIndex + 1)/\(multipartIdentifier)") - let completeUploadEndpoint: APIEndpoint = .completeUpload(fileIdentifier: fileIdentifier) - XCTAssertEqual(completeUploadEndpoint.method, .post) - XCTAssertEqual(completeUploadEndpoint.url(with: baseURL).absoluteString, baseURLString + "files/\(fileIdentifier)/uploads/complete") + let completeTransferFileUploadEndpoint: APIEndpoint = .completeTransferFileUpload(transferIdentifier: transferIdentifier, fileIdentifier: fileIdentifier) + XCTAssertEqual(completeTransferFileUploadEndpoint.method, .put) + XCTAssertEqual(completeTransferFileUploadEndpoint.url(with: baseURL).absoluteString, baseURLString + "transfers/\(transferIdentifier)/files/\(fileIdentifier)/uploads/complete") + + let completeBoardFileUploadEndpoint: APIEndpoint = .completeBoardFileUpload(boardIdentifier: boardIdentifier, fileIdentifier: fileIdentifier) + XCTAssertEqual(completeBoardFileUploadEndpoint.method, .put) + XCTAssertEqual(completeBoardFileUploadEndpoint.url(with: baseURL).absoluteString, baseURLString + "boards/\(boardIdentifier)/files/\(fileIdentifier)/upload-complete") + + let finlizeTransferEndpoint: APIEndpoint = .finalizeTransfer(transferIdentifier: transferIdentifier) + XCTAssertEqual(finlizeTransferEndpoint.method, .put) + XCTAssertEqual(finlizeTransferEndpoint.url(with: baseURL).absoluteString, baseURLString + "transfers/\(transferIdentifier)/finalize") } func testUnconfiguredRequestCreation() { diff --git a/WeTransferTests/Transfer/ChunksTests.swift b/WeTransferTests/Transfer/TransferChunksTests.swift similarity index 76% rename from WeTransferTests/Transfer/ChunksTests.swift rename to WeTransferTests/Transfer/TransferChunksTests.swift index 95f4dff..35ee536 100644 --- a/WeTransferTests/Transfer/ChunksTests.swift +++ b/WeTransferTests/Transfer/TransferChunksTests.swift @@ -1,5 +1,5 @@ // -// ChunksTests.swift +// TransferChunksTests // WeTransferTests // // Created by Pim Coumans on 28/05/2018. @@ -9,7 +9,7 @@ import XCTest @testable import WeTransfer -final class ChunksTests: XCTestCase { +final class TransferChunksTests: XCTestCase { override func setUp() { super.setUp() @@ -39,19 +39,13 @@ final class ChunksTests: XCTestCase { createdChunksExpectation.fulfill() return case .success(let transfer): - if case .failure(let error) = result { - XCTFail("Adding files failed: \(error)") - createdChunksExpectation.fulfill() - return - } - updatedFile = transfer.files.first guard let file = updatedFile else { XCTFail("File not added to transfer") createdChunksExpectation.fulfill() return } - let operation = CreateChunkOperation(file: file, chunkIndex: 0) + let operation = CreateChunkOperation(container: transfer, file: file, chunkIndex: 0) operation.onResult = { result in switch result { case .failure(let error): @@ -71,8 +65,10 @@ final class ChunksTests: XCTestCase { return } XCTAssertNotNil(file.numberOfChunks, "File object doesn't have numberOfChunks") - XCTAssertNotNil(file.multipartUploadIdentifier, "File object doesn't have numberOfChunks") - XCTAssertEqual(file.numberOfChunks, Int(ceil(Double(file.filesize) / Double(Chunk.defaultChunkSize))), "File doesn't have correct number of chunks") + XCTAssertNotNil(file.chunkSize, "File object must have a chunkSize") + if let chunkSize = file.chunkSize { + XCTAssertEqual(file.numberOfChunks, Int(ceil(Double(file.filesize) / Double(chunkSize))), "File doesn't have correct number of chunks") + } XCTAssertNotNil(createdChunk, "Chunk not created") } } diff --git a/WeTransferTests/Transfer/UploadTests.swift b/WeTransferTests/Transfer/TransferUploadTests.swift similarity index 97% rename from WeTransferTests/Transfer/UploadTests.swift rename to WeTransferTests/Transfer/TransferUploadTests.swift index dae88db..2218b79 100644 --- a/WeTransferTests/Transfer/UploadTests.swift +++ b/WeTransferTests/Transfer/TransferUploadTests.swift @@ -9,7 +9,7 @@ import XCTest @testable import WeTransfer -final class UploadTests: XCTestCase { +final class TransferUploadTests: XCTestCase { override func setUp() { super.setUp() From dff5da63a448b60a6b7a41cfa157330057a000d7 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:24:37 +0200 Subject: [PATCH 09/43] Transferrable protocol for both transfers and boards --- WeTransfer.xcodeproj/project.pbxproj | 6 ++++++ WeTransfer/Models/Board.swift | 24 ++++++++++++------------ WeTransfer/Models/Transfer.swift | 7 +++---- WeTransfer/Models/Transferrable.swift | 16 ++++++++++++++++ 4 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 WeTransfer/Models/Transferrable.swift diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index bd2fe5b..92e89cb 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -84,6 +84,8 @@ 3E9915D92111B1A30071FDF7 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9413BC2098BC25003A42D1 /* APIClient.swift */; }; 3E9915DA2111B1A30071FDF7 /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8EB08B20E503E2002EB5AB /* Authenticator.swift */; }; 3E9915DE211201150071FDF7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E5E514820EA816600485FA3 /* Assets.xcassets */; }; + 3E9D48D021663EE400499760 /* Transferrable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48CF21663EE400499760 /* Transferrable.swift */; }; + 3E9D48D121663EE400499760 /* Transferrable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48CF21663EE400499760 /* Transferrable.swift */; }; 3E9D48D32166535D00499760 /* SimpleBoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */; }; 3E9D48D42166535D00499760 /* SimpleBoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */; }; 3E9D48D62166537D00499760 /* BoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D52166537C00499760 /* BoardUploadTests.swift */; }; @@ -185,6 +187,7 @@ 3E9413C92099DB05003A42D1 /* Upload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Upload.swift; sourceTree = ""; }; 3E9413CB2099E390003A42D1 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; 3E9413CD2099E3F5003A42D1 /* Chunk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chunk.swift; sourceTree = ""; }; + 3E9D48CF21663EE400499760 /* Transferrable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transferrable.swift; sourceTree = ""; }; 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleBoardUploadTests.swift; sourceTree = ""; }; 3E9D48D52166537C00499760 /* BoardUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardUploadTests.swift; sourceTree = ""; }; 3E9D48E0216B89CB00499760 /* BoardChunksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardChunksTests.swift; sourceTree = ""; }; @@ -294,6 +297,7 @@ 3EB1E191215916AF00E1E4EE /* Board.swift */, 3E9413CB2099E390003A42D1 /* File.swift */, 3E9413CD2099E3F5003A42D1 /* Chunk.swift */, + 3E9D48CF21663EE400499760 /* Transferrable.swift */, ); path = Models; sourceTree = ""; @@ -732,6 +736,7 @@ 3E0EEBB420C175D0009E6B2D /* AddFilesOperation.swift in Sources */, 3E80E0A72091D0E700114711 /* WeTransfer.swift in Sources */, 3E80E0AE2091D16E00114711 /* Result.swift in Sources */, + 3E9D48D021663EE400499760 /* Transferrable.swift in Sources */, 3E9413CA2099DB05003A42D1 /* Upload.swift in Sources */, 3EB1E1982159283600E1E4EE /* CreateBoard.swift in Sources */, 3E0EEBC020C19074009E6B2D /* UploadFileOperation.swift in Sources */, @@ -788,6 +793,7 @@ 3E9915C32111B1A30071FDF7 /* Transfer.swift in Sources */, 3E9915C42111B1A30071FDF7 /* File.swift in Sources */, 3E9915C62111B1A30071FDF7 /* Request.swift in Sources */, + 3E9D48D121663EE400499760 /* Transferrable.swift in Sources */, 3E9915C72111B1A30071FDF7 /* Authorize.swift in Sources */, 3EB1E1992159283600E1E4EE /* CreateBoard.swift in Sources */, 3E9915D42111B1A30071FDF7 /* UploadFilesOperation.swift in Sources */, diff --git a/WeTransfer/Models/Board.swift b/WeTransfer/Models/Board.swift index 77081ce..f7b8727 100644 --- a/WeTransfer/Models/Board.swift +++ b/WeTransfer/Models/Board.swift @@ -8,20 +8,20 @@ import Foundation -/// Desribes a single board to be created, updated and sent. Used as an identifier between each request to be made and a local representation of the server-side transfer. -/// Can be initialized with files or these can be added later through the add files function -public final class Board { +/// Desribes a single board to be created, adding files to and uploading files from. Used as an identifier between each request to be made and a local representation of the server-side board. +/// Files should be added through the approapriate addFiles method +public final class Board: Transferrable { public private(set) var identifier: String? - /// The name of the transfer. This name will be shown when viewing the transfer on wetransfer.com + /// The name of the board. This name will be shown when viewing the transfer on wetransfer.com public let name: String - /// Optional description of the transfer. This will be shown when viewing the transfer on wetransfer.com + /// Optional description of the board. This will be shown when viewing the transfer on wetransfer.com public let description: String? - /// References to all the files added to the transfer. Add other files with the public method on the WeTransfer struct or add them directly when initializing the transfer object + /// References to all the files added to the board. Files can be added with the public method on the WeTransfer struct public private(set) var files: [File] = [] - /// Available when the transfer is created on the server + /// Available when the board is created on the server public private(set) var shortURL: URL? init(name: String, description: String?) { @@ -33,19 +33,19 @@ public final class Board { // MARK: - Private updating methods extension Board { - /// Updates the transfer with server-side information + /// Updates the board with server-side information /// /// - Parameters: - /// - identifier: Identifier to point to global transfer - /// - shortURL: URL of where the transfer can be found online + /// - identifier: Identifier to point to global board + /// - shortURL: URL of where the board can be found online func update(with identifier: String, shortURL: URL) { self.identifier = identifier self.shortURL = shortURL } - /// Adds provided files to the transfer locally + /// Adds provided files to the board locally /// - /// - Parameter files: Files to be added to the transfer + /// - Parameter files: Files to be added to the board func add(_ files: [File]) { for file in files where !self.files.contains(file) { self.files.append(file) diff --git a/WeTransfer/Models/Transfer.swift b/WeTransfer/Models/Transfer.swift index b05d569..34a2e3c 100644 --- a/WeTransfer/Models/Transfer.swift +++ b/WeTransfer/Models/Transfer.swift @@ -8,10 +8,9 @@ import Foundation -/// Desribes a single transfer to be created, updated and sent. Used as an identifier between each request to be made and a local representation of the server-side transfer. -/// Can only be initialized from the create transfer request -public final class Transfer { - public let identifier: String +/// Desribes a single transfer to be created and uploaded. Used as an identifier between each request to be made and a local representation of the server-side transfer. +public final class Transfer: Transferrable { + public let identifier: String? /// The name of the transfer. This name will be shown when viewing the transfer on wetransfer.com public let message: String diff --git a/WeTransfer/Models/Transferrable.swift b/WeTransfer/Models/Transferrable.swift new file mode 100644 index 0000000..5b2fe03 --- /dev/null +++ b/WeTransfer/Models/Transferrable.swift @@ -0,0 +1,16 @@ +// +// Transferrable.swift +// WeTransfer +// +// Created by Pim Coumans on 04/10/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import Foundation + +/// Shared properties for both transfers and boards +public protocol Transferrable { + var identifier: String? { get } + var files: [File] { get } + var shortURL: URL? { get } +} From 4ab199d387b868669888821ecf7669a1be8fdcda Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:38:30 +0200 Subject: [PATCH 10/43] Board/Transfer agnostic operations, updated chunk creation logic --- WeTransfer/Models/Chunk.swift | 9 ++-- WeTransfer/Models/File.swift | 12 +++-- WeTransfer/Server/Methods/AddFiles.swift | 10 +++- .../Operations/CompleteUploadOperation.swift | 54 ++++++++++++++----- .../Operations/CreateChunkOperation.swift | 30 +++++++---- .../Operations/CreateTransferOperation.swift | 2 +- .../Operations/UploadFileOperation.swift | 9 ++-- .../Operations/UploadFilesOperation.swift | 20 +++---- 8 files changed, 101 insertions(+), 45 deletions(-) diff --git a/WeTransfer/Models/Chunk.swift b/WeTransfer/Models/Chunk.swift index 1fd1a64..ac91fd2 100644 --- a/WeTransfer/Models/Chunk.swift +++ b/WeTransfer/Models/Chunk.swift @@ -8,10 +8,10 @@ import Foundation -/// Represents a chunk of data from a file in a transfer. Used only in the uploading proces +/// Represents a chunk of data from a file in a transfer or board. Used only in the uploading proces struct Chunk: Encodable { - /// Size of all chunks except the last, as the last chunk holds the remaining data (filesize % defaultChunkSize) + /// Fallback size chunks except the last, as the last chunk holds the remaining data (filesize % defaultChunkSize) static let defaultChunkSize: Bytes = (6 * 1024 * 1024) /// Zero-based index of chunk @@ -37,11 +37,12 @@ extension Chunk { /// - chunkIndex: The index of the chunk /// - uploadURL: The URL to where the chunk should be uploaded init(file: File, chunkIndex: Int, uploadURL: URL) { - let byteOffset = Chunk.defaultChunkSize * Bytes(chunkIndex) + let chunkSize = file.chunkSize ?? Chunk.defaultChunkSize + let byteOffset = chunkSize * Bytes(chunkIndex) self.init(chunkIndex: chunkIndex, fileURL: file.url, uploadURL: uploadURL, - size: min(file.filesize - byteOffset, Chunk.defaultChunkSize), + size: min(file.filesize - byteOffset, chunkSize), byteOffset: byteOffset) } } diff --git a/WeTransfer/Models/File.swift b/WeTransfer/Models/File.swift index 679ab62..562adf0 100644 --- a/WeTransfer/Models/File.swift +++ b/WeTransfer/Models/File.swift @@ -11,8 +11,8 @@ import Foundation /// Amount of bytes in a file or chunk public typealias Bytes = UInt64 -/// A file used in a Transfer object. Should be initialized with a URL pointing only to a local file -/// As files should be readily available for uploading, only local files accessible by NSFileManager should be used for transfers +/// A file used in a Transfer or a Board. Should be initialized with a URL pointing only to a local file +/// As files should be readily available for uploading, only local files accessible by NSFileManager should be used public final class File: Encodable { public enum Error: Swift.Error, LocalizedError { @@ -30,7 +30,7 @@ public final class File: Encodable { /// Location of the file on disk public let url: URL - /// Server-side identifier when file is added to the transfer on the server + /// Server-side identifier when file is added to the transfer or board on the server public private(set) var identifier: String? /// Will be set to yes when all chunks of the file have been uploaded @@ -43,6 +43,9 @@ public final class File: Encodable { /// Size of the file in Bytes public let filesize: Bytes + + /// Maximum size that each chunk needs to be + public internal(set) var chunkSize: Bytes? public private(set) var numberOfChunks: Int? private(set) var multipartUploadIdentifier: String? @@ -67,9 +70,10 @@ extension File: Equatable { } extension File { - func update(with identifier: String, numberOfChunks: Int, multipartUploadIdentifier: String?) { + func update(with identifier: String, numberOfChunks: Int, chunkSize: Bytes, multipartUploadIdentifier: String?) { self.identifier = identifier self.numberOfChunks = numberOfChunks + self.chunkSize = chunkSize self.multipartUploadIdentifier = multipartUploadIdentifier } } diff --git a/WeTransfer/Server/Methods/AddFiles.swift b/WeTransfer/Server/Methods/AddFiles.swift index 3207d7c..6fbfca6 100644 --- a/WeTransfer/Server/Methods/AddFiles.swift +++ b/WeTransfer/Server/Methods/AddFiles.swift @@ -25,8 +25,16 @@ extension WeTransfer { completion(result) } } + + // Externally create the board if not already in the queue + if board.identifier == nil && client.operationQueue.operations.first(where: { $0 is CreateBoardOperation }) == nil { + let createBoardOperation = CreateBoardOperation(board: board) + operation.addDependency(createBoardOperation) + client.operationQueue.addOperation(createBoardOperation) + } + // Add the latest AddFilesOperation in the queue as a dependency so all files are added in the correct order - if let queuedAddFilesOperation = client.operationQueue.operations.reversed().first(where: { $0 is AddFilesOperation}) { + if let queuedAddFilesOperation = client.operationQueue.operations.last(where: { $0 is AddFilesOperation}) { operation.addDependency(queuedAddFilesOperation) } client.operationQueue.addOperation(operation) diff --git a/WeTransfer/Server/Operations/CompleteUploadOperation.swift b/WeTransfer/Server/Operations/CompleteUploadOperation.swift index 6657be9..3e2e4d3 100644 --- a/WeTransfer/Server/Operations/CompleteUploadOperation.swift +++ b/WeTransfer/Server/Operations/CompleteUploadOperation.swift @@ -26,17 +26,25 @@ final class CompleteUploadOperation: AsynchronousResultOperation { /// File to complete the uploading of private let file: File + /// Transfer or Board containing file + private let container: Transferrable + /// Initializes the operation with a file to complete the upload for /// - /// - Parameter file: File struct for which to complete the upload for - required init(file: File) { + /// - Parameters: + /// - container: Transferrable object containing the file + /// - file: File struct for which to complete the upload for + required init(container: Transferrable, file: File) { + self.container = container self.file = file super.init() } override func execute() { - guard let fileIdentifier = file.identifier else { + guard let containerIdentifier = container.identifier, + let fileIdentifier = file.identifier, + let numberOfChunks = file.numberOfChunks else { finish(with: .failure(Error.fileNotYetAdded)) return } @@ -49,19 +57,39 @@ final class CompleteUploadOperation: AsynchronousResultOperation { return } - WeTransfer.request(.completeUpload(fileIdentifier: fileIdentifier)) { [weak self] result in - guard let strongSelf = self else { - return + guard container is Transfer || container is Board else { + fatalError("Container type '\(type(of: container))' is not supported") + } + + if container is Transfer { + let request: APIEndpoint = .completeTransferFileUpload(transferIdentifier: containerIdentifier, fileIdentifier: fileIdentifier) + let parameters = CompleteTransferFileUploadParameters(partNumbers: numberOfChunks) + WeTransfer.request(request, parameters: parameters) { [weak self] result in + guard let self = self else { + return + } + switch result { + case .failure(let error): + self.finish(with: .failure(error)) + case .success: + self.finish(with: .success(self.file)) + } } - switch result { - case .failure(let error): - strongSelf.finish(with: .failure(error)) - case .success(let response): - guard response.ok else { - strongSelf.finish(with: .failure(WeTransfer.RequestError.serverError(errorMessage: response.message, httpCode: nil))) + } else if container is Board { + WeTransfer.request(.completeBoardFileUpload(boardIdentifier: containerIdentifier, fileIdentifier: fileIdentifier)) { [weak self] result in + guard let self = self else { return } - strongSelf.finish(with: .success(strongSelf.file)) + switch result { + case .failure(let error): + self.finish(with: .failure(error)) + case .success(let response): + guard response.success else { + self.finish(with: .failure(WeTransfer.RequestError.serverError(errorMessage: response.message, httpCode: nil))) + return + } + self.finish(with: .success(self.file)) + } } } } diff --git a/WeTransfer/Server/Operations/CreateChunkOperation.swift b/WeTransfer/Server/Operations/CreateChunkOperation.swift index 935a38b..65e8580 100644 --- a/WeTransfer/Server/Operations/CreateChunkOperation.swift +++ b/WeTransfer/Server/Operations/CreateChunkOperation.swift @@ -23,6 +23,8 @@ final class CreateChunkOperation: AsynchronousResultOperation { } } + private let container: Transferrable + /// File to create chunk from private let file: File /// Index of chunk from file @@ -33,32 +35,42 @@ final class CreateChunkOperation: AsynchronousResultOperation { /// - Parameters: /// - file: File struct of the file to create the chunk from /// - chunkIndex: Index of the chunk to be created - required init(file: File, chunkIndex: Int) { + required init(container: Transferrable, file: File, chunkIndex: Int) { + self.container = container self.file = file self.chunkIndex = chunkIndex } override func execute() { - guard let fileIdentifier = file.identifier, let uploadIdentifier = file.multipartUploadIdentifier else { + guard let containerIdentifier = container.identifier, let fileIdentifier = file.identifier else { self.finish(with: .failure(Error.fileNotYetAdded)) return } + + let endpoint: APIEndpoint + if container is Transfer { + endpoint = .requestTransferUploadURL(transferIdentifier: containerIdentifier, fileIdentifier: fileIdentifier, chunkIndex: chunkIndex) + } else if container is Board { + guard let multipartIdentifier = file.multipartUploadIdentifier else { + self.finish(with: .failure(Error.fileNotYetAdded)) + return + } + endpoint = .requestBoardUploadURL(boardIdentifier: containerIdentifier, fileIdentifier: fileIdentifier, chunkIndex: chunkIndex, multipartIdentifier: multipartIdentifier) + } else { + fatalError("Container type '\(type(of: container))' is not supported") + } - let endpoint: APIEndpoint = .requestUploadURL(fileIdentifier: fileIdentifier, - chunkIndex: chunkIndex, - multipartIdentifier: uploadIdentifier) WeTransfer.request(endpoint) { [weak self] result in switch result { case .failure(let error): self?.finish(with: .failure(error)) case .success(let response): - guard let file = self?.file else { + guard let self = self else { return } // Chunks are locally referenced in a zero-based index. Subtract 1 from partNumber value - let chunkIndex = response.partNumber - 1 - let chunk = Chunk(file: file, chunkIndex: chunkIndex, uploadURL: response.uploadUrl) - self?.finish(with: .success(chunk)) + let chunk = Chunk(file: self.file, chunkIndex: self.chunkIndex, uploadURL: response.url) + self.finish(with: .success(chunk)) } } } diff --git a/WeTransfer/Server/Operations/CreateTransferOperation.swift b/WeTransfer/Server/Operations/CreateTransferOperation.swift index f2cd14e..556b75a 100644 --- a/WeTransfer/Server/Operations/CreateTransferOperation.swift +++ b/WeTransfer/Server/Operations/CreateTransferOperation.swift @@ -43,7 +43,7 @@ final class CreateTransferOperation: AsynchronousResultOperation { case .success(let response): let updatedFiles: [File] = zip(response.files, files).map({ (files) in let (responseFile, file) = files - file.update(with: response.id, numberOfChunks: responseFile.multipart.partNumbers, multipartUploadIdentifier: nil) + file.update(with: responseFile.id, numberOfChunks: responseFile.multipart.partNumbers, chunkSize: responseFile.multipart.chunkSize, multipartUploadIdentifier: nil) return file }) let transfer = Transfer(identifier: response.id, message: parameters.message, files: updatedFiles) diff --git a/WeTransfer/Server/Operations/UploadFileOperation.swift b/WeTransfer/Server/Operations/UploadFileOperation.swift index b7cfedb..17d71ff 100644 --- a/WeTransfer/Server/Operations/UploadFileOperation.swift +++ b/WeTransfer/Server/Operations/UploadFileOperation.swift @@ -23,6 +23,8 @@ final class UploadFileOperation: AsynchronousResultOperation { } } + /// Transfer or Board containing file + private let container: Transferrable /// File to upload private let file: File /// Queue to add the created operations to @@ -36,7 +38,8 @@ final class UploadFileOperation: AsynchronousResultOperation { /// - file: The file from which to create and upload the chunks /// - operationQueue: Operation queue to add the operations to /// - session: URLSession that should handle the actual uploading - required init(file: File, operationQueue: OperationQueue, session: URLSession) { + required init(container: Transferrable, file: File, operationQueue: OperationQueue, session: URLSession) { + self.container = container self.file = file self.operationQueue = operationQueue self.session = session @@ -53,7 +56,7 @@ final class UploadFileOperation: AsynchronousResultOperation { } var operations = [Operation]() for chunkIndex in 0.. { return } - let completeOperation = CompleteUploadOperation(file: file) + let completeOperation = CompleteUploadOperation(container: container, file: file) let chunkOperations = chainedChunkOperations(with: completeOperation) diff --git a/WeTransfer/Server/Operations/UploadFilesOperation.swift b/WeTransfer/Server/Operations/UploadFilesOperation.swift index 919dd3f..f6708a4 100644 --- a/WeTransfer/Server/Operations/UploadFilesOperation.swift +++ b/WeTransfer/Server/Operations/UploadFilesOperation.swift @@ -9,7 +9,7 @@ import Foundation /// Handles the uploading of all files -- that are not uploaded yet -- in the provided transfer. Creates an operation queue with an `UploadFileOperation` for each file to be uploaded. -final class UploadFilesOperation: ChainedAsynchronousResultOperation, URLSessionDataDelegate { +final class UploadFilesOperation: ChainedAsynchronousResultOperation, URLSessionDataDelegate where Container: Transferrable { /// Amount of bytes already sent private var bytesSent: Bytes = 0 @@ -19,16 +19,16 @@ final class UploadFilesOperation: ChainedAsynchronousResultOperation() let fileOperations = files.compactMap { file -> UploadFileOperation? in - guard file.identifier != nil, file.multipartUploadIdentifier != nil else { + guard file.identifier != nil else { // File may have been added while operations have created, fail silently return nil } - let operation = UploadFileOperation(file: file, operationQueue: chunkOperationQueue, session: uploadSession) + let operation = UploadFileOperation(container: container, file: file, operationQueue: chunkOperationQueue, session: uploadSession) operation.onResult = { result in if case .success(let file) = result { file.isUploaded = true @@ -85,7 +85,7 @@ final class UploadFilesOperation: ChainedAsynchronousResultOperation Date: Mon, 8 Oct 2018 16:40:19 +0200 Subject: [PATCH 11/43] Convenience init of CreateBoardOperation --- .../Server/Operations/CreateBoardOperation.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/WeTransfer/Server/Operations/CreateBoardOperation.swift b/WeTransfer/Server/Operations/CreateBoardOperation.swift index 108a2cf..24d0d99 100644 --- a/WeTransfer/Server/Operations/CreateBoardOperation.swift +++ b/WeTransfer/Server/Operations/CreateBoardOperation.swift @@ -12,11 +12,20 @@ import Foundation /// This operation does not handle the requests necessary to add files to the server side transfer, which `AddFilesOperation` is responsible for final class CreateBoardOperation: AsynchronousResultOperation { - let board: Board + private let board: Board - /// Initalized the operation with the necessary parameters for a transfer + /// Initializes the operation with the necessary properties for a new board /// - /// - Parameter transfer: Transfer object with optionally some files already added + /// - Parameters: + /// - name: Name of the board to be created + /// - description: Optional description of the board to be created + convenience init(name: String, description: String?) { + self.init(board: Board(name: name, description: description)) + } + + /// Initalizes the operation with a board to be created on the server + /// + /// - Parameter board: Board object required init(board: Board) { self.board = board super.init() From 71de5938a5fa1fd15acbc321b7e259ad3c5ace61 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:40:56 +0200 Subject: [PATCH 12/43] Added FinalizeTransferOperation --- WeTransfer.xcodeproj/project.pbxproj | 6 +++ .../FinalizeTransferOperation.swift | 42 +++++++++++++++++++ WeTransfer/WeTransfer.swift | 15 +++++-- 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 WeTransfer/Server/Operations/FinalizeTransferOperation.swift diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index 92e89cb..e3e493f 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -90,6 +90,8 @@ 3E9D48D42166535D00499760 /* SimpleBoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */; }; 3E9D48D62166537D00499760 /* BoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D52166537C00499760 /* BoardUploadTests.swift */; }; 3E9D48D72166537D00499760 /* BoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D52166537C00499760 /* BoardUploadTests.swift */; }; + 3E9D48DE216B6FF800499760 /* FinalizeTransferOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48DB216B6FBC00499760 /* FinalizeTransferOperation.swift */; }; + 3E9D48DF216B6FF900499760 /* FinalizeTransferOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48DB216B6FBC00499760 /* FinalizeTransferOperation.swift */; }; 3E9D48E1216B89CB00499760 /* BoardChunksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48E0216B89CB00499760 /* BoardChunksTests.swift */; }; 3E9D48E2216B89CB00499760 /* BoardChunksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48E0216B89CB00499760 /* BoardChunksTests.swift */; }; 3EA0EFBA20BD4D90009E4BB1 /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA0EFB920BD4D90009E4BB1 /* AsynchronousOperation.swift */; }; @@ -190,6 +192,7 @@ 3E9D48CF21663EE400499760 /* Transferrable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transferrable.swift; sourceTree = ""; }; 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleBoardUploadTests.swift; sourceTree = ""; }; 3E9D48D52166537C00499760 /* BoardUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardUploadTests.swift; sourceTree = ""; }; + 3E9D48DB216B6FBC00499760 /* FinalizeTransferOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinalizeTransferOperation.swift; sourceTree = ""; }; 3E9D48E0216B89CB00499760 /* BoardChunksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardChunksTests.swift; sourceTree = ""; }; 3EA0EFB920BD4D90009E4BB1 /* AsynchronousOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousOperation.swift; sourceTree = ""; }; 3EA0EFCD20BD7743009E4BB1 /* AsynchronousResultOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsynchronousResultOperation.swift; sourceTree = ""; }; @@ -347,6 +350,7 @@ 3E0EEBB520C177FE009E6B2D /* CreateChunkOperation.swift */, 3E0EEBBB20C19004009E6B2D /* UploadChunkOperation.swift */, 3E0EEBBD20C19037009E6B2D /* CompleteUploadOperation.swift */, + 3E9D48DB216B6FBC00499760 /* FinalizeTransferOperation.swift */, ); path = Operations; sourceTree = ""; @@ -749,6 +753,7 @@ 3E9413C22098C132003A42D1 /* Authorize.swift in Sources */, 3E8EB08C20E503E2002EB5AB /* Authenticator.swift in Sources */, 3E9413C42099D78A003A42D1 /* Request.swift in Sources */, + 3E9D48DE216B6FF800499760 /* FinalizeTransferOperation.swift in Sources */, 3E0EEBBE20C19037009E6B2D /* CompleteUploadOperation.swift in Sources */, 3E0EEBB020C1444C009E6B2D /* ChainedAsynchronousResultOperation.swift in Sources */, 3EB1E19B215928BF00E1E4EE /* CreateBoardOperation.swift in Sources */, @@ -806,6 +811,7 @@ 3E9915CD2111B1A30071FDF7 /* Result.swift in Sources */, 3E9915D32111B1A30071FDF7 /* AddFilesOperation.swift in Sources */, 3E9915D62111B1A30071FDF7 /* CreateChunkOperation.swift in Sources */, + 3E9D48DF216B6FF900499760 /* FinalizeTransferOperation.swift in Sources */, 3E9915C92111B1A30071FDF7 /* AddFiles.swift in Sources */, 3E9915C22111B1A30071FDF7 /* WeTransfer.swift in Sources */, 3EB1E19C215928BF00E1E4EE /* CreateBoardOperation.swift in Sources */, diff --git a/WeTransfer/Server/Operations/FinalizeTransferOperation.swift b/WeTransfer/Server/Operations/FinalizeTransferOperation.swift new file mode 100644 index 0000000..7804e29 --- /dev/null +++ b/WeTransfer/Server/Operations/FinalizeTransferOperation.swift @@ -0,0 +1,42 @@ +// +// FinalizeTransferOperation.swift +// WeTransfer +// +// Created by Pim Coumans on 08/10/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import Foundation + +final class FinalizeTransferOperation: ChainedAsynchronousResultOperation { + + /// Initializes the operation with a transfer. When initalized as part of a chain this operation can be initialized without any arguments + /// + /// - Parameter container: Transfer object to finalize on the server + convenience init(container: Transfer) { + self.init(input: container) + } + + override func execute(_ transfer: Transfer) { + guard transfer.shortURL == nil else { + finish(with: .failure(WeTransfer.Error.transferAlreadyFinalized)) + return + } + + guard let identifier = transfer.identifier else { + finish(with: .failure(WeTransfer.Error.transferNotYetCreated)) + return + } + + WeTransfer.request(.finalizeTransfer(transferIdentifier: identifier)) { [weak self] result in + switch result { + case .success(let response): + transfer.update(with: response.url) + self?.finish(with: .success(transfer)) + case .failure(let error): + self?.finish(with: .failure(error)) + } + } + } + +} diff --git a/WeTransfer/WeTransfer.swift b/WeTransfer/WeTransfer.swift index bfd0b54..eb125b9 100644 --- a/WeTransfer/WeTransfer.swift +++ b/WeTransfer/WeTransfer.swift @@ -34,6 +34,8 @@ extension WeTransfer { case transferNotYetCreated /// Transfer has no files to share as no files are added yet or all files are already uploaded case noFilesAvailable + /// Transfer already finalized, not need to call finalize again + case transferAlreadyFinalized public var errorDescription: String? { switch self { @@ -45,6 +47,8 @@ extension WeTransfer { return "Transfer already created: create transfer request should not be called multiple times for the same transfer" case .noFilesAvailable: return "No files available or all files have already been uploaded: add files to the transfer to upload" + case .transferAlreadyFinalized: + return "Transfer already finalized" default: return "\(self)" } @@ -86,8 +90,7 @@ extension WeTransfer { /// - fileURLS: Array of URLs pointing to files to be added to the transfer /// - stateChanged: Closure that will be called for state updates. /// - state: Enum describing the current transfer's state. See the `State` enum description for more details for each state - /// - Returns: Transfer object used to handle the transfer process. - public static func uploadTransfer(saying message: String, containing fileURLS: [URL], stateChanged: @escaping (_ state: State) -> Void) { + public static func uploadTransfer(saying message: String, containing fileURLS: [URL], stateChanged: @escaping (_ state: State) -> Void) { // Make sure stateChanges closure is called on the main thread let changeState = { state in @@ -100,12 +103,16 @@ extension WeTransfer { let creationOperation = CreateTransferOperation(message: message, fileURLs: fileURLS) // Upload all files from the chunks - let uploadFilesOperation = UploadFilesOperation() + let uploadFilesOperation = UploadFilesOperation() // Handle transfer created result - creationOperation.onResult = { result in + creationOperation.onResult = { [weak uploadFilesOperation] result in if case .success(let transfer) = result { changeState(.created(transfer)) + + if let operation = uploadFilesOperation { + stateChanged(.uploading(operation.progress)) + } } } From 6e60e48fe56166e1cbee9d086b5de94f4384e247 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:46:26 +0200 Subject: [PATCH 13/43] Generic State, added finalize to transfer upload, simple board upload method --- WeTransfer/Server/Methods/Upload.swift | 48 +++++++++++--- WeTransfer/WeTransfer.swift | 65 ++++++++++++++++++- .../Transfer/SimpleTransferTests.swift | 1 + 3 files changed, 104 insertions(+), 10 deletions(-) diff --git a/WeTransfer/Server/Methods/Upload.swift b/WeTransfer/Server/Methods/Upload.swift index 1dcf072..bcfda75 100644 --- a/WeTransfer/Server/Methods/Upload.swift +++ b/WeTransfer/Server/Methods/Upload.swift @@ -11,13 +11,13 @@ import Foundation extension WeTransfer { /// State of current transfer - public enum State { - /// Transfer is created server side, share url is now available - case created(Transfer) + public enum State { + /// Object is created server side, in case of Board now has url available + case created(T) /// Upload has started, track progress with progress object case uploading(Progress) /// Transfer is completed - case completed(Transfer) + case completed(T) /// Transfer failed due to provided error case failed(Swift.Error) } @@ -27,7 +27,7 @@ extension WeTransfer { /// - Parameters: /// - transfer: The Transfer object to be sent /// - stateChanged: Enum describing the current transfer's state. See the `State` enum description for more details for each state - public static func upload(_ transfer: Transfer, stateChanged: @escaping (State) -> Void) { + public static func upload(_ transfer: Transfer, stateChanged: @escaping (State) -> Void) { let changeState = { result in DispatchQueue.main.async { @@ -35,11 +35,12 @@ extension WeTransfer { } } - let operation = UploadFilesOperation(transfer: transfer) - changeState(.created(transfer)) - changeState(.uploading(operation.progress)) + let uploadOperation = UploadFilesOperation(container: transfer) + changeState(.uploading(uploadOperation.progress)) - operation.onResult = { result in + let finalizeOperation = FinalizeTransferOperation() + + finalizeOperation.onResult = { result in switch result { case .failure(let error): changeState(.failed(error)) @@ -47,6 +48,35 @@ extension WeTransfer { changeState(.completed(transfer)) } } + + let operations = [uploadOperation, finalizeOperation].chained() + client.operationQueue.addOperations(operations, waitUntilFinished: false) + } + + /// Uploads the files of the provided board, assuming it's created on the server and has files to be uploaded + /// + /// - Parameters: + /// - board: The Board object to upload files from + /// - stateChanged: Enum describing the state of the upload process. See the `State` enum description for more details for each state + public static func upload(_ board: Board, stateChanged: @escaping (State) -> Void) { + + let changeState = { result in + DispatchQueue.main.async { + stateChanged(result) + } + } + + let operation = UploadFilesOperation(container: board) + changeState(.uploading(operation.progress)) + + operation.onResult = { result in + switch result { + case .failure(let error): + changeState(.failed(error)) + case .success(let board): + changeState(.completed(board)) + } + } client.operationQueue.addOperation(operation) } } diff --git a/WeTransfer/WeTransfer.swift b/WeTransfer/WeTransfer.swift index eb125b9..0001838 100644 --- a/WeTransfer/WeTransfer.swift +++ b/WeTransfer/WeTransfer.swift @@ -116,8 +116,71 @@ extension WeTransfer { } } + // Finalize transfer to get the url + let finalizeTransferOperation = FinalizeTransferOperation() + + // Perform all operations in a chain + let operations = [creationOperation, uploadFilesOperation, finalizeTransferOperation].chained() + client.operationQueue.addOperations(operations, waitUntilFinished: false) + + // Handle the result of the very last operation that's executed + finalizeTransferOperation.onResult = { result in + switch result { + case .failure(let error): + changeState(.failed(error)) + case .success(let transfer): + changeState(.completed(transfer)) + } + } + } + + /// Immediately uploads files to a board with the provided name and file URLs + /// + /// - Parameters: + /// - name: Name of the board, shown when user opens the resulting link + /// - description: Optional description of the board + /// - fileURLS: Array of URLs pointing to files to be added to the board + /// - stateChanged: Closure that will be called for state updates. + /// - state: Enum describing the current board's state. See the `State` enum description for more details for each state + /// - Returns: Board object used to handle the transfer process. + public static func uploadBoard(named name: String, description: String?, containing fileURLS: [URL], stateChanged: @escaping (_ state: State) -> Void) { + + // Make sure stateChanges closure is called on the main thread + let changeState = { state in + DispatchQueue.main.async { + stateChanged(state) + } + } + + let board = Board(name: name, description: description) + + // Add files to board (also creates board on the server) + let files: [File] + do { + files = try fileURLS.map({ try File(url: $0) }) + } catch { + stateChanged(.failed(error)) + return + } + + let addFilesOperation = AddFilesOperation(board: board, files: files) + + // Upload all files from the chunks + let uploadFilesOperation = UploadFilesOperation() + + // Handle transfer created result + addFilesOperation.onResult = { [weak uploadFilesOperation] result in + if case .success(let transfer) = result { + changeState(.created(transfer)) + + if let operation = uploadFilesOperation { + stateChanged(.uploading(operation.progress)) + } + } + } + // Perform all operations in a chain - let operations = [creationOperation, uploadFilesOperation].chained() + let operations = [addFilesOperation, uploadFilesOperation].chained() client.operationQueue.addOperations(operations, waitUntilFinished: false) // Handle the result of the very last operation that's executed diff --git a/WeTransferTests/Transfer/SimpleTransferTests.swift b/WeTransferTests/Transfer/SimpleTransferTests.swift index 4ba9c2b..aae73cb 100644 --- a/WeTransferTests/Transfer/SimpleTransferTests.swift +++ b/WeTransferTests/Transfer/SimpleTransferTests.swift @@ -58,6 +58,7 @@ final class SimpleTransferTests: XCTestCase { waitForExpectations(timeout: 60) { _ in XCTAssertNotNil(updatedTransfer, "Transfer was not completed") + XCTAssertNotNil(updatedTransfer?.shortURL, "Transfer should have a URL when uploaded") } } From b6cd1983694e454cdb1aa740e1e51d6bc460bf22 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:46:50 +0200 Subject: [PATCH 14/43] Always fulfull expectation, even when failing --- WeTransferTests/Board/AddFilesTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/WeTransferTests/Board/AddFilesTests.swift b/WeTransferTests/Board/AddFilesTests.swift index d6405a4..bae4e98 100644 --- a/WeTransferTests/Board/AddFilesTests.swift +++ b/WeTransferTests/Board/AddFilesTests.swift @@ -68,7 +68,6 @@ final class AddFilesTests: XCTestCase { WeTransfer.add([file], to: board, completion: { (result) in if case .failure(let error) = result { XCTFail("Add files to transfer failed: \(error)") - return } addedFilesExpectation.fulfill() }) From 80685a37479f57e53012a1a62fbd93afe9a5e5e8 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:47:20 +0200 Subject: [PATCH 15/43] Legibility line ending --- WeTransferTests/Transfer/CreateTransferTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WeTransferTests/Transfer/CreateTransferTests.swift b/WeTransferTests/Transfer/CreateTransferTests.swift index 0c35e5a..eed2c36 100644 --- a/WeTransferTests/Transfer/CreateTransferTests.swift +++ b/WeTransferTests/Transfer/CreateTransferTests.swift @@ -38,6 +38,7 @@ final class CreateTransferTests: XCTestCase { } createdTransferExpectation.fulfill() } + waitForExpectations(timeout: 10) { _ in XCTAssertNotNil(transferResult) if let transfer = transferResult { From 5f58d8963d7ca553fc75f1e25e3daf30ffa1188a Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 16:50:57 +0200 Subject: [PATCH 16/43] Use `zip()` to update each file with the response --- .../Server/Operations/AddFilesOperation.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/WeTransfer/Server/Operations/AddFilesOperation.swift b/WeTransfer/Server/Operations/AddFilesOperation.swift index 8349256..59cb81e 100644 --- a/WeTransfer/Server/Operations/AddFilesOperation.swift +++ b/WeTransfer/Server/Operations/AddFilesOperation.swift @@ -37,15 +37,15 @@ final class AddFilesOperation: ChainedAsynchronousResultOperation return } - WeTransfer.request(.addItems(transferIdentifier: identifier), parameters: parameters) { [weak self] result in + WeTransfer.request(.addFiles(boardIdentifier: identifier), parameters: parameters.files) { [weak self] result in switch result { - case .success(let response): - board.files.forEach({ file in - guard let responseFile = response.first(where: {$0.id == file.identifier}) else { - return - } - - file.update(with: responseFile.id, numberOfChunks: responseFile.meta.multipartParts, multipartUploadIdentifier: responseFile.meta.multipartUploadId) + case .success(let responseFiles): + zip(files, responseFiles).forEach({ + let (file, responseFile) = $0 + file.update(with: responseFile.id, + numberOfChunks: responseFile.multipart.partNumbers, + chunkSize: responseFile.multipart.chunkSize, + multipartUploadIdentifier: responseFile.multipart.id) }) self?.finish(with: .success(board)) case .failure(let error): From bd1ee130aad00a14ac055bc1bc81d9fe855794ef Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 8 Oct 2018 17:02:04 +0200 Subject: [PATCH 17/43] Correctly dating new files --- WeTransfer/Models/Board.swift | 2 +- WeTransfer/Server/Methods/CreateBoard.swift | 2 +- WeTransferTests/Board/AddFilesTests.swift | 2 +- WeTransferTests/Board/BoardChunksTests.swift | 2 +- WeTransferTests/Board/BoardUploadTests.swift | 2 +- WeTransferTests/Board/CreateBoardTests.swift | 2 +- WeTransferTests/Board/SimpleBoardUploadTests.swift | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WeTransfer/Models/Board.swift b/WeTransfer/Models/Board.swift index f7b8727..f207388 100644 --- a/WeTransfer/Models/Board.swift +++ b/WeTransfer/Models/Board.swift @@ -2,7 +2,7 @@ // Transfer.swift // WeTransfer Swift SDK // -// Created by Pim Coumans on 26/04/2018. +// Created by Pim Coumans on 01/10/2018. // Copyright © 2018 WeTransfer. All rights reserved. // diff --git a/WeTransfer/Server/Methods/CreateBoard.swift b/WeTransfer/Server/Methods/CreateBoard.swift index f964a91..61182cf 100644 --- a/WeTransfer/Server/Methods/CreateBoard.swift +++ b/WeTransfer/Server/Methods/CreateBoard.swift @@ -2,7 +2,7 @@ // CreateTransfer.swift // WeTransfer // -// Created by Pim Coumans on 02/05/2018. +// Created by Pim Coumans on 01/10/2018. // Copyright © 2018 WeTransfer. All rights reserved. // diff --git a/WeTransferTests/Board/AddFilesTests.swift b/WeTransferTests/Board/AddFilesTests.swift index bae4e98..4b0acde 100644 --- a/WeTransferTests/Board/AddFilesTests.swift +++ b/WeTransferTests/Board/AddFilesTests.swift @@ -2,7 +2,7 @@ // AddFilesTests.swift // WeTransferTests // -// Created by Pim Coumans on 22/05/2018. +// Created by Pim Coumans on 01/10/2018. // Copyright © 2018 WeTransfer. All rights reserved. // diff --git a/WeTransferTests/Board/BoardChunksTests.swift b/WeTransferTests/Board/BoardChunksTests.swift index 40adf15..20718b1 100644 --- a/WeTransferTests/Board/BoardChunksTests.swift +++ b/WeTransferTests/Board/BoardChunksTests.swift @@ -2,7 +2,7 @@ // BoardChunksTests.swift // WeTransferTests // -// Created by Pim Coumans on 28/05/2018. +// Created by Pim Coumans on 01/10/2018. // Copyright © 2018 WeTransfer. All rights reserved. // diff --git a/WeTransferTests/Board/BoardUploadTests.swift b/WeTransferTests/Board/BoardUploadTests.swift index 7f6b8e3..60aa62a 100644 --- a/WeTransferTests/Board/BoardUploadTests.swift +++ b/WeTransferTests/Board/BoardUploadTests.swift @@ -2,7 +2,7 @@ // ChunksTests.swift // WeTransferTests // -// Created by Pim Coumans on 28/05/2018. +// Created by Pim Coumans on 01/10/2018. // Copyright © 2018 WeTransfer. All rights reserved. // diff --git a/WeTransferTests/Board/CreateBoardTests.swift b/WeTransferTests/Board/CreateBoardTests.swift index 156826d..c834f70 100644 --- a/WeTransferTests/Board/CreateBoardTests.swift +++ b/WeTransferTests/Board/CreateBoardTests.swift @@ -2,7 +2,7 @@ // CreateTransferTests.swift // WeTransferTests // -// Created by Pim Coumans on 22/05/2018. +// Created by Pim Coumans on 01/10/2018. // Copyright © 2018 WeTransfer. All rights reserved. // diff --git a/WeTransferTests/Board/SimpleBoardUploadTests.swift b/WeTransferTests/Board/SimpleBoardUploadTests.swift index 6a5904b..9e5cdf0 100644 --- a/WeTransferTests/Board/SimpleBoardUploadTests.swift +++ b/WeTransferTests/Board/SimpleBoardUploadTests.swift @@ -2,7 +2,7 @@ // SimpleTransferTests.swift // WeTransferTests // -// Created by Pim Coumans on 22/05/2018. +// Created by Pim Coumans on 01/10/2018. // Copyright © 2018 WeTransfer. All rights reserved. // From ace8f54bb619888ceb0fdbd6dbb6971516fd44ac Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 9 Oct 2018 14:28:05 +0200 Subject: [PATCH 18/43] Fixing typos --- WeTransfer/Models/Board.swift | 7 ++++--- WeTransfer/Models/File.swift | 2 +- WeTransfer/Models/Transfer.swift | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/WeTransfer/Models/Board.swift b/WeTransfer/Models/Board.swift index f207388..b622a3e 100644 --- a/WeTransfer/Models/Board.swift +++ b/WeTransfer/Models/Board.swift @@ -8,8 +8,8 @@ import Foundation -/// Desribes a single board to be created, adding files to and uploading files from. Used as an identifier between each request to be made and a local representation of the server-side board. -/// Files should be added through the approapriate addFiles method +/// Describes a single board to be created, adding files to and uploading files from. Used as an identifier between each request to be made and a local representation of the server-side board. +/// Files should be added through the appropriate addFiles method public final class Board: Transferrable { public private(set) var identifier: String? @@ -23,7 +23,8 @@ public final class Board: Transferrable { /// Available when the board is created on the server public private(set) var shortURL: URL? - + + /// Internal initializer with required properties init(name: String, description: String?) { self.name = name self.description = description diff --git a/WeTransfer/Models/File.swift b/WeTransfer/Models/File.swift index 562adf0..2d8ab0a 100644 --- a/WeTransfer/Models/File.swift +++ b/WeTransfer/Models/File.swift @@ -63,7 +63,7 @@ public final class File: Encodable { extension File: Equatable { /// Only compares the url and optional identifier of the file - // Note: Disregards any state, so the `uploaded` property is ignored + /// Note: Disregards any state, so the `uploaded` property is ignored public static func == (lhs: File, rhs: File) -> Bool { return lhs.url == rhs.url && lhs.identifier == rhs.identifier } diff --git a/WeTransfer/Models/Transfer.swift b/WeTransfer/Models/Transfer.swift index 34a2e3c..f30355f 100644 --- a/WeTransfer/Models/Transfer.swift +++ b/WeTransfer/Models/Transfer.swift @@ -8,7 +8,7 @@ import Foundation -/// Desribes a single transfer to be created and uploaded. Used as an identifier between each request to be made and a local representation of the server-side transfer. +/// Describes a single transfer to be created and uploaded. Used as an identifier between each request to be made and a local representation of the server-side transfer. public final class Transfer: Transferrable { public let identifier: String? @@ -21,6 +21,7 @@ public final class Transfer: Transferrable { /// Available when the transfer is created on the server public private(set) var shortURL: URL? + /// Internal initializer with required properties init(identifier: String, message: String, files: [File] = []) { self.identifier = identifier self.message = message From ea3891c9342ac6fd2df49a70d823a4831d5487f2 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 9 Oct 2018 14:28:50 +0200 Subject: [PATCH 19/43] Transferrable -> Transferable --- WeTransfer.xcodeproj/project.pbxproj | 12 ++++++------ WeTransfer/Models/Board.swift | 2 +- WeTransfer/Models/Transfer.swift | 2 +- .../{Transferrable.swift => Transferable.swift} | 4 ++-- .../Server/Operations/CompleteUploadOperation.swift | 6 +++--- .../Server/Operations/CreateChunkOperation.swift | 4 ++-- .../Server/Operations/UploadFileOperation.swift | 4 ++-- .../Server/Operations/UploadFilesOperation.swift | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) rename WeTransfer/Models/{Transferrable.swift => Transferable.swift} (83%) diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index e3e493f..83e30fc 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -84,8 +84,8 @@ 3E9915D92111B1A30071FDF7 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9413BC2098BC25003A42D1 /* APIClient.swift */; }; 3E9915DA2111B1A30071FDF7 /* Authenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E8EB08B20E503E2002EB5AB /* Authenticator.swift */; }; 3E9915DE211201150071FDF7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E5E514820EA816600485FA3 /* Assets.xcassets */; }; - 3E9D48D021663EE400499760 /* Transferrable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48CF21663EE400499760 /* Transferrable.swift */; }; - 3E9D48D121663EE400499760 /* Transferrable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48CF21663EE400499760 /* Transferrable.swift */; }; + 3E9D48D021663EE400499760 /* Transferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48CF21663EE400499760 /* Transferable.swift */; }; + 3E9D48D121663EE400499760 /* Transferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48CF21663EE400499760 /* Transferable.swift */; }; 3E9D48D32166535D00499760 /* SimpleBoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */; }; 3E9D48D42166535D00499760 /* SimpleBoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */; }; 3E9D48D62166537D00499760 /* BoardUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9D48D52166537C00499760 /* BoardUploadTests.swift */; }; @@ -189,7 +189,7 @@ 3E9413C92099DB05003A42D1 /* Upload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Upload.swift; sourceTree = ""; }; 3E9413CB2099E390003A42D1 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; 3E9413CD2099E3F5003A42D1 /* Chunk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chunk.swift; sourceTree = ""; }; - 3E9D48CF21663EE400499760 /* Transferrable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transferrable.swift; sourceTree = ""; }; + 3E9D48CF21663EE400499760 /* Transferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transferable.swift; sourceTree = ""; }; 3E9D48D22166535D00499760 /* SimpleBoardUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleBoardUploadTests.swift; sourceTree = ""; }; 3E9D48D52166537C00499760 /* BoardUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardUploadTests.swift; sourceTree = ""; }; 3E9D48DB216B6FBC00499760 /* FinalizeTransferOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinalizeTransferOperation.swift; sourceTree = ""; }; @@ -300,7 +300,7 @@ 3EB1E191215916AF00E1E4EE /* Board.swift */, 3E9413CB2099E390003A42D1 /* File.swift */, 3E9413CD2099E3F5003A42D1 /* Chunk.swift */, - 3E9D48CF21663EE400499760 /* Transferrable.swift */, + 3E9D48CF21663EE400499760 /* Transferable.swift */, ); path = Models; sourceTree = ""; @@ -740,7 +740,7 @@ 3E0EEBB420C175D0009E6B2D /* AddFilesOperation.swift in Sources */, 3E80E0A72091D0E700114711 /* WeTransfer.swift in Sources */, 3E80E0AE2091D16E00114711 /* Result.swift in Sources */, - 3E9D48D021663EE400499760 /* Transferrable.swift in Sources */, + 3E9D48D021663EE400499760 /* Transferable.swift in Sources */, 3E9413CA2099DB05003A42D1 /* Upload.swift in Sources */, 3EB1E1982159283600E1E4EE /* CreateBoard.swift in Sources */, 3E0EEBC020C19074009E6B2D /* UploadFileOperation.swift in Sources */, @@ -798,7 +798,7 @@ 3E9915C32111B1A30071FDF7 /* Transfer.swift in Sources */, 3E9915C42111B1A30071FDF7 /* File.swift in Sources */, 3E9915C62111B1A30071FDF7 /* Request.swift in Sources */, - 3E9D48D121663EE400499760 /* Transferrable.swift in Sources */, + 3E9D48D121663EE400499760 /* Transferable.swift in Sources */, 3E9915C72111B1A30071FDF7 /* Authorize.swift in Sources */, 3EB1E1992159283600E1E4EE /* CreateBoard.swift in Sources */, 3E9915D42111B1A30071FDF7 /* UploadFilesOperation.swift in Sources */, diff --git a/WeTransfer/Models/Board.swift b/WeTransfer/Models/Board.swift index b622a3e..a37cdcd 100644 --- a/WeTransfer/Models/Board.swift +++ b/WeTransfer/Models/Board.swift @@ -10,7 +10,7 @@ import Foundation /// Describes a single board to be created, adding files to and uploading files from. Used as an identifier between each request to be made and a local representation of the server-side board. /// Files should be added through the appropriate addFiles method -public final class Board: Transferrable { +public final class Board: Transferable { public private(set) var identifier: String? /// The name of the board. This name will be shown when viewing the transfer on wetransfer.com diff --git a/WeTransfer/Models/Transfer.swift b/WeTransfer/Models/Transfer.swift index f30355f..6ca7af3 100644 --- a/WeTransfer/Models/Transfer.swift +++ b/WeTransfer/Models/Transfer.swift @@ -9,7 +9,7 @@ import Foundation /// Describes a single transfer to be created and uploaded. Used as an identifier between each request to be made and a local representation of the server-side transfer. -public final class Transfer: Transferrable { +public final class Transfer: Transferable { public let identifier: String? /// The name of the transfer. This name will be shown when viewing the transfer on wetransfer.com diff --git a/WeTransfer/Models/Transferrable.swift b/WeTransfer/Models/Transferable.swift similarity index 83% rename from WeTransfer/Models/Transferrable.swift rename to WeTransfer/Models/Transferable.swift index 5b2fe03..83c8b1e 100644 --- a/WeTransfer/Models/Transferrable.swift +++ b/WeTransfer/Models/Transferable.swift @@ -1,5 +1,5 @@ // -// Transferrable.swift +// Transferable.swift // WeTransfer // // Created by Pim Coumans on 04/10/2018. @@ -9,7 +9,7 @@ import Foundation /// Shared properties for both transfers and boards -public protocol Transferrable { +public protocol Transferable { var identifier: String? { get } var files: [File] { get } var shortURL: URL? { get } diff --git a/WeTransfer/Server/Operations/CompleteUploadOperation.swift b/WeTransfer/Server/Operations/CompleteUploadOperation.swift index 3e2e4d3..033b7d7 100644 --- a/WeTransfer/Server/Operations/CompleteUploadOperation.swift +++ b/WeTransfer/Server/Operations/CompleteUploadOperation.swift @@ -27,14 +27,14 @@ final class CompleteUploadOperation: AsynchronousResultOperation { private let file: File /// Transfer or Board containing file - private let container: Transferrable + private let container: Transferable /// Initializes the operation with a file to complete the upload for /// /// - Parameters: - /// - container: Transferrable object containing the file + /// - container: Transferable object containing the file /// - file: File struct for which to complete the upload for - required init(container: Transferrable, file: File) { + required init(container: Transferable, file: File) { self.container = container self.file = file super.init() diff --git a/WeTransfer/Server/Operations/CreateChunkOperation.swift b/WeTransfer/Server/Operations/CreateChunkOperation.swift index 65e8580..863b3d3 100644 --- a/WeTransfer/Server/Operations/CreateChunkOperation.swift +++ b/WeTransfer/Server/Operations/CreateChunkOperation.swift @@ -23,7 +23,7 @@ final class CreateChunkOperation: AsynchronousResultOperation { } } - private let container: Transferrable + private let container: Transferable /// File to create chunk from private let file: File @@ -35,7 +35,7 @@ final class CreateChunkOperation: AsynchronousResultOperation { /// - Parameters: /// - file: File struct of the file to create the chunk from /// - chunkIndex: Index of the chunk to be created - required init(container: Transferrable, file: File, chunkIndex: Int) { + required init(container: Transferable, file: File, chunkIndex: Int) { self.container = container self.file = file self.chunkIndex = chunkIndex diff --git a/WeTransfer/Server/Operations/UploadFileOperation.swift b/WeTransfer/Server/Operations/UploadFileOperation.swift index 17d71ff..fc2b635 100644 --- a/WeTransfer/Server/Operations/UploadFileOperation.swift +++ b/WeTransfer/Server/Operations/UploadFileOperation.swift @@ -24,7 +24,7 @@ final class UploadFileOperation: AsynchronousResultOperation { } /// Transfer or Board containing file - private let container: Transferrable + private let container: Transferable /// File to upload private let file: File /// Queue to add the created operations to @@ -38,7 +38,7 @@ final class UploadFileOperation: AsynchronousResultOperation { /// - file: The file from which to create and upload the chunks /// - operationQueue: Operation queue to add the operations to /// - session: URLSession that should handle the actual uploading - required init(container: Transferrable, file: File, operationQueue: OperationQueue, session: URLSession) { + required init(container: Transferable, file: File, operationQueue: OperationQueue, session: URLSession) { self.container = container self.file = file self.operationQueue = operationQueue diff --git a/WeTransfer/Server/Operations/UploadFilesOperation.swift b/WeTransfer/Server/Operations/UploadFilesOperation.swift index f6708a4..2a403e2 100644 --- a/WeTransfer/Server/Operations/UploadFilesOperation.swift +++ b/WeTransfer/Server/Operations/UploadFilesOperation.swift @@ -9,7 +9,7 @@ import Foundation /// Handles the uploading of all files -- that are not uploaded yet -- in the provided transfer. Creates an operation queue with an `UploadFileOperation` for each file to be uploaded. -final class UploadFilesOperation: ChainedAsynchronousResultOperation, URLSessionDataDelegate where Container: Transferrable { +final class UploadFilesOperation: ChainedAsynchronousResultOperation, URLSessionDataDelegate where Container: Transferable { /// Amount of bytes already sent private var bytesSent: Bytes = 0 From edd9589cf5ac255f2d7609532e1f2da84b9123d0 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 9 Oct 2018 14:31:44 +0200 Subject: [PATCH 20/43] Added documentation to FinalizeTransferOperation --- WeTransfer/Server/Operations/FinalizeTransferOperation.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WeTransfer/Server/Operations/FinalizeTransferOperation.swift b/WeTransfer/Server/Operations/FinalizeTransferOperation.swift index 7804e29..5a65f02 100644 --- a/WeTransfer/Server/Operations/FinalizeTransferOperation.swift +++ b/WeTransfer/Server/Operations/FinalizeTransferOperation.swift @@ -8,6 +8,7 @@ import Foundation +/// Finalizes the provided Transfer after all files have been uploaded. The Transfer object will be updated with a URL as a result. final class FinalizeTransferOperation: ChainedAsynchronousResultOperation { /// Initializes the operation with a transfer. When initalized as part of a chain this operation can be initialized without any arguments From c80090a203592236a8ae010175dba47ad2cbb8f0 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 9 Oct 2018 14:44:11 +0200 Subject: [PATCH 21/43] Added generic EmptyResponse --- WeTransfer/Server/Endpoints/Endpoints.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/WeTransfer/Server/Endpoints/Endpoints.swift b/WeTransfer/Server/Endpoints/Endpoints.swift index 39ef2a2..d01fd65 100644 --- a/WeTransfer/Server/Endpoints/Endpoints.swift +++ b/WeTransfer/Server/Endpoints/Endpoints.swift @@ -69,8 +69,8 @@ extension APIEndpoint { /// - transferIdentifier: Identifier for the containing transfer /// - fileIdentifier: Identifier of the file /// - Returns: APIEndpoint with `POST` to `/files/{file-id}/uploads/complete` - static func completeTransferFileUpload(transferIdentifier: String, fileIdentifier: String) -> APIEndpoint { - return APIEndpoint(method: .put, path: "transfers/\(transferIdentifier)/files/\(fileIdentifier)/uploads/complete") + static func completeTransferFileUpload(transferIdentifier: String, fileIdentifier: String) -> APIEndpoint { + return APIEndpoint(method: .put, path: "transfers/\(transferIdentifier)/files/\(fileIdentifier)/uploads/complete") } /// Completes the upload of file, assuming all chunks have finished uploading @@ -92,6 +92,12 @@ extension APIEndpoint { } } +// MARK: - Request parameters and responses + +/// Generic empty response when no response data is expected or needed +struct EmptyResponse: Decodable { +} + // MARK: - Authorize /// Response from authenticate request @@ -250,10 +256,6 @@ struct CompleteTransferFileUploadParameters: Encodable { let partNumbers: Int } -/// Response from complete upload request -struct CompleteTransferFileUploadResponse: Decodable { -} - /// Response from complete upload request struct CompleteBoardFileUploadResponse: Decodable { /// Whether the upload of all the chunks has succeeded From cd7d26f4b97f7c0607c44714637782f1ebd4d183 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 9 Oct 2018 14:52:02 +0200 Subject: [PATCH 22/43] Moved fatalError to bottom of if statements --- WeTransfer/Server/Operations/CompleteUploadOperation.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/WeTransfer/Server/Operations/CompleteUploadOperation.swift b/WeTransfer/Server/Operations/CompleteUploadOperation.swift index 033b7d7..05674c0 100644 --- a/WeTransfer/Server/Operations/CompleteUploadOperation.swift +++ b/WeTransfer/Server/Operations/CompleteUploadOperation.swift @@ -57,10 +57,6 @@ final class CompleteUploadOperation: AsynchronousResultOperation { return } - guard container is Transfer || container is Board else { - fatalError("Container type '\(type(of: container))' is not supported") - } - if container is Transfer { let request: APIEndpoint = .completeTransferFileUpload(transferIdentifier: containerIdentifier, fileIdentifier: fileIdentifier) let parameters = CompleteTransferFileUploadParameters(partNumbers: numberOfChunks) @@ -91,6 +87,8 @@ final class CompleteUploadOperation: AsynchronousResultOperation { self.finish(with: .success(self.file)) } } + } else { + fatalError("Container type '\(type(of: container))' is not supported") } } From 80268e154eca4649e5c172a51a4fe43012a0c8c1 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 9 Oct 2018 15:16:13 +0200 Subject: [PATCH 23/43] Fixed Swift 4.2 CI errors --- WeTransfer.xcodeproj/project.pbxproj | 10 ++++++---- WeTransferTests/Board/SimpleBoardUploadTests.swift | 2 +- WeTransferTests/Transfer/SimpleTransferTests.swift | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index 83e30fc..883d549 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -584,9 +584,11 @@ }; 3E7116F720FC85F100115E55 = { CreatedOnToolsVersion = 9.4.1; + LastSwiftMigration = 1000; }; 3E7116FF20FC85F100115E55 = { CreatedOnToolsVersion = 9.4.1; + LastSwiftMigration = 1000; }; }; }; @@ -1044,7 +1046,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1072,7 +1074,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1093,7 +1095,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.wetransfer.WeTransfer-macOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1114,7 +1116,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.wetransfer.WeTransfer-macOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/WeTransferTests/Board/SimpleBoardUploadTests.swift b/WeTransferTests/Board/SimpleBoardUploadTests.swift index 9e5cdf0..38228e9 100644 --- a/WeTransferTests/Board/SimpleBoardUploadTests.swift +++ b/WeTransferTests/Board/SimpleBoardUploadTests.swift @@ -41,7 +41,7 @@ final class SimpleBoardUploadTests: XCTestCase { timer = Timer(timeInterval: 1 / 30, repeats: true, block: { _ in print("Progress: \(progress.fractionCompleted)") }) - RunLoop.main.add(timer!, forMode: RunLoopMode.commonModes) + RunLoop.main.add(timer!, forMode: RunLoop.Mode.common) case .completed(let board): timer?.invalidate() timer = nil diff --git a/WeTransferTests/Transfer/SimpleTransferTests.swift b/WeTransferTests/Transfer/SimpleTransferTests.swift index aae73cb..72bfcd3 100644 --- a/WeTransferTests/Transfer/SimpleTransferTests.swift +++ b/WeTransferTests/Transfer/SimpleTransferTests.swift @@ -41,7 +41,7 @@ final class SimpleTransferTests: XCTestCase { timer = Timer(timeInterval: 1 / 30, repeats: true, block: { _ in print("Progress: \(progress.fractionCompleted)") }) - RunLoop.main.add(timer!, forMode: RunLoopMode.commonModes) + RunLoop.main.add(timer!, forMode: RunLoop.Mode.common) case .completed(let transfer): timer?.invalidate() timer = nil From b5dedd6aa8d706c193e0e21ca0fd5ed00941a671 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 9 Oct 2018 15:16:44 +0200 Subject: [PATCH 24/43] Corrected simple Board transfer adding CreateBoardOperation --- WeTransfer/WeTransfer.swift | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/WeTransfer/WeTransfer.swift b/WeTransfer/WeTransfer.swift index 0001838..377482c 100644 --- a/WeTransfer/WeTransfer.swift +++ b/WeTransfer/WeTransfer.swift @@ -152,9 +152,16 @@ extension WeTransfer { } } + // Create the board locally and on the server let board = Board(name: name, description: description) + let createOperation = CreateBoardOperation(board: board) + createOperation.onResult = { result in + if case .success(let board) = result { + changeState(.created(board)) + } + } - // Add files to board (also creates board on the server) + // Add files to board let files: [File] do { files = try fileURLS.map({ try File(url: $0) }) @@ -162,27 +169,18 @@ extension WeTransfer { stateChanged(.failed(error)) return } - let addFilesOperation = AddFilesOperation(board: board, files: files) // Upload all files from the chunks let uploadFilesOperation = UploadFilesOperation() - // Handle transfer created result + // Set state to uploading when uploadFilesOperation is about to begin addFilesOperation.onResult = { [weak uploadFilesOperation] result in - if case .success(let transfer) = result { - changeState(.created(transfer)) - - if let operation = uploadFilesOperation { - stateChanged(.uploading(operation.progress)) - } + if case .success = result, let operation = uploadFilesOperation { + stateChanged(.uploading(operation.progress)) } } - // Perform all operations in a chain - let operations = [addFilesOperation, uploadFilesOperation].chained() - client.operationQueue.addOperations(operations, waitUntilFinished: false) - // Handle the result of the very last operation that's executed uploadFilesOperation.onResult = { result in switch result { @@ -192,5 +190,9 @@ extension WeTransfer { changeState(.completed(transfer)) } } + + // Perform all operations in a chain + let operations = [createOperation, addFilesOperation, uploadFilesOperation].chained() + client.operationQueue.addOperations(operations, waitUntilFinished: false) } } From 60708a3e73dabd8119cdf6d742860434686819de Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 14:56:44 +0200 Subject: [PATCH 25/43] Renamed `Multipart` to more descriptive `UploadInfo` --- WeTransfer/Server/Endpoints/Endpoints.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WeTransfer/Server/Endpoints/Endpoints.swift b/WeTransfer/Server/Endpoints/Endpoints.swift index d01fd65..01e522f 100644 --- a/WeTransfer/Server/Endpoints/Endpoints.swift +++ b/WeTransfer/Server/Endpoints/Endpoints.swift @@ -228,7 +228,7 @@ struct AddFilesParameters: Encodable { /// Response from the add files request struct AddFilesResponse: Decodable { /// Contains information about the chunks and the upload identifier - struct Multipart: Decodable { + struct UploadInfo: Decodable { let id: String let partNumbers: Int let chunkSize: UInt64 @@ -236,8 +236,8 @@ struct AddFilesResponse: Decodable { /// Identifier of the File on the server let id: String - /// Number of multiparts (chunks) and upload identifier for uploading - let multipart: Multipart + /// Upload info for the file + let multipart: UploadInfo } // MARK: - Request upload URL From 93cbe6e6468bc8529fc683225975a872ac770fac Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 15:01:56 +0200 Subject: [PATCH 26/43] Removed unnecessary references to self --- WeTransfer/Server/Operations/CreateBoardOperation.swift | 2 +- WeTransfer/Server/Operations/CreateChunkOperation.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WeTransfer/Server/Operations/CreateBoardOperation.swift b/WeTransfer/Server/Operations/CreateBoardOperation.swift index 24d0d99..4f8fe22 100644 --- a/WeTransfer/Server/Operations/CreateBoardOperation.swift +++ b/WeTransfer/Server/Operations/CreateBoardOperation.swift @@ -33,7 +33,7 @@ final class CreateBoardOperation: AsynchronousResultOperation { override func execute() { guard board.identifier == nil else { - self.finish(with: .success(board)) + finish(with: .success(board)) return } diff --git a/WeTransfer/Server/Operations/CreateChunkOperation.swift b/WeTransfer/Server/Operations/CreateChunkOperation.swift index 863b3d3..b864c54 100644 --- a/WeTransfer/Server/Operations/CreateChunkOperation.swift +++ b/WeTransfer/Server/Operations/CreateChunkOperation.swift @@ -43,7 +43,7 @@ final class CreateChunkOperation: AsynchronousResultOperation { override func execute() { guard let containerIdentifier = container.identifier, let fileIdentifier = file.identifier else { - self.finish(with: .failure(Error.fileNotYetAdded)) + finish(with: .failure(Error.fileNotYetAdded)) return } @@ -52,7 +52,7 @@ final class CreateChunkOperation: AsynchronousResultOperation { endpoint = .requestTransferUploadURL(transferIdentifier: containerIdentifier, fileIdentifier: fileIdentifier, chunkIndex: chunkIndex) } else if container is Board { guard let multipartIdentifier = file.multipartUploadIdentifier else { - self.finish(with: .failure(Error.fileNotYetAdded)) + finish(with: .failure(Error.fileNotYetAdded)) return } endpoint = .requestBoardUploadURL(boardIdentifier: containerIdentifier, fileIdentifier: fileIdentifier, chunkIndex: chunkIndex, multipartIdentifier: multipartIdentifier) From a4cd3ab2507e7623e1aa7fd438f137d787b676a5 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 15:03:17 +0200 Subject: [PATCH 27/43] Simpler generic casting for UploadFilesOperation --- WeTransfer/Server/Operations/UploadFilesOperation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WeTransfer/Server/Operations/UploadFilesOperation.swift b/WeTransfer/Server/Operations/UploadFilesOperation.swift index 2a403e2..291f404 100644 --- a/WeTransfer/Server/Operations/UploadFilesOperation.swift +++ b/WeTransfer/Server/Operations/UploadFilesOperation.swift @@ -9,7 +9,7 @@ import Foundation /// Handles the uploading of all files -- that are not uploaded yet -- in the provided transfer. Creates an operation queue with an `UploadFileOperation` for each file to be uploaded. -final class UploadFilesOperation: ChainedAsynchronousResultOperation, URLSessionDataDelegate where Container: Transferable { +final class UploadFilesOperation: ChainedAsynchronousResultOperation, URLSessionDataDelegate { /// Amount of bytes already sent private var bytesSent: Bytes = 0 From 99e4e29b5cd135fc5e50a25b2eaaeb01d768148c Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 15:09:09 +0200 Subject: [PATCH 28/43] Set a base test case for all tests using same configuration --- WeTransfer.xcodeproj/project.pbxproj | 6 +++++ WeTransferTests/AuthorizationTests.swift | 12 +--------- WeTransferTests/BaseTestCase.swift | 22 +++++++++++++++++++ WeTransferTests/Board/AddFilesTests.swift | 12 +--------- WeTransferTests/Board/BoardChunksTests.swift | 12 +--------- WeTransferTests/Board/BoardUploadTests.swift | 12 +--------- WeTransferTests/Board/CreateBoardTests.swift | 12 +--------- .../Board/SimpleBoardUploadTests.swift | 12 +--------- .../Transfer/CreateTransferTests.swift | 12 +--------- .../Transfer/SimpleTransferTests.swift | 12 +--------- .../Transfer/TransferChunksTests.swift | 12 +--------- .../Transfer/TransferUploadTests.swift | 12 +--------- 12 files changed, 38 insertions(+), 110 deletions(-) create mode 100644 WeTransferTests/BaseTestCase.swift diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index 883d549..12bf718 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ 3E3B9D3C20F7ADDC0064915C /* MediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3B9D3B20F7ADDC0064915C /* MediaPicker.swift */; }; 3E5E514F20EA816600485FA3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5E514720EA816600485FA3 /* MainViewController.swift */; }; 3E5E515320EA816600485FA3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5E514D20EA816600485FA3 /* AppDelegate.swift */; }; + 3E7219A92174C7FC00143492 /* BaseTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7219A82174C7FC00143492 /* BaseTestCase.swift */; }; + 3E7219AA2174C7FC00143492 /* BaseTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E7219A82174C7FC00143492 /* BaseTestCase.swift */; }; 3E80E0A72091D0E700114711 /* WeTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E80E0A62091D0E700114711 /* WeTransfer.swift */; }; 3E80E0AA2091D12B00114711 /* Transfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E80E0A92091D12B00114711 /* Transfer.swift */; }; 3E80E0AE2091D16E00114711 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E80E0AD2091D16E00114711 /* Result.swift */; }; @@ -169,6 +171,7 @@ 3E7116E420FC85D400115E55 /* WeTransfer iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "WeTransfer iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 3E7116F820FC85F100115E55 /* WeTransfer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WeTransfer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3E71170020FC85F100115E55 /* WeTransfer macOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "WeTransfer macOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3E7219A82174C7FC00143492 /* BaseTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTestCase.swift; sourceTree = ""; }; 3E7348BD20B4040D009B41D0 /* TestConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConfiguration.swift; sourceTree = ""; }; 3E7348BF20B41895009B41D0 /* InitializationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializationTests.swift; sourceTree = ""; }; 3E7348C120B41936009B41D0 /* SimpleTransferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleTransferTests.swift; sourceTree = ""; }; @@ -425,6 +428,7 @@ 3ED1C0A120B43E780045D8B3 /* Transfer */, 3E3A87612162549F00568775 /* Board */, 3E7348BD20B4040D009B41D0 /* TestConfiguration.swift */, + 3E7219A82174C7FC00143492 /* BaseTestCase.swift */, 3E7348BF20B41895009B41D0 /* InitializationTests.swift */, 3E7348C520B41EC0009B41D0 /* RequestTests.swift */, 3E7348C320B41DBA009B41D0 /* AuthorizationTests.swift */, @@ -778,6 +782,7 @@ 3E9915B32111B0C50071FDF7 /* RequestTests.swift in Sources */, 3E9915AC2111B0C50071FDF7 /* SimpleTransferTests.swift in Sources */, 3E9915B02111B0C50071FDF7 /* TransferUploadTests.swift in Sources */, + 3E7219A92174C7FC00143492 /* BaseTestCase.swift in Sources */, 3E9D48D32166535D00499760 /* SimpleBoardUploadTests.swift in Sources */, 3E3A87642162798800568775 /* CreateBoardTests.swift in Sources */, 3E9915AE2111B0C50071FDF7 /* AddFilesTests.swift in Sources */, @@ -836,6 +841,7 @@ 3E9915BF2111B0C60071FDF7 /* RequestTests.swift in Sources */, 3E9915B82111B0C60071FDF7 /* SimpleTransferTests.swift in Sources */, 3E9915BC2111B0C60071FDF7 /* TransferUploadTests.swift in Sources */, + 3E7219AA2174C7FC00143492 /* BaseTestCase.swift in Sources */, 3E9D48D42166535D00499760 /* SimpleBoardUploadTests.swift in Sources */, 3E3A87632162798800568775 /* CreateBoardTests.swift in Sources */, 3E9915BA2111B0C60071FDF7 /* AddFilesTests.swift in Sources */, diff --git a/WeTransferTests/AuthorizationTests.swift b/WeTransferTests/AuthorizationTests.swift index 37bc5fc..f732066 100644 --- a/WeTransferTests/AuthorizationTests.swift +++ b/WeTransferTests/AuthorizationTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class AuthorizationTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class AuthorizationTests: BaseTestCase { func testUnauthorized() { do { diff --git a/WeTransferTests/BaseTestCase.swift b/WeTransferTests/BaseTestCase.swift new file mode 100644 index 0000000..52a5e78 --- /dev/null +++ b/WeTransferTests/BaseTestCase.swift @@ -0,0 +1,22 @@ +// +// BaseTestCase.swift +// WeTransfer +// +// Created by Pim Coumans on 15/10/2018. +// Copyright © 2018 WeTransfer. All rights reserved. +// + +import XCTest +@testable import WeTransfer + +class BaseTestCase: XCTestCase { + + override func setUp() { + TestConfiguration.configure(environment: .production) + } + + override func tearDown() { + TestConfiguration.resetConfiguration() + } + +} diff --git a/WeTransferTests/Board/AddFilesTests.swift b/WeTransferTests/Board/AddFilesTests.swift index 4b0acde..cd49cd3 100644 --- a/WeTransferTests/Board/AddFilesTests.swift +++ b/WeTransferTests/Board/AddFilesTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class AddFilesTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class AddFilesTests: BaseTestCase { func testAddingFilesToBoardModel() { diff --git a/WeTransferTests/Board/BoardChunksTests.swift b/WeTransferTests/Board/BoardChunksTests.swift index 20718b1..5dec94c 100644 --- a/WeTransferTests/Board/BoardChunksTests.swift +++ b/WeTransferTests/Board/BoardChunksTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class BoardChunksTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class BoardChunksTests: BaseTestCase { func testChunkCreationRequest() { guard let file = TestConfiguration.fileModel else { diff --git a/WeTransferTests/Board/BoardUploadTests.swift b/WeTransferTests/Board/BoardUploadTests.swift index 60aa62a..027848b 100644 --- a/WeTransferTests/Board/BoardUploadTests.swift +++ b/WeTransferTests/Board/BoardUploadTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class BoardUploadTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class BoardUploadTests: BaseTestCase { var observation: NSKeyValueObservation? diff --git a/WeTransferTests/Board/CreateBoardTests.swift b/WeTransferTests/Board/CreateBoardTests.swift index c834f70..8b39b1d 100644 --- a/WeTransferTests/Board/CreateBoardTests.swift +++ b/WeTransferTests/Board/CreateBoardTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class CreateBoardTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class CreateBoardTests: BaseTestCase { func testCreateBoardRequest() { let createdBoardExpectation = expectation(description: "Transfer is created") diff --git a/WeTransferTests/Board/SimpleBoardUploadTests.swift b/WeTransferTests/Board/SimpleBoardUploadTests.swift index 38228e9..d615e80 100644 --- a/WeTransferTests/Board/SimpleBoardUploadTests.swift +++ b/WeTransferTests/Board/SimpleBoardUploadTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class SimpleBoardUploadTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class SimpleBoardUploadTests: BaseTestCase { func testSimpleUpload() { diff --git a/WeTransferTests/Transfer/CreateTransferTests.swift b/WeTransferTests/Transfer/CreateTransferTests.swift index eed2c36..90861b7 100644 --- a/WeTransferTests/Transfer/CreateTransferTests.swift +++ b/WeTransferTests/Transfer/CreateTransferTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class CreateTransferTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class CreateTransferTests: BaseTestCase { func testCreateTransferRequest() { guard let fileURL = TestConfiguration.imageFileURL else { diff --git a/WeTransferTests/Transfer/SimpleTransferTests.swift b/WeTransferTests/Transfer/SimpleTransferTests.swift index 72bfcd3..2088663 100644 --- a/WeTransferTests/Transfer/SimpleTransferTests.swift +++ b/WeTransferTests/Transfer/SimpleTransferTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class SimpleTransferTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class SimpleTransferTests: BaseTestCase { func testSimpleTransfer() { diff --git a/WeTransferTests/Transfer/TransferChunksTests.swift b/WeTransferTests/Transfer/TransferChunksTests.swift index 35ee536..3058a99 100644 --- a/WeTransferTests/Transfer/TransferChunksTests.swift +++ b/WeTransferTests/Transfer/TransferChunksTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class TransferChunksTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class TransferChunksTests: BaseTestCase { func testChunkCreationRequest() { guard let fileURL = TestConfiguration.imageFileURL else { diff --git a/WeTransferTests/Transfer/TransferUploadTests.swift b/WeTransferTests/Transfer/TransferUploadTests.swift index 2218b79..52967c8 100644 --- a/WeTransferTests/Transfer/TransferUploadTests.swift +++ b/WeTransferTests/Transfer/TransferUploadTests.swift @@ -9,17 +9,7 @@ import XCTest @testable import WeTransfer -final class TransferUploadTests: XCTestCase { - - override func setUp() { - super.setUp() - TestConfiguration.configure(environment: .production) - } - - override func tearDown() { - super.tearDown() - TestConfiguration.resetConfiguration() - } +final class TransferUploadTests: BaseTestCase { var observation: NSKeyValueObservation? From 462612c458b79fe1d4fc7426ccd45c5ca7253dc8 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 15:13:23 +0200 Subject: [PATCH 29/43] Update WeTransferTests/BaseTestCase.swift --- WeTransferTests/BaseTestCase.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WeTransferTests/BaseTestCase.swift b/WeTransferTests/BaseTestCase.swift index 52a5e78..a07e865 100644 --- a/WeTransferTests/BaseTestCase.swift +++ b/WeTransferTests/BaseTestCase.swift @@ -12,10 +12,12 @@ import XCTest class BaseTestCase: XCTestCase { override func setUp() { + super.setUp() TestConfiguration.configure(environment: .production) } override func tearDown() { + super.tearDown() TestConfiguration.resetConfiguration() } From 87ffceee0ecfdf0a6d8845de8a7cae1a5d7523f0 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 15:13:56 +0200 Subject: [PATCH 30/43] Split logic of transfer upload test --- .../Transfer/TransferUploadTests.swift | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/WeTransferTests/Transfer/TransferUploadTests.swift b/WeTransferTests/Transfer/TransferUploadTests.swift index 52967c8..51aac96 100644 --- a/WeTransferTests/Transfer/TransferUploadTests.swift +++ b/WeTransferTests/Transfer/TransferUploadTests.swift @@ -13,46 +13,57 @@ final class TransferUploadTests: BaseTestCase { var observation: NSKeyValueObservation? - func testFileUpload() { - + private func createTransfer(completion: @escaping (Transfer?) -> Void) { guard let file = TestConfiguration.fileModel else { XCTFail("File not available") return } - - let transferSentExpectation = expectation(description: "Transfer is sent") - var resultTransfer: Transfer? WeTransfer.createTransfer(saying: "TestTransfer", fileURLs: [file.url]) { result in switch result { case .failure(let error): XCTFail("Transfer creation failed: \(error)") - transferSentExpectation.fulfill() + completion(nil) return case .success(let transfer): - resultTransfer = transfer - WeTransfer.upload(transfer, stateChanged: { (state) in - switch state { - case .created(let transfer): - print("Transfer created: \(String(describing: transfer.identifier))") - case .uploading(let progress): - print("Upload started") - var percentage = 0.0 - self.observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, _) in - let newPercentage = (progress.fractionCompleted * 100).rounded(FloatingPointRoundingRule.up) - if newPercentage != percentage { - percentage = newPercentage - print("PROGRESS: \(newPercentage)% (\(progress.completedUnitCount) bytes)") - } - }) - case .failed(let error): - XCTFail("Sending transfer failed: \(error)") - transferSentExpectation.fulfill() - case .completed: - transferSentExpectation.fulfill() - } - }) + completion(transfer) + } + } + + } + + func testFileUpload() { + + let transferSentExpectation = expectation(description: "Transfer is sent") + var resultTransfer: Transfer? + + createTransfer { (transfer) in + guard let transfer = transfer else { + transferSentExpectation.fulfill() + return } + resultTransfer = transfer + WeTransfer.upload(transfer, stateChanged: { (state) in + switch state { + case .created(let transfer): + print("Transfer created: \(String(describing: transfer.identifier))") + case .uploading(let progress): + print("Upload started") + var percentage = 0.0 + self.observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, _) in + let newPercentage = (progress.fractionCompleted * 100).rounded(FloatingPointRoundingRule.up) + if newPercentage != percentage { + percentage = newPercentage + print("PROGRESS: \(newPercentage)% (\(progress.completedUnitCount) bytes)") + } + }) + case .failed(let error): + XCTFail("Sending transfer failed: \(error)") + transferSentExpectation.fulfill() + case .completed: + transferSentExpectation.fulfill() + } + }) } waitForExpectations(timeout: 60) { _ in From bb6cb183f67daf5390a131f34334bc153693073e Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 15:23:42 +0200 Subject: [PATCH 31/43] Seperate Transfer and Board requests made in CompleteUploadOperation --- .../Operations/CompleteUploadOperation.swift | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/WeTransfer/Server/Operations/CompleteUploadOperation.swift b/WeTransfer/Server/Operations/CompleteUploadOperation.swift index 05674c0..eb98bfb 100644 --- a/WeTransfer/Server/Operations/CompleteUploadOperation.swift +++ b/WeTransfer/Server/Operations/CompleteUploadOperation.swift @@ -58,38 +58,45 @@ final class CompleteUploadOperation: AsynchronousResultOperation { } if container is Transfer { - let request: APIEndpoint = .completeTransferFileUpload(transferIdentifier: containerIdentifier, fileIdentifier: fileIdentifier) - let parameters = CompleteTransferFileUploadParameters(partNumbers: numberOfChunks) - WeTransfer.request(request, parameters: parameters) { [weak self] result in - guard let self = self else { - return - } - switch result { - case .failure(let error): - self.finish(with: .failure(error)) - case .success: - self.finish(with: .success(self.file)) - } - } + performTransferRequest(transferIdentifier: containerIdentifier, fileIdentifier: fileIdentifier, numberOfChunks: numberOfChunks) } else if container is Board { - WeTransfer.request(.completeBoardFileUpload(boardIdentifier: containerIdentifier, fileIdentifier: fileIdentifier)) { [weak self] result in - guard let self = self else { - return - } - switch result { - case .failure(let error): - self.finish(with: .failure(error)) - case .success(let response): - guard response.success else { - self.finish(with: .failure(WeTransfer.RequestError.serverError(errorMessage: response.message, httpCode: nil))) - return - } - self.finish(with: .success(self.file)) - } - } + performBoardRequest(boardIdentifier: containerIdentifier, fileIdentifier: fileIdentifier) } else { fatalError("Container type '\(type(of: container))' is not supported") } } + private func performTransferRequest(transferIdentifier: String, fileIdentifier: String, numberOfChunks: Int) { + let request: APIEndpoint = .completeTransferFileUpload(transferIdentifier: transferIdentifier, fileIdentifier: fileIdentifier) + let parameters = CompleteTransferFileUploadParameters(partNumbers: numberOfChunks) + WeTransfer.request(request, parameters: parameters) { [weak self] result in + guard let self = self else { + return + } + switch result { + case .failure(let error): + self.finish(with: .failure(error)) + case .success: + self.finish(with: .success(self.file)) + } + } + } + + private func performBoardRequest(boardIdentifier: String, fileIdentifier: String) { + WeTransfer.request(.completeBoardFileUpload(boardIdentifier: boardIdentifier, fileIdentifier: fileIdentifier)) { [weak self] result in + guard let self = self else { + return + } + switch result { + case .failure(let error): + self.finish(with: .failure(error)) + case .success(let response): + guard response.success else { + self.finish(with: .failure(WeTransfer.RequestError.serverError(errorMessage: response.message, httpCode: nil))) + return + } + self.finish(with: .success(self.file)) + } + } + } } From 7a5b6d93e82e6924e2b084216737828469c9d21c Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 15:26:43 +0200 Subject: [PATCH 32/43] Keep danger happy by defining base test case subclassability --- WeTransferTests/BaseTestCase.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WeTransferTests/BaseTestCase.swift b/WeTransferTests/BaseTestCase.swift index a07e865..8224f88 100644 --- a/WeTransferTests/BaseTestCase.swift +++ b/WeTransferTests/BaseTestCase.swift @@ -9,7 +9,7 @@ import XCTest @testable import WeTransfer -class BaseTestCase: XCTestCase { +open class BaseTestCase: XCTestCase { override func setUp() { super.setUp() From cac6a3cdf6fe5081eae35e214a24a5fd605f2d54 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Mon, 15 Oct 2018 15:34:00 +0200 Subject: [PATCH 33/43] Fixing build errors --- WeTransferTests/BaseTestCase.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WeTransferTests/BaseTestCase.swift b/WeTransferTests/BaseTestCase.swift index 8224f88..f548483 100644 --- a/WeTransferTests/BaseTestCase.swift +++ b/WeTransferTests/BaseTestCase.swift @@ -11,12 +11,12 @@ import XCTest open class BaseTestCase: XCTestCase { - override func setUp() { + override open func setUp() { super.setUp() TestConfiguration.configure(environment: .production) } - override func tearDown() { + override open func tearDown() { super.tearDown() TestConfiguration.resetConfiguration() } From 2cd125eba0fd375dae766a3c4fd6a68d028ec7ad Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 16 Oct 2018 15:25:51 +0200 Subject: [PATCH 34/43] Selecting which scheme to check for Danger --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 57b81de..0f6dfee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: global: - NOKOGIRI_USE_SYSTEM_LIBRARIES=true script: -- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer" +- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer 'WeTransfer iOS'" before_install: - openssl aes-256-cbc -K $encrypted_fce2bbf28b94_key -iv $encrypted_fce2bbf28b94_iv -in WeTransferTests/Secrets.plist.enc -out WeTransferTests/Secrets.plist -d From d9f00d79c74ed68a4eafaa359c084392f101df45 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 16 Oct 2018 15:46:55 +0200 Subject: [PATCH 35/43] Update iOS CI changes --- Submodules/WeTransfer-iOS-CI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Submodules/WeTransfer-iOS-CI b/Submodules/WeTransfer-iOS-CI index de7710f..1323ecb 160000 --- a/Submodules/WeTransfer-iOS-CI +++ b/Submodules/WeTransfer-iOS-CI @@ -1 +1 @@ -Subproject commit de7710f6f26ea4268fe35d4373ebdb22663dc7eb +Subproject commit 1323ecb582f7db933490dda590d8a5fc598d0ebb From ea3c2d4c8129f79e5e77a6ded2a5d551ae867282 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 16 Oct 2018 16:37:46 +0200 Subject: [PATCH 36/43] Use correct variable project scheme --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7c8d6cc..c1620d8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -13,7 +13,7 @@ lane :test do |options| ENV["FASTLANE_XCODE_LIST_TIMEOUT"] = "120" scan( - scheme: "#{options[:project_name]} iOS", + scheme: "#{options[:scheme]}", project: "#{options[:project_name]}.xcodeproj", device: "iPhone 7", clean: true, From 17eb34c493dd2ab1238f7c1007e1f1cd3ad4eab7 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 16 Oct 2018 17:43:12 +0200 Subject: [PATCH 37/43] Updated WeTransfer-iOS-CI submodule --- Submodules/WeTransfer-iOS-CI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Submodules/WeTransfer-iOS-CI b/Submodules/WeTransfer-iOS-CI index 1323ecb..93c505b 160000 --- a/Submodules/WeTransfer-iOS-CI +++ b/Submodules/WeTransfer-iOS-CI @@ -1 +1 @@ -Subproject commit 1323ecb582f7db933490dda590d8a5fc598d0ebb +Subproject commit 93c505b35871f453c2de159e963eb86ed781c21e From 7915a10265c1e8ba45ae4714b9457c15afb51faa Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 16 Oct 2018 18:01:23 +0200 Subject: [PATCH 38/43] Trying a different way of handling the space in the scheme --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cd614de..a2a424c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: global: - NOKOGIRI_USE_SYSTEM_LIBRARIES=true script: -- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer 'WeTransfer iOS'" +- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer WeTransfer\ iOS" before_install: - openssl aes-256-cbc -K $encrypted_fce2bbf28b94_key -iv $encrypted_fce2bbf28b94_iv -in WeTransferTests/Secrets.plist.enc -out WeTransferTests/Secrets.plist -d From 9857d7f39bb087db46dcb5e1da5c1488512b3e13 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 16 Oct 2018 18:09:29 +0200 Subject: [PATCH 39/43] Escaping the escape character --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a2a424c..b348698 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: global: - NOKOGIRI_USE_SYSTEM_LIBRARIES=true script: -- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer WeTransfer\ iOS" +- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer WeTransfer\\ iOS" before_install: - openssl aes-256-cbc -K $encrypted_fce2bbf28b94_key -iv $encrypted_fce2bbf28b94_iv -in WeTransferTests/Secrets.plist.enc -out WeTransferTests/Secrets.plist -d From a58d99a48c06daa091cfd3359f98081c6ae912b0 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 16 Oct 2018 18:47:24 +0200 Subject: [PATCH 40/43] Using single quotes instead of escape character --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b348698..cd614de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: global: - NOKOGIRI_USE_SYSTEM_LIBRARIES=true script: -- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer WeTransfer\\ iOS" +- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer 'WeTransfer iOS'" before_install: - openssl aes-256-cbc -K $encrypted_fce2bbf28b94_key -iv $encrypted_fce2bbf28b94_iv -in WeTransferTests/Secrets.plist.enc -out WeTransferTests/Secrets.plist -d From 6bbce38f4900d70eef773866b8b2ee592f09bdf8 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Tue, 16 Oct 2018 19:01:21 +0200 Subject: [PATCH 41/43] Renamed schemes replacing whitespace with dash --- .travis.yml | 2 +- .../{WeTransfer iOS.xcscheme => WeTransfer-iOS.xcscheme} | 0 .../{WeTransfer macOS.xcscheme => WeTransfer-macOS.xcscheme} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename WeTransfer.xcodeproj/xcshareddata/xcschemes/{WeTransfer iOS.xcscheme => WeTransfer-iOS.xcscheme} (100%) rename WeTransfer.xcodeproj/xcshareddata/xcschemes/{WeTransfer macOS.xcscheme => WeTransfer-macOS.xcscheme} (100%) diff --git a/.travis.yml b/.travis.yml index cd614de..b942f62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ env: global: - NOKOGIRI_USE_SYSTEM_LIBRARIES=true script: -- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer 'WeTransfer iOS'" +- "./Submodules/WeTransfer-iOS-CI/Scripts/travis.sh WeTransfer WeTransfer-iOS" before_install: - openssl aes-256-cbc -K $encrypted_fce2bbf28b94_key -iv $encrypted_fce2bbf28b94_iv -in WeTransferTests/Secrets.plist.enc -out WeTransferTests/Secrets.plist -d diff --git a/WeTransfer.xcodeproj/xcshareddata/xcschemes/WeTransfer iOS.xcscheme b/WeTransfer.xcodeproj/xcshareddata/xcschemes/WeTransfer-iOS.xcscheme similarity index 100% rename from WeTransfer.xcodeproj/xcshareddata/xcschemes/WeTransfer iOS.xcscheme rename to WeTransfer.xcodeproj/xcshareddata/xcschemes/WeTransfer-iOS.xcscheme diff --git a/WeTransfer.xcodeproj/xcshareddata/xcschemes/WeTransfer macOS.xcscheme b/WeTransfer.xcodeproj/xcshareddata/xcschemes/WeTransfer-macOS.xcscheme similarity index 100% rename from WeTransfer.xcodeproj/xcshareddata/xcschemes/WeTransfer macOS.xcscheme rename to WeTransfer.xcodeproj/xcshareddata/xcschemes/WeTransfer-macOS.xcscheme From eac101551d9c1e05647ff9e1679495856150b718 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Thu, 18 Oct 2018 11:15:02 +0200 Subject: [PATCH 42/43] Updated sample app to use SDK V2 --- WeTransfer Sample Project/MainViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WeTransfer Sample Project/MainViewController.swift b/WeTransfer Sample Project/MainViewController.swift index 6ff1672..de79592 100644 --- a/WeTransfer Sample Project/MainViewController.swift +++ b/WeTransfer Sample Project/MainViewController.swift @@ -96,7 +96,7 @@ extension MainViewController { let files = selectedMedia.map({ $0.url }) // Creates a transfer and uploads all provided files - WeTransfer.uploadTransfer(named: "Sample Transfer", containing: files) { [weak self] state in + WeTransfer.uploadTransfer(saying: "Sample Transfer", containing: files) { [weak self] state in switch state { case .uploading(let progress): self?.viewState = .transferInProgress From 5230efd361744ca67fe879262d932fb257eada15 Mon Sep 17 00:00:00 2001 From: Pim Coumans Date: Thu, 18 Oct 2018 11:16:17 +0200 Subject: [PATCH 43/43] Reordered model files --- WeTransfer.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WeTransfer.xcodeproj/project.pbxproj b/WeTransfer.xcodeproj/project.pbxproj index 12bf718..7981c53 100644 --- a/WeTransfer.xcodeproj/project.pbxproj +++ b/WeTransfer.xcodeproj/project.pbxproj @@ -299,11 +299,11 @@ 3E80E0A82091D11300114711 /* Models */ = { isa = PBXGroup; children = ( + 3E9D48CF21663EE400499760 /* Transferable.swift */, 3E80E0A92091D12B00114711 /* Transfer.swift */, 3EB1E191215916AF00E1E4EE /* Board.swift */, 3E9413CB2099E390003A42D1 /* File.swift */, 3E9413CD2099E3F5003A42D1 /* Chunk.swift */, - 3E9D48CF21663EE400499760 /* Transferable.swift */, ); path = Models; sourceTree = "";