99
83
- (void)checkForNewAssets;
101
NSLog(@"Checking for new assets.");
102
85
[self walkAssetsInLibrary];
105
88
- (void)libraryChanged:(NSNotification*)notification;
107
NSLog(@"The library changed, so we'll walk the assets again.");
108
90
[self walkAssetsInLibrary];
93
- (void)autoUploadSettingsChanged:(NSNotification *)notification;
95
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
97
if ([defaults boolForKey:@"auto_upload"])
99
// Turned it on, start your engines
100
[self.uploadQueue setSuspended:NO];
101
[self walkAssetsInLibrary];
105
// Turned it off, cancel all operations
106
[self.uploadQueue setSuspended:YES];
110
- (void)reachabilityChanged:(NSNotification*)notification;
112
Reachability *reachability = [notification object];
113
NetworkStatus status = [reachability currentReachabilityStatus];
116
case ReachableViaWiFi:
117
[self.uploadQueue setSuspended:NO];
119
case ReachableViaWWAN:
121
BOOL shouldSuspend = [[NSUserDefaults standardUserDefaults] boolForKey:@"wifi_only"];
122
[self.uploadQueue setSuspended:shouldSuspend];
126
[self.uploadQueue setSuspended:YES];
111
131
- (void)walkAssetsInLibrary;
113
[self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
114
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
115
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
117
ALAsset *someAsset = asset;
118
if (someAsset == nil)
120
dispatch_async(dispatch_get_main_queue(), ^(void) {
121
ALAssetRepresentation *defaultRep = [someAsset defaultRepresentation];
122
NSString *assetURL = [[defaultRep url] absoluteString];
123
NSString *groupId = [group valueForProperty:ALAssetsGroupPropertyPersistentID];
133
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
134
if ([defaults boolForKey:@"auto_upload"]) {
135
[self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
136
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
137
__block NSMutableArray *batch = [[NSMutableArray alloc] initWithCapacity:50];
138
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
125
NSPredicate *p = [NSPredicate predicateWithFormat:@"groupId = %@ and url = %@",
127
NSError *error = nil;
128
NSArray *results = [self.dataRepository resultsForEntityClass:[U1Asset class] matchingPredicate:p withSortDescriptors:nil error:&error];
129
U1Asset *trackedAsset = [results lastObject];
130
NSLog(@"Did we find an asset? (%@)", [trackedAsset description]);
131
if (trackedAsset == nil)
144
[batch addObject:asset];
146
if ([batch count] >= 50)
133
if ([[someAsset valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
134
[self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
136
U1Asset *newAsset = [U1Asset insertInManagedObjectContext:context];
137
newAsset.groupId = groupId;
138
newAsset.url = assetURL;
139
newAsset.filename = [self generateFilenameForAsset:someAsset];
140
NSLog(@"Adding and uploading an asset for %@ (filename: %@)", newAsset.url, newAsset.filename);
141
[self.localAssetsToUpload addObject:newAsset];
142
[self uploadRepresentation:defaultRep forAsset:newAsset];
143
[self.dataRepository save:NULL];
148
NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"defaultRepresentation.url.absoluteString" ascending:YES];
149
[batch sortUsingDescriptors:[NSArray arrayWithObject:sortBy]];
150
[self processAssetBatch:batch inGroup:group];
152
batch = [[NSMutableArray alloc] initWithCapacity:50];
150
failureBlock:^(NSError *error) {
151
NSLog(@"Some error happened.");
155
if ([batch count] > 0) // Deal with last partial batch
157
NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"defaultRepresentation.url.absoluteString" ascending:YES];
158
[batch sortUsingDescriptors:[NSArray arrayWithObject:sortBy]];
159
[self processAssetBatch:batch inGroup:group];
164
failureBlock:^(NSError *error) {
165
NSLog(@"Some error happened: %@", error);
170
- (void)processAssetBatch:(NSArray*)batch inGroup:(ALAssetsGroup *)group;
172
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
174
// instead of dealing with a single asset, loop over our batch
175
NSMutableArray *URLs = [batch valueForKeyPath:@"defaultRepresentation.url.absoluteString"];
177
dispatch_async(dispatch_get_main_queue(), ^(void) {
179
NSPredicate *p = [NSPredicate predicateWithFormat:@"url in %@", URLs];
180
NSError *error = nil;
181
NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"url" ascending:YES];
182
NSArray *existingU1Assets = [self.dataRepository resultsForEntityClass:[U1Asset class] matchingPredicate:p withSortDescriptors:[NSArray arrayWithObject:sortBy] error:&error];
184
// If batch's asset isn't in existingU1Assets or existingU1Asset's generation is nil, we upload it.
186
// walk batch, looking for
187
NSEnumerator *assetEnumerator = [existingU1Assets objectEnumerator];
188
__block U1Asset *nextU1Asset = [assetEnumerator nextObject];
189
[batch enumerateObjectsUsingBlock:^(ALAsset *asset, NSUInteger idx, BOOL *stop) {
190
BOOL shouldUpload = NO;
191
U1Asset *assetToUpload = nil;
193
NSString *url = [[[asset defaultRepresentation] url] absoluteString];
194
while (nextU1Asset && [nextU1Asset.url compare:url] == NSOrderedAscending)
195
nextU1Asset = [assetEnumerator nextObject];
197
if ([nextU1Asset.url isEqualToString:url])
199
// matched... if the generation is nil, still need to upload it, but don't need to create it
200
if (nextU1Asset.generation == nil)
203
assetToUpload = nextU1Asset;
208
// no U1asset, need to create/upload
209
NSString *assetType = [asset valueForProperty:ALAssetPropertyType];
211
if ([defaults boolForKey:@"include_video"])
213
canUpload = ([assetType isEqualToString:ALAssetTypePhoto] ||
214
[assetType isEqualToString:ALAssetTypeVideo]);
218
canUpload = [assetType isEqualToString:ALAssetTypePhoto];
222
__block U1Asset *newAsset = nil;
223
[self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
225
newAsset = [U1Asset insertInManagedObjectContext:context];
226
newAsset.groupId = [group valueForProperty: ALAssetsGroupPropertyPersistentID];
228
newAsset.filename = [self generateFilenameForAsset:asset];
229
NSString *resourcePath = [self.remoteUploadFolder.resourcePath stringByAppendingPathComponent:newAsset.filename];
230
[self createPendingFileNodeForResourcePath:resourcePath asset:newAsset];
234
assetToUpload = newAsset;
237
// Check shouldUpload flag
238
if (shouldUpload && ![self.localAssetsToUpload member:assetToUpload])
240
[self uploadRepresentation:[asset defaultRepresentation] forAsset:assetToUpload withPriority:NSOperationQueuePriorityNormal];
245
dispatch_async(dispatch_get_main_queue(), ^(void) {
246
[self.dataRepository save:NULL];
155
250
- (NSString *)generateFilenameForAsset:(ALAsset *)asset;
157
252
// get the type of the asset
158
NSString *ext = @"JPG";
160
if ([asset valueForProperty:ALAssetPropertyType] == ALAssetTypeVideo) {
253
U1UTIMapper *utiMapper = [U1UTIMapper sharedU1UTIMapper];
255
NSString *ext = [utiMapper extensionForUTI:[asset.defaultRepresentation UTI]];
257
int idx = (int)[self numberOfAssets];
259
return [NSString stringWithFormat:@"IMG_%d.%@", idx, ext];
262
- (void)uploadRepresentation:(ALAssetRepresentation *)rep forAsset:(U1Asset *)asset withPriority:(NSOperationQueuePriority)priority;
264
NSString *resourcePath = [self.remoteUploadFolder.resourcePath stringByAppendingPathComponent:asset.filename];
266
if (![self isResourceUploading:resourcePath])
268
[self.localAssetsToUpload addObject:asset];
270
U1AssetUploadOperation *operation = [[U1AssetUploadOperation alloc] init];
271
operation.asset = asset;
272
operation.folder = self.remoteUploadFolder;
273
operation.filename = asset.filename;
274
operation.mimetype = [[U1UTIMapper sharedU1UTIMapper] MIMETypeForUTI:[rep UTI]];
275
operation.representation = rep;
276
[operation setCompletionBlock:^{
277
dispatch_async(dispatch_get_main_queue(), ^(void) {
278
if (operation.error == nil) // or the error is somehow unrecoverable (e.g., over quota)
280
asset.generation = asset.fileNode.generation;
281
[self.dataRepository save:NULL];
282
[self.localAssetsToUpload removeObject:asset];
283
[[NSNotificationCenter defaultCenter] postNotificationName:@"imageUploaded" object:nil];
284
// Fire a notification that our U1LocalAssetsViewController can subscribe to
288
NSLog(@"Error trying to upload %@: %@", operation.filename, operation.error);
289
// Try it again (this is brittle if the error isn't recoverable (e.g. over quota)
290
[self uploadRepresentation:rep forAsset:asset withPriority:NSOperationQueuePriorityHigh];
294
[self.uploadQueue addOperation:operation];
298
- (U1FileNode *)createPendingFileNodeForResourcePath:(NSString *)resourcePath asset:(U1Asset *)asset;
300
__block U1FileNode *node = nil;
302
// TODO: temporary, create node if doesn't exist
303
[self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
304
node = [U1FileNode insertInManagedObjectContext:context];
305
node.resourcePath = resourcePath;
307
node.parent = self.remoteUploadFolder;
308
node.contentPath = [self.remoteUploadFolder.contentPath stringByAppendingPathComponent:[resourcePath lastPathComponent]];
315
- (int)numberOfAssets;
164
317
NSError *error = nil;
165
int idx = [[self.dataRepository resultsForEntityClass:[U1Asset class]
166
matchingPredicate:nil
167
withSortDescriptors:nil
168
error:&error] count];
170
return [NSString stringWithFormat:@"IMG_%d.%@", idx, ext];
173
- (void)uploadPendingAssets;
175
NSLog(@"Uploading %d assets", [self.localAssetsToUpload count]);
176
[self.localAssetsToUpload enumerateObjectsUsingBlock:^(U1Asset *obj, NSUInteger idx, BOOL *stop) {
177
[self.assetsLibrary assetForURL:[NSURL URLWithString:obj.url]
178
resultBlock:^(ALAsset *asset) {
179
[self uploadRepresentation:[asset defaultRepresentation] forAsset:obj];
180
} failureBlock:^(NSError *error) {
181
NSLog(@"Error: %@", error);
186
- (void)uploadRepresentation:(ALAssetRepresentation *)rep forAsset:(U1Asset *)asset
188
// iterate self.localAssetsToUpload, creating NSOperations for each, giving each a completionBlock to remove the asset from localAssetsToUpload if it completed properly
189
Byte *buffer = (Byte*)malloc(rep.size);
190
NSUInteger length = [rep getBytes:buffer fromOffset:0 length:rep.size error:nil];
191
NSData *assetData = [NSData dataWithBytesNoCopy:buffer length:length freeWhenDone:YES];
193
U1AssetUploadOperation *operation = [[U1AssetUploadOperation alloc] init];
194
operation.asset = asset;
195
operation.folder = [self.remoteUploadVolume rootFolder];
196
operation.filename = asset.filename;
197
operation.assetData = assetData;
198
[operation setCompletionBlock:^{
199
dispatch_async(dispatch_get_main_queue(), ^(void) {
200
if (operation.error == nil) // or the error is somehow unrecoverable (e.g., over quota)
202
[self.localAssetsToUpload removeObject:asset];
206
[self.uploadQueue addOperation:operation];
318
return [[self.dataRepository resultsForEntityClass:[U1Asset class]
319
matchingPredicate:nil
320
withSortDescriptors:nil
321
error:&error] count];
324
- (int)numberOfAssetsUploaded;
326
return [self numberOfAssets] - [self.localAssetsToUpload count];
329
- (BOOL)isResourceUploading:(NSString *)resourcePath;
331
__block BOOL isUploading = NO;
333
[[self.uploadQueue operations] enumerateObjectsUsingBlock:^(U1AssetUploadOperation *operation, NSUInteger idx, BOOL *stop) {
334
if ([operation.filename isEqualToString:[resourcePath lastPathComponent]] && [operation isExecuting])
344
- (void)thumbnailForNode:(U1FileNode *)fileNode completionBlock:(void(^)(CGImageRef thumbnail))completionBlock;
346
[self.assetsLibrary assetForURL:[NSURL URLWithString:fileNode.asset.url]
347
resultBlock:^(ALAsset *asset) {
348
completionBlock(asset.thumbnail);
349
} failureBlock:^(NSError *error) {
350
NSLog(@"Couldn't find the asset to get a thumbnail: %@", error);