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