Skip to content

Commit

Permalink
Fix implementation issues found in LBFile and SLRESTAdapter.
Browse files Browse the repository at this point in the history
Add a feature to the adapter's invoke methods for handling response data
via output stream.

LBFile and SLRESTAdapter seemed left in a temporary implementation to get
the file upload/download working. (e.g. the contract for file download needed
to be "/%@/:container/download" with multipart specified, which should have
been "/%@/:container/download/:name" w/o multipart).

In order to support file downloading in an efficient manner, adapter's invoke
methods now include the versions that takes an output stream as a parameter.
Those are used in file download in LBFile.  Those are also intended to be used
for binary payload download to a data buffer in future.

Multipart form handling was made better (it used to be specialized just for
the purpose of file uploading).  If multipart is set to YES in the contract,
SLRESTAdapter's invoke methods construct each part based on a StreamParam
object supplied via the parameters.

Made SLRESTContractItem's instance initialization methods private, as those
won't be called directly by developers.
  • Loading branch information
hideya committed Apr 10, 2015
1 parent d5e943a commit 0cd281c
Show file tree
Hide file tree
Showing 17 changed files with 443 additions and 129 deletions.
26 changes: 17 additions & 9 deletions LoopBack.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@
895B41811AB16D670000A9D7 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B278D50187B584F00FFC135 /* MobileCoreServices.framework */; };
895B41821AB16D6A0000A9D7 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B278D4A187B584A00FFC135 /* SystemConfiguration.framework */; };
895B41831AB16D6B0000A9D7 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B278D4A187B584A00FFC135 /* SystemConfiguration.framework */; };
8973753E1AB2A60A00FB31A2 /* SLStreamParam.h in Headers */ = {isa = PBXBuildFile; fileRef = 8973753C1AB2A60A00FB31A2 /* SLStreamParam.h */; };
8973753F1AB2A60A00FB31A2 /* SLStreamParam.m in Sources */ = {isa = PBXBuildFile; fileRef = 8973753D1AB2A60A00FB31A2 /* SLStreamParam.m */; };
89D3DB0E1AB174F1005A1B90 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 47C48BB51962123900995044 /* InfoPlist.strings */; };
89D3DB0F1AB174F1005A1B90 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 47C48BB51962123900995044 /* InfoPlist.strings */; };
89EB399D1AB16ED700B1EB9D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3D2214317725DAB00B7CB63 /* UIKit.framework */; };
Expand Down Expand Up @@ -225,6 +227,8 @@
47D471B7196DF4A1002E2358 /* LoopBackTests copy.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LoopBackTests copy.octest"; sourceTree = BUILT_PRODUCTS_DIR; };
47F8E811185B7A4E0088BB73 /* LBPushNotification.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LBPushNotification.h; sourceTree = "<group>"; };
47F8E812185B7A4E0088BB73 /* LBPushNotification.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LBPushNotification.m; sourceTree = "<group>"; };
8973753C1AB2A60A00FB31A2 /* SLStreamParam.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLStreamParam.h; sourceTree = "<group>"; };
8973753D1AB2A60A00FB31A2 /* SLStreamParam.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLStreamParam.m; sourceTree = "<group>"; };
89D3C9521AB17258005A1B90 /* index.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = index.js; sourceTree = "<group>"; };
89D3DB001AB17263005A1B90 /* package.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = package.json; sourceTree = "<group>"; };
89D3DB031AB17263005A1B90 /* f1.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = f1.txt; sourceTree = "<group>"; };
Expand Down Expand Up @@ -321,7 +325,6 @@
47C48B881962120E00995044 /* SLRemoting */ = {
isa = PBXGroup;
children = (
89D3DB111AB178B7005A1B90 /* Supporting File */,
47C48B891962120E00995044 /* SLAdapter.h */,
47C48B8A1962120E00995044 /* SLAdapter.m */,
47C48B8B1962120E00995044 /* SLObject.h */,
Expand All @@ -333,39 +336,42 @@
47C48B921962120E00995044 /* SLRESTAdapter.m */,
47C48B931962120E00995044 /* SLRESTContract.h */,
47C48B941962120E00995044 /* SLRESTContract.m */,
8973753C1AB2A60A00FB31A2 /* SLStreamParam.h */,
8973753D1AB2A60A00FB31A2 /* SLStreamParam.m */,
89D3DB111AB178B7005A1B90 /* Supporting File */,
);
path = SLRemoting;
sourceTree = "<group>";
};
47C48BB41962123900995044 /* SLRemotingTests */ = {
isa = PBXGroup;
children = (
89D3DB101AB175C7005A1B90 /* Supporting Files */,
47C48BB71962123900995044 /* server */,
47C48BC81962123900995044 /* SLRESTAdapterTests.h */,
47C48BC91962123900995044 /* SLRESTAdapterTests.m */,
47C48BCA1962123900995044 /* SLRESTContractTests.h */,
47C48BCB1962123900995044 /* SLRESTContractTests.m */,
47C48BC51962123900995044 /* SLRESTAdapterNonRootTests.h */,
47C48BC61962123900995044 /* SLRESTAdapterNonRootTests.m */,
47C48BC71962123900995044 /* SLRESTAdapterSSLTests.m */,
89D3DB101AB175C7005A1B90 /* Supporting Files */,
47C48BB71962123900995044 /* server */,
);
path = SLRemotingTests;
sourceTree = "<group>";
};
47C48BB71962123900995044 /* server */ = {
isa = PBXGroup;
children = (
47C48BB81962123900995044 /* .gitignore */,
47C48BB91962123900995044 /* contract-class.js */,
47C48BBA1962123900995044 /* contract.js */,
47C48BBB1962123900995044 /* index.js */,
47C48BBC1962123900995044 /* nonroot.js */,
47C48BBD1962123900995044 /* package.json */,
47C48BBE1962123900995044 /* private */,
47C48BC11962123900995044 /* simple-class.js */,
47C48BC21962123900995044 /* simple.js */,
47C48BC31962123900995044 /* ssl-config.js */,
47C48BBD1962123900995044 /* package.json */,
47C48BB81962123900995044 /* .gitignore */,
47C48BBE1962123900995044 /* private */,
);
path = server;
sourceTree = "<group>";
Expand Down Expand Up @@ -472,7 +478,6 @@
B3D220FC17722AE800B7CB63 /* LoopBack */ = {
isa = PBXGroup;
children = (
B3D220FD17722AE800B7CB63 /* Supporting Files */,
B3D220FF17722AE800B7CB63 /* LoopBack.h */,
B3D2212217722B1700B7CB63 /* LBModel.h */,
B3D2212317722B1700B7CB63 /* LBModel.m */,
Expand All @@ -490,6 +495,7 @@
0B786865189B1C3300AB6782 /* LBFile.m */,
0B8022DE18A2F1BA00AF845E /* LBContainer.h */,
0B8022E418A2F27600AF845E /* LBContainer.m */,
B3D220FD17722AE800B7CB63 /* Supporting Files */,
);
path = LoopBack;
sourceTree = "<group>";
Expand All @@ -505,8 +511,6 @@
B3D2211117722AE800B7CB63 /* LoopBackTests */ = {
isa = PBXGroup;
children = (
B3D2211217722AE800B7CB63 /* Supporting Files */,
89D3C9511AB17258005A1B90 /* server */,
B3D2213917722BAB00B7CB63 /* LBModelTests.h */,
B3D2213A17722BAB00B7CB63 /* LBModelTests.m */,
B3D2214017725A7F00B7CB63 /* LBModelSubclassingTests.h */,
Expand All @@ -519,6 +523,8 @@
0B3A201A18A5856F006772C8 /* LBFileTests.m */,
0B3A200D18A55B5A006772C8 /* LBContainerTests.h */,
0B3A201518A56BA5006772C8 /* LBContainerTests.m */,
B3D2211217722AE800B7CB63 /* Supporting Files */,
89D3C9511AB17258005A1B90 /* server */,
);
path = LoopBackTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -552,6 +558,7 @@
0B6DCCA518806F2E00F7E57A /* LBUser.h in Headers */,
47C48B9D1962120E00995044 /* SLAFNetworkActivityIndicatorManager.h in Headers */,
47C48BA81962120E00995044 /* SLAdapter.h in Headers */,
8973753E1AB2A60A00FB31A2 /* SLStreamParam.h in Headers */,
0B4F52AA18908089004F675A /* LBAccessToken.h in Headers */,
47C48BAA1962120E00995044 /* SLObject.h in Headers */,
47C48BA61962120E00995044 /* UIImageView+SLAFNetworking.h in Headers */,
Expand Down Expand Up @@ -789,6 +796,7 @@
47C48BB11962120E00995044 /* SLRESTAdapter.m in Sources */,
0B6DCCA618806F2E00F7E57A /* LBUser.m in Sources */,
47C48BAB1962120E00995044 /* SLObject.m in Sources */,
8973753F1AB2A60A00FB31A2 /* SLStreamParam.m in Sources */,
0B8022E518A2F27600AF845E /* LBContainer.m in Sources */,
47C48BA31962120E00995044 /* SLAFURLConnectionOperation.m in Sources */,
0B786866189B1C3300AB6782 /* LBFile.m in Sources */,
Expand Down
53 changes: 43 additions & 10 deletions LoopBack/LBFile.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,41 @@

#import "LBFile.h"
#import "LBRESTAdapter.h"
#import "SLStreamParam.h"

static NSString *mimeTypeForFileName(NSString *fileName) {
CFStringRef pathExtension = (__bridge_retained CFStringRef)[fileName pathExtension];
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
pathExtension,
NULL);
CFRelease(pathExtension);
NSString *mimeType =
(__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType);

return (mimeType != nil) ? mimeType : @"application/octet-stream";
}

@implementation LBFile

- (void)uploadWithSuccess:(LBFileUploadSuccessBlock)success
failure:(SLFailureBlock)failure {

NSString *fullLocalPath = [self.localPath stringByAppendingPathComponent:self.name];
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:fullLocalPath];
NSString *mimeType = mimeTypeForFileName(self.name);
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullLocalPath
error:NULL];
NSInteger length = attributes.fileSize;

__block SLStreamParam *streamParam = [SLStreamParam streamParamWithInputStream:inputStream
fileName:self.name
contentType:mimeType
length:length];

[self invokeMethod:@"upload"
parameters:[self toDictionary]
parameters:@{@"container": self.container,
@"name": self.name,
@"file": streamParam}
success:^(id value) {
success();
}
Expand All @@ -22,12 +50,18 @@ - (void)uploadWithSuccess:(LBFileUploadSuccessBlock)success

- (void)downloadWithSuccess:(LBFileDownloadSuccessBlock)success
failure:(SLFailureBlock)failure {
[self invokeMethod:@"download"
parameters:[self toDictionary]
success:^(id value) {
success();
}
failure:failure];
NSString *fullLocalPath = [self.localPath stringByAppendingPathComponent:self.name];
__block NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:fullLocalPath
append:NO];

[self invokeMethod:@"download"
parameters:@{@"container": self.container,
@"name": self.name}
outputStream:outputStream
success:^(id value) {
success();
}
failure:failure];
}

@end
Expand All @@ -47,9 +81,8 @@ - (SLRESTContract *)contract {
verb:@"POST"
multipart:YES]
forMethod:[NSString stringWithFormat:@"%@.prototype.upload", self.className]];
[contract addItem:[SLRESTContractItem itemWithPattern:[NSString stringWithFormat:@"/%@/:container/download", self.className]
verb:@"GET"
multipart:YES]
[contract addItem:[SLRESTContractItem itemWithPattern:[NSString stringWithFormat:@"/%@/:container/download/:name", self.className]
verb:@"GET"]
forMethod:[NSString stringWithFormat:@"%@.prototype.download", self.className]];
[contract addItem:[SLRESTContractItem itemWithPattern:[NSString stringWithFormat:@"/%@/:container/files/:name", self.className]
verb:@"GET"]
Expand Down
6 changes: 5 additions & 1 deletion LoopBackTests/LBFileTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,12 @@ - (void)testDownload {
STAssertEqualObjects(file.name, @"uploadTest.txt", @"Invalid name");
[file downloadWithSuccess:^(void) {
STAssertTrue([fileManager fileExistsAtPath:fullPath], @"File missing.");
NSString *fileContents = [NSString stringWithContentsOfFile:fullPath
encoding:NSUTF8StringEncoding
error:nil];
STAssertEqualObjects(fileContents, @"Upload test", @"File corrupted");
ASYNC_TEST_SIGNAL
} failure:ASYNC_TEST_FAILURE_BLOCK];
ASYNC_TEST_SIGNAL
} failure:ASYNC_TEST_FAILURE_BLOCK];
ASYNC_TEST_END
}
Expand Down
4 changes: 3 additions & 1 deletion LoopBackTests/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ loopback.autoAttach();
app.enableAuth();
app.use(loopback.token({ model: app.models.AccessToken }));
app.use(loopback.rest());
app.listen(3000);
app.listen(3000, function() {
console.log('https server is ready at https://localhost:3000.');
});
52 changes: 52 additions & 0 deletions SLRemoting/SLAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,53 @@ extern NSString *SLAdapterNotConnectedErrorDescription;
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure;

