Merge lp:~threeve/ubuntuone-ios-files/background-processing into lp:ubuntuone-ios-files

Proposed by Jason Foreman
Status: Merged
Merged at revision: 37
Proposed branch: lp:~threeve/ubuntuone-ios-files/background-processing
Merge into: lp:ubuntuone-ios-files
Diff against target: 1483 lines (+478/-287)
23 files modified
Files.xcodeproj/project.pbxproj (+11/-11)
Files/Files-Prefix.pch (+4/-2)
Files/U1AssetRepresenationDataProvider.h (+4/-3)
Files/U1AssetRepresenationDataProvider.m (+47/-8)
Files/U1AutoUploadOperation.h (+8/-3)
Files/U1AutoUploadOperation.m (+21/-11)
Files/U1AutoUploadsManager.h (+1/-1)
Files/U1AutoUploadsManager.m (+195/-144)
Files/U1DataRepository.h (+4/-2)
Files/U1DataRepository.m (+27/-10)
Files/U1FilesClient.h (+1/-3)
Files/U1FilesClient.m (+32/-17)
Files/U1FilesService.h (+6/-1)
Files/U1FilesService.m (+43/-32)
Files/U1FolderItemCell.m (+16/-1)
Files/U1FolderItemCell.xib (+3/-1)
Files/U1FolderViewController.m (+4/-3)
Files/U1ReportingInputStream.h (+3/-2)
Files/U1ReportingInputStream.m (+6/-8)
Files/U1UploadOperation.m (+21/-4)
Files/U1UploadsPoolViewController.m (+1/-1)
Files/U1Volume.m (+1/-1)
Files/iPhone/en.lproj/MainWindow_iPhone.xib (+19/-18)
To merge this branch: bzr merge lp:~threeve/ubuntuone-ios-files/background-processing
Reviewer Review Type Date Requested Status
Zachery Bir Approve
Review via email: mp+85186@code.launchpad.net

Description of the change

Do node loading and parsing in the background to avoid UI blocking.

To post a comment you must log in.
32. By Jason Foreman

Merge lp:~urbanape/ubuntuone-ios-files/background-processing

Revision history for this message
Zachery Bir (urbanape) wrote :

