~threeve/ubuntuone-ios-files/logout-cleanup

« back to all changes in this revision

Viewing changes to Files/U1LocalAssetsManager.m

  • Committer: Zachery Bir
  • Date: 2011-11-01 14:28:05 UTC
  • mfrom: (6.2.116 auto-uploads-view)
  • Revision ID: zachery.bir@canonical.com-20111101142805-uyjwtzm552h638r2
Merged urbanape's auto-uploads-view branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
 
16
16
#import <AssetsLibrary/AssetsLibrary.h>
17
17
 
 
18
#import "Reachability.h"
 
19
 
18
20
#import "U1Asset.h"
19
21
#import "U1DataRepository.h"
20
22
#import "U1LocalAssetsManager.h"
21
23
#import "U1AssetUploadOperation.h"
 
24
#import "U1FileNode.h"
22
25
#import "U1FilesClient.h"
23
26
#import "U1Volume.h"
 
27
#import "U1UTIMapper.h"
 
28
 
24
29
 
25
30
@interface U1LocalAssetsManager ()
26
31
@property (retain) ALAssetsLibrary *assetsLibrary;
27
32
@property (retain) NSOperationQueue *uploadQueue;
28
33
- (void)walkAssetsInLibrary;
29
 
- (void)uploadRepresentation:(ALAssetRepresentation*)rep forAsset:(U1Asset*)asset;
 
34
- (void)uploadRepresentation:(ALAssetRepresentation*)rep forAsset:(U1Asset*)asset withPriority:(NSOperationQueuePriority)priority;
30
35
- (NSString *)generateFilenameForAsset:(ALAsset *)asset;
 
36
- (U1FileNode *)createPendingFileNodeForResourcePath:(NSString *)resourcePath asset:(U1Asset *)asset;
 
37
- (void)processAssetBatch:(NSArray*)batch inGroup:(ALAssetsGroup *)group;
 
38
- (void)autoUploadSettingsChanged:(NSNotification*)notification;
31
39
@end
32
40
 
33
 
static U1LocalAssetsManager *sharedFilesManager = nil;
 
41
static U1LocalAssetsManager *sharedLocalAssetsManager = nil;
34
42
 
35
43
@implementation U1LocalAssetsManager
36
44
 
37
 
@synthesize dataRepository, uploadQueue, localAssetsToUpload, remoteUploadVolume;
 
45
@synthesize dataRepository, uploadQueue, localAssetsToUpload, remoteUploadFolder;
38
46
@synthesize assetsLibrary;
39
47
 
40
 
+ (U1LocalAssetsManager *)sharedFilesManager;
 
48
+ (U1LocalAssetsManager *)sharedLocalAssetsManager;
41
49
{
42
 
        if (sharedFilesManager == nil)
 
50
        if (sharedLocalAssetsManager == nil)
43
51
        {
44
 
                sharedFilesManager = [[super allocWithZone:NULL] init];
 
52
                sharedLocalAssetsManager = [[self alloc] init];
45
53
        }
46
 
        return sharedFilesManager;
47
 
}
48
 
 
49
 
+ (id)allocWithZone:(NSZone *)zone;
50
 
{
51
 
        return [[self sharedFilesManager] retain];
52
 
}
53
 
 
54
 
- (id)copyWithZone:(NSZone *)zone;
55
 
{
56
 
        return self;
57
 
}
58
 
 
59
 
- (id)retain;
60
 
{
61
 
        return self;
62
 
}
63
 
 
64
 
- (NSUInteger)retainCount;
65
 
{
66
 
        return NSUIntegerMax;
67
 
}
68
 
 
69
 
- (void)release;
70
 
{
71
 
        
72
 
}
73
 
 
74
 
- (id)autorelease;
75
 
{
76
 
        return self;
 
54
        return sharedLocalAssetsManager;
77
55
}
78
56
 
79
57
- (id)init
81
59
    if (!(self = [super init]))
82
60
                return nil;
83
61
 
 
62
        NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
 
63
        
84
64
    assetsLibrary = [[ALAssetsLibrary alloc] init];
85
 
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(libraryChanged:) name:ALAssetsLibraryChangedNotification object:assetsLibrary];
 
65
        [defaultCenter addObserver:self selector:@selector(libraryChanged:) name:ALAssetsLibraryChangedNotification object:assetsLibrary];
 