/**
* Invokes a remotable method exposed statically on the server.
*
* Unlike SLAdapter::invokeInstanceMethod:constructorParameters:parameters:success:failure:,
* no object needs to be created on the server.
*
* @param method The method to invoke, e.g. `module.doSomething`.
* @param parameters The parameters to invoke with.
* @param outputStream The stream to which all the response data goes.
* If this is set, no data is routed for further
* processing and the success block is invoked with `nil`.
* @param success An SLSuccessBlock to be executed when the invocation
* succeeds.
* @param failure An SLFailureBlock to be executed when the invocation
* fails.
*/
- (void)invokeStaticMethod:(NSString *)method
parameters:(NSDictionary *)parameters
outputStream:(NSOutputStream *)outputStream
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure;

/**
* Invokes a remotable method exposed within a prototype on the server.
*
* This should be thought of as a two-step process. First, the server loads or
* creates an object with the appropriate type. Then and only then is the method
* invoked on that object. The two parameter dictionaries correspond to these
* two steps: `creationParameters` for the former, and `parameters` for the
* latter.
*
* @param method The method to invoke, e.g.
* `MyClass.prototype.doSomething`.
* @param constructorParameters The parameters the virual object should be
* created with.
* @param parameters The parameters to invoke with.
* @param success An SLSuccessBlock to be executed when the
* invocation succeeds.
* @param failure An SLFailureBlock to be executed when the
* invocation fails.
*/
- (void)invokeInstanceMethod:(NSString *)method
constructorParameters:(NSDictionary *)constructorParameters
parameters:(NSDictionary *)parameters
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure;

