mirror of https://github.com/perkeep/perkeep.git
268 lines
9.6 KiB
Objective-C
268 lines
9.6 KiB
Objective-C
//
|
|
// LACamliUploadOperation.m
|
|
// photobackup
|
|
//
|
|
// Created by Nick O'Neill on 11/29/13.
|
|
// Copyright (c) 2013 The Camlistore Authors. All rights reserved.
|
|
//
|
|
|
|
#import "LACamliUploadOperation.h"
|
|
#import "LACamliFile.h"
|
|
#import "LACamliClient.h"
|
|
#import "LACamliUtil.h"
|
|
|
|
static NSUInteger const camliVersion = 1;
|
|
static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
|
|
|
|
@implementation LACamliUploadOperation
|
|
|
|
- (id)initWithFile:(LACamliFile *)file andClient:(LACamliClient *)client
|
|
{
|
|
NSParameterAssert(file);
|
|
NSParameterAssert(client);
|
|
|
|
if (self = [super init]) {
|
|
_file = file;
|
|
_client = client;
|
|
_isExecuting = NO;
|
|
_isFinished = NO;
|
|
_failedTransfer = NO;
|
|
_session = [NSURLSession sessionWithConfiguration:_client.sessionConfig delegate:self delegateQueue:nil];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isConcurrent
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (NSString *)name
|
|
{
|
|
return _file.blobRef;
|
|
}
|
|
|
|
// request stats for each chunk, making sure the server doesn't already have the chunk
|
|
- (void)start
|
|
{
|
|
[LACamliUtil statusText:@[@"performing stat..."]];
|
|
|
|
_taskID = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"uploadtask" expirationHandler:^{
|
|
LALog(@"upload task expired");
|
|
}];
|
|
|
|
if (_client.backgroundID) {
|
|
[[UIApplication sharedApplication] endBackgroundTask:_client.backgroundID];
|
|
}
|
|
|
|
[self willChangeValueForKey:@"isExecuting"];
|
|
_isExecuting = YES;
|
|
[self didChangeValueForKey:@"isExecuting"];
|
|
|
|
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
|
[params setObject:[NSNumber numberWithInt:camliVersion] forKey:@"camliversion"];
|
|
|
|
int i = 1;
|
|
for (NSString *blobRef in _file.allBlobRefs) {
|
|
[params setObject:blobRef forKey:[NSString stringWithFormat:@"blob%d",i]];
|
|
i++;
|
|
}
|
|
|
|
NSString *formValues = @"";
|
|
for (NSString *key in params) {
|
|
formValues = [formValues stringByAppendingString:[NSString stringWithFormat:@"%@=%@&",key,params[key]]];
|
|
}
|
|
|
|
LALog(@"uploading to %@",[_client statUrl]);
|
|
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[_client statUrl]];
|
|
[req setHTTPMethod:@"POST"];
|
|
[req setHTTPBody:[formValues dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
NSURLSessionDataTask *statTask = [_session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
|
|
|
if (!error) {
|
|
// LALog(@"data: %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
|
|
|
|
// we can remove any chunks that the server claims it already has
|
|
NSError *err;
|
|
NSMutableDictionary *resObj = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
|
|
if (err) {
|
|
LALog(@"error getting json: %@",err);
|
|
}
|
|
|
|
if (resObj[@"stat"] != [NSNull null]) {
|
|
for (NSDictionary *stat in resObj[@"stat"]) {
|
|
for (NSString *blobRef in _file.allBlobRefs) {
|
|
if ([stat[@"blobRef"] isEqualToString:blobRef]) {
|
|
[_file.uploadMarks replaceObjectAtIndex:[_file.allBlobRefs indexOfObject:blobRef] withObject:@NO];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL allUploaded = YES;
|
|
for (NSNumber *upload in _file.uploadMarks) {
|
|
if ([upload boolValue]) {
|
|
allUploaded = NO;
|
|
}
|
|
}
|
|
|
|
// TODO: there's a posibility all chunks have been uploaded but no permanode exists
|
|
if (allUploaded) {
|
|
LALog(@"everything's been uploaded already for this file");
|
|
[LACamliUtil logText:@[@"everything already uploaded for ", _file.blobRef]];
|
|
[self finished];
|
|
return;
|
|
}
|
|
|
|
[self uploadChunks];
|
|
} else {
|
|
LALog(@"failed stat: %@",error);
|
|
[LACamliUtil errorText:@[@"failed to stat: ",[error description]]];
|
|
[LACamliUtil logText:@[[NSString stringWithFormat:@"failed to stat: %@",error]]];
|
|
|
|
_failedTransfer = YES;
|
|
[self finished];
|
|
}
|
|
}];
|
|
|
|
[statTask resume];
|
|
}
|
|
|
|
// post the chunks in a multipart request
|
|
//
|
|
- (void)uploadChunks
|
|
{
|
|
[LACamliUtil statusText:@[@"uploading..."]];
|
|
|
|
NSMutableURLRequest *uploadReq = [NSMutableURLRequest requestWithURL:[_client uploadUrl]];
|
|
[uploadReq setHTTPMethod:@"POST"];
|
|
[uploadReq setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", multipartBoundary] forHTTPHeaderField:@"Content-Type"];
|
|
|
|
NSMutableData *uploadData = [self multipartDataForChunks];
|
|
|
|
NSURLSessionUploadTask *upload = [_session uploadTaskWithRequest:uploadReq fromData:uploadData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
|
|
|
// LALog(@"upload response: %@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
|
|
|
|
if (error) {
|
|
LALog(@"upload error: %@",error);
|
|
[LACamliUtil errorText:@[@"error uploading: ",error]];
|
|
_failedTransfer = YES;
|
|
[self finished];
|
|
} else {
|
|
[self vivifyChunks];
|
|
}
|
|
}];
|
|
|
|
[upload resume];
|
|
}
|
|
|
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
|
{
|
|
if ([_client.delegate respondsToSelector:@selector(uploadProgress:forOperation:)]) {
|
|
float progress = (float)totalBytesSent/(float)totalBytesExpectedToSend;
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[_client.delegate uploadProgress:progress forOperation:self];
|
|
});
|
|
}
|
|
}
|
|
|
|
// ask the server to vivify the blobrefs into a file
|
|
- (void)vivifyChunks
|
|
{
|
|
[LACamliUtil statusText:@[@"vivify"]];
|
|
|
|
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[_client uploadUrl]];
|
|
[req setHTTPMethod:@"POST"];
|
|
[req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", multipartBoundary] forHTTPHeaderField:@"Content-Type"];
|
|
[req addValue:@"1" forHTTPHeaderField:@"X-Camlistore-Vivify"];
|
|
|
|
NSMutableData *vivifyData = [self multipartVivifyDataForChunks];
|
|
|
|
NSURLSessionUploadTask *vivify = [_session uploadTaskWithRequest:req fromData:vivifyData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
|
if (error) {
|
|
LALog(@"error vivifying: %@",error);
|
|
[LACamliUtil errorText:@[@"error vivify: ",error]];
|
|
_failedTransfer = YES;
|
|
}
|
|
|
|
[self finished];
|
|
}];
|
|
|
|
[vivify resume];
|
|
}
|
|
|
|
- (void)finished
|
|
{
|
|
[LACamliUtil statusText:@[@"cleaning up..."]];
|
|
|
|
_client.backgroundID = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"queuesync" expirationHandler:^{
|
|
LALog(@"queue sync task expired");
|
|
}];
|
|
|
|
[[UIApplication sharedApplication] endBackgroundTask:_taskID];
|
|
|
|
LALog(@"finished op %@",_file.blobRef);
|
|
|
|
[self willChangeValueForKey:@"isExecuting"];
|
|
[self willChangeValueForKey:@"isFinished"];
|
|
|
|
_isExecuting = NO;
|
|
_isFinished = YES;
|
|
|
|
[self didChangeValueForKey:@"isExecuting"];
|
|
[self didChangeValueForKey:@"isFinished"];
|
|
}
|
|
|
|
#pragma mark - multipart bits
|
|
|
|
- (NSMutableData *)multipartDataForChunks
|
|
{
|
|
NSMutableData *data = [NSMutableData data];
|
|
|
|
for (NSData *chunk in [_file blobsToUpload]) {
|
|
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
// server ignores this filename and mimetype, so it doesn't matter what it is
|
|
[data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"image.jpg\"\r\n", [LACamliUtil blobRef:chunk]] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
[data appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
[data appendData:chunk];
|
|
[data appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
}
|
|
|
|
[data appendData:[[NSString stringWithFormat:@"--%@--\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
return data;
|
|
}
|
|
|
|
- (NSMutableData *)multipartVivifyDataForChunks
|
|
{
|
|
NSMutableData *data = [NSMutableData data];
|
|
|
|
NSMutableDictionary *schemaBlob = [NSMutableDictionary dictionaryWithObjectsAndKeys:@1, @"camliVersion", @"file", @"camliType", [LACamliUtil rfc3339StringFromDate:_file.creation], @"unixMTime", nil];
|
|
|
|
NSMutableArray *parts = [NSMutableArray array];
|
|
int i = 0;
|
|
for (NSString *blobRef in _file.allBlobRefs) {
|
|
[parts addObject:@{@"blobRef":blobRef,@"size":[NSNumber numberWithInteger:[[_file.allBlobs objectAtIndex:i] length]]}];
|
|
i++;
|
|
}
|
|
[schemaBlob setObject:parts forKey:@"parts"];
|
|
|
|
NSData *schemaData = [NSJSONSerialization dataWithJSONObject:schemaBlob options:NSJSONWritingPrettyPrinted error:nil];
|
|
|
|
[data appendData:[[NSString stringWithFormat:@"--%@\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
[data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"json\"\r\n", [LACamliUtil blobRef:schemaData]] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
[data appendData:[@"Content-Type: application/json\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
[data appendData:schemaData];
|
|
[data appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
[data appendData:[[NSString stringWithFormat:@"--%@--\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
return data;
|
|
}
|
|
|
|
@end
|