66
        [defaultCenter addObserver:self selector:@selector(autoUploadSettingsChanged:) name:NSUserDefaultsDidChangeNotification object:[NSUserDefaults standardUserDefaults]];
86
67
        dataRepository = [U1DataRepository sharedDataRepository];
87
68
        uploadQueue = [[NSOperationQueue alloc] init];
88
 
        self.localAssetsToUpload = [NSMutableArray array];
 
69
        [uploadQueue setMaxConcurrentOperationCount:4];
 
70
        self.localAssetsToUpload = [NSMutableSet set];
 
71
        [defaultCenter addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
89
72
    return self;
 
73
        
90
74
}
91
75
 
92
76
- (void)dealloc
98
82
 
99
83
- (void)checkForNewAssets;
100
84
{
101
 
        NSLog(@"Checking for new assets.");
102
85
        [self walkAssetsInLibrary];
103
86
}
104
87
 
105
88
- (void)libraryChanged:(NSNotification*)notification;
106
89
{
107
 
        NSLog(@"The library changed, so we'll walk the assets again.");
108
90
        [self walkAssetsInLibrary];
109
91
}
110
92
 
 
93
- (void)autoUploadSettingsChanged:(NSNotification *)notification;
 
94
{
 
95
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
 
96
        
 
97
        if ([defaults boolForKey:@"auto_upload"])
 
98
        {
 
99
                // Turned it on, start your engines
 
100
                [self.uploadQueue setSuspended:NO];
 
101
                [self walkAssetsInLibrary];
 
102
        }
 
103
        else
 
104
        {
 
105
                // Turned it off, cancel all operations
 
106
                [self.uploadQueue setSuspended:YES];
 
107
        }
 
108
}
 
109
 
 
110
- (void)reachabilityChanged:(NSNotification*)notification;
 
111
{
 
112
        Reachability *reachability = [notification object];
 
113
        NetworkStatus status = [reachability currentReachabilityStatus];
 
114
        switch (status)
 
115
        {
 
116
                case ReachableViaWiFi:
 
117
                        [self.uploadQueue setSuspended:NO];
 
118
                        break;
 
119
                case ReachableViaWWAN:
 
120
                {
 
121
                        BOOL shouldSuspend = [[NSUserDefaults standardUserDefaults] boolForKey:@"wifi_only"];
 
122
                        [self.uploadQueue setSuspended:shouldSuspend];
 
123
                        break;
 
124
                }
 
125
                case NotReachable:
 
126
                        [self.uploadQueue setSuspended:YES];
 
127
                        break;
 
128
        }
 
129
}
 
130
 
111
131
- (void)walkAssetsInLibrary;
112
132
{
113
 
        [self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
114
 
                                                                          usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
115
 
                                                                                  [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
116
 
                                                                                          
117
 
                                                                                          ALAsset *someAsset = asset;
118
 
                                                                                          if (someAsset == nil)
119
 
                                                                                                  return;
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) {
 
139
                                                                                                  if (asset == nil)
 
140
                                                                                                  {
 
141
                                                                                                          return;
 
142
                                                                                                  }
124
143
                                                                                                  
125
 
                                                                                                  NSPredicate *p = [NSPredicate predicateWithFormat:@"groupId = %@ and url = %@",
126
 
                                                                                                                                        groupId, assetURL];
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];
 
145
 
 
146
                                                                                                  if ([batch count] >= 50)
132
147
                                                                                                  {
133
 
                                                                                                          if ([[someAsset valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
134
 
                                                                                                                  [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
135
 
                                                                                                                          
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];
144
 
                                                                                                                  }];
145
 
                                                                                                          }
 
148
                                                                                                          NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"defaultRepresentation.url.absoluteString" ascending:YES];
 
149
                                                                                                          [batch sortUsingDescriptors:[NSArray arrayWithObject:sortBy]];
 
150
                                                                                                          [self processAssetBatch:batch inGroup:group];
 
151
                                                                                                          [batch release];
 
152
                                                                                                          batch = [[NSMutableArray alloc] initWithCapacity:50];
146
153
                                                                                                  }
147
 
                                                                                          });
148
 
                                                                                  }];
149
 
                                                                          }
150
 
                                                                        failureBlock:^(NSError *error) {
151
 
                                                                                NSLog(@"Some error happened.");
152
 
                                                                        }];
153
 
}
154
 
 
 
154
                                                                                          }];
 
155
                                                                                          if ([batch count] > 0) // Deal with last partial batch
 
