diff --git a/CHANGELOG.md b/CHANGELOG.md index b98f92b1..e9ccb113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ _None._ ### Breaking Changes - Reworked the `NSDate` RFC3339 / WordPress.com JSON conversions API [#759] +- Changed `FilePart` `filename` property to `fileName` [#765] ### New Features diff --git a/Sources/WordPressKit/Services/MediaServiceRemoteREST.m b/Sources/WordPressKit/Services/MediaServiceRemoteREST.m index 4652c811..9d684c3b 100644 --- a/Sources/WordPressKit/Services/MediaServiceRemoteREST.m +++ b/Sources/WordPressKit/Services/MediaServiceRemoteREST.m @@ -139,11 +139,11 @@ - (void)uploadMedia:(NSArray *)mediaItems if (remoteMedia.postID != nil && [remoteMedia.postID compare:@(0)] == NSOrderedDescending) { parameters[@"attrs[0][parent_id]"] = remoteMedia.postID; } - FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:remoteMedia.localURL filename:filename mimeType:type]; + FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:remoteMedia.localURL fileName:filename mimeType:type]; [fileParts addObject:filePart]; } - [self.wordPressComRestApi multipartPOST:requestUrl + [self.wordPressComRESTAPI multipartPOST:requestUrl parameters:parameters fileParts:fileParts requestEnqueued:^(NSNumber *taskID) { @@ -203,8 +203,8 @@ - (void)uploadMedia:(RemoteMedia *)media } return; } - FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL filename:filename mimeType:type]; - __block NSProgress *localProgress = [self.wordPressComRestApi multipartPOST:requestUrl + FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL fileName:filename mimeType:type]; + __block NSProgress *localProgress = [self.wordPressComRESTAPI multipartPOST:requestUrl parameters:parameters fileParts:@[filePart] requestEnqueued:nil diff --git a/Sources/WordPressKit/Services/PostServiceRemoteREST.m b/Sources/WordPressKit/Services/PostServiceRemoteREST.m index c072091f..50401de1 100644 --- a/Sources/WordPressKit/Services/PostServiceRemoteREST.m +++ b/Sources/WordPressKit/Services/PostServiceRemoteREST.m @@ -164,8 +164,8 @@ - (void)createPost:(RemotePost *)post parameters[@"content"] = post.content; parameters[@"title"] = post.title; parameters[@"status"] = post.status; - FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL filename:filename mimeType:type]; - [self.wordPressComRestApi multipartPOST:requestUrl + FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL fileName:filename mimeType:type]; + [self.wordPressComRESTAPI multipartPOST:requestUrl parameters:parameters fileParts:@[filePart] requestEnqueued:^(NSNumber *taskID) { diff --git a/Sources/WordPressKit/WordPressAPI/FilePart.h b/Sources/WordPressKit/WordPressAPI/FilePart.h new file mode 100644 index 00000000..5590a5fc --- /dev/null +++ b/Sources/WordPressKit/WordPressAPI/FilePart.h @@ -0,0 +1,16 @@ +#import + +/// Represents the infomartion needed to encode a file on a multipart form request. +@interface FilePart: NSObject + +@property (strong, nonatomic) NSString * _Nonnull parameterName; +@property (strong, nonatomic) NSURL * _Nonnull url; +@property (strong, nonatomic) NSString * _Nonnull fileName; +@property (strong, nonatomic) NSString * _Nonnull mimeType; + +- (instancetype _Nonnull)initWithParameterName:(NSString * _Nonnull)parameterName + url:(NSURL * _Nonnull)url + fileName:(NSString * _Nonnull)fileName + mimeType:(NSString * _Nonnull)mimeType; + +@end diff --git a/Sources/WordPressKit/WordPressAPI/FilePart.m b/Sources/WordPressKit/WordPressAPI/FilePart.m new file mode 100644 index 00000000..5648f04d --- /dev/null +++ b/Sources/WordPressKit/WordPressAPI/FilePart.m @@ -0,0 +1,18 @@ +#import "FilePart.h" + +@implementation FilePart + +- (instancetype)initWithParameterName:(NSString *)parameterName + url:(NSURL *)url + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType +{ + self = [super init]; + self.parameterName = parameterName; + self.url = url; + self.fileName = fileName; + self.mimeType = mimeType; + return self; +} + +@end diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRESTAPIInterfacing.h b/Sources/WordPressKit/WordPressAPI/WordPressComRESTAPIInterfacing.h index 119f52d0..e1a669e8 100644 --- a/Sources/WordPressKit/WordPressAPI/WordPressComRESTAPIInterfacing.h +++ b/Sources/WordPressKit/WordPressAPI/WordPressComRESTAPIInterfacing.h @@ -1,5 +1,7 @@ @import Foundation; +@class FilePart; + @protocol WordPressComRESTAPIInterfacing @property (strong, nonatomic, readonly) NSURL * _Nonnull baseURL; @@ -14,4 +16,11 @@ success:(void (^ _Nonnull)(id _Nonnull, NSHTTPURLResponse * _Nullable))success failure:(void (^ _Nonnull)(NSError * _Nonnull, NSHTTPURLResponse * _Nullable))failure; +- (NSProgress * _Nullable)multipartPOST:(NSString * _Nonnull)URLString + parameters:(NSDictionary * _Nullable)parameters + fileParts:(NSArray * _Nonnull)fileParts + requestEnqueued:(void (^ _Nullable)(NSNumber * _Nonnull))requestEnqueue + success:(void (^ _Nonnull)(id _Nonnull, NSHTTPURLResponse * _Nullable))success + failure:(void (^ _Nonnull)(NSError * _Nonnull, NSHTTPURLResponse * _Nullable))failure; + @end diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift index 3d919297..3764903c 100644 --- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift +++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift @@ -268,16 +268,18 @@ open class WordPressComRestApi: NSObject { - parameter success: callback to be called on successful request - parameter failure: callback to be called on failed request - - returns: a NSProgress object that can be used to track the progress of the upload and to cancel the upload. If the method + - returns: a `Progerss` object that can be used to track the progress of the upload and to cancel the upload. If the method returns nil it's because something happened on the request serialization and the network request was not started, but the failure callback will be invoked with the error specificing the serialization issues. */ - @objc @discardableResult open func multipartPOST(_ URLString: String, - parameters: [String: AnyObject]?, - fileParts: [FilePart], - requestEnqueued: RequestEnqueuedBlock? = nil, - success: @escaping SuccessResponseBlock, - failure: @escaping FailureReponseBlock) -> Progress? { + @nonobjc @discardableResult open func multipartPOST( + _ URLString: String, + parameters: [String: AnyObject]?, + fileParts: [FilePart], + requestEnqueued: RequestEnqueuedBlock? = nil, + success: @escaping SuccessResponseBlock, + failure: @escaping FailureReponseBlock + ) -> Progress? { let progress = Progress.discreteProgress(totalUnitCount: 100) Task { @MainActor in @@ -452,7 +454,7 @@ open class WordPressComRestApi: NSObject { let builder: HTTPRequestBuilder do { let form = try fileParts.map { - try MultipartFormField(fileAtPath: $0.url.path, name: $0.parameterName, filename: $0.filename, mimeType: $0.mimeType) + try MultipartFormField(fileAtPath: $0.url.path, name: $0.parameterName, filename: $0.fileName, mimeType: $0.mimeType) } builder = try requestBuilder(URLString: URLString) .method(.post) @@ -476,23 +478,6 @@ open class WordPressComRestApi: NSObject { } -// MARK: - FilePart - -/// FilePart represents the infomartion needed to encode a file on a multipart form request -public final class FilePart: NSObject { - @objc let parameterName: String - @objc let url: URL - @objc let filename: String - @objc let mimeType: String - - @objc public init(parameterName: String, url: URL, filename: String, mimeType: String) { - self.parameterName = parameterName - self.url = url - self.filename = filename - self.mimeType = mimeType - } -} - // MARK: - Error processing extension WordPressComRestApi { @@ -622,7 +607,6 @@ extension WordPressAPIError { } extension WordPressComRestApi: WordPressComRESTAPIInterfacing { - // A note on the naming: Even if defined as `GET` in Objective-C, then method gets converted to Swift as `get`. // // Also, there is no Objective-C direct equivalent of `AnyObject`, which here is used in `parameters: [String: AnyObject]?`. @@ -646,4 +630,28 @@ extension WordPressComRestApi: WordPressComRESTAPIInterfacing { ) -> Progress? { POST(URLString, parameters: parameters, success: success, failure: failure) } + + public func multipartPOST( + _ URLString: String, + parameters: [String: NSObject]?, + fileParts: [FilePart], + // Notice this does not require @escaping because it is Optional. + // + // Annotate with @escaping, and the compiler will fail with: + // > Closure is already escaping in optional type argument + // + // It is necessary to explicitly set this as Optional because of the _Nullable parameter requirement in the Objective-C protocol. + requestEnqueued: ((NSNumber) -> Void)?, + success: @escaping (Any, HTTPURLResponse?) -> Void, + failure: @escaping (any Error, HTTPURLResponse?) -> Void + ) -> Progress? { + multipartPOST( + URLString, + parameters: parameters, + fileParts: fileParts, + requestEnqueued: requestEnqueued, + success: success as SuccessResponseBlock, + failure: failure as FailureReponseBlock + ) + } } diff --git a/Sources/WordPressKit/WordPressKit.h b/Sources/WordPressKit/WordPressKit.h index 657e44fc..9552d17f 100644 --- a/Sources/WordPressKit/WordPressKit.h +++ b/Sources/WordPressKit/WordPressKit.h @@ -6,7 +6,9 @@ FOUNDATION_EXPORT double WordPressKitVersionNumber; //! Project version string for WordPressKit. FOUNDATION_EXPORT const unsigned char WordPressKitVersionString[]; +#import #import + #import #import #import diff --git a/Tests/WordPressKitTests/Tests/WordPressComRestApiTests.swift b/Tests/WordPressKitTests/Tests/WordPressComRestApiTests.swift index 877096a5..611b4298 100644 --- a/Tests/WordPressKitTests/Tests/WordPressComRestApiTests.swift +++ b/Tests/WordPressKitTests/Tests/WordPressComRestApiTests.swift @@ -295,7 +295,7 @@ class WordPressComRestApiTests: XCTestCase { let expect = self.expectation(description: "One callback should be invoked") let api = WordPressComRestApi(oAuthToken: "fakeToken") - let filePart = FilePart(parameterName: "file", url: URL(fileURLWithPath: "/a.txt") as URL, filename: "a.txt", mimeType: "image/jpeg") + let filePart = FilePart(parameterName: "file", url: URL(fileURLWithPath: "/a.txt") as URL, fileName: "a.txt", mimeType: "image/jpeg") api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, fileParts: [filePart], success: { (_: AnyObject, _: HTTPURLResponse?) in expect.fulfill() XCTFail("This call should fail") @@ -319,7 +319,7 @@ class WordPressComRestApiTests: XCTestCase { let mediaURL = URL(fileURLWithPath: mediaPath) let expect = self.expectation(description: "One callback should be invoked") let api = WordPressComRestApi(oAuthToken: "fakeToken") - let filePart = FilePart(parameterName: "media[]", url: mediaURL as URL, filename: "test-image.jpg", mimeType: "image/jpeg") + let filePart = FilePart(parameterName: "media[]", url: mediaURL as URL, fileName: "test-image.jpg", mimeType: "image/jpeg") let progress1 = api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, fileParts: [filePart], success: { (_: AnyObject, _: HTTPURLResponse?) in XCTFail("This call should fail") }, failure: { (error, _) in diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index 9f4bf60c..f2e4880c 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -71,6 +71,8 @@ 3F758FD324F6C68200BBA2FC /* AnnouncementServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */; }; 3F8308A729EE683500354497 /* ActivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8308A629EE683500354497 /* ActivityTests.swift */; }; 3FB8642C2888089F003A86BE /* BuildkiteTestCollector in Frameworks */ = {isa = PBXBuildFile; productRef = 3FB8642B2888089F003A86BE /* BuildkiteTestCollector */; }; + 3FE2E94F2BB29A1B002CA2E1 /* FilePart.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FE2E94D2BB29A1B002CA2E1 /* FilePart.m */; }; + 3FE2E9502BB29A1B002CA2E1 /* FilePart.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FE2E94E2BB29A1B002CA2E1 /* FilePart.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3FFCC0412BA995290051D229 /* Date+WordPressComTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */; }; 3FFCC0472BAA6EF40051D229 /* NSDate+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */; }; 3FFCC0492BAB98130051D229 /* DateFormatter+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */; }; @@ -803,6 +805,8 @@ 3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementServiceRemote.swift; sourceTree = ""; }; 3F8308A629EE683500354497 /* ActivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTests.swift; sourceTree = ""; }; 3FB8642D288813E9003A86BE /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; + 3FE2E94D2BB29A1B002CA2E1 /* FilePart.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FilePart.m; sourceTree = ""; }; + 3FE2E94E2BB29A1B002CA2E1 /* FilePart.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FilePart.h; sourceTree = ""; }; 3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+WordPressComTests.swift"; sourceTree = ""; }; 3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+WordPressCom.swift"; sourceTree = ""; }; 3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+WordPressCom.swift"; sourceTree = ""; }; @@ -1534,6 +1538,8 @@ 3FE2E9352BB10D92002CA2E1 /* WordPressAPI */ = { isa = PBXGroup; children = ( + 3FE2E94E2BB29A1B002CA2E1 /* FilePart.h */, + 3FE2E94D2BB29A1B002CA2E1 /* FilePart.m */, 3FFCC0552BABC78B0051D229 /* WordPressComRESTAPIInterfacing.h */, 4A05E7952B2FCB6400C25E3B /* NonceRetrieval.swift */, 4A05E7992B2FDC3200C25E3B /* WordPressOrgRestApi.swift */, @@ -2579,6 +2585,7 @@ 7430C9B11F1927C50051B8E6 /* RemoteReaderPost.h in Headers */, 7430C9A51F1927180051B8E6 /* ReaderSiteServiceRemote.h in Headers */, 7430C9A31F1927180051B8E6 /* ReaderPostServiceRemote.h in Headers */, + 3FE2E9502BB29A1B002CA2E1 /* FilePart.h in Headers */, 742362D61F10250600BD0A7F /* MenusServiceRemote.h in Headers */, 740B23BA1F17EC7300067A2A /* PostServiceRemoteXMLRPC.h in Headers */, 740B23BD1F17ECB500067A2A /* PostServiceRemoteOptions.h in Headers */, @@ -3291,6 +3298,7 @@ 82FFBF501F45EFD100F4573F /* RemoteBlogJetpackSettings.swift in Sources */, 74650F741F0EA1E200188EDB /* RemoteGravatarProfile.swift in Sources */, 40E7FEB4221063480032834E /* StatsTodayInsight.swift in Sources */, + 3FE2E94F2BB29A1B002CA2E1 /* FilePart.m in Sources */, F41D98EA2B48602B004EC050 /* SessionDetails.swift in Sources */, 436D563C2118E18D00CEAA33 /* WPState.swift in Sources */, 439A44DA2107C93000795ED7 /* RemotePlan_ApiVersion1_3.swift in Sources */,