From 9ab52710686243169cbd3f173dbf7344b0978046 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 12 Sep 2022 22:27:53 -0400 Subject: [PATCH] feat: add ParseFileTransferable protocol for direct file uploads (#410) * feat: add ParseFileTransferable protocol for direct uploads to file storage * nit --- CHANGELOG.md | 7 ++- ParseSwift.xcodeproj/project.pbxproj | 20 +++++++ .../Documentation.docc/ParseSwift.md | 4 +- .../ParseSwift/Extensions/URLSession.swift | 33 ++++++----- Sources/ParseSwift/Objects/ParseObject.swift | 2 +- Sources/ParseSwift/Parse.swift | 12 ++++ Sources/ParseSwift/ParseConstants.swift | 2 +- .../Protocols/ParseFileTransferable.swift | 58 +++++++++++++++++++ Sources/ParseSwift/Types/ParseCLP.swift | 8 +-- .../ParseSwift/Types/ParseConfiguration.swift | 14 +++++ .../Types/ParseFileDefaultTransfer.swift | 11 ++++ Sources/ParseSwift/Types/ParseOperation.swift | 6 +- .../ParseSwiftTests/InitializeSDKTests.swift | 3 +- 13 files changed, 154 insertions(+), 26 deletions(-) create mode 100644 Sources/ParseSwift/Protocols/ParseFileTransferable.swift create mode 100644 Sources/ParseSwift/Types/ParseFileDefaultTransfer.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 139a23597..b6276c051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,15 @@ # Parse-Swift Changelog ### main -[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.10.0...main) +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.12.0...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ +### 4.12.0 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.11.0...4.12.0) + __New features__ +- Add the ParseFileTransferable protocol for overriding the default transfer behavior for ParseFile's. Allows for direct uploads to other file storage + providers ([#408](https://github.com/parse-community/Parse-Swift/pull/408)), thanks to [Corey Baker](https://github.com/cbaker6). - Add the become method to ParseInstallation which allows any ParseInstallation to be copied to the current installation. This method can be used to migrate any ParseInstallation to the current installation in the Swift SDK ([#407](https://github.com/parse-community/Parse-Swift/pull/407)), thanks to [Corey Baker](https://github.com/cbaker6). __Fixes__ diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 15c6ed524..564e74b4a 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -319,6 +319,14 @@ 704C886D28BE69A8008E6B01 /* ParseConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704C886B28BE69A8008E6B01 /* ParseConfiguration.swift */; }; 704C886E28BE69A8008E6B01 /* ParseConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704C886B28BE69A8008E6B01 /* ParseConfiguration.swift */; }; 704C886F28BE69A8008E6B01 /* ParseConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704C886B28BE69A8008E6B01 /* ParseConfiguration.swift */; }; + 704E781728CFD8A00075F952 /* ParseFileTransferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781628CFD8A00075F952 /* ParseFileTransferable.swift */; }; + 704E781828CFD8A00075F952 /* ParseFileTransferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781628CFD8A00075F952 /* ParseFileTransferable.swift */; }; + 704E781928CFD8A00075F952 /* ParseFileTransferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781628CFD8A00075F952 /* ParseFileTransferable.swift */; }; + 704E781A28CFD8A00075F952 /* ParseFileTransferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781628CFD8A00075F952 /* ParseFileTransferable.swift */; }; + 704E781C28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */; }; + 704E781D28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */; }; + 704E781E28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */; }; + 704E781F28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */; }; 705025992842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; }; 7050259A2842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; }; 7050259B2842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; }; @@ -1234,6 +1242,8 @@ 7045769726BD917500F86F71 /* Query+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+async.swift"; sourceTree = ""; }; 7045769C26BD934000F86F71 /* ParseFile+async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseFile+async.swift"; sourceTree = ""; }; 704C886B28BE69A8008E6B01 /* ParseConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfiguration.swift; sourceTree = ""; }; + 704E781628CFD8A00075F952 /* ParseFileTransferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileTransferable.swift; sourceTree = ""; }; + 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileDefaultTransfer.swift; sourceTree = ""; }; 705025982842FD3B008D6624 /* ParseCLPTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCLPTests.swift; sourceTree = ""; }; 7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchemaAsyncTests.swift; sourceTree = ""; }; 705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchemaCombineTests.swift; sourceTree = ""; }; @@ -1790,6 +1800,7 @@ 703B090626BD9764005A112F /* ParseCloud+async.swift */, 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */, 70647E9B259E3A9A004C1004 /* ParseEncodable.swift */, + 704E781628CFD8A00075F952 /* ParseFileTransferable.swift */, 70CE0AB6285A83B100DAEA86 /* ParseHookable.swift */, 70385E752858E1000084D306 /* ParseHookFunctionable.swift */, 70CE0AC0285FD59B00DAEA86 /* ParseHookFunctionable+async.swift */, @@ -2124,6 +2135,7 @@ F97B45C124D9C6F200F4A88B /* ParseFile.swift */, 7045769C26BD934000F86F71 /* ParseFile+async.swift */, 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */, + 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */, F97B45BC24D9C6F200F4A88B /* ParseGeoPoint.swift */, 70F79A182639CE6F00731C46 /* ParseHealth.swift */, 703B08FC26BD953B005A112F /* ParseHealth+async.swift */, @@ -2707,6 +2719,7 @@ 705025E628514F36008D6624 /* ParsePushPayloadAny.swift in Sources */, 709A148228395ED100BF85E5 /* ParseSchema+async.swift in Sources */, 705025D1284CFCDE008D6624 /* ParsePushAppleAlert.swift in Sources */, + 704E781C28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */, 7045769826BD917500F86F71 /* Query+async.swift in Sources */, 703B094E26BF47E3005A112F /* ParseTwitter+combine.swift in Sources */, 70386A3825D998D90048EC1B /* ParseLDAP.swift in Sources */, @@ -2765,6 +2778,7 @@ 707A3C2025B14BD0000D215C /* ParseApple.swift in Sources */, 703B095D26BF481F005A112F /* ParseFacebook+async.swift in Sources */, 709A147D283949D100BF85E5 /* ParseSchema.swift in Sources */, + 704E781728CFD8A00075F952 /* ParseFileTransferable.swift in Sources */, F97B462224D9C6F200F4A88B /* ParseKeyValueStore.swift in Sources */, 703B090226BD9652005A112F /* ParseAnalytics+async.swift in Sources */, 703B093F26BF47AC005A112F /* ParseApple+async.swift in Sources */, @@ -3016,6 +3030,7 @@ 705025E728514F36008D6624 /* ParsePushPayloadAny.swift in Sources */, 709A148328395ED100BF85E5 /* ParseSchema+async.swift in Sources */, 705025D2284CFCDE008D6624 /* ParsePushAppleAlert.swift in Sources */, + 704E781D28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */, 703B094F26BF47E3005A112F /* ParseTwitter+combine.swift in Sources */, 70386A3925D998D90048EC1B /* ParseLDAP.swift in Sources */, 700395F325A171320052CB31 /* LiveQueryable.swift in Sources */, @@ -3074,6 +3089,7 @@ 703B095E26BF481F005A112F /* ParseFacebook+async.swift in Sources */, F97B462324D9C6F200F4A88B /* ParseKeyValueStore.swift in Sources */, 709A147E283949D100BF85E5 /* ParseSchema.swift in Sources */, + 704E781828CFD8A00075F952 /* ParseFileTransferable.swift in Sources */, 703B090326BD9652005A112F /* ParseAnalytics+async.swift in Sources */, 703B094026BF47AC005A112F /* ParseApple+async.swift in Sources */, F97B45E724D9C6F200F4A88B /* Query.swift in Sources */, @@ -3457,6 +3473,7 @@ 705025E928514F36008D6624 /* ParsePushPayloadAny.swift in Sources */, 709A148528395ED100BF85E5 /* ParseSchema+async.swift in Sources */, 705025D4284CFCDE008D6624 /* ParsePushAppleAlert.swift in Sources */, + 704E781F28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */, 7045769B26BD917500F86F71 /* Query+async.swift in Sources */, 703B095126BF47E3005A112F /* ParseTwitter+combine.swift in Sources */, 70386A3B25D998D90048EC1B /* ParseLDAP.swift in Sources */, @@ -3515,6 +3532,7 @@ 703B096026BF481F005A112F /* ParseFacebook+async.swift in Sources */, F97B462124D9C6F200F4A88B /* ParseStorage.swift in Sources */, 709A1480283949D100BF85E5 /* ParseSchema.swift in Sources */, + 704E781A28CFD8A00075F952 /* ParseFileTransferable.swift in Sources */, 703B090526BD9652005A112F /* ParseAnalytics+async.swift in Sources */, 703B094226BF47AC005A112F /* ParseApple+async.swift in Sources */, F97B466724D9C88600F4A88B /* SecureStorage.swift in Sources */, @@ -3643,6 +3661,7 @@ 705025E828514F36008D6624 /* ParsePushPayloadAny.swift in Sources */, 709A148428395ED100BF85E5 /* ParseSchema+async.swift in Sources */, 705025D3284CFCDE008D6624 /* ParsePushAppleAlert.swift in Sources */, + 704E781E28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */, 7045769A26BD917500F86F71 /* Query+async.swift in Sources */, 703B095026BF47E3005A112F /* ParseTwitter+combine.swift in Sources */, 70386A3A25D998D90048EC1B /* ParseLDAP.swift in Sources */, @@ -3701,6 +3720,7 @@ 703B095F26BF481F005A112F /* ParseFacebook+async.swift in Sources */, F97B462024D9C6F200F4A88B /* ParseStorage.swift in Sources */, 709A147F283949D100BF85E5 /* ParseSchema.swift in Sources */, + 704E781928CFD8A00075F952 /* ParseFileTransferable.swift in Sources */, 703B090426BD9652005A112F /* ParseAnalytics+async.swift in Sources */, 703B094126BF47AC005A112F /* ParseApple+async.swift in Sources */, F97B466624D9C88600F4A88B /* SecureStorage.swift in Sources */, diff --git a/Sources/ParseSwift/Documentation.docc/ParseSwift.md b/Sources/ParseSwift/Documentation.docc/ParseSwift.md index a58992d85..d8b904f4b 100644 --- a/Sources/ParseSwift/Documentation.docc/ParseSwift.md +++ b/Sources/ParseSwift/Documentation.docc/ParseSwift.md @@ -12,8 +12,8 @@ To learn how to use or experiment with ParseSwift, you can run and edit the [Par ### Configure SDK - ``ParseSwift/initialize(configuration:)`` -- ``ParseSwift/initialize(applicationId:clientKey:masterKey:serverURL:liveQueryServerURL:allowingCustomObjectIds:usingTransactions:usingEqualQueryConstraint:usingPostForQuery:keyValueStore:requestCachePolicy:cacheMemoryCapacity:cacheDiskCapacity:usingDataProtectionKeychain:deletingKeychainIfNeeded:httpAdditionalHeaders:maxConnectionAttempts:authentication:)`` -- ``ParseSwift/initialize(applicationId:clientKey:masterKey:serverURL:liveQueryServerURL:allowingCustomObjectIds:usingTransactions:usingEqualQueryConstraint:usingPostForQuery:keyValueStore:requestCachePolicy:cacheMemoryCapacity:cacheDiskCapacity:migratingFromObjcSDK:usingDataProtectionKeychain:deletingKeychainIfNeeded:httpAdditionalHeaders:maxConnectionAttempts:authentication:)`` +- ``ParseSwift/initialize(applicationId:clientKey:masterKey:serverURL:liveQueryServerURL:allowingCustomObjectIds:usingTransactions:usingEqualQueryConstraint:usingPostForQuery:keyValueStore:requestCachePolicy:cacheMemoryCapacity:cacheDiskCapacity:usingDataProtectionKeychain:deletingKeychainIfNeeded:httpAdditionalHeaders:maxConnectionAttempts:parseFileTransfer:authentication:)`` +- ``ParseSwift/initialize(applicationId:clientKey:masterKey:serverURL:liveQueryServerURL:allowingCustomObjectIds:usingTransactions:usingEqualQueryConstraint:usingPostForQuery:keyValueStore:requestCachePolicy:cacheMemoryCapacity:cacheDiskCapacity:migratingFromObjcSDK:usingDataProtectionKeychain:deletingKeychainIfNeeded:httpAdditionalHeaders:maxConnectionAttempts:parseFileTransfer:authentication:)`` - ``ParseSwift/configuration`` - ``ParseSwift/setAccessGroup(_:synchronizeAcrossDevices:)`` - ``ParseSwift/updateAuthentication(_:)`` diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift index c2095de3b..9691c80fe 100644 --- a/Sources/ParseSwift/Extensions/URLSession.swift +++ b/Sources/ParseSwift/Extensions/URLSession.swift @@ -226,7 +226,10 @@ internal extension URLSession { ) { var task: URLSessionTask? if let data = data { - task = uploadTask(with: request, from: data) { (responseData, urlResponse, responseError) in + task = ParseSwift + .configuration + .parseFileTransfer + .upload(with: request, from: data) { (responseData, urlResponse, responseError) in completion(self.makeResult(request: request, responseData: responseData, urlResponse: urlResponse, @@ -234,7 +237,10 @@ internal extension URLSession { mapper: mapper)) } } else if let file = file { - task = uploadTask(with: request, fromFile: file) { (responseData, urlResponse, responseError) in + task = ParseSwift + .configuration + .parseFileTransfer + .upload(with: request, fromFile: file) { (responseData, urlResponse, responseError) in completion(self.makeResult(request: request, responseData: responseData, urlResponse: urlResponse, @@ -244,19 +250,20 @@ internal extension URLSession { } else { completion(.failure(ParseError(code: .unknownError, message: "data and file both cannot be nil"))) } - if let task = task { - #if compiler(>=5.5.2) && canImport(_Concurrency) - Task { - await Parse.sessionDelegate.delegates.updateUpload(task, callback: progress) - await Parse.sessionDelegate.delegates.updateTask(task, queue: notificationQueue) - task.resume() - } - #else - Parse.sessionDelegate.uploadDelegates[task] = progress - Parse.sessionDelegate.taskCallbackQueues[task] = notificationQueue + guard let task = task else { + return + } + #if compiler(>=5.5.2) && canImport(_Concurrency) + Task { + await Parse.sessionDelegate.delegates.updateUpload(task, callback: progress) + await Parse.sessionDelegate.delegates.updateTask(task, queue: notificationQueue) task.resume() - #endif } + #else + Parse.sessionDelegate.uploadDelegates[task] = progress + Parse.sessionDelegate.taskCallbackQueues[task] = notificationQueue + task.resume() + #endif } func downloadTask( diff --git a/Sources/ParseSwift/Objects/ParseObject.swift b/Sources/ParseSwift/Objects/ParseObject.swift index eb24758fa..81bff03ec 100644 --- a/Sources/ParseSwift/Objects/ParseObject.swift +++ b/Sources/ParseSwift/Objects/ParseObject.swift @@ -312,7 +312,7 @@ public extension ParseObject { /** Set the value of a specific `KeyPath` on a `ParseObject`. - parameter key: The `KeyPath` of the value to set. - - parameter to: The value to set the `KeyPath` to. + - parameter value: The value to set the `KeyPath` to. - returns: The updated `ParseObject`. - important: This method should be used when updating a `ParseObject` that has already been saved to a Parse Server. You can also use this method on a new `ParseObject`'s that has not been saved to a Parse Server diff --git a/Sources/ParseSwift/Parse.swift b/Sources/ParseSwift/Parse.swift index 6768ff83c..7ded22d05 100644 --- a/Sources/ParseSwift/Parse.swift +++ b/Sources/ParseSwift/Parse.swift @@ -205,6 +205,10 @@ public func initialize(configuration: ParseConfiguration) { - parameter httpAdditionalHeaders: A dictionary of additional headers to send with requests. See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders) for more info. + - parameter maxConnectionAttempts: Maximum number of times to try to connect to Parse Server. + Defaults to 5. + - parameter parseFileTransfer: Override the default transfer behavior for `ParseFile`'s. + Allows for direct uploads to other file storage providers. - parameter authentication: A callback block that will be used to receive/accept/decline network challenges. Defaults to `nil` in which the SDK will use the default OS authentication methods for challenges. It should have the following argument signature: `(challenge: URLAuthenticationChallenge, @@ -234,6 +238,7 @@ public func initialize( deletingKeychainIfNeeded: Bool = false, httpAdditionalHeaders: [AnyHashable: Any]? = nil, maxConnectionAttempts: Int = 5, + parseFileTransfer: ParseFileTransferable? = nil, authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? = nil @@ -255,6 +260,7 @@ public func initialize( deletingKeychainIfNeeded: deletingKeychainIfNeeded, httpAdditionalHeaders: httpAdditionalHeaders, maxConnectionAttempts: maxConnectionAttempts, + parseFileTransfer: parseFileTransfer, authentication: authentication) initialize(configuration: configuration) } @@ -292,6 +298,10 @@ public func initialize( - parameter httpAdditionalHeaders: A dictionary of additional headers to send with requests. See Apple's [documentation](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411532-httpadditionalheaders) for more info. + - parameter maxConnectionAttempts: Maximum number of times to try to connect to Parse Server. + Defaults to 5. + - parameter parseFileTransfer: Override the default transfer behavior for `ParseFile`'s. + Allows for direct uploads to other file storage providers. - parameter authentication: A callback block that will be used to receive/accept/decline network challenges. Defaults to `nil` in which the SDK will use the default OS authentication methods for challenges. It should have the following argument signature: `(challenge: URLAuthenticationChallenge, @@ -323,6 +333,7 @@ public func initialize( deletingKeychainIfNeeded: Bool = false, httpAdditionalHeaders: [AnyHashable: Any]? = nil, maxConnectionAttempts: Int = 5, + parseFileTransfer: ParseFileTransferable? = nil, authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? = nil @@ -344,6 +355,7 @@ public func initialize( deletingKeychainIfNeeded: deletingKeychainIfNeeded, httpAdditionalHeaders: httpAdditionalHeaders, maxConnectionAttempts: maxConnectionAttempts, + parseFileTransfer: parseFileTransfer, authentication: authentication) configuration.isMigratingFromObjcSDK = migratingFromObjcSDK initialize(configuration: configuration) diff --git a/Sources/ParseSwift/ParseConstants.swift b/Sources/ParseSwift/ParseConstants.swift index fa8f121a4..e4af0d4c2 100644 --- a/Sources/ParseSwift/ParseConstants.swift +++ b/Sources/ParseSwift/ParseConstants.swift @@ -10,7 +10,7 @@ import Foundation enum ParseConstants { static let sdk = "swift" - static let version = "4.11.0" + static let version = "4.12.0" static let fileManagementDirectory = "parse/" static let fileManagementPrivateDocumentsDirectory = "Private Documents/" static let fileManagementLibraryDirectory = "Library/" diff --git a/Sources/ParseSwift/Protocols/ParseFileTransferable.swift b/Sources/ParseSwift/Protocols/ParseFileTransferable.swift new file mode 100644 index 000000000..6171e3caf --- /dev/null +++ b/Sources/ParseSwift/Protocols/ParseFileTransferable.swift @@ -0,0 +1,58 @@ +// +// ParseFileAdaptable.swift +// ParseSwift +// +// Created by Corey Baker on 9/12/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +/** + A protocol for overriding the default transfer behavior for `ParseFile`'s. + Allows for direct uploads to other file storage providers. + */ +public protocol ParseFileTransferable: AnyObject { + /** + Creates a task that performs an HTTP request for uploading the specified file, + then calls a handler upon completion. + - parameter request: The Parse URL request object that provides the URL, cache policy, + request type, and so on. + - parameter fileURL: The URL of the file to upload. + - parameter completion: The completion handler to call when the load request + is complete. This handler is executed on the delegate queue. + */ + func upload(with request: URLRequest, + fromFile fileURL: URL, + completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask + + /** + Creates a task that performs an HTTP request for the specified URL request + object, uploads the provided data, and calls a handler upon completion. + - parameter request: The Parse URL request object that provides the URL, cache policy, + request type, and so on. + - parameter bodyData: The body data for the request. + - parameter completion: The completion handler to call when the load request + is complete. This handler is executed on the delegate queue. + */ + func upload(with request: URLRequest, + from bodyData: Data?, + completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask +} + +extension ParseFileTransferable { + func upload(with request: URLRequest, + fromFile fileURL: URL, + completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask { + URLSession.parse.uploadTask(with: request, fromFile: fileURL, completionHandler: completion) + } + + func upload(with request: URLRequest, + from bodyData: Data?, + completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask { + URLSession.parse.uploadTask(with: request, from: bodyData, completionHandler: completion) + } +} diff --git a/Sources/ParseSwift/Types/ParseCLP.swift b/Sources/ParseSwift/Types/ParseCLP.swift index c76760c14..e6d520acc 100644 --- a/Sources/ParseSwift/Types/ParseCLP.swift +++ b/Sources/ParseSwift/Types/ParseCLP.swift @@ -472,8 +472,8 @@ public extension ParseCLP { /** Sets whether the given `ParseUser` objectId has access to perform create/update/delete/addField actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. - parameter objectId: The `ParseUser` objectId to provide/restrict access to. - - parameter to: **true** if access should be allowed, **false** otherwise. - parameter canAddField: **true** if access should be allowed to `addField`, **false** otherwise. Defaults to **false**. - returns: A mutated instance of `ParseCLP` for easy chaining. @@ -495,8 +495,8 @@ public extension ParseCLP { /** Sets whether the given `ParseUser` has access to perform create/update/delete/addField actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. - parameter user: The `ParseUser` to provide/restrict access to. - - parameter to: **true** if access should be allowed, **false** otherwise. - parameter canAddField: **true** if access should be allowed to `addField`, **false** otherwise. Defaults to **false**. - returns: A mutated instance of `ParseCLP` for easy chaining. @@ -512,8 +512,8 @@ public extension ParseCLP { /** Sets whether the given `ParseUser`pointer has access to perform create/update/delete/addField actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. - parameter user: The `ParseUser` to provide/restrict access to. - - parameter to: **true** if access should be allowed, **false** otherwise. - parameter canAddField: **true** if access should be allowed to `addField`, **false** otherwise. Defaults to **false**. - returns: A mutated instance of `ParseCLP` for easy chaining. @@ -526,8 +526,8 @@ public extension ParseCLP { /** Sets whether the given `ParseRole` has access to perform create/update/delete/addField actions on a Parse class. + - parameter allow: **true** if access should be allowed, **false** otherwise. - parameter role: The `ParseRole` to provide/restrict access to. - - parameter to: **true** if access should be allowed, **false** otherwise. - parameter canAddField: **true** if access should be allowed to `addField`, **false** otherwise. Defaults to **false**. - returns: A mutated instance of `ParseCLP` for easy chaining. diff --git a/Sources/ParseSwift/Types/ParseConfiguration.swift b/Sources/ParseSwift/Types/ParseConfiguration.swift index 533a6f9ac..1b67d293a 100644 --- a/Sources/ParseSwift/Types/ParseConfiguration.swift +++ b/Sources/ParseSwift/Types/ParseConfiguration.swift @@ -94,6 +94,12 @@ public struct ParseConfiguration { /// Defaults to 5. public internal(set) var maxConnectionAttempts: Int = 5 + /** + Override the default transfer behavior for `ParseFile`'s. + Allows for direct uploads to other file storage providers. + */ + public internal(set) var parseFileTransfer: ParseFileTransferable + internal var authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? @@ -137,6 +143,8 @@ public struct ParseConfiguration { for more info. - parameter maxConnectionAttempts: Maximum number of times to try to connect to Parse Server. Defaults to 5. + - parameter parseFileTransfer: Override the default transfer behavior for `ParseFile`'s. + Allows for direct uploads to other file storage providers. - parameter authentication: A callback block that will be used to receive/accept/decline network challenges. Defaults to `nil` in which the SDK will use the default OS authentication methods for challenges. It should have the following argument signature: `(challenge: URLAuthenticationChallenge, @@ -166,6 +174,7 @@ public struct ParseConfiguration { deletingKeychainIfNeeded: Bool = false, httpAdditionalHeaders: [AnyHashable: Any]? = nil, maxConnectionAttempts: Int = 5, + parseFileTransfer: ParseFileTransferable? = nil, authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? = nil) { @@ -189,6 +198,7 @@ public struct ParseConfiguration { self.isDeletingKeychainIfNeeded = deletingKeychainIfNeeded self.httpAdditionalHeaders = httpAdditionalHeaders self.maxConnectionAttempts = maxConnectionAttempts + self.parseFileTransfer = parseFileTransfer ?? ParseFileDefaultTransfer() ParseStorage.shared.use(keyValueStore ?? InMemoryKeyValueStore()) } @@ -226,6 +236,8 @@ public struct ParseConfiguration { for more info. - parameter maxConnectionAttempts: Maximum number of times to try to connect to Parse Server. Defaults to 5. + - parameter parseFileTransfer: Override the default transfer behavior for `ParseFile`'s. + Allows for direct uploads to other file storage providers. - parameter authentication: A callback block that will be used to receive/accept/decline network challenges. Defaults to `nil` in which the SDK will use the default OS authentication methods for challenges. It should have the following argument signature: `(challenge: URLAuthenticationChallenge, @@ -257,6 +269,7 @@ public struct ParseConfiguration { deletingKeychainIfNeeded: Bool = false, httpAdditionalHeaders: [AnyHashable: Any]? = nil, maxConnectionAttempts: Int = 5, + parseFileTransfer: ParseFileTransferable? = nil, authentication: ((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? = nil) { @@ -278,6 +291,7 @@ public struct ParseConfiguration { deletingKeychainIfNeeded: deletingKeychainIfNeeded, httpAdditionalHeaders: httpAdditionalHeaders, maxConnectionAttempts: maxConnectionAttempts, + parseFileTransfer: parseFileTransfer ?? ParseFileDefaultTransfer(), authentication: authentication) self.isMigratingFromObjcSDK = migratingFromObjcSDK } diff --git a/Sources/ParseSwift/Types/ParseFileDefaultTransfer.swift b/Sources/ParseSwift/Types/ParseFileDefaultTransfer.swift new file mode 100644 index 000000000..8ae9e2b34 --- /dev/null +++ b/Sources/ParseSwift/Types/ParseFileDefaultTransfer.swift @@ -0,0 +1,11 @@ +// +// ParseFileDefaultTransfer.swift +// ParseSwift +// +// Created by Corey Baker on 9/12/22. +// Copyright © 2022 Parse Community. All rights reserved. +// + +import Foundation + +final class ParseFileDefaultTransfer: ParseFileTransferable {} diff --git a/Sources/ParseSwift/Types/ParseOperation.swift b/Sources/ParseSwift/Types/ParseOperation.swift index 3e60051e7..5f5d252bd 100644 --- a/Sources/ParseSwift/Types/ParseOperation.swift +++ b/Sources/ParseSwift/Types/ParseOperation.swift @@ -48,7 +48,7 @@ public struct ParseOperation: Savable where T: ParseObject { An operation that sets a field's value. - Parameters: - keyPath: The respective `KeyPath` of the object. - - to: The value to set the `KeyPath` to. + - value: The value to set the `KeyPath` to. - returns: The updated operations. - warning: Do not combine operations using this method with other operations that do not use this method to **set** all operations. If you need to combine multiple types @@ -91,7 +91,7 @@ public struct ParseOperation: Savable where T: ParseObject { An operation that sets a field's value if it has changed from its previous value. - Parameters: - key: A tuple consisting of the key and the respective `KeyPath` of the object. - - to: The value to set the `KeyPath` to. + - value: The value to set the `KeyPath` to. - returns: The updated operations. - Note: Set the value to "nil" if you want it to be "null" on the Parse Server. */ @@ -125,7 +125,7 @@ public struct ParseOperation: Savable where T: ParseObject { An operation that force sets a field's value. - Parameters: - key: A tuple consisting of the key and the respective `KeyPath` of the object. - - to: The value to set the `KeyPath` to. + - value: The value to set the `KeyPath` to. - returns: The updated operations. - Note: Set the value to "nil" if you want it to be "null" on the Parse Server. */ diff --git a/Tests/ParseSwiftTests/InitializeSDKTests.swift b/Tests/ParseSwiftTests/InitializeSDKTests.swift index b6ecce658..c1a347adc 100644 --- a/Tests/ParseSwiftTests/InitializeSDKTests.swift +++ b/Tests/ParseSwiftTests/InitializeSDKTests.swift @@ -573,7 +573,8 @@ class InitializeSDKTests: XCTestCase { clientKey: "clientKey", masterKey: "masterKey", serverURL: url, - migratingFromObjcSDK: true) + migratingFromObjcSDK: true, + parseFileTransfer: nil) guard let installation = Installation.current else { XCTFail("Should have installation") return