github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/clients/ios-objc/photobackup/LACamliClient/LACamliClient.m (about)

     1  //
     2  //  LACamliClient.m
     3  //
     4  //  Created by Nick O'Neill on 1/10/13.
     5  //  Copyright (c) 2013 The Camlistore Authors. All rights reserved.
     6  //
     7  
     8  #import "LACamliClient.h"
     9  #import "LACamliUploadOperation.h"
    10  #import "LACamliFile.h"
    11  #import "LACamliUtil.h"
    12  
    13  @implementation LACamliClient
    14  
    15  NSString* const CamliStorageGenerationKey = @"org.camlistore.storagetoken";
    16  
    17  - (id)initWithServer:(NSURL*)server
    18              username:(NSString*)username
    19           andPassword:(NSString*)password
    20  {
    21      NSParameterAssert(server);
    22      NSParameterAssert(username);
    23      NSParameterAssert(password);
    24  
    25      if (self = [super init]) {
    26          _serverURL = server;
    27          _username = username;
    28          _password = password;
    29  
    30          if ([[NSFileManager defaultManager]
    31                  fileExistsAtPath:[self uploadedFilenamesArchivePath]]) {
    32              self.uploadedFileNames = [NSMutableArray
    33                  arrayWithContentsOfFile:[self uploadedFilenamesArchivePath]];
    34          }
    35  
    36          if (!self.uploadedFileNames) {
    37              self.uploadedFileNames = [NSMutableArray array];
    38          }
    39  
    40          [LACamliUtil logText:@[
    41                                   @"uploads in cache: ",
    42                                   [NSString stringWithFormat:@"%lu", (unsigned long)
    43                                                              [self.uploadedFileNames count]]
    44                               ]];
    45  
    46          self.uploadQueue = [[NSOperationQueue alloc] init];
    47          self.uploadQueue.maxConcurrentOperationCount = 1;
    48          self.totalUploads = 0;
    49  
    50          self.isAuthorized = false;
    51          self.authorizing = false;
    52  
    53          self.sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    54          self.sessionConfig.HTTPAdditionalHeaders = @{
    55              @"Authorization" :
    56              [NSString stringWithFormat:@"Basic %@", [self encodedAuth]]
    57          };
    58      }
    59  
    60      return self;
    61  }
    62  
    63  #pragma mark - ready state
    64  
    65  - (BOOL)readyToUpload
    66  {
    67      // can't upload if we don't have credentials
    68      if (!self.username || !self.password || !self.serverURL) {
    69          [LACamliUtil logText:@[
    70                                   @"not ready: no u/p/s"
    71                               ]];
    72          return NO;
    73      }
    74  
    75      // don't want to start a new upload if we're already going
    76      if ([self.uploadQueue operationCount] > 0) {
    77          [LACamliUtil logText:@[
    78                                   @"not ready: already uploading"
    79                               ]];
    80          return NO;
    81      }
    82  
    83      [LACamliUtil logText:@[
    84                               @"starting upload"
    85                           ]];
    86      return YES;
    87  }
    88  
    89  #pragma mark - discovery
    90  
    91  // discovery is done on demand when we have a new file to upload
    92  - (void)discoveryWithUsername:(NSString*)user andPassword:(NSString*)pass
    93  {
    94      [LACamliUtil statusText:@[
    95                                  @"discovering..."
    96                              ]];
    97      self.authorizing = YES;
    98  
    99      NSURLSessionConfiguration* discoverConfig =
   100          [NSURLSessionConfiguration defaultSessionConfiguration];
   101      discoverConfig.HTTPAdditionalHeaders = @{
   102          @"Accept" : @"text/x-camli-configuration",
   103          @"Authorization" :
   104          [NSString stringWithFormat:@"Basic %@", [self encodedAuth]]
   105      };
   106      NSURLSession* discoverSession =
   107          [NSURLSession sessionWithConfiguration:discoverConfig
   108                                        delegate:self
   109                                   delegateQueue:nil];
   110  
   111      NSURLSessionDataTask *data = [discoverSession dataTaskWithURL:self.serverURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
   112      {
   113  
   114          if (error) {
   115              if ([error code] == NSURLErrorNotConnectedToInternet || [error code] == NSURLErrorNetworkConnectionLost) {
   116                  LALog(@"connection lost or unavailable");
   117                  [LACamliUtil statusText:@[
   118                                              @"internet connection appears offline"
   119                                          ]];
   120              } else if ([error code] == NSURLErrorCannotConnectToHost || [error code] == NSURLErrorCannotFindHost) {
   121                  LALog(@"can't connect to server");
   122                  [LACamliUtil statusText:@[
   123                                              @"can't connect to server"
   124                                          ]];
   125  
   126              } else {
   127                  LALog(@"error discovery: %@", error);
   128                  [LACamliUtil errorText:@[
   129                                             @"discovery error: ",
   130                                             [error description]
   131                                         ]];
   132              }
   133  
   134          } else {
   135              NSHTTPURLResponse* res = (NSHTTPURLResponse*)response;
   136  
   137              if (res.statusCode != 200) {
   138                  NSString* serverSaid = [[NSString alloc]
   139                      initWithData:data
   140                          encoding:NSUTF8StringEncoding];
   141  
   142                  [LACamliUtil
   143                      errorText:@[
   144                                    @"error discovery: ",
   145                                    serverSaid
   146                                ]];
   147                  [LACamliUtil
   148                      logText:@[
   149                                  [NSString stringWithFormat:
   150                                                @"server said: %@",
   151                                                serverSaid]
   152                              ]];
   153  
   154                  if ([self.delegate respondsToSelector:@selector(finishedDiscovery:)]) {
   155                      [self.delegate finishedDiscovery:@{
   156                                                       @"error" : serverSaid
   157                                                   }];
   158                  }
   159              } else {
   160                  NSError* err;
   161                  NSDictionary* config = [NSJSONSerialization JSONObjectWithData:data
   162                                                                         options:0
   163                                                                           error:&err];
   164                  if (!err) {
   165                      self.blobRootComponent = config[@"blobRoot"];
   166                      self.isAuthorized = YES;
   167                      [self.uploadQueue setSuspended:NO];
   168  
   169                      // files may have already been rejected for being previously uploaded when
   170                      // dicovery returns, this doesn't kick off a new check for files. The next
   171                      // file check will catch anything that was missed by timing
   172  
   173                      // if the storage generation changes, zero the saved array
   174                      if (![[self storageToken] isEqualToString:config[@"storageGeneration"]]) {
   175                          self.uploadedFileNames = [NSMutableArray array];
   176                          [self saveStorageToken:config[@"storageGeneration"]];
   177                      }
   178  
   179                      [LACamliUtil
   180                          logText:
   181                              @[
   182                                  [NSString stringWithFormat:@"Welcome to %@'s camlistore",
   183                                                             config[@"ownerName"]]
   184                              ]];
   185  
   186                      [LACamliUtil statusText:@[
   187                                                  @"discovery OK"
   188                                              ]];
   189  
   190                      if ([self.delegate respondsToSelector:@selector(finishedDiscovery:)]) {
   191                          [self.delegate finishedDiscovery:config];
   192                      }
   193                  } else {
   194                      [LACamliUtil
   195                          errorText:@[
   196                                        @"bad json from discovery",
   197                                        [err description]
   198                                    ]];
   199                      [LACamliUtil
   200                          logText:@[
   201                                      @"json from discovery: ",
   202                                      [err description]
   203                                  ]];
   204  
   205                      if ([self.delegate respondsToSelector:@selector(finishedDiscovery:)]) {
   206                          [self.delegate finishedDiscovery:@{
   207                                                           @"error" : [err description]
   208                                                       }];
   209                      }
   210                  }
   211              }
   212          }
   213      }];
   214  
   215      [data resume];
   216  }
   217  
   218  #pragma mark - upload methods
   219  
   220  - (BOOL)fileAlreadyUploaded:(NSString*)filename
   221  {
   222      NSParameterAssert(filename);
   223  
   224      if ([self.uploadedFileNames containsObject:filename]) {
   225          return YES;
   226      }
   227  
   228      return NO;
   229  }
   230  
   231  // starts uploading immediately
   232  - (void)addFile:(LACamliFile*)file withCompletion:(void (^)())completion
   233  {
   234      NSParameterAssert(file);
   235  
   236      self.totalUploads++;
   237  
   238      if (![self isAuthorized]) {
   239          [self.uploadQueue setSuspended:YES];
   240  
   241          if (!self.authorizing) {
   242              [self discoveryWithUsername:self.username
   243                              andPassword:self.password];
   244          }
   245      }
   246  
   247      LACamliUploadOperation* op =
   248          [[LACamliUploadOperation alloc] initWithFile:file
   249                                             andClient:self];
   250  
   251      __block LACamliUploadOperation* weakOp = op;
   252      op.completionBlock = ^{
   253          LALog(@"finished op %@", file.blobRef);
   254          if ([self.delegate respondsToSelector:@selector(finishedUploadOperation:)]) {
   255              [self.delegate performSelector:@selector(finishedUploadOperation:)
   256                                onThread:[NSThread mainThread]
   257                              withObject:weakOp
   258                           waitUntilDone:NO];
   259          }
   260  
   261          if (weakOp.failedTransfer) {
   262              LALog(@"failed transfer");
   263          } else {
   264              [self.uploadedFileNames addObject:file.name];
   265              [self.uploadedFileNames writeToFile:[self uploadedFilenamesArchivePath]
   266                                  atomically:YES];
   267          }
   268  
   269          if (![self.uploadQueue operationCount]) {
   270              self.totalUploads = 0;
   271              [LACamliUtil statusText:@[@"done uploading"]];
   272          }
   273  
   274          if (completion) {
   275              completion();
   276          }
   277      };
   278  
   279      if ([self.delegate respondsToSelector:@selector(addedUploadOperation:)]) {
   280          [self.delegate performSelector:@selector(addedUploadOperation:)
   281                            onThread:[NSThread mainThread]
   282                          withObject:op
   283                       waitUntilDone:NO];
   284      }
   285  
   286      [self.uploadQueue addOperation:op];
   287  }
   288  
   289  #pragma mark - utility
   290  
   291  - (NSString*)storageToken
   292  {
   293      NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
   294      if ([defaults objectForKey:CamliStorageGenerationKey]) {
   295          return [defaults objectForKey:CamliStorageGenerationKey];
   296      }
   297  
   298      return nil;
   299  }
   300  
   301  - (void)saveStorageToken:(NSString*)token
   302  {
   303      NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
   304      [defaults setObject:token
   305                   forKey:CamliStorageGenerationKey];
   306      [defaults synchronize];
   307  }
   308  
   309  - (NSURL*)blobRoot
   310  {
   311      return [self.serverURL URLByAppendingPathComponent:self.blobRootComponent];
   312  }
   313  
   314  - (NSURL*)statURL
   315  {
   316      return [[self blobRoot] URLByAppendingPathComponent:@"camli/stat"];
   317  }
   318  
   319  - (NSURL*)uploadURL
   320  {
   321      return [[self blobRoot] URLByAppendingPathComponent:@"camli/upload"];
   322  }
   323  
   324  - (NSString*)encodedAuth
   325  {
   326      NSString* auth = [NSString stringWithFormat:@"%@:%@", self.username, self.password];
   327  
   328      return [LACamliUtil base64EncodedStringFromString:auth];
   329  }
   330  
   331  - (NSString*)uploadedFilenamesArchivePath
   332  {
   333      NSString* documents = NSSearchPathForDirectoriesInDomains(
   334          NSDocumentDirectory, NSUserDomainMask, YES)[0];
   335  
   336      return [documents stringByAppendingPathComponent:@"uploadedFilenames.plist"];
   337  }
   338  
   339  @end