/**
* Invokes a remotable method exposed within a prototype on the server.
*
Expand All @@ -136,6 +183,10 @@ extern NSString *SLAdapterNotConnectedErrorDescription;
* @param constructorParameters The parameters the virual object should be
* created with.
* @param parameters The parameters to invoke with.
* @param outputStream The stream to which all the response data goes.
* If this is set, no data is routed for further
* processing and the success block is invoked
* with `nil`.
* @param success An SLSuccessBlock to be executed when the
* invocation succeeds.
* @param failure An SLFailureBlock to be executed when the
Expand All @@ -144,6 +195,7 @@ extern NSString *SLAdapterNotConnectedErrorDescription;
- (void)invokeInstanceMethod:(NSString *)method
constructorParameters:(NSDictionary *)constructorParameters
parameters:(NSDictionary *)parameters
outputStream:(NSOutputStream *)outputStream
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure;

Expand Down
17 changes: 17 additions & 0 deletions SLRemoting/SLAdapter.m
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ - (void)invokeStaticMethod:(NSString *)path
NSAssert(NO, @"Invalid Adapter.");
}

- (void)invokeStaticMethod:(NSString *)path
parameters:(NSDictionary *)parameters
outputStream:(NSOutputStream *)outputStream
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure {
NSAssert(NO, @"Invalid Adapter.");
}

- (void)invokeInstanceMethod:(NSString *)path
constructorParameters:(NSDictionary *)constructorParameters
parameters:(NSDictionary *)parameters
Expand All @@ -73,4 +81,13 @@ - (void)invokeInstanceMethod:(NSString *)path
NSAssert(NO, @"Invalid Adapter.");
}

- (void)invokeInstanceMethod:(NSString *)method
constructorParameters:(NSDictionary *)constructorParameters
parameters:(NSDictionary *)parameters
outputStream:(NSOutputStream *)outputStream
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure {
NSAssert(NO, @"Invalid Adapter.");
}

@end
21 changes: 21 additions & 0 deletions SLRemoting/SLObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,27 @@ extern NSString *SLObjectInvalidRepositoryDescription;
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure;

/**
* Invokes a remotable method exposed within instances of this class on the
* server.
*
* @see SLAdapter::invokeInstanceMethod:constructorParameters:parameters:outputStream:success:failure:
*
* @param name The method to invoke (without the prototype), e.g.
* `doSomething`.
* @param parameters The parameters to invoke with.
* @param outputStream The stream to which all the response data goes.
* @param success An SLSuccessBlock to be executed when the invocation
* succeeds.
* @param failure An SLFailureBlock to be executed when the invocation
* fails.
*/
- (void)invokeMethod:(NSString *)name
parameters:(NSDictionary *)parameters
outputStream:(NSOutputStream *)outputStream
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure;

@end

/**
Expand Down
20 changes: 20 additions & 0 deletions SLRemoting/SLObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ - (void)invokeMethod:(NSString *)name
failure:failure];
}

- (void)invokeMethod:(NSString *)name
parameters:(NSDictionary *)parameters
outputStream:(NSOutputStream *)outputStream
success:(SLSuccessBlock)success
failure:(SLFailureBlock)failure {

NSAssert(self.repository, SLObjectInvalidRepositoryDescription);

NSString *path = [NSString stringWithFormat:@"%@.prototype.%@",
self.repository.className,
name];

[self.repository.adapter invokeInstanceMethod:path
constructorParameters:self.creationParameters
parameters:parameters
outputStream:outputStream
success:success
failure:failure];
}

@end

@implementation SLRepository
Expand Down
Loading

0 comments on commit 0cd281c

Please sign in to comment.