156
                                                                                          {
 
157
                                                                                                  NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"defaultRepresentation.url.absoluteString" ascending:YES];
 
158
                                                                                                  [batch sortUsingDescriptors:[NSArray arrayWithObject:sortBy]];
 
159
                                                                                                  [self processAssetBatch:batch inGroup:group];
 
160
                                                                                          }
 
161
                                                                                          [batch release];
 
162
                                                                                          batch = nil;
 
163
                                                                                  }
 
164
                                                                                failureBlock:^(NSError *error) {
 
165
                                                                                        NSLog(@"Some error happened: %@", error);
 
166
                                                                                }];
 
167
        }
 
168
}
 
169
 
 
170
- (void)processAssetBatch:(NSArray*)batch inGroup:(ALAssetsGroup *)group;
 
171
{
 
172
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
 
173
        
 
174
        // instead of dealing with a single asset, loop over our batch
 
175
        NSMutableArray *URLs = [batch valueForKeyPath:@"defaultRepresentation.url.absoluteString"];
 
176
 
 
177
        dispatch_async(dispatch_get_main_queue(), ^(void) {
 
178
                
 
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];
 
183
                
 
184
                // If batch's asset isn't in existingU1Assets or existingU1Asset's generation is nil, we upload it.
 
185
 
 
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;
 
192
                        
 
193
                        NSString *url = [[[asset defaultRepresentation] url] absoluteString];
 
194
                        while (nextU1Asset && [nextU1Asset.url compare:url] == NSOrderedAscending)
 
195
                                nextU1Asset = [assetEnumerator nextObject];
 
196
                        
 
197
                        if ([nextU1Asset.url isEqualToString:url])
 
198
                        {
 
199
                                // matched... if the generation is nil, still need to upload it, but don't need to create it
 
200
                                if (nextU1Asset.generation == nil)
 
201
                                {
 
202
                                        shouldUpload = YES;
 
203
                                        assetToUpload = nextU1Asset;
 
204
                                }
 
205
                        }
 
206
                        else
 
207
                        {
 
208
                                // no U1asset, need to create/upload
 
209
                                NSString *assetType = [asset valueForProperty:ALAssetPropertyType];
 
210
                                BOOL canUpload = NO;
 
211
                                if ([defaults boolForKey:@"include_video"])
 
212
                                {
 
213
                                        canUpload = ([assetType isEqualToString:ALAssetTypePhoto] ||
 
214
                                                                        [assetType isEqualToString:ALAssetTypeVideo]);
 
215
                                }
 
216
                                else
 
217
                                {
 
218
                                        canUpload = [assetType isEqualToString:ALAssetTypePhoto];
 
219
                                }
 
220
                                
 
221
                                if (canUpload) {
 
222
                                        __block U1Asset *newAsset = nil;
 
223
                                        [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
 
224
                                                
 
225
                                                newAsset = [U1Asset insertInManagedObjectContext:context];
 
226
                                                newAsset.groupId = [group valueForProperty: ALAssetsGroupPropertyPersistentID];
 
227
                                                newAsset.url = url;
 
228
                                                newAsset.filename = [self generateFilenameForAsset:asset];
 
229
                                                NSString *resourcePath = [self.remoteUploadFolder.resourcePath stringByAppendingPathComponent:newAsset.filename];
 
230
                                                [self createPendingFileNodeForResourcePath:resourcePath asset:newAsset];
 
231
                                        }];
 
232
 
 
233
                                        shouldUpload = YES;
 
234
                                        assetToUpload = newAsset;
 
235
                                }
 
236
                        }
 
237
                        // Check shouldUpload flag
 
238
                        if (shouldUpload && ![self.localAssetsToUpload member:assetToUpload])
 
239
                        {
 
240
                                [self uploadRepresentation:[asset defaultRepresentation] forAsset:assetToUpload withPriority:NSOperationQueuePriorityNormal];
 
241
                        }
 
242
                }];
 
243
        });
 
244
        
 
245
        dispatch_async(dispatch_get_main_queue(), ^(void) {
 
246
                [self.dataRepository save:NULL];
 
247
        });
 
248
}
 
249
        
