Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include parameters in multipart POST request #338

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions WordPressKit/MediaServiceRemoteREST.m
Original file line number Diff line number Diff line change
Expand Up @@ -131,22 +131,24 @@ - (void)uploadMedia:(NSArray *)mediaItems
NSString *apiPath = [NSString stringWithFormat:@"sites/%@/media/new", self.siteID];
NSString *requestUrl = [self pathForEndpoint:apiPath
withVersion:ServiceRemoteWordPressComRESTApiVersion_1_1];
NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithDictionary:@{}];
NSMutableArray *fileParts = [NSMutableArray array];

NSMutableArray *bodyParts = [NSMutableArray array];

for (RemoteMedia *remoteMedia in mediaItems) {
NSString *type = remoteMedia.mimeType;
NSString *filename = remoteMedia.file;
if (remoteMedia.postID != nil && [remoteMedia.postID compare:@(0)] == NSOrderedDescending) {
parameters[@"attrs[0][parent_id]"] = remoteMedia.postID;
NSNumber* postID = remoteMedia.postID;
if (postID != nil && [postID compare:@(0)] == NSOrderedDescending) {
BodyPart *parentIDPart = [[BodyPart alloc] initWithName:@"attrs[0][parent_id]" data:[NSData dataWithBytes:&postID length:sizeof(postID)]];
[bodyParts addObject: parentIDPart];
}
FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:remoteMedia.localURL filename:filename mimeType:type];
[fileParts addObject:filePart];
BodyPart *mediaPart = [[BodyPart alloc] initWithName:@"media[]" url:remoteMedia.localURL fileName:filename mimeType:type];
[bodyParts addObject:mediaPart];
}

