github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/clients/ios-objc/photobackup/LACamliClient/LACamliUploadOperation.m (about)

     1  //
     2  //  LACamliUploadOperation.m
     3  //  photobackup
     4  //
     5  //  Created by Nick O'Neill on 11/29/13.
     6  //  Copyright (c) 2013 The Camlistore Authors. All rights reserved.
     7  //
     8  
     9  #import "LACamliUploadOperation.h"
    10  #import "LACamliFile.h"
    11  #import "LACamliClient.h"
    12  #import "LACamliUtil.h"
    13  
    14  static NSUInteger const camliVersion = 1;
    15  static NSString *const multipartBoundary = @"Qe43VdbVVaGtkkMd";
    16  
    17  @implementation LACamliUploadOperation
    18  
    19  - (id)initWithFile:(LACamliFile *)file andClient:(LACamliClient *)client
    20  {
    21      NSParameterAssert(file);
    22      NSParameterAssert(client);
    23  
    24      if (self = [super init]) {
    25          _file = file;
    26          _client = client;
    27          _isExecuting = NO;
    28          _isFinished = NO;
    29          _failedTransfer = NO;
    30          _session = [NSURLSession sessionWithConfiguration:_client.sessionConfig delegate:self delegateQueue:nil];
    31      }
    32  
    33      return self;
    34  }
    35  
    36  - (BOOL)isConcurrent
    37  {
    38      return YES;
    39  }
    40  
    41  - (NSString *)name
    42  {
    43      return _file.blobRef;
    44  }
    45  
    46  // request stats for each chunk, making sure the server doesn't already have the chunk
    47  - (void)start
    48  {
    49      [LACamliUtil statusText:@[@"performing stat..."]];
    50  
    51      _taskID = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"uploadtask" expirationHandler:^{
    52          LALog(@"upload task expired");
    53      }];
    54  
    55      if (_client.backgroundID) {
    56          [[UIApplication sharedApplication] endBackgroundTask:_client.backgroundID];
    57      }
    58  
    59      [self willChangeValueForKey:@"isExecuting"];
    60      _isExecuting = YES;
    61      [self didChangeValueForKey:@"isExecuting"];
    62      
    63      NSMutableDictionary *params = [NSMutableDictionary dictionary];
    64      [params setObject:[NSNumber numberWithInt:camliVersion] forKey:@"camliversion"];
    65      
    66      int i = 1;
    67      for (NSString *blobRef in _file.allBlobRefs) {
    68          [params setObject:blobRef forKey:[NSString stringWithFormat:@"blob%d",i]];
    69          i++;
    70      }
    71  
    72      NSString *formValues = @"";
    73      for (NSString *key in params) {
    74          formValues = [formValues stringByAppendingString:[NSString stringWithFormat:@"%@=%@&",key,params[key]]];
    75      }
    76  
    77      LALog(@"uploading to %@",[_client statUrl]);
    78      NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[_client statUrl]];
    79      [req setHTTPMethod:@"POST"];
    80      [req setHTTPBody:[formValues dataUsingEncoding:NSUTF8StringEncoding]];
    81  
    82      NSURLSessionDataTask *statTask = [_session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    83  
    84          if (!error) {
    85  //            LALog(@"data: %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    86  
    87              // we can remove any chunks that the server claims it already has
    88              NSError *err;
    89              NSMutableDictionary *resObj = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
    90              if (err) {
    91                  LALog(@"error getting json: %@",err);
    92              }
    93  
    94              if (resObj[@"stat"] != [NSNull null]) {
    95                  for (NSDictionary *stat in resObj[@"stat"]) {
    96                      for (NSString *blobRef in _file.allBlobRefs) {
    97                          if ([stat[@"blobRef"] isEqualToString:blobRef]) {
    98                              [_file.uploadMarks replaceObjectAtIndex:[_file.allBlobRefs indexOfObject:blobRef] withObject:@NO];
    99                          }
   100                      }
   101                  }
   102              }
   103  
   104              BOOL allUploaded = YES;
   105              for (NSNumber *upload in _file.uploadMarks) {
   106                  if ([upload boolValue]) {
   107                      allUploaded = NO;
   108                  }
   109              }
   110              
   111              // TODO: there's a posibility all chunks have been uploaded but no permanode exists
   112              if (allUploaded) {
   113                  LALog(@"everything's been uploaded already for this file");
   114                  [LACamliUtil logText:@[@"everything already uploaded for ", _file.blobRef]];
   115                  [self finished];
   116                  return;
   117              }
   118  
   119              [self uploadChunks];
   120          } else {
   121              LALog(@"failed stat: %@",error);
   122              [LACamliUtil errorText:@[@"failed to stat: ",[error description]]];
   123              [LACamliUtil logText:@[[NSString stringWithFormat:@"failed to stat: %@",error]]];
   124  
   125              _failedTransfer = YES;
   126              [self finished];
   127          }
   128      }];
   129  
   130      [statTask resume];
   131  }
   132  
   133  // post the chunks in a multipart request
   134  //
   135  - (void)uploadChunks
   136  {
   137      [LACamliUtil statusText:@[@"uploading..."]];
   138  
   139      NSMutableURLRequest *uploadReq = [NSMutableURLRequest requestWithURL:[_client uploadUrl]];
   140      [uploadReq setHTTPMethod:@"POST"];
   141      [uploadReq setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", multipartBoundary] forHTTPHeaderField:@"Content-Type"];
   142  
   143      NSMutableData *uploadData = [self multipartDataForChunks];
   144  
   145      NSURLSessionUploadTask *upload = [_session uploadTaskWithRequest:uploadReq fromData:uploadData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
   146  
   147  //        LALog(@"upload response: %@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
   148  
   149          if (error) {
   150              LALog(@"upload error: %@",error);
   151              [LACamliUtil errorText:@[@"error uploading: ",error]];
   152              _failedTransfer = YES;
   153              [self finished];
   154          } else {
   155              [self vivifyChunks];
   156          }
   157      }];
   158  
   159      [upload resume];
   160  }
   161  
   162  - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
   163  {
   164      if ([_client.delegate respondsToSelector:@selector(uploadProgress:forOperation:)]) {
   165          float progress = (float)totalBytesSent/(float)totalBytesExpectedToSend;
   166  
   167          dispatch_async(dispatch_get_main_queue(), ^{
   168              [_client.delegate uploadProgress:progress forOperation:self];
   169          });
   170      }
   171  }
   172  
   173  // ask the server to vivify the blobrefs into a file
   174  - (void)vivifyChunks
   175  {
   176      [LACamliUtil statusText:@[@"vivify"]];
   177  
   178      NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[_client uploadUrl]];
   179      [req setHTTPMethod:@"POST"];
   180      [req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", multipartBoundary] forHTTPHeaderField:@"Content-Type"];
   181      [req addValue:@"1" forHTTPHeaderField:@"X-Camlistore-Vivify"];
   182  
   183      NSMutableData *vivifyData = [self multipartVivifyDataForChunks];
   184  
   185      NSURLSessionUploadTask *vivify = [_session uploadTaskWithRequest:req fromData:vivifyData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
   186          if (error) {
   187              LALog(@"error vivifying: %@",error);
   188              [LACamliUtil errorText:@[@"error vivify: ",error]];
   189              _failedTransfer = YES;
   190          }
   191  
   192          [self finished];
   193      }];
   194  
   195      [vivify resume];
   196  }
   197  
   198  - (void)finished
   199  {
   200      [LACamliUtil statusText:@[@"cleaning up..."]];
   201  
   202      _client.backgroundID = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"queuesync" expirationHandler:^{
   203          LALog(@"queue sync task expired");
   204      }];
   205  
   206      [[UIApplication sharedApplication] endBackgroundTask:_taskID];
   207  
   208      LALog(@"finished op %@",_file.blobRef);
   209  
   210      [self willChangeValueForKey:@"isExecuting"];
   211      [self willChangeValueForKey:@"isFinished"];
   212  
   213      _isExecuting = NO;
   214      _isFinished = YES;
   215  
   216      [self didChangeValueForKey:@"isExecuting"];
   217      [self didChangeValueForKey:@"isFinished"];
   218  }
   219  
   220  #pragma mark - multipart bits
   221  
   222  - (NSMutableData *)multipartDataForChunks
   223  {
   224      NSMutableData *data = [NSMutableData data];
   225  
   226      for (NSData *chunk in [_file blobsToUpload]) {
   227          [data appendData:[[NSString stringWithFormat:@"--%@\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
   228          // server ignores this filename and mimetype, so it doesn't matter what it is
   229          [data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"image.jpg\"\r\n", [LACamliUtil blobRef:chunk]] dataUsingEncoding:NSUTF8StringEncoding]];
   230          [data appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
   231          [data appendData:chunk];
   232          [data appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
   233      }
   234  
   235      [data appendData:[[NSString stringWithFormat:@"--%@--\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
   236  
   237      return data;
   238  }
   239  
   240  - (NSMutableData *)multipartVivifyDataForChunks
   241  {
   242      NSMutableData *data = [NSMutableData data];
   243  
   244      NSMutableDictionary *schemaBlob = [NSMutableDictionary dictionaryWithObjectsAndKeys:@1, @"camliVersion", @"file", @"camliType", [LACamliUtil rfc3339StringFromDate:_file.creation], @"unixMTime", nil];
   245  
   246      NSMutableArray *parts = [NSMutableArray array];
   247      int i = 0;
   248      for (NSString *blobRef in _file.allBlobRefs) {
   249          [parts addObject:@{@"blobRef":blobRef,@"size":[NSNumber numberWithInteger:[[_file.allBlobs objectAtIndex:i] length]]}];
   250          i++;
   251      }
   252      [schemaBlob setObject:parts forKey:@"parts"];
   253  
   254      NSData *schemaData = [NSJSONSerialization dataWithJSONObject:schemaBlob options:NSJSONWritingPrettyPrinted error:nil];
   255  
   256      [data appendData:[[NSString stringWithFormat:@"--%@\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
   257      [data appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"json\"\r\n", [LACamliUtil blobRef:schemaData]] dataUsingEncoding:NSUTF8StringEncoding]];
   258      [data appendData:[@"Content-Type: application/json\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
   259      [data appendData:schemaData];
   260      [data appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
   261  
   262      [data appendData:[[NSString stringWithFormat:@"--%@--\r\n", multipartBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
   263  
   264      return data;
   265  }
   266  
   267  @end