155
250
- (NSString *)generateFilenameForAsset:(ALAsset *)asset;
156
251
{
157
252
        // get the type of the asset
158
 
        NSString *ext = @"JPG";
159
 
        
160
 
        if ([asset valueForProperty:ALAssetPropertyType] == ALAssetTypeVideo) {
161
 
                ext = @"M4V";
 
253
        U1UTIMapper *utiMapper = [U1UTIMapper sharedU1UTIMapper];
 
254
        
 
255
        NSString *ext = [utiMapper extensionForUTI:[asset.defaultRepresentation UTI]];
 
256
        
 
257
        int idx = (int)[self numberOfAssets];
 
258
 
 
259
        return [NSString stringWithFormat:@"IMG_%d.%@", idx, ext];
 
260
}
 
261
 
 
262
- (void)uploadRepresentation:(ALAssetRepresentation *)rep forAsset:(U1Asset *)asset withPriority:(NSOperationQueuePriority)priority;
 
263
{
 
264
        NSString *resourcePath = [self.remoteUploadFolder.resourcePath stringByAppendingPathComponent:asset.filename];
 
265
        
 
266
        if (![self isResourceUploading:resourcePath])
 
267
        {
 
268
                [self.localAssetsToUpload addObject:asset];
 
269
                
 
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)
 
279
                                {
 
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
 
285
                                }
 
286
                                else
 
287
                                {
 
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];
 
291
                                }
 
292
                        });
 
293
                }];
 
294
                [self.uploadQueue addOperation:operation];
162
295
        }
 
296
}
 
297
 
 
298
- (U1FileNode *)createPendingFileNodeForResourcePath:(NSString *)resourcePath asset:(U1Asset *)asset;
 
299
{
 
300
        __block U1FileNode *node = nil;
163
301
        
 
302
        // TODO: temporary, create node if doesn't exist
 
303
        [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
 
304
                node = [U1FileNode insertInManagedObjectContext:context];
 
305
                node.resourcePath = resourcePath;
 
306
                node.kind = @"file";
 
307
                node.parent = self.remoteUploadFolder;
 
308
                node.contentPath = [self.remoteUploadFolder.contentPath stringByAppendingPathComponent:[resourcePath lastPathComponent]];
 
309
                node.asset = asset;
 
310
        }];
 
311
 
 
312
        return node;
 
313
}
 
314
 
 
315
- (int)numberOfAssets;
 
316
{
164
317
        NSError *error = nil;
165
 
        int idx = [[self.dataRepository resultsForEntityClass:[U1Asset class]
166
 
                                                                                matchingPredicate:nil
167
 
                                                                          withSortDescriptors:nil
168
 
                                                                                                        error:&error] count];
169
 
 
170
 
        return [NSString stringWithFormat:@"IMG_%d.%@", idx, ext];
171
 
}
172
 
 
173
 
- (void)uploadPendingAssets;
174
 
{
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);
182
 
                                                        }];
183
 
        }];
184
 
}
185
 
 
186
 
- (void)uploadRepresentation:(ALAssetRepresentation *)rep forAsset:(U1Asset *)asset
187
 
{
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];
192
 
        
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)
201
 
                        {
202
 
                                [self.localAssetsToUpload removeObject:asset];
203
 
                        }
204
 
                });
205
 
        }];
206
 
        [self.uploadQueue addOperation:operation];
 
318
        return [[self.dataRepository resultsForEntityClass:[U1Asset class]
 
319
                                                                        matchingPredicate:nil
 
320
                                                                  withSortDescriptors:nil
 
321
                                                                                                error:&error] count];
 
322
}
 
323
 
 
324
- (int)numberOfAssetsUploaded;
 
325
{
 
326
        return [self numberOfAssets] - [self.localAssetsToUpload count];
 
327
}
 
328
 
 
329
- (BOOL)isResourceUploading:(NSString *)resourcePath;
 
330
{
 
331
        __block BOOL isUploading = NO;
 
332
 
 
333
        [[self.uploadQueue operations] enumerateObjectsUsingBlock:^(U1AssetUploadOperation *operation, NSUInteger idx, BOOL *stop) {
 
334
                if ([operation.filename isEqualToString:[resourcePath lastPathComponent]] && [operation isExecuting])
 
335
                {
 
336
                        isUploading = YES;
 
337
                        *stop = YES;
 
338
                }
 
339
        }];
 
340
 
 
341
        return isUploading;
 
342
}
 
343
 
 
344
- (void)thumbnailForNode:(U1FileNode *)fileNode completionBlock:(void(^)(CGImageRef thumbnail))completionBlock;
 
345
{
 
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);
 
351
                                                }];
207
352
}
208
353
 
209
354
@end