[self.wordPressComRestApi multipartPOST:requestUrl
parameters:parameters
fileParts:fileParts
parameters: nil
bodyParts:bodyParts
requestEnqueued:^(NSNumber *taskID) {
if (requestEnqueued) {
requestEnqueued(taskID);
Expand Down Expand Up @@ -193,8 +195,6 @@ - (void)uploadMedia:(RemoteMedia *)media
NSString *requestUrl = [self pathForEndpoint:apiPath
withVersion:ServiceRemoteWordPressComRESTApiVersion_1_1];

NSDictionary *parameters = [self parametersForUploadMedia:media];

if (media.localURL == nil || filename == nil || type == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain
Expand All @@ -204,10 +204,11 @@ - (void)uploadMedia:(RemoteMedia *)media
}
return;
}
FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL filename:filename mimeType:type];

NSArray *bodyParts = [self bodyPartsForUploadMedia: media];
__block NSProgress *localProgress = [self.wordPressComRestApi multipartPOST:requestUrl
parameters:parameters
fileParts:@[filePart]
parameters:nil
bodyParts:bodyParts
requestEnqueued:nil
success:^(id _Nonnull responseObject, NSHTTPURLResponse * _Nullable httpResponse) {
NSDictionary *response = (NSDictionary *)responseObject;
Expand Down Expand Up @@ -402,18 +403,25 @@ - (NSDictionary *)parametersFromRemoteMedia:(RemoteMedia *)remoteMedia
return [NSDictionary dictionaryWithDictionary:parameters];
}

- (NSDictionary *)parametersForUploadMedia:(RemoteMedia *)media
- (NSArray *)bodyPartsForUploadMedia:(RemoteMedia *)media
{
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];

if (media.caption != nil) {
parameters[@"attrs[0][caption]"] = media.caption;
NSMutableArray *bodyParts = [NSMutableArray array];
NSString *caption = media.caption;
NSNumber *postID = media.postID;

if (caption != nil) {
BodyPart *captionPart = [[BodyPart alloc] initWithName:@"attrs[0][caption]" data:[caption dataUsingEncoding:NSUTF8StringEncoding]];
[bodyParts addObject: captionPart];
}
if (media.postID != nil && [media.postID compare:@(0)] == NSOrderedDescending) {
parameters[@"attrs[0][parent_id]"] = media.postID;
if (postID != nil && [postID compare:@(0)] == NSOrderedDescending) {
BodyPart *parentIDPart = [[BodyPart alloc] initWithName:@"attrs[0][parent_id]" data:[NSData dataWithBytes:&postID length:sizeof(postID)]];
[bodyParts addObject: parentIDPart];
}

return [NSDictionary dictionaryWithDictionary:parameters];
BodyPart *mediaPart = [[BodyPart alloc] initWithName:@"media[]" url:media.localURL fileName:media.file mimeType:media.mimeType];
[bodyParts addObject: mediaPart];

return bodyParts;
}

@end
15 changes: 8 additions & 7 deletions WordPressKit/PostServiceRemoteREST.m
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,15 @@ - (void)createPost:(RemotePost *)post
NSString *requestUrl = [self pathForEndpoint:path
withVersion:ServiceRemoteWordPressComRESTApiVersion_1_2];

NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithDictionary:@{}];
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];

BodyPart *contentPart =[[BodyPart alloc] initWithName:@"content" data:[post.content dataUsingEncoding:NSUTF8StringEncoding]];
BodyPart *titlePart = [[BodyPart alloc] initWithName:@"title" data:[post.title dataUsingEncoding:NSUTF8StringEncoding]];
BodyPart *statusPart = [[BodyPart alloc] initWithName:@"status" data:[post.status dataUsingEncoding:NSUTF8StringEncoding]];
BodyPart *mediaPart = [[BodyPart alloc] initWithName:@"media[]" url:media.localURL fileName:filename mimeType:type];

[self.wordPressComRestApi multipartPOST:requestUrl
parameters:parameters
fileParts:@[filePart]
parameters:nil
bodyParts:@[contentPart, titlePart, statusPart, mediaPart]
requestEnqueued:^(NSNumber *taskID) {
if (requestEnqueued) {
requestEnqueued(taskID);
Expand Down
76 changes: 60 additions & 16 deletions WordPressKit/WordPressComRestApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,12 @@ open class WordPressComRestApi: NSObject {
}

/**
Executes a multipart POST using the current serializer, the parameters defined and the fileParts defined in the request
Executes a multipart POST to the specified endpoint defined on URLString, including the parameters and bodyParts defined in the request.
This request will be streamed from disk, so it's ideally to be used for large media post uploads.

- parameter URLString: the endpoint to connect
- parameter parameters: the parameters to use on the request
- parameter fileParts: the file parameters that are added to the multipart request
- parameter bodyParts: the body parameters that are added to the multipart request
- parameter requestEnqueued: callback to be called when the fileparts are serialized and request is added to the background session. Defaults to nil
- parameter success: callback to be called on successful request
- parameter failure: callback to be called on failed request
Expand All @@ -306,7 +306,7 @@ open class WordPressComRestApi: NSObject {
*/
@objc @discardableResult open func multipartPOST(_ URLString: String,
parameters: [String: AnyObject]?,
fileParts: [FilePart],
bodyParts: [BodyPart],
requestEnqueued: RequestEnqueuedBlock? = nil,
success: @escaping SuccessResponseBlock,
failure: @escaping FailureReponseBlock) -> Progress? {
Expand All @@ -324,9 +324,10 @@ open class WordPressComRestApi: NSObject {
}

uploadSessionManager.upload(multipartFormData: { (multipartFormData) in
for filePart in fileParts {
multipartFormData.append(filePart.url, withName: filePart.parameterName, fileName: filePart.filename, mimeType: filePart.mimeType)
for part in bodyParts {
part.appendToFormData(multipartFormData)
}

}, to: URLString, encodingCompletion: { (encodingResult) in
switch encodingResult {
case .success(let upload, _, _):
Expand Down Expand Up @@ -423,21 +424,64 @@ open class WordPressComRestApi: NSObject {
}
}

// MARK: - FilePart
// MARK: - BodyPart

/// 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
/// BodyPart represents the information needed to encode a part on a multipart form request
public final class BodyPart: NSObject {
@objc let name: String
@objc let data: Data?
@objc let url: URL?
@objc let fileName: String?
@objc let mimeType: String?

@objc public init(name: String, data: Data) {
self.name = name
self.data = data
self.url = nil
self.fileName = nil
self.mimeType = nil
}

@objc public init(name: String, url: URL) {
self.name = name
self.url = url
self.filename = filename
self.data = nil
self.fileName = nil
self.mimeType = nil
}

@objc public init(name: String, url: URL, fileName: String?, mimeType: String?) {
self.name = name
self.url = url
self.data = nil
self.fileName = fileName
self.mimeType = mimeType
}

@objc public init(name: String, data: Data, fileName: String?, mimeType: String?) {
self.name = name
self.data = data
self.url = nil
self.fileName = fileName
self.mimeType = mimeType
}

public func appendToFormData(_ multipartFormData: MultipartFormData) {
if let url = self.url {
if let fileName = self.fileName, let mimeType = self.mimeType {
multipartFormData.append(url, withName: self.name, fileName: fileName, mimeType: mimeType)
} else {
multipartFormData.append(url, withName: self.name)
}
}
else if let data = self.data {
if let fileName = self.fileName, let mimeType = self.mimeType {
multipartFormData.append(data, withName: self.name, fileName: fileName, mimeType: mimeType)
} else {
multipartFormData.append(data, withName: self.name)
}
}
}
}

// MARK: - Error processing
Expand Down
2 changes: 1 addition & 1 deletion WordPressKitTests/MockWordPressComRestApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class MockWordPressComRestApi: WordPressComRestApi {

override func multipartPOST(_ URLString: String,
parameters: [String : AnyObject]?,
fileParts: [FilePart],
bodyParts: [BodyPart],
requestEnqueued: RequestEnqueuedBlock? = nil,
success: @escaping SuccessResponseBlock,
failure: @escaping FailureReponseBlock) -> Progress? {
Expand Down
12 changes: 6 additions & 6 deletions WordPressKitTests/WordPressComRestApiTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class WordPressComRestApiTests: XCTestCase {
}
let expect = self.expectation(description: "One callback should be invoked")
let api = WordPressComRestApi(oAuthToken: "fakeToken")
api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, fileParts: [], success: { (responseObject: AnyObject, httpResponse: HTTPURLResponse?) in
api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, bodyParts: [], success: { (responseObject: AnyObject, httpResponse: HTTPURLResponse?) in
expect.fulfill()
XCTFail("This call should fail")
}, failure: { (error, httpResponse) in
Expand All @@ -206,8 +206,8 @@ 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")
api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, fileParts: [filePart], success: { (responseObject: AnyObject, httpResponse: HTTPURLResponse?) in
let filePart = BodyPart(name: "file", url: URL(fileURLWithPath: "/a.txt") as URL, fileName: "a.txt", mimeType: "image/jpeg")
api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, bodyParts: [filePart], success: { (responseObject: AnyObject, httpResponse: HTTPURLResponse?) in
expect.fulfill()
XCTFail("This call should fail")
}, failure: { (error, httpResponse) in
Expand All @@ -230,8 +230,8 @@ 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 progress1 = api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, fileParts: [filePart], success: { (responseObject: AnyObject, httpResponse: HTTPURLResponse?) in
let mediaPart = BodyPart(name: "media[]", url: mediaURL as URL, fileName: "test-image.jpg", mimeType: "image/jpeg")
let progress1 = api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, bodyParts: [mediaPart], success: { (responseObject: AnyObject, httpResponse: HTTPURLResponse?) in
XCTFail("This call should fail")
}, failure: { (error, httpResponse) in
print(error)
Expand All @@ -240,7 +240,7 @@ class WordPressComRestApiTests: XCTestCase {
}
)
progress1?.cancel()
api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, fileParts: [filePart], success: { (responseObject: AnyObject, httpResponse: HTTPURLResponse?) in
api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, bodyParts: [mediaPart], success: { (responseObject: AnyObject, httpResponse: HTTPURLResponse?) in
expect.fulfill()

}, failure: { (error, httpResponse) in
Expand Down