LAND, LAND

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Files.xcodeproj/project.pbxproj'
2--- Files.xcodeproj/project.pbxproj 2011-12-07 14:26:35 +0000
3+++ Files.xcodeproj/project.pbxproj 2011-12-09 20:00:29 +0000
4@@ -640,6 +640,13 @@
5 96CC17E414180C5F00EFC1BA /* U1FilesClient.m */,
6 91B3F2D7141FC9BE00939B3C /* U1AutoUploadsManager.h */,
7 91A5E2DB142A70DF00EAAC2B /* U1AutoUploadsManager.m */,
8+ 910393E61475B118004DE69D /* U1AutoUploadOperation.h */,
9+ 910393E71475B118004DE69D /* U1AutoUploadOperation.m */,
10+ 91A007E8147559F200D0E5FF /* U1AssetRepresenationDataProvider.h */,
11+ 91A007E9147559F200D0E5FF /* U1AssetRepresenationDataProvider.m */,
12+ 916E0080143C9A390037F6D3 /* U1UploadsPoolViewController.h */,
13+ 916E0081143C9A390037F6D3 /* U1UploadsPoolViewController.m */,
14+ 9190AAF31444CA0A0063614A /* U1UploadsPoolViewController.xib */,
15 969EF22613F8C10C00CEF6CB /* U1VolumesViewController.h */,
16 969EF22713F8C10C00CEF6CB /* U1VolumesViewController.m */,
17 969EF22813F8C10C00CEF6CB /* U1VolumesViewController.xib */,
18@@ -654,20 +661,13 @@
19 960D4627140D8B4400B73177 /* U1FriendlyDateValueTransformer.m */,
20 91A5E2DD142B727500EAAC2B /* U1UploadOperation.h */,
21 91A5E2DE142B727500EAAC2B /* U1UploadOperation.m */,
22- 910393E61475B118004DE69D /* U1AutoUploadOperation.h */,
23- 910393E71475B118004DE69D /* U1AutoUploadOperation.m */,
24 965D7EB71429690C00E4754F /* U1NavigationBar.h */,
25 965D7EB81429690C00E4754F /* U1NavigationBar.m */,
26 96A169A21430D53600E4C990 /* U1LocalFileInfo.h */,
27 96A169A31430D53600E4C990 /* U1LocalFileInfo.m */,
28- 916E0080143C9A390037F6D3 /* U1UploadsPoolViewController.h */,
29- 916E0081143C9A390037F6D3 /* U1UploadsPoolViewController.m */,
30- 9190AAF31444CA0A0063614A /* U1UploadsPoolViewController.xib */,
31 91EC184B145B8E3A00DF31F4 /* U1SettingsViewController.h */,
32 91EC184C145B8E3A00DF31F4 /* U1SettingsViewController.m */,
33 91EC184D145B8E3B00DF31F4 /* U1SettingsViewController.xib */,
34- 91A007E8147559F200D0E5FF /* U1AssetRepresenationDataProvider.h */,
35- 91A007E9147559F200D0E5FF /* U1AssetRepresenationDataProvider.m */,
36 9623AE6014868F7000ACDF1C /* U1ReportingInputStream.h */,
37 9623AE6114868F7000ACDF1C /* U1ReportingInputStream.m */,
38 9172C86E148FA4F600FC7737 /* U1TopPinningLabel.h */,
39@@ -1198,8 +1198,8 @@
40 armv6,
41 armv7,
42 );
43- CODE_SIGN_IDENTITY = "iPhone Distribution: Canonical Group Limited";
44- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Canonical Group Limited";
45+ CODE_SIGN_IDENTITY = "iPhone Distribution";
46+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
47 FRAMEWORK_SEARCH_PATHS = (
48 "$(inherited)",
49 "\"$(SRCROOT)\"",
50@@ -1214,8 +1214,8 @@
51 "\"$(SRCROOT)/Dependencies/TestFlightSDK\"",
52 );
53 PRODUCT_NAME = "$(TARGET_NAME)";
54- PROVISIONING_PROFILE = "AC4373DF-1FF8-4EA3-A098-888460CA563B";
55- "PROVISIONING_PROFILE[sdk=iphoneos*]" = "AC4373DF-1FF8-4EA3-A098-888460CA563B";
56+ PROVISIONING_PROFILE = "";
57+ "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
58 TARGETED_DEVICE_FAMILY = 1;
59 VALID_ARCHS = "arm6 armv7";
60 WRAPPER_EXTENSION = app;
61
62=== modified file 'Files/Files-Prefix.pch'
63--- Files/Files-Prefix.pch 2011-10-26 03:39:52 +0000
64+++ Files/Files-Prefix.pch 2011-12-09 20:00:29 +0000
65@@ -15,14 +15,16 @@
66
67 #import <Availability.h>
68
69-#ifndef __IPHONE_3_0
70-#warning "This project uses features only available in iPhone SDK 3.0 and later."
71+#ifndef __IPHONE_4_0
72+#warning "This project uses features only available in iPhone SDK 4.0 and later."
73 #endif
74
75 #ifdef __OBJC__
76
77 #import <UIKit/UIKit.h>
78 #import <Foundation/Foundation.h>
79+#import <CoreData/CoreData.h>
80+
81 #import "TestFlight.h"
82 #import "NSDictionary+U1Additions.h"
83
84
85=== modified file 'Files/U1AssetRepresenationDataProvider.h'
86--- Files/U1AssetRepresenationDataProvider.h 2011-11-18 16:27:59 +0000
87+++ Files/U1AssetRepresenationDataProvider.h 2011-12-09 20:00:29 +0000
88@@ -14,9 +14,10 @@
89 // along with this program. If not, see <http://www.gnu.org/licenses/>.
90
91 #import <Foundation/Foundation.h>
92-#import <AssetsLibrary/AssetsLibrary.h>
93-#import "U1FilesClient.h"
94+
95+#import "U1FilesService.h"
96+
97
98 @interface U1AssetRepresenationDataProvider : NSObject <U1UploadDataProvider>
99-@property (retain) ALAssetRepresentation *representation;
100+@property (retain) NSURL *assetURL;
101 @end
102
103=== modified file 'Files/U1AssetRepresenationDataProvider.m'
104--- Files/U1AssetRepresenationDataProvider.m 2011-11-18 16:27:59 +0000
105+++ Files/U1AssetRepresenationDataProvider.m 2011-12-09 20:00:29 +0000
106@@ -15,22 +15,61 @@
107
108 #import "U1AssetRepresenationDataProvider.h"
109
110+#import <AssetsLibrary/AssetsLibrary.h>
111+#import <MobileCoreServices/MobileCoreServices.h>
112+
113+
114 @implementation U1AssetRepresenationDataProvider
115-
116-@synthesize representation;
117+{
118+ ALAssetsLibrary *library;
119+}
120+
121+@synthesize assetURL;
122+
123+- (id)init;
124+{
125+ self = [super init];
126+ if (self == nil)
127+ return nil;
128+ library = [[ALAssetsLibrary alloc] init];
129+ return self;
130+}
131
132 - (void)dealloc;
133 {
134- [representation release];
135+ [assetURL release];
136+ [library release];
137 [super dealloc];
138 }
139
140-- (NSData *)serializeData;
141+- (void)prepareDataStreamWithBlock:(void(^)(NSInputStream *dataStream, NSString *mimeType, NSUInteger length, NSError *error))block;
142 {
143- Byte *buffer = (Byte*)malloc(self.representation.size);
144- NSUInteger length = [self.representation getBytes:buffer fromOffset:0 length:self.representation.size error:nil];
145- NSData *assetData = [NSData dataWithBytesNoCopy:buffer length:length freeWhenDone:YES];
146- return assetData;
147+ [library assetForURL:self.assetURL resultBlock:^(ALAsset *asset) {
148+
149+ if (asset == nil)
150+ {
151+ if (block)
152+ {
153+ NSError *error = [NSError errorWithDomain:@"nonexistentAsset" code:1 userInfo:nil];
154+ block(nil, nil, nil, error);
155+ }
156+ }
157+ else
158+ {
159+ ALAssetRepresentation *representation = [asset defaultRepresentation];
160+ uint8_t *buffer = (uint8_t*)malloc(representation.size);
161+ NSUInteger length = [representation getBytes:buffer fromOffset:0 length:representation.size error:nil];
162+ NSData *imageData = [NSData dataWithBytesNoCopy:buffer length:length freeWhenDone:YES];
163+ NSString *mimeType = (id)UTTypeCopyPreferredTagWithClass((CFStringRef)[representation UTI], kUTTagClassMIMEType);
164+ NSInputStream *dataStream = [NSInputStream inputStreamWithData:imageData];
165+ if (block)
166+ block(dataStream, mimeType, length, nil);
167+ }
168+
169+ } failureBlock:^(NSError *error) {
170+ if (block)
171+ block(nil, nil, 0, error);
172+ }];
173 }
174
175 @end
176
177=== modified file 'Files/U1AutoUploadOperation.h'
178--- Files/U1AutoUploadOperation.h 2011-11-18 16:28:43 +0000
179+++ Files/U1AutoUploadOperation.h 2011-12-09 20:00:29 +0000
180@@ -14,17 +14,22 @@
181 // along with this program. If not, see <http://www.gnu.org/licenses/>.
182
183 #import <Foundation/Foundation.h>
184-#import "U1FilesClient.h"
185+#import <CoreData/CoreData.h>
186
187+@protocol U1UploadDataProvider;
188 @class U1Asset;
189 @class U1FolderNode;
190
191+
192 @interface U1AutoUploadOperation : NSOperation
193
194 @property (retain) id<U1UploadDataProvider> dataProvider;
195-@property (retain) U1Asset *u1asset;
196-@property (retain) U1FolderNode *folder;
197+
198+@property (retain) NSManagedObjectID *assetID;
199+@property (retain) NSManagedObjectID *folderID;
200+
201 @property (retain) NSString *filename;
202 @property (retain) NSString *contentType;
203 @property (atomic) NSOperationQueuePriority priority;
204+
205 @end
206
207=== modified file 'Files/U1AutoUploadOperation.m'
208--- Files/U1AutoUploadOperation.m 2011-12-06 20:53:05 +0000
209+++ Files/U1AutoUploadOperation.m 2011-12-09 20:00:29 +0000
210@@ -13,11 +13,14 @@
211 // You should have received a copy of the GNU Affero General Public License
212 // along with this program. If not, see <http://www.gnu.org/licenses/>.
213
214+#import "U1AutoUploadOperation.h"
215+
216 #import "U1Asset.h"
217-#import "U1AutoUploadOperation.h"
218 #import "U1DataRepository.h"
219 #import "U1FileNode.h"
220 #import "U1FilesClient.h"
221+#import "U1FilesService.h"
222+
223
224 @interface U1AutoUploadOperation ()
225 @property (getter=isExecuting) BOOL executing;
226@@ -26,9 +29,10 @@
227 - (void)beginUpload;
228 @end
229
230+
231 @implementation U1AutoUploadOperation
232
233-@synthesize executing, finished, dataProvider, u1asset, folder, filename, contentType, priority;
234+@synthesize executing, finished, dataProvider, assetID, folderID, filename, contentType, priority;
235
236 - (BOOL)isConcurrent;
237 {
238@@ -64,32 +68,38 @@
239 - (void)dealloc
240 {
241 [dataProvider release];
242- [u1asset release];
243- [folder release];
244 [filename release];
245 [contentType release];
246+ [assetID release];
247+ [folderID release];
248 [super dealloc];
249 }
250
251 - (void)beginUpload;
252 {
253- dispatch_async(dispatch_get_main_queue(), ^(void) {
254- [[U1FilesClient sharedFilesClient] uploadContentDataProvider:dataProvider toFolder:self.folder withResourceName:self.filename withContentType:self.contentType withPriority:self.priority progressBlock:^(long long bytesUploaded, long long totalBytes) {
255-
256+ U1DataRepository *dataRepository = [U1DataRepository sharedDataRepository];
257+ [dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
258+
259+ U1Asset *asset = (id)[context objectWithID:self.assetID];
260+ U1FolderNode *folder = (id)[context objectWithID:self.folderID];
261+
262+ [[U1FilesClient sharedFilesClient] uploadContentDataProvider:dataProvider toFolder:folder withResourceName:self.filename withContentType:self.contentType withPriority:self.priority progressBlock:^(long long bytesUploaded, long long totalBytes) {
263+
264 } completionBlock:^(U1FileNode *fileNode, NSError *error) {
265 if (error)
266 {
267 NSLog(@"auto-upload failed: %@", error);
268 }
269-
270+
271 dispatch_async(dispatch_get_main_queue(), ^(void) {
272
273- self.u1asset.generation = self.u1asset.fileNode.generation;
274- [[U1DataRepository sharedDataRepository] save:NULL];
275+ asset.generation = asset.fileNode.generation;
276+ [dataRepository save:NULL];
277 [self finishExecuting];
278 });
279 }];
280- });
281+
282+ }];
283 }
284
285 @end
286
287=== modified file 'Files/U1AutoUploadsManager.h'
288--- Files/U1AutoUploadsManager.h 2011-12-06 18:03:30 +0000
289+++ Files/U1AutoUploadsManager.h 2011-12-09 20:00:29 +0000
290@@ -29,8 +29,8 @@
291 + (U1AutoUploadsManager *)sharedAutoUploadsManager;
292 - (int)numberOfAssets;
293 - (void)checkForNewAssets;
294-- (U1Asset *)createU1AssetWithAsset:(ALAsset *)asset group:(ALAssetsGroup *)group URLString:(NSString *)urlString fileName:(NSString *)filename inFolder:(U1FolderNode *)folderNode;
295 - (void)thumbnailForNode:(U1FileNode *)fileNode completionBlock:(void(^)(CGImageRef thumbnail))completionBlock;
296 - (NSOperation*)queueAutoUploadForAsset:(U1Asset *)assetToUpload andRepresentation:(ALAssetRepresentation *)representation;
297 - (int)pendingCount;
298+- (U1Asset *)createU1AssetWithAsset:(ALAsset *)asset group:(ALAssetsGroup *)group URLString:(NSString *)urlString fileName:(NSString *)filename inFolder:(U1FolderNode *)folderNode context:(NSManagedObjectContext*)context;
299 @end
300
301=== modified file 'Files/U1AutoUploadsManager.m'
302--- Files/U1AutoUploadsManager.m 2011-12-07 20:42:55 +0000
303+++ Files/U1AutoUploadsManager.m 2011-12-09 20:00:29 +0000
304@@ -29,9 +29,11 @@
305 #import "U1FolderNode.h"
306 #import "U1Volume.h"
307
308+
309 @interface U1AutoUploadsManager ()
310 @property (retain) U1FilesClient *filesClient;
311 @property (retain) NSOperationQueue *autoUploadPreparationQueue;
312+@property (retain) NSManagedObjectID *autoUploadsFolderID;
313 - (void)walkAssetsInLibrary;
314 - (NSString *)generateFilenameForAsset:(ALAsset *)asset;
315 - (void)processAssetBatch:(NSArray*)batch inGroup:(ALAssetsGroup *)group toArray:(NSMutableArray*)ops;
316@@ -43,16 +45,16 @@
317 - (void)volumesFetched:(NSNotification *)notification;
318 @end
319
320-static U1AutoUploadsManager *sharedAutoUploadsManager = nil;
321
322 @implementation U1AutoUploadsManager
323
324 @synthesize filesClient, dataRepository, assetsLibrary;
325-@synthesize autoUploadPreparationQueue;
326+@synthesize autoUploadPreparationQueue, autoUploadsFolderID;
327 @synthesize remoteUploadFolder;
328
329 + (U1AutoUploadsManager *)sharedAutoUploadsManager;
330 {
331+ static U1AutoUploadsManager *sharedAutoUploadsManager = nil;
332 if (sharedAutoUploadsManager == nil)
333 {
334 sharedAutoUploadsManager = [[U1AutoUploadsManager alloc] init];
335@@ -86,6 +88,7 @@
336 [assetsLibrary release];
337 [autoUploadPreparationQueue release];
338 [remoteUploadFolder release];
339+ [autoUploadsFolderID release];
340 [super dealloc];
341 }
342
343@@ -169,6 +172,7 @@
344 if (error == nil)
345 {
346 self.remoteUploadFolder = volume.rootFolder;
347+ self.autoUploadsFolderID = [volume.rootFolder objectID];
348 [self fetchRemoteUploadFolderContents];
349 }
350 else
351@@ -180,6 +184,7 @@
352 else
353 {
354 self.remoteUploadFolder = volume.rootFolder;
355+ self.autoUploadsFolderID = [volume.rootFolder objectID];
356 [self fetchRemoteUploadFolderContents];
357 }
358 }
359@@ -200,6 +205,7 @@
360 if (resultsDataSourceType == U1DataSourceRemote)
361 {
362 [childrenResultsController performFetch:NULL];
363+ // TODO: this
364 [self queuePreviousUploads];
365 [self checkForNewAssets];
366 }
367@@ -218,49 +224,56 @@
368
369 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
370
371- if ([defaults boolForKey:@"auto_upload"]) {
372- [self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
373- usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
374- NSMutableArray *ops = [NSMutableArray array];
375- __block NSMutableArray *batch = [[NSMutableArray alloc] initWithCapacity:50];
376- [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
377- if (asset == nil)
378- {
379- return;
380- }
381-
382- [batch addObject:asset];
383+ if ([defaults boolForKey:@"auto_upload"])
384+ {
385+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
386
387- if ([batch count] >= 50)
388- {
389- NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"defaultRepresentation.url.absoluteString" ascending:YES];
390- [batch sortUsingDescriptors:[NSArray arrayWithObject:sortBy]];
391- [self processAssetBatch:batch inGroup:group toArray:ops];
392- [batch release];
393- batch = [[NSMutableArray alloc] initWithCapacity:50];
394- }
395- }];
396- if ([batch count] > 0) // Deal with last partial batch
397- {
398- NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"defaultRepresentation.url.absoluteString" ascending:YES];
399- [batch sortUsingDescriptors:[NSArray arrayWithObject:sortBy]];
400- [self processAssetBatch:batch inGroup:group toArray:ops];
401- }
402- [batch release];
403- batch = nil;
404- NSArray *sortedOps = [ops sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"filename" ascending:YES]]];
405- [sortedOps enumerateObjectsUsingBlock:^(U1AutoUploadOperation *operation, NSUInteger idx, BOOL *stop) {
406- [self.autoUploadPreparationQueue addOperation:operation];
407- [[NSNotificationCenter defaultCenter] postNotificationName:@"uploadQueued" object:nil];
408- }];
409- [ops removeAllObjects];
410- alreadyWalking = NO;
411- }
412- failureBlock:^(NSError *error) {
413- NSLog(@"Some error happened: %@", error);
414- }];
415+ [self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
416+
417+ NSMutableArray *ops = [NSMutableArray array];
418+ __block NSMutableArray *batch = [[NSMutableArray alloc] initWithCapacity:50];
419+ [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
420+
421+ if (asset == nil)
422+ {
423+ return;
424+ }
425+
426+ [batch addObject:asset];
427+
428+ if ([batch count] >= 50)
429+ {
430+ NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"defaultRepresentation.url.absoluteString" ascending:YES];
431+ [batch sortUsingDescriptors:[NSArray arrayWithObject:sortBy]];
432+ [self processAssetBatch:batch inGroup:group toArray:ops];
433+ [batch release];
434+ batch = [[NSMutableArray alloc] initWithCapacity:50];
435+ }
436+ }];
437+ if ([batch count] > 0) // Deal with last partial batch
438+ {
439+ NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"defaultRepresentation.url.absoluteString" ascending:YES];
440+ [batch sortUsingDescriptors:[NSArray arrayWithObject:sortBy]];
441+ [self processAssetBatch:batch inGroup:group toArray:ops];
442+ }
443+ [batch release];
444+ batch = nil;
445+
446+ NSArray *sortedOps = [ops sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"filename" ascending:YES]]];
447+ [sortedOps enumerateObjectsUsingBlock:^(U1AutoUploadOperation *operation, NSUInteger idx, BOOL *stop) {
448+ [self.autoUploadPreparationQueue addOperation:operation];
449+ [[NSNotificationCenter defaultCenter] postNotificationName:@"uploadQueued" object:nil];
450+ }];
451+ [ops removeAllObjects];
452+ alreadyWalking = NO;
453+ } failureBlock:^(NSError *error) {
454+ NSLog(@"Some error happened: %@", error);
455+ }];
456+
457+ });
458
459 }
460+
461 }
462
463 - (void)processAssetBatch:(NSArray*)batch inGroup:(ALAssetsGroup *)group toArray:(NSMutableArray*)ops;
464@@ -270,16 +283,19 @@
465 // instead of dealing with a single asset, loop over our batch
466 NSMutableArray *URLs = [batch valueForKeyPath:@"defaultRepresentation.url.absoluteString"];
467
468-
469 [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
470- NSPredicate *p = [NSPredicate predicateWithFormat:@"url in %@ and (fileNode.parent = %@ OR fileNode = nil)", URLs, self.remoteUploadFolder];
471+
472+ U1FolderNode *autoUploadsFolder = (id)[context objectWithID:self.autoUploadsFolderID];
473+ NSMutableSet *assetsToUpload = [NSMutableSet set];
474+
475+ NSPredicate *p = [NSPredicate predicateWithFormat:@"url in %@ and (fileNode.parent = %@ OR fileNode = nil)", URLs, autoUploadsFolder];
476 NSError *error = nil;
477 NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"url" ascending:YES];
478- NSArray *existingU1Assets = [[self.dataRepository resultsForEntityClass:[U1Asset class]
479- matchingPredicate:p
480- withSortDescriptors:[NSArray arrayWithObject:sortBy]
481- error:&error] retain];
482-
483+ NSArray *existingU1Assets = [self.dataRepository resultsForEntityClass:[U1Asset class]
484+ inContext:context
485+ withPredicate:p
486+ sortDescriptors:[NSArray arrayWithObject:sortBy]
487+ error:&error];
488
489 // If batch's asset isn't in existingU1Assets or existingU1Asset's generation is nil, we upload it.
490
491@@ -320,22 +336,25 @@
492
493 if (canUpload) {
494 shouldUpload = YES;
495- assetToUpload = [self createU1AssetWithAsset:asset group:group URLString:url fileName:nil inFolder:self.remoteUploadFolder];
496+ assetToUpload = [self createU1AssetWithAsset:asset group:group URLString:url fileName:nil inFolder:autoUploadsFolder context:context];
497 }
498 }
499- // Check shouldUpload flag
500 if (shouldUpload)
501 {
502- NSOperation *op = [self queueAutoUploadForAsset:assetToUpload andRepresentation:[asset defaultRepresentation]];
503- if (op)
504- [ops addObject:op];
505-
506- }
507- }];
508-
509- [self.dataRepository save:NULL];
510-
511-
512+ [assetsToUpload addObject:assetToUpload];
513+ }
514+ }];
515+
516+ [context save:NULL];
517+
518+ [assetsToUpload enumerateObjectsUsingBlock:^(U1Asset *u1asset, BOOL *stop) {
519+
520+ NSOperation *op = [self queueAutoUploadForAsset:u1asset andRepresentation:nil];
521+ if (op)
522+ {
523+ [ops addObject:op];
524+ }
525+ }];
526 }];
527
528
529@@ -344,54 +363,91 @@
530 - (void)queuePreviousUploads;
531 {
532 // We should poll our data repository for all U1Assets which have generation=nil. They *should* all have U1FileNodes associated with them. If the U1FileNode's parent equals our autoUploadsManager's remoteUploadFolder, then it's an auto-upload, and we let the LAM handle it. Otherwise, kick it off immediately to the FilesClient and get it back in the queue.
533- NSPredicate *p = [NSPredicate predicateWithFormat:@"generation = %@", nil];
534- NSError *error = nil;
535- NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"filename" ascending:YES];
536- NSArray *previouslyQueuedU1Assets = [self.dataRepository resultsForEntityClass:[U1Asset class]
537- matchingPredicate:p
538- withSortDescriptors:[NSArray arrayWithObject:sortBy]
539- error:&error];
540- [previouslyQueuedU1Assets enumerateObjectsUsingBlock:^(U1Asset *u1asset, NSUInteger idx, BOOL *stop) {
541- if (u1asset.fileNode.parent == self.remoteUploadFolder)
542- {
543- [self.assetsLibrary assetForURL:[NSURL URLWithString:u1asset.url]
544- resultBlock:^(ALAsset *asset) {
545+ NSPredicate *p = [NSPredicate predicateWithFormat:@"generation = %@", nil];
546+ NSError *error = nil;
547+ NSSortDescriptor *sortBy = [NSSortDescriptor sortDescriptorWithKey:@"filename" ascending:YES];
548+ NSArray *previouslyQueuedU1Assets = [self.dataRepository resultsForEntityClass:[U1Asset class]
549+ matchingPredicate:p
550+ withSortDescriptors:[NSArray arrayWithObject:sortBy]
551+ error:&error];
552+ __block NSMutableArray *assetsDeletedFromLibrary = [NSMutableArray array];
553+
554+ [previouslyQueuedU1Assets enumerateObjectsUsingBlock:^(U1Asset *u1asset, NSUInteger idx, BOOL *stop) {
555+ if (u1asset.fileNode.parent == self.remoteUploadFolder)
556+ {
557+ [self.assetsLibrary assetForURL:[NSURL URLWithString:u1asset.url]
558+ resultBlock:^(ALAsset *asset) {
559+ if (asset == nil)
560+ {
561+ NSLog(@"In the results block, but asset is nil!");
562+ [assetsDeletedFromLibrary addObject:u1asset];
563+ }
564+ else
565+ {
566 ALAssetRepresentation *rep = [asset defaultRepresentation];
567 // Belongs to LAM, could punt and let it get picked up by checkForNewAssets
568 NSOperation *op = [self queueAutoUploadForAsset:u1asset andRepresentation:rep];
569 if (op)
570+ {
571 [self.autoUploadPreparationQueue addOperation:op];
572- } failureBlock:^(NSError *error) {
573- NSLog(@"Error: %@", error);
574- // TODO: delete asset
575- }];
576- }
577- else
578+ }
579+ }
580+ } failureBlock:^(NSError *error) {
581+ NSLog(@"Error: %@", error);
582+ [assetsDeletedFromLibrary addObject:u1asset];
583+ }];
584+ }
585+ else
586+ {
587+ NSURL *imageURL = [NSURL URLWithString:u1asset.url];
588+
589+ [self.assetsLibrary assetForURL:imageURL
590+ resultBlock:^(ALAsset *asset) {
591+ if (asset == nil)
592+ {
593+ NSLog(@"In the results block, but asset is nil!");
594+ [assetsDeletedFromLibrary addObject:u1asset];
595+ }
596+ else
597+ {
598+ ALAssetRepresentation *representation = [asset defaultRepresentation];
599+ NSString *mimetype = (id)UTTypeCopyPreferredTagWithClass((CFStringRef)[representation UTI], kUTTagClassMIMEType);
600+
601+ U1AssetRepresenationDataProvider *provider = [[U1AssetRepresenationDataProvider alloc] init];
602+ provider.assetURL = imageURL;
603+
604+ [self.filesClient uploadContentDataProvider:provider
605+ toFolder:u1asset.fileNode.parent
606+ withResourceName:u1asset.filename
607+ withContentType:mimetype
608+ withPriority:NSOperationQueuePriorityVeryHigh
609+ progressBlock:^(long long bytesUploaded, long long totalBytes) {}
610+ completionBlock:^(U1FileNode *updatedNode, NSError *error) {
611+ u1asset.generation = updatedNode.generation;
612+ }];
613+ }
614+ } failureBlock:^(NSError *error) {
615+ NSLog(@"Error: %@", error);
616+ [assetsDeletedFromLibrary addObject:u1asset];
617+ }];
618+ }
619+ }];
620+
621+ // Iterate over the assetsDeletedFromLibrary, and clean them up.
622+ [self.dataRepository dispatchAsyncBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
623+ for (int i = 0; i < [assetsDeletedFromLibrary count]; i++)
624+ {
625+ U1Asset *assetToBeDeleted = [assetsDeletedFromLibrary objectAtIndex:i];
626+ U1FileNode *placeholderNode = assetToBeDeleted.fileNode;
627+ [context deleteObject:assetToBeDeleted];
628+ if (placeholderNode.generation == nil)
629 {
630- NSURL *imageURL = [NSURL URLWithString:u1asset.url];
631-
632- [self.assetsLibrary assetForURL:imageURL
633- resultBlock:^(ALAsset *asset) {
634- ALAssetRepresentation *representation = [asset defaultRepresentation];
635- NSString *mimetype = (id)UTTypeCopyPreferredTagWithClass((CFStringRef)[representation UTI], kUTTagClassMIMEType);
636-
637- U1AssetRepresenationDataProvider *provider = [[U1AssetRepresenationDataProvider alloc] init];
638- provider.representation = representation;
639-
640- [self.filesClient uploadContentDataProvider:provider
641- toFolder:u1asset.fileNode.parent
642- withResourceName:u1asset.filename
643- withContentType:mimetype
644- withPriority:NSOperationQueuePriorityVeryHigh
645- progressBlock:^(long long bytesUploaded, long long totalBytes) {}
646- completionBlock:^(U1FileNode *updatedNode, NSError *error) {
647- u1asset.generation = updatedNode.generation;
648- }];
649- } failureBlock:^(NSError *error) {
650- NSLog(@"Error: %@", error);
651- }];
652+ [context deleteObject:placeholderNode];
653 }
654- }];
655+ [self.dataRepository save:NULL];
656+ }
657+ [assetsDeletedFromLibrary release];
658+ }];
659 }
660
661 - (NSString *)remoteUploadFolderPath;
662@@ -421,47 +477,41 @@
663 return filename;
664 }
665
666-- (U1Asset *)createU1AssetWithAsset:(ALAsset *)asset group:(ALAssetsGroup *)group URLString:(NSString *)urlString fileName:(NSString *)filename inFolder:(U1FolderNode *)folderNode;
667+- (U1Asset *)createU1AssetWithAsset:(ALAsset *)asset group:(ALAssetsGroup *)group URLString:(NSString *)urlString fileName:(NSString *)filename inFolder:(U1FolderNode *)folderNode context:(NSManagedObjectContext*)context;
668 {
669- __block U1Asset *newAsset = nil;
670- __block NSString *newFilename = filename;
671-
672- // TODO: temporary, create node if doesn't exist
673- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
674- newAsset = [U1Asset insertInManagedObjectContext:context];
675- newAsset.groupId = [group valueForProperty: ALAssetsGroupPropertyPersistentID];
676- newAsset.url = urlString;
677- if (filename == nil)
678- {
679- newFilename = [self generateFilenameForAsset:asset];
680- }
681- newAsset.filename = newFilename;
682- NSString *resourcePath = [folderNode.resourcePath stringByAppendingPathComponent:newFilename];
683- NSString *contentPath = [folderNode.contentPath stringByAppendingPathComponent:newFilename];
684-
685- __block BOOL found = NO;
686- __block U1FileNode *node = nil;
687-
688- [folderNode.children enumerateObjectsUsingBlock:^(U1FileNode *childNode, BOOL *stop) {
689- if ([childNode.name isEqualToString:newFilename]) {
690- found = YES;
691- newAsset.generation = childNode.generation;
692- newAsset.fileNode = childNode;
693- *stop = YES;
694- }
695- }];
696- // Now we have an asset. We need to check to see if a corresponding FileNode is in our remoteUploadFolderChildren, and use it if it is.
697- if (!found)
698- {
699- node = [U1FileNode insertInManagedObjectContext:context];
700- node.resourcePath = resourcePath;
701- node.kind = @"file";
702- node.parent = folderNode;
703- node.contentPath = contentPath;
704- node.asset = newAsset;
705+ U1Asset *newAsset = [U1Asset insertInManagedObjectContext:context];
706+ newAsset.groupId = [group valueForProperty: ALAssetsGroupPropertyPersistentID];
707+ newAsset.url = urlString;
708+ if (filename == nil)
709+ {
710+ filename = [self generateFilenameForAsset:asset];
711+ }
712+ newAsset.filename = filename;
713+ NSString *resourcePath = [folderNode.resourcePath stringByAppendingPathComponent:filename];
714+ NSString *contentPath = [folderNode.contentPath stringByAppendingPathComponent:filename];
715+
716+ __block BOOL found = NO;
717+ __block U1FileNode *node = nil;
718+
719+ [folderNode.children enumerateObjectsUsingBlock:^(U1FileNode *childNode, BOOL *stop) {
720+ if ([childNode.name isEqualToString:filename]) {
721+ found = YES;
722+ newAsset.generation = childNode.generation;
723+ newAsset.fileNode = childNode;
724+ *stop = YES;
725 }
726 }];
727-
728+ // Now we have an asset. We need to check to see if a corresponding FileNode is in our remoteUploadFolderChildren, and use it if it is.
729+ if (!found)
730+ {
731+ node = [U1FileNode insertInManagedObjectContext:context];
732+ node.resourcePath = resourcePath;
733+ node.kind = @"file";
734+ node.parent = folderNode;
735+ node.contentPath = contentPath;
736+ node.asset = newAsset;
737+ }
738+
739 return newAsset;
740 }
741
742@@ -477,15 +527,16 @@
743 if ((![self isPending:assetToUpload.filename]) && (assetToUpload.generation == nil))
744 {
745 U1AssetRepresenationDataProvider *provider = [[U1AssetRepresenationDataProvider alloc] init];
746- provider.representation = representation;
747+ provider.assetURL = [NSURL URLWithString:assetToUpload.url];
748
749 // Let's create an operation!
750 U1AutoUploadOperation *operation = [[U1AutoUploadOperation alloc] init];
751 operation.dataProvider = provider;
752- operation.u1asset = assetToUpload;
753- operation.folder = self.remoteUploadFolder;
754+ operation.assetID = [assetToUpload objectID];
755+ operation.folderID = [self.remoteUploadFolder objectID];
756 operation.filename = assetToUpload.filename;
757- operation.contentType = (id)UTTypeCopyPreferredTagWithClass((CFStringRef)[provider.representation UTI], kUTTagClassMIMEType);
758+ // TODO:
759+// operation.contentType = (id)UTTypeCopyPreferredTagWithClass((CFStringRef)[provider.representation UTI], kUTTagClassMIMEType);
760 operation.priority = NSOperationQueuePriorityNormal;
761 return [operation autorelease];
762 }
763
764=== modified file 'Files/U1DataRepository.h'
765--- Files/U1DataRepository.h 2011-09-23 18:35:34 +0000
766+++ Files/U1DataRepository.h 2011-12-09 20:00:29 +0000
767@@ -26,10 +26,12 @@
768
769 - (U1Node*)nodeWithResourcePath:(NSString*)resourcePath;
770
771-//- (void)fetchResultsForEntityClass:(Class)entityClass matchingPredicate:(NSPredicate*)predicate withSortDescriptors:(NSArray*)sortDescriptors completionBlock:(void(^)(NSArray *results, NSError *error))completionBlock;
772-
773 - (NSArray*)resultsForEntityClass:(Class)entityClass matchingPredicate:(NSPredicate*)predicate withSortDescriptors:(NSArray*)sortDescriptors error:(NSError**)error;
774
775+- (NSArray*)resultsForEntityClass:(Class)class inContext:(NSManagedObjectContext*)context withPredicate:(NSPredicate*)predicate sortDescriptors:(NSArray*)sortDescriptors error:(NSError**)error;
776+
777+- (void)dispatchBlockWithMainContext:(void(^)(NSManagedObjectContext *context))block;
778+
779 - (void)dispatchBlockWithManagedObjectContext:(void(^)(NSManagedObjectContext *context))block;
780
781 - (void)dispatchAsyncBlockWithManagedObjectContext:(void(^)(NSManagedObjectContext *context))block;
782
783=== modified file 'Files/U1DataRepository.m'
784--- Files/U1DataRepository.m 2011-10-25 23:36:04 +0000
785+++ Files/U1DataRepository.m 2011-12-09 20:00:29 +0000
786@@ -27,7 +27,7 @@
787 @property (nonatomic, retain) NSManagedObjectContext *mainContext;
788 @property (nonatomic, retain) NSPersistentStoreCoordinator *storeCoordinator;
789
790-- (NSArray*)fetchEntitiesForClass:(Class)class inContext:(NSManagedObjectContext*)context withPredicate:(NSPredicate*)predicate sortDescriptors:(NSArray*)sortDescriptors error:(NSError**)error;
791+- (NSArray*)resultsForEntityClass:(Class)class inContext:(NSManagedObjectContext*)context withPredicate:(NSPredicate*)predicate sortDescriptors:(NSArray*)sortDescriptors error:(NSError**)error;
792
793 @end
794
795@@ -101,22 +101,17 @@
796 NSParameterAssert(resourcePath);
797
798 NSPredicate *p = [NSPredicate predicateWithFormat:@"resourcePath = %@", resourcePath];
799- NSArray *nodes = [self fetchEntitiesForClass:[U1Node class] inContext:self.mainContext withPredicate:p sortDescriptors:nil error:NULL];
800+ NSArray *nodes = [self resultsForEntityClass:[U1Node class] inContext:self.mainContext withPredicate:p sortDescriptors:nil error:NULL];
801 U1Node *node = [nodes lastObject];
802 return node;
803 }
804
805-//- (void)fetchResultsForEntityClass:(Class)entityClass matchingPredicate:(NSPredicate*)predicate withSortDescriptors:(NSArray*)sortDescriptors completionBlock:(void(^)(NSArray *results, NSError *error))completionBlock;
806-//{
807-//
808-//}
809-
810 - (NSArray*)resultsForEntityClass:(Class)entityClass matchingPredicate:(NSPredicate*)predicate withSortDescriptors:(NSArray*)sortDescriptors error:(NSError**)error;
811 {
812- return [self fetchEntitiesForClass:entityClass inContext:self.mainContext withPredicate:predicate sortDescriptors:sortDescriptors error:error];
813+ return [self resultsForEntityClass:entityClass inContext:self.mainContext withPredicate:predicate sortDescriptors:sortDescriptors error:error];
814 }
815
816-- (NSArray*)fetchEntitiesForClass:(Class)class inContext:(NSManagedObjectContext*)context withPredicate:(NSPredicate*)predicate sortDescriptors:(NSArray*)sortDescriptors error:(NSError**)error;
817+- (NSArray*)resultsForEntityClass:(Class)class inContext:(NSManagedObjectContext*)context withPredicate:(NSPredicate*)predicate sortDescriptors:(NSArray*)sortDescriptors error:(NSError**)error;
818 {
819 NSParameterAssert(class != nil);
820 NSParameterAssert(context != nil);
821@@ -129,7 +124,7 @@
822 return results;
823 }
824
825-- (void)dispatchBlockWithManagedObjectContext:(void(^)(NSManagedObjectContext *context))block;
826+- (void)dispatchBlockWithMainContext:(void(^)(NSManagedObjectContext *context))block;
827 {
828 NSParameterAssert(block != NULL);
829 if (![NSThread isMainThread])
830@@ -146,6 +141,22 @@
831 }
832 }
833
834+- (void)dispatchBlockWithManagedObjectContext:(void(^)(NSManagedObjectContext *context))block;
835+{
836+ NSParameterAssert(block != NULL);
837+
838+ NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
839+ id observation = [[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:context queue:nil usingBlock:^(NSNotification *note) {
840+ dispatch_async(dispatch_get_main_queue(), ^{
841+ [self.mainContext mergeChangesFromContextDidSaveNotification:note];
842+ });
843+ }] retain];
844+ [context setPersistentStoreCoordinator:self.storeCoordinator];
845+ block(context);
846+ [context release];
847+ [observation release];
848+}
849+
850 - (void)dispatchAsyncBlockWithManagedObjectContext:(void(^)(NSManagedObjectContext *context))block;
851 {
852 NSParameterAssert(block != NULL);
853@@ -153,10 +164,16 @@
854 typeof(block) contextBlock = [block copy];
855 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
856 NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
857+ id observation = [[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:context queue:nil usingBlock:^(NSNotification *note) {
858+ dispatch_async(dispatch_get_main_queue(), ^{
859+ [self.mainContext mergeChangesFromContextDidSaveNotification:note];
860+ });
861+ }] retain];
862 [context setPersistentStoreCoordinator:self.storeCoordinator];
863 contextBlock(context);
864 [contextBlock release];
865 [context release];
866+ [observation release];
867 });
868 }
869
870
871=== modified file 'Files/U1FilesClient.h'
872--- Files/U1FilesClient.h 2011-12-01 17:37:53 +0000
873+++ Files/U1FilesClient.h 2011-12-09 20:00:29 +0000
874@@ -22,10 +22,8 @@
875
876 extern NSString * const U1FilesClientNodeStatusChangedNotification;
877
878+@protocol U1UploadDataProvider;
879
880-@protocol U1UploadDataProvider <NSObject>
881-- (NSData *)serializeData;
882-@end
883
884 typedef enum U1DataSourceType {
885 U1DataSourceLocal,
886
887=== modified file 'Files/U1FilesClient.m'
888--- Files/U1FilesClient.m 2011-12-06 20:53:05 +0000
889+++ Files/U1FilesClient.m 2011-12-09 20:00:29 +0000
890@@ -117,7 +117,7 @@
891 __block NSFetchedResultsController *cloudFoldersResultsController = nil;
892 __block U1Volume *rootVolume = nil; // vestigial
893
894- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
895+ [self.dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
896
897 NSFetchRequest *request = [[NSFetchRequest alloc] init];
898 [request setEntity:[U1Volume entityInManagedObjectContext:context]];
899@@ -142,7 +142,7 @@
900
901 [self.filesService volumeInfoWithCompletionBlock:^(NSArray *volumeInfos, NSError *error) {
902
903- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
904+ [self.dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
905
906 NSMutableArray *fetchedVolumes = [NSMutableArray array];
907 for (NSDictionary *volumeInfo in volumeInfos)
908@@ -193,7 +193,7 @@
909 __block U1FolderNode *node = [(id)[self.dataRepository nodeWithResourcePath:nodePath] retain];
910 __block NSFetchedResultsController *resultsController = nil;
911
912- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
913+ [self.dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
914
915 if (!node)
916 {
917@@ -237,17 +237,24 @@
918
919 [node updatePropertiesFromJSONDictionary:nodeInfo];
920
921+ NSManagedObjectID *nodeID = [node objectID];
922+
923 NSArray *childInfos = [nodeInfo objectForKey:@"children"];
924- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
925+ [self.dataRepository dispatchAsyncBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
926
927+ U1FolderNode *folderNode = (id)[context objectWithID:nodeID];
928 NSMutableSet *orphans = [NSMutableSet set];
929- [orphans unionSet:node.children];
930+ [orphans unionSet:folderNode.children];
931
932+ NSUInteger batchCounter = 0;
933 for (NSDictionary *childInfo in childInfos)
934 {
935 NSString *childResourcePath = [childInfo objectForKey:@"resource_path"];
936 NSString *kind = [childInfo objectForKey:@"kind"];
937- U1Node *childNode = [self.dataRepository nodeWithResourcePath:childResourcePath];
938+ NSError *error = nil;
939+ NSPredicate *p = [NSPredicate predicateWithFormat:@"resourcePath = %@", childResourcePath];
940+ NSArray *results = [self.dataRepository resultsForEntityClass:[U1Node class] inContext:context withPredicate:p sortDescriptors:nil error:&error];
941+ U1Node *childNode = [results lastObject];
942 if (!childNode)
943 {
944 if ([@"file" isEqualToString:kind])
945@@ -257,8 +264,14 @@
946 }
947
948 [childNode updatePropertiesFromJSONDictionary:childInfo];
949- childNode.parent = node;
950+ childNode.parent = folderNode;
951 [orphans removeObject:childNode];
952+
953+ if (++batchCounter > 100)
954+ {
955+ [context save:NULL];
956+ batchCounter = 0;
957+ }
958 }
959
960 for (U1Node *orphan in orphans)
961@@ -270,13 +283,15 @@
962 }
963
964 [context save:NULL];
965+
966+ dispatch_async(dispatch_get_main_queue(), ^(void) {
967+ resultsBlock(node, resultsController, U1DataSourceRemote);
968+ [resultsController release];
969+ [node release];
970+ });
971+
972 }];
973
974- dispatch_async(dispatch_get_main_queue(), ^(void) {
975- resultsBlock(node, resultsController, U1DataSourceRemote);
976- [resultsController release];
977- [node release];
978- });
979
980 }];
981 return nil;
982@@ -288,7 +303,7 @@
983
984 if (!node)
985 {
986- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
987+ [self.dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
988 node = [[U1FileNode insertInManagedObjectContext:context] retain];
989 node.resourcePath = nodePath;
990 [context save:NULL];
991@@ -333,7 +348,7 @@
992 return [self.filesService deleteNodeAtResourcePath:node.resourcePath completionBlock:^(NSError *error) {
993 if (!error)
994 {
995- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
996+ [self.dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
997 [context deleteObject:node];
998 [context save:NULL];
999 }];
1000@@ -348,7 +363,7 @@
1001 U1FileNode *node = (id)[self.dataRepository nodeWithResourcePath:resourcePath];
1002 if (!node)
1003 {
1004- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
1005+ [self.dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
1006 U1FileNode *newNode = [U1FileNode insertInManagedObjectContext:context];
1007 newNode.resourcePath = resourcePath;
1008 newNode.kind = @"file";
1009@@ -429,7 +444,7 @@
1010 U1FolderNode *node = (id)[self.dataRepository nodeWithResourcePath:resourcePath];
1011 if (!node)
1012 {
1013- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
1014+ [self.dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
1015 U1FolderNode *newNode = [U1FolderNode insertInManagedObjectContext:context];
1016 newNode.resourcePath = resourcePath;
1017 newNode.kind = @"directory";
1018@@ -452,7 +467,7 @@
1019 - (id)createVolumeAtPath:(NSString*)folderPath completionBlock:(void(^)(U1Volume *volume, NSError *error))completionBlock;
1020 {
1021 return [self.filesService createVolumeAtPath:folderPath completionBlock:^(NSDictionary *volumeInfo, NSError *error) {
1022- [self.dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
1023+ [self.dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
1024 U1Volume *volume = [U1Volume insertInManagedObjectContext:context];
1025 if (!error)
1026 {
1027
1028=== modified file 'Files/U1FilesService.h'
1029--- Files/U1FilesService.h 2011-11-01 01:17:53 +0000
1030+++ Files/U1FilesService.h 2011-12-09 20:00:29 +0000
1031@@ -18,6 +18,11 @@
1032 @class U1Node, U1FileNode, U1FolderNode;
1033
1034
1035+@protocol U1UploadDataProvider <NSObject>
1036+- (void)prepareDataStreamWithBlock:(void(^)(NSInputStream *dataStream, NSString *mimeType, NSUInteger length, NSError *error))block;
1037+@end
1038+
1039+
1040 @interface U1FilesService : NSObject
1041
1042 + (U1FilesService *)sharedFilesService;
1043@@ -28,7 +33,7 @@
1044 - (id)infoForNode:(U1Node*)node includeChildren:(BOOL)includeChildren completionBlock:(void(^)(NSDictionary *node, NSError *error))completionBlock;
1045 - (id)contentForNode:(U1FileNode*)node progressBlock:(void(^)(long long bytesReceived, long long bytesExpected))progressBlock completionBlock:(void(^)(NSURL *contentURL))completionBlock;
1046 - (id)uploadContentAtURL:(NSURL*)contentURL forNode:(U1FileNode*)node progressBlock:(void(^)(long long bytesUploaded, long long totalBytes))progressBlock completionBlock:(void(^)(U1FileNode *updatedNode, NSError *error))completionBlock;
1047-- (id)uploadContentData:(NSData*)contentData forNode:(U1FileNode*)node withContentType:(NSString*)contentType progressBlock:(void(^)(long long bytesUploaded, long long totalBytes))progressBlock completionBlock:(void(^)(NSDictionary *updatedNodeInfo, NSError *error))completionBlock;
1048+- (id)uploadContentData:(id<U1UploadDataProvider>)contentData forNode:(U1FileNode*)node withContentType:(NSString*)contentType progressBlock:(void(^)(long long bytesUploaded, long long totalBytes))progressBlock completionBlock:(void(^)(NSDictionary *updatedNodeInfo, NSError *error))completionBlock;
1049 - (id)moveNodeAtResourcePath:(NSString*)resourcePath toPath:(NSString*)newPath withCompletionBlock:(void(^)(NSDictionary *nodeInfo, NSError *error))completionBlock;
1050 - (id)deleteNodeAtResourcePath:(NSString*)resourcePath completionBlock:(void(^)(NSError *error))completionBlock;
1051 - (id)publishNode:(U1FileNode*)node completionBlock:(void(^)(U1FileNode *node, NSError *error))completionBlock;
1052
1053=== modified file 'Files/U1FilesService.m'
1054--- Files/U1FilesService.m 2011-12-06 18:38:47 +0000
1055+++ Files/U1FilesService.m 2011-12-09 20:00:29 +0000
1056@@ -196,20 +196,28 @@
1057 return nil;
1058 }
1059
1060-- (id)uploadContentData:(NSData*)contentData forNode:(U1FileNode*)node withContentType:(NSString*)contentType progressBlock:(void(^)(long long bytesUploaded, long long totalBytes))progressBlock completionBlock:(void(^)(NSDictionary *updatedNodeInfo, NSError *error))completionBlock;
1061+- (id)uploadContentData:(id<U1UploadDataProvider>)contentData forNode:(U1FileNode*)node withContentType:(NSString*)contentType progressBlock:(void(^)(long long bytesUploaded, long long totalBytes))progressBlock completionBlock:(void(^)(NSDictionary *updatedNodeInfo, NSError *error))completionBlock;
1062 {
1063 NSString *contentPath = node.contentPath;
1064 NSURL *requestURL = [NSURL URLWithString:[[U1FilesServiceAPIContentRoot stringByAppendingPathComponent:contentPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
1065
1066-
1067- U1ReportingInputStream *stream = [[U1ReportingInputStream alloc] initWithData:contentData];
1068- stream.progressBlock = progressBlock;
1069-
1070- [self execute:@"PUT" toURL:requestURL withParameters:nil requestBody:stream contentType:contentType contentLength:[contentData length] parseResponseBody:YES completionBlock:^(id results, NSError *error) {
1071-
1072- completionBlock(results, error);
1073+ [contentData prepareDataStreamWithBlock:^(NSInputStream *dataStream, NSString *mimeType, NSUInteger length, NSError *error) {
1074+
1075+ if (error)
1076+ {
1077+ completionBlock(nil, error);
1078+ return;
1079+ }
1080+
1081+ U1ReportingInputStream *stream = [U1ReportingInputStream inputStreamWithSourceStream:dataStream];
1082+ stream.progressBlock = progressBlock;
1083+ stream.dataLength = length;
1084+ [self execute:@"PUT" toURL:requestURL withParameters:nil requestBody:stream contentType:mimeType contentLength:length parseResponseBody:YES completionBlock:^(id results, NSError *error) {
1085+
1086+ completionBlock(results, error);
1087+ }];
1088 }];
1089-
1090+
1091 return nil;
1092 }
1093
1094@@ -350,30 +358,33 @@
1095 [request release];
1096
1097 [httpOperation setCompletionBlock:^(void) {
1098+
1099+ id result = nil;
1100+ NSHTTPURLResponse *response = httpOperation.response;
1101+ NSError *error = httpOperation.error;
1102+
1103+ if ([response statusCode] == 401)
1104+ {
1105+ dispatch_async(dispatch_get_main_queue(), ^(void) {
1106+ [[U1AccountManager sharedAccountManager] removeCredentials];
1107+ });
1108+ return;
1109+ }
1110+
1111+ if (!error &&
1112+ ![[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)] containsIndex:[response statusCode]])
1113+ {
1114+ // TODO: semantic error messages
1115+ error = [NSError errorWithDomain:@"U1FilesServiceErrorDomain" code:[response statusCode] userInfo:nil];
1116+ }
1117+ if (!error && parseResponseBody)
1118+ {
1119+ NSData *data = [collector data];
1120+ result = [data objectFromJSONDataWithParseOptions:JKParseOptionNone error:&error];
1121+ }
1122+
1123 dispatch_async(dispatch_get_main_queue(), ^(void) {
1124-
1125- id result = nil;
1126- NSHTTPURLResponse *response = httpOperation.response;
1127- NSError *error = httpOperation.error;
1128-
1129- if ([response statusCode] == 401)
1130- {
1131- [[U1AccountManager sharedAccountManager] removeCredentials];
1132- return;
1133- }
1134-
1135- if (!error &&
1136- ![[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)] containsIndex:[response statusCode]])
1137- {
1138- // TODO: semantic error messages
1139- error = [NSError errorWithDomain:@"U1FilesServiceErrorDomain" code:[response statusCode] userInfo:nil];
1140- }
1141- if (!error && parseResponseBody)
1142- {
1143- NSData *data = [collector data];
1144- result = [data objectFromJSONDataWithParseOptions:JKParseOptionNone error:&error];
1145- }
1146-
1147+
1148 if (completionBlock)
1149 completionBlock(result, error);
1150
1151
1152=== modified file 'Files/U1FolderItemCell.m'
1153--- Files/U1FolderItemCell.m 2011-12-01 17:37:53 +0000
1154+++ Files/U1FolderItemCell.m 2011-12-09 20:00:29 +0000
1155@@ -19,6 +19,11 @@
1156 #import "U1ViewNibLoader.h"
1157
1158
1159+@interface U1FolderItemCell ()
1160++ (UINib*)cellNib;
1161+@end
1162+
1163+
1164 @implementation U1FolderItemCell
1165
1166 @synthesize iconImageView, nameLabel, detailLabel, uploadIndicator, uploadProgressView;
1167@@ -29,11 +34,21 @@
1168 U1FolderItemCell *cell = (id)[tableView dequeueReusableCellWithIdentifier:NSStringFromClass(self)];
1169 if (!cell)
1170 {
1171- cell = [U1ViewNibLoader viewFromNib:NSStringFromClass(self)];
1172+ cell = [U1ViewNibLoader viewFromUINib:[U1FolderItemCell cellNib]];
1173 }
1174 return cell;
1175 }
1176
1177++ (UINib*)cellNib;
1178+{
1179+ static UINib *cellNib;
1180+ static dispatch_once_t onceToken;
1181+ dispatch_once(&onceToken, ^{
1182+ cellNib = [[UINib nibWithNibName:NSStringFromClass(self) bundle:nil] retain];
1183+ });
1184+ return cellNib;
1185+}
1186+
1187 - (void)dealloc
1188 {
1189 [iconImageView release];
1190
1191=== modified file 'Files/U1FolderItemCell.xib'
1192--- Files/U1FolderItemCell.xib 2011-12-01 17:37:53 +0000
1193+++ Files/U1FolderItemCell.xib 2011-12-09 20:00:29 +0000
1194@@ -101,7 +101,7 @@
1195 <string key="NSFrame">{{52, 28}, {234, 17}}</string>
1196 <reference key="NSSuperview" ref="604950473"/>
1197 <reference key="NSWindow"/>
1198- <reference key="NSNextKeyView"/>
1199+ <reference key="NSNextKeyView" ref="23474122"/>
1200 <bool key="IBUIOpaque">NO</bool>
1201 <bool key="IBUIClipsSubviews">YES</bool>
1202 <int key="IBUIContentMode">7</int>
1203@@ -134,6 +134,7 @@
1204 <string key="NSFrame">{{293, 17}, {20, 20}}</string>
1205 <reference key="NSSuperview" ref="604950473"/>
1206 <reference key="NSWindow"/>
1207+ <reference key="NSNextKeyView"/>
1208 <bool key="IBUIOpaque">NO</bool>
1209 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1210 <bool key="IBUIHidesWhenStopped">NO</bool>
1211@@ -145,6 +146,7 @@
1212 <string key="NSFrame">{{52, 32}, {234, 9}}</string>
1213 <reference key="NSSuperview" ref="604950473"/>
1214 <reference key="NSWindow"/>
1215+ <reference key="NSNextKeyView" ref="587955385"/>
1216 <bool key="IBUIOpaque">NO</bool>
1217 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1218 <object class="NSColor" key="IBUIProgressTintColor">
1219
1220=== modified file 'Files/U1FolderViewController.m'
1221--- Files/U1FolderViewController.m 2011-12-07 18:23:32 +0000
1222+++ Files/U1FolderViewController.m 2011-12-09 20:00:29 +0000
1223@@ -253,12 +253,13 @@
1224 NSString *filename = [NSString stringWithFormat:@"%@ %@.%@", assetType, [dateFormatter stringFromDate:assetDate], ext];
1225 [dateFormatter release];
1226
1227- U1Asset *u1asset = [autoUploadsManager createU1AssetWithAsset:asset group:nil URLString:[imageURL absoluteString] fileName:filename inFolder:self.node];
1228+ // TODO: fix!
1229+ U1Asset *u1asset = [autoUploadsManager createU1AssetWithAsset:asset group:nil URLString:[imageURL absoluteString] fileName:filename inFolder:self.node context:nil];
1230
1231 if (u1asset.generation == nil) // Prevent duplicate uploading to the same folder
1232 {
1233 U1AssetRepresenationDataProvider *provider = [[U1AssetRepresenationDataProvider alloc] init];
1234- provider.representation = representation;
1235+ provider.assetURL = [NSURL URLWithString:[u1asset url]];
1236 dispatch_async(dispatch_get_main_queue(), ^(void) {
1237 [self.filesClient uploadContentDataProvider:provider
1238 toFolder:self.node
1239@@ -271,7 +272,7 @@
1240 if (error)
1241 {
1242 // TODO: remove direct access to data repository
1243- [[U1DataRepository sharedDataRepository] dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
1244+ [[U1DataRepository sharedDataRepository] dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
1245 [context deleteObject:u1asset.fileNode];
1246 }];
1247 }
1248
1249=== modified file 'Files/U1ReportingInputStream.h'
1250--- Files/U1ReportingInputStream.h 2011-11-30 21:42:52 +0000
1251+++ Files/U1ReportingInputStream.h 2011-12-09 20:00:29 +0000
1252@@ -19,8 +19,9 @@
1253 @interface U1ReportingInputStream : NSProxy
1254
1255 @property (copy) void (^progressBlock)(long long, long long);
1256+@property long long dataLength;
1257
1258-+ (NSInputStream*)inputStreamWithData:(NSData*)data;
1259-- (id)initWithData:(NSData*)data;
1260++ (U1ReportingInputStream*)inputStreamWithSourceStream:(NSInputStream*)sourceStream;
1261+- (id)initWithSourceStream:(NSInputStream*)sourceStream;
1262
1263 @end
1264
1265=== modified file 'Files/U1ReportingInputStream.m'
1266--- Files/U1ReportingInputStream.m 2011-11-30 21:42:52 +0000
1267+++ Files/U1ReportingInputStream.m 2011-12-09 20:00:29 +0000
1268@@ -23,22 +23,20 @@
1269
1270 @implementation U1ReportingInputStream
1271 {
1272- NSUInteger contentLength;
1273 NSUInteger readSoFar;
1274 }
1275
1276-@synthesize progressBlock;
1277+@synthesize progressBlock, dataLength;
1278 @synthesize targetStream;
1279
1280-+ (NSInputStream *)inputStreamWithData:(NSData *)data;
1281++ (U1ReportingInputStream *)inputStreamWithSourceStream:(NSInputStream*)sourceStream;
1282 {
1283- return [[[self alloc] initWithData:data] autorelease];
1284+ return [[[self alloc] initWithSourceStream:sourceStream] autorelease];
1285 }
1286
1287-- (id)initWithData:(NSData *)theData;
1288+- (id)initWithSourceStream:(NSInputStream*)sourceStream;
1289 {
1290- targetStream = [[NSInputStream alloc] initWithData:theData];
1291- contentLength = [theData length];
1292+ targetStream = [sourceStream retain];
1293 return self;
1294 }
1295
1296@@ -54,7 +52,7 @@
1297 NSInteger read = [self.targetStream read:buffer maxLength:len];
1298 readSoFar += read;
1299 if (self.progressBlock)
1300- self.progressBlock(readSoFar, contentLength);
1301+ self.progressBlock(readSoFar, self.dataLength);
1302 return read;
1303 }
1304
1305
1306=== modified file 'Files/U1UploadOperation.m'
1307--- Files/U1UploadOperation.m 2011-12-06 20:53:05 +0000
1308+++ Files/U1UploadOperation.m 2011-12-09 20:00:29 +0000
1309@@ -85,9 +85,7 @@
1310
1311 U1FilesService *filesService = [U1FilesService sharedFilesService];
1312 fileInfo.uploading = YES;
1313- NSData *uploadData = [self.dataProvider serializeData];
1314-
1315- [filesService uploadContentData:uploadData forNode:self.fileNode withContentType:self.mimetype progressBlock:^(long long uploaded, long long totalLength) {
1316+ [filesService uploadContentData:self.dataProvider forNode:self.fileNode withContentType:self.mimetype progressBlock:^(long long uploaded, long long totalLength) {
1317
1318 dispatch_async(dispatch_get_main_queue(), ^{
1319 [self.fileInfo setUploadPercentage:((double)uploaded / (double)totalLength)];
1320@@ -107,7 +105,26 @@
1321 if (filesServiceError != nil)
1322 {
1323 self.error = filesServiceError;
1324- [self beginUpload];
1325+ if ([filesServiceError.domain isEqualToString:@"nonexistentAsset"])
1326+ {
1327+ // Asset is gone, delete U1Asset and potentially the Node, and stop running
1328+ U1DataRepository *dataRepository = [U1DataRepository sharedDataRepository];
1329+ [dataRepository dispatchAsyncBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
1330+ U1Asset *assetToBeDeleted = self.fileNode.asset;
1331+ [context deleteObject:assetToBeDeleted];
1332+ if (self.fileNode.generation == nil)
1333+ {
1334+ [context deleteObject:self.fileNode];
1335+ }
1336+ [dataRepository save:NULL];
1337+ }];
1338+ [self finishExecuting];
1339+ }
1340+ else
1341+ {
1342+ // File upload timed out or something else, so reschedule
1343+ [self beginUpload];
1344+ }
1345 }
1346 else
1347 {
1348
1349=== modified file 'Files/U1UploadsPoolViewController.m'
1350--- Files/U1UploadsPoolViewController.m 2011-12-06 18:36:40 +0000
1351+++ Files/U1UploadsPoolViewController.m 2011-12-09 20:00:29 +0000
1352@@ -275,7 +275,7 @@
1353
1354 - (void)loadCurrentUploads;
1355 {
1356- [[U1DataRepository sharedDataRepository] dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
1357+ [[U1DataRepository sharedDataRepository] dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
1358
1359 NSFetchRequest *request = [[NSFetchRequest alloc] init];
1360 [request setEntity:[U1Asset entityInManagedObjectContext:context]];
1361
1362=== modified file 'Files/U1Volume.m'
1363--- Files/U1Volume.m 2011-10-26 04:40:14 +0000
1364+++ Files/U1Volume.m 2011-12-09 20:00:29 +0000
1365@@ -41,7 +41,7 @@
1366 if (rootFolder == nil)
1367 {
1368 // Upload
1369- [dataRepository dispatchBlockWithManagedObjectContext:^(NSManagedObjectContext *context) {
1370+ [dataRepository dispatchBlockWithMainContext:^(NSManagedObjectContext *context) {
1371
1372 U1FolderNode *rootFolder = [U1FolderNode insertInManagedObjectContext:context];
1373 rootFolder.resourcePath = self.nodePath;
1374
1375=== modified file 'Files/iPhone/en.lproj/MainWindow_iPhone.xib'
1376--- Files/iPhone/en.lproj/MainWindow_iPhone.xib 2011-11-18 17:53:03 +0000
1377+++ Files/iPhone/en.lproj/MainWindow_iPhone.xib 2011-12-09 20:00:29 +0000
1378@@ -45,10 +45,11 @@
1379 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1380 </object>
1381 <object class="IBUIWindow" id="380026005">
1382- <nil key="NSNextResponder"/>
1383+ <reference key="NSNextResponder"/>
1384 <int key="NSvFlags">1316</int>
1385 <object class="NSPSMatrix" key="NSFrameMatrix"/>
1386 <string key="NSFrameSize">{320, 480}</string>
1387+ <reference key="NSSuperview"/>
1388 <object class="NSColor" key="IBUIBackgroundColor">
1389 <int key="NSColorSpace">1</int>
1390 <bytes key="NSRGB">MSAxIDEAA</bytes>
1391@@ -68,12 +69,12 @@
1392 </object>
1393 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1394 <bool key="IBUIHorizontal">NO</bool>
1395- <object class="IBUINavigationController" key="IBUISelectedViewController" id="941272465">
1396- <object class="IBUITabBarItem" key="IBUITabBarItem" id="463875161">
1397- <string key="IBUITitle">Uploads</string>
1398+ <object class="IBUINavigationController" key="IBUISelectedViewController" id="175250829">
1399+ <object class="IBUITabBarItem" key="IBUITabBarItem" id="1047769425">
1400+ <string key="IBUITitle">Files</string>
1401 <object class="NSCustomResource" key="IBUIImage">
1402 <string key="NSClassName">NSImage</string>
1403- <string key="NSResourceName">upload.png</string>
1404+ <string key="NSResourceName">folder.png</string>
1405 </object>
1406 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1407 </object>
1408@@ -85,7 +86,7 @@
1409 </object>
1410 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1411 <bool key="IBUIHorizontal">NO</bool>
1412- <object class="IBUINavigationBar" key="IBUINavigationBar" id="499706903">
1413+ <object class="IBUINavigationBar" key="IBUINavigationBar" id="131246382">
1414 <nil key="NSNextResponder"/>
1415 <int key="NSvFlags">256</int>
1416 <string key="NSFrameSize">{0, 0}</string>
1417@@ -100,12 +101,12 @@
1418 </object>
1419 <object class="NSMutableArray" key="IBUIViewControllers">
1420 <bool key="EncodedWithXMLCoder">YES</bool>
1421- <object class="IBUIViewController" id="743143347">
1422- <object class="IBUINavigationItem" key="IBUINavigationItem" id="374575231">
1423+ <object class="IBUIViewController" id="852027692">
1424+ <object class="IBUINavigationItem" key="IBUINavigationItem" id="570921376">
1425 <string key="IBUITitle">Root View Controller</string>
1426 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1427 </object>
1428- <reference key="IBUIParentViewController" ref="941272465"/>
1429+ <reference key="IBUIParentViewController" ref="175250829"/>
1430 <object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
1431 <int key="IBUIInterfaceOrientation">1</int>
1432 <int key="interfaceOrientation">1</int>
1433@@ -117,12 +118,13 @@
1434 </object>
1435 <object class="NSMutableArray" key="IBUIViewControllers">
1436 <bool key="EncodedWithXMLCoder">YES</bool>
1437- <object class="IBUINavigationController" id="175250829">
1438- <object class="IBUITabBarItem" key="IBUITabBarItem" id="1047769425">
1439- <string key="IBUITitle">Files</string>
1440+ <reference ref="175250829"/>
1441+ <object class="IBUINavigationController" id="941272465">
1442+ <object class="IBUITabBarItem" key="IBUITabBarItem" id="463875161">
1443+ <string key="IBUITitle">Uploads</string>
1444 <object class="NSCustomResource" key="IBUIImage">
1445 <string key="NSClassName">NSImage</string>
1446- <string key="NSResourceName">folder.png</string>
1447+ <string key="NSResourceName">upload.png</string>
1448 </object>
1449 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1450 </object>
1451@@ -134,7 +136,7 @@
1452 </object>
1453 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1454 <bool key="IBUIHorizontal">NO</bool>
1455- <object class="IBUINavigationBar" key="IBUINavigationBar" id="131246382">
1456+ <object class="IBUINavigationBar" key="IBUINavigationBar" id="499706903">
1457 <nil key="NSNextResponder"/>
1458 <int key="NSvFlags">256</int>
1459 <string key="NSFrameSize">{0, 0}</string>
1460@@ -149,12 +151,12 @@
1461 </object>
1462 <object class="NSMutableArray" key="IBUIViewControllers">
1463 <bool key="EncodedWithXMLCoder">YES</bool>
1464- <object class="IBUIViewController" id="852027692">
1465- <object class="IBUINavigationItem" key="IBUINavigationItem" id="570921376">
1466+ <object class="IBUIViewController" id="743143347">
1467+ <object class="IBUINavigationItem" key="IBUINavigationItem" id="374575231">
1468 <string key="IBUITitle">Root View Controller</string>
1469 <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
1470 </object>
1471- <reference key="IBUIParentViewController" ref="175250829"/>
1472+ <reference key="IBUIParentViewController" ref="941272465"/>
1473 <object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
1474 <int key="IBUIInterfaceOrientation">1</int>
1475 <int key="interfaceOrientation">1</int>
1476@@ -164,7 +166,6 @@
1477 </object>
1478 </object>
1479 </object>
1480- <reference ref="941272465"/>
1481 <object class="IBUINavigationController" id="631281359">
1482 <object class="IBUITabBarItem" key="IBUITabBarItem" id="460599548">
1483 <string key="IBUITitle">Settings</string>

Subscribers

People subscribed via source and target branches