Merge lp:~rockstar/ubuntuone-ios-music/dial-down into lp:~ubuntuone-ios-client-team/ubuntuone-ios-music/moriarty

Proposed by Paul Hummer
Status: Merged
Merge reported by: Paul Hummer
Merged at revision: not available
Proposed branch: lp:~rockstar/ubuntuone-ios-music/dial-down
Merge into: lp:~ubuntuone-ios-client-team/ubuntuone-ios-music/moriarty
Prerequisite: lp:~rockstar/ubuntuone-ios-music/coredata
Diff against target: 1432 lines (+558/-320)
24 files modified
Music/AppDelegate.m (+6/-31)
Music/Categories/RKRequest+Plaintext.h (+0/-13)
Music/Categories/RKRequest+Plaintext.m (+0/-55)
Music/Models/Album.h (+2/-0)
Music/Models/Album.m (+28/-11)
Music/Models/Artist.m (+9/-8)
Music/Models/Playlist.m (+9/-8)
Music/Models/Song.m (+74/-32)
Music/Models/UOModel.h (+2/-2)
Music/Models/UOModel.m (+8/-3)
Music/Utilities/UOWebServiceController.h (+39/-0)
Music/Utilities/UOWebServiceController.m (+204/-0)
Music/View Controllers/AlbumViewController.h (+1/-1)
Music/View Controllers/AlbumViewController.m (+52/-25)
Music/View Controllers/AlbumsViewController.m (+9/-8)
Music/View Controllers/ArtistViewController.h (+1/-1)
Music/View Controllers/ArtistViewController.m (+58/-31)
Music/View Controllers/ArtistsViewController.m (+10/-10)
Music/View Controllers/PlaylistsViewController.m (+8/-7)
Music/View Controllers/SongsViewController.m (+6/-5)
Music/View Controllers/UOIndexedViewController.h (+2/-2)
Music/View Controllers/UOIndexedViewController.m (+21/-37)
U1Music.xcodeproj/project.pbxproj (+6/-21)
U1Music_Prefix.pch (+3/-9)
To merge this branch: bzr merge lp:~rockstar/ubuntuone-ios-music/dial-down
Reviewer Review Type Date Requested Status
Michał Karnicki (community) Approve
Review via email: mp+141684@code.launchpad.net

Description of the change

This branch adds the core data support to the ancilliary views that are dialed
down to through selection. It also warranted an upgrade to Core Data so that
we could only fetch when absolutely necessary. I *think* this is likely to be
the last big branch. Sorry it's so big.

To post a comment you must log in.
Revision history for this message
Michał Karnicki (karni) wrote :

if ([error code] == 2) { // Network is down.
-- just nitpicking: a constant instead of comment would be better

int interval = -(60*60*24*5); // 5 days ago
-- this update interval feels arbitrary to me. How do we know that I haven't purchased a music album today?

Couple of lines moved in PlaylistsViewController.m
-- there was no change intentionally, right? I understand you just found a better place for that snippet.

Would it be difficult to add at least some basic tests exercising the updated Core Data model?

I noticed you've set 'Ubuntu One iOS Client Team' as the reviewer. I think you could go with Ubuntu One Client Engineering, I've already dumped the Android equivalent, as we basically became two one-manned sub-teams of Client Engineering after the re-org :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Music/AppDelegate.m'
2--- Music/AppDelegate.m 2013-01-02 23:49:22 +0000
3+++ Music/AppDelegate.m 2013-01-02 23:49:23 +0000
4@@ -37,7 +37,6 @@
5 @synthesize storyboard = _storyboard;
6
7 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8- [self initRestKit];
9
10 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
11
12@@ -164,6 +163,12 @@
13 return _persistentStoreCoordinator;
14 }
15
16+#pragma mark - Convenience methods
17+
18++ (AppDelegate *)delegate {
19+ return (AppDelegate *)[UIApplication sharedApplication].delegate;
20+}
21+
22 #pragma mark - Application's Documents directory
23
24 // Returns the URL to the application's Documents directory.
25@@ -180,34 +185,4 @@
26 [self.window makeKeyAndVisible];
27 }
28
29-#pragma mark - Convenience methods
30-
31-+ (AppDelegate *)delegate {
32- return (AppDelegate *)[UIApplication sharedApplication].delegate;
33-}
34-
35-#pragma mark - Private methods
36-
37-- (void)initRestKit {
38- NSDictionary *credentials = [[UOAuthManager sharedAuthManager] oauthCredentials];
39- RKURL *baseURL = [RKURL URLWithBaseURLString:@"https://one.ubuntu.com/api/music/v2"];
40-
41- RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:baseURL];
42- objectManager.client.baseURL = baseURL;
43- objectManager.client.OAuth1ConsumerKey = [credentials objectForKey:UOOAuthConsumerKey];
44- objectManager.client.OAuth1ConsumerSecret = [credentials objectForKey:UOOAuthConsumerSecret];
45- objectManager.client.OAuth1AccessToken = [credentials objectForKey:UOOAuthToken];
46- objectManager.client.OAuth1AccessTokenSecret = [credentials objectForKey:UOOAuthTokenSecret];
47- objectManager.client.authenticationType = RKRequestAuthenticationTypeOAuth1;
48- static NSString *DatabaseName = @"UOMusic.sql";
49-
50- RKManagedObjectStore *objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:DatabaseName];
51- objectManager.objectStore = objectStore;
52-
53- [Album registerMapping];
54- [Artist registerMapping];
55- [Playlist registerMapping];
56- [Song registerMapping];
57-}
58-
59 @end
60
61=== removed file 'Music/Categories/RKRequest+Plaintext.h'
62--- Music/Categories/RKRequest+Plaintext.h 2012-12-07 22:39:22 +0000
63+++ Music/Categories/RKRequest+Plaintext.h 1970-01-01 00:00:00 +0000
64@@ -1,13 +0,0 @@
65-//
66-// RKRequest+Plaintext.h
67-// U1Music
68-//
69-// Created by Paul Hummer on 10/5/12.
70-// Copyright (c) 2012 Canonical. All rights reserved.
71-//
72-
73-#import <RestKit/RestKit.h>
74-
75-@interface RKRequest (Plaintext)
76-
77-@end
78
79=== removed file 'Music/Categories/RKRequest+Plaintext.m'
80--- Music/Categories/RKRequest+Plaintext.m 2012-12-07 22:39:22 +0000
81+++ Music/Categories/RKRequest+Plaintext.m 1970-01-01 00:00:00 +0000
82@@ -1,55 +0,0 @@
83-//
84-// RKRequest+Plaintext.m
85-// U1Music
86-//
87-// Created by Paul Hummer on 10/5/12.
88-// Copyright (c) 2012 Canonical. All rights reserved.
89-//
90-
91-#import <RestKit/RestKit.h>
92-#import "RKRequest+Plaintext.h"
93-#import "UOAuthManager.h"
94-
95-@implementation RKRequest (Plaintext)
96-
97-/* rockstar: 5 Oct 2012 - This is as copy/pasted as I could possibly get. It's a hack, and
98- I'm so very very sorry. The fact remains that Ubuntu One doesn't currently support the
99- HMAC-SHA1 signature method, and RestKit's oauth library doesn't support Plaintext. I'm going
100- to work towards getting Plaintext support into RestKit.
101- */
102-- (void)addHeadersToRequest
103-{
104- NSString *header = nil;
105- for (header in self.additionalHTTPHeaders) {
106- [self.URLRequest setValue:[self.additionalHTTPHeaders valueForKey:header] forHTTPHeaderField:header];
107- }
108-
109- [self.URLRequest setValue:nil forHTTPHeaderField:@"Content-Length"];
110-
111- /* This is the specific part that needs to be overridden. The OAuth2 stuff has been removed,
112- and since we know that we're using OAuth, we don't check against it.
113- */
114- NSURLRequest *echo = nil;
115-
116- // use the suitable parameters dict
117- NSDictionary *parameters = nil;
118- if ([self.params isKindOfClass:[RKParams class]])
119- parameters = [(RKParams *)self.params dictionaryOfPlainTextParams];
120- else
121- parameters = [self.URL queryParameters];
122-
123- echo = [UOAuthManager oauthRequestForPath:[self.URL path]];
124- [self.URLRequest setValue:[echo valueForHTTPHeaderField:@"Authorization"] forHTTPHeaderField:@"Authorization"];
125- [self.URLRequest setValue:[echo valueForHTTPHeaderField:@"Accept-Encoding"] forHTTPHeaderField:@"Accept-Encoding"];
126- [self.URLRequest setValue:[echo valueForHTTPHeaderField:@"User-Agent"] forHTTPHeaderField:@"User-Agent"];
127-
128- if (self.cachePolicy & RKRequestCachePolicyEtag) {
129- NSString *etag = [self.cache etagForRequest:self];
130- if (etag) {
131- RKLogTrace(@"Setting If-None-Match header to '%@'", etag);
132- [self.URLRequest setValue:etag forHTTPHeaderField:@"If-None-Match"];
133- }
134- }
135-}
136-
137-@end
138
139=== modified file 'Music/Models/Album.h'
140--- Music/Models/Album.h 2013-01-02 23:49:22 +0000
141+++ Music/Models/Album.h 2013-01-02 23:49:23 +0000
142@@ -24,4 +24,6 @@
143
144 @property (nonatomic, retain) NSString *artistId;
145 @property (nonatomic, retain, readonly) Artist *artist;
146+
147+@property (nonatomic, retain) NSSet *songs;
148 @end
149
150=== modified file 'Music/Models/Album.m'
151--- Music/Models/Album.m 2013-01-02 23:49:22 +0000
152+++ Music/Models/Album.m 2013-01-02 23:49:23 +0000
153@@ -8,6 +8,7 @@
154
155 #import "Album.h"
156 #import <RestKit/RestKit.h>
157+#import <RestKit/CoreData.h>
158 #import "Artist.h"
159 #import "NSString+Extras.h"
160
161@@ -24,18 +25,21 @@
162 @synthesize artistId;
163 @dynamic artist;
164
165-+ (RKManagedObjectMapping *)objectMapping {
166+@dynamic songs;
167+
168++ (RKEntityMapping *)objectMapping {
169 RKObjectManager *manager = [RKObjectManager sharedManager];
170
171- RKManagedObjectMapping *mapping = [RKManagedObjectMapping mappingForClass:[self class] inManagedObjectStore:manager.objectStore];
172- [mapping mapKeyPath:@"id" toAttribute:@"albumId"];
173- [mapping mapKeyPath:@"album_url" toAttribute:@"url"];
174- [mapping mapKeyPath:@"cover_art" toAttribute:@"art"];
175- [mapping mapKeyPath:@"album_art_url" toAttribute:@"artUrl"];
176- [mapping mapKeyPath:@"title" toAttribute:@"title"];
177- [mapping mapKeyPath:@"artist_id" toAttribute:@"artistId"];
178-
179- mapping.primaryKeyAttribute = @"albumId";
180+ RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"Album" inManagedObjectStore:manager.managedObjectStore];
181+ [mapping setIdentificationAttributes:@[@"albumId"]];
182+ [mapping addAttributeMappingsFromDictionary:@{
183+ @"id": @"albumId",
184+ @"album_url": @"url",
185+ @"cover_art": @"art",
186+ @"album_art_url": @"artUrl",
187+ @"title": @"title",
188+ @"artist_id": @"artistId"
189+ }];
190
191 return mapping;
192 }
193@@ -50,7 +54,20 @@
194 }
195
196 - (void)setArtistId:(NSString *)_artistId {
197- Artist *_artist = [Artist findByPrimaryKey:_artistId inContext:self.managedObjectContext];
198+ NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
199+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:self.managedObjectContext];
200+ [fetchRequest setEntity:entity];
201+
202+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(artistId = %@)", _artistId];
203+ [fetchRequest setPredicate:predicate];
204+
205+ NSError *error;
206+ NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
207+ if (error || [results count] != 1) {
208+ abort();
209+ }
210+
211+ Artist *_artist = [results objectAtIndex:0];
212 [self setPrimitiveValue:_artist forKey:@"artist"];
213 }
214
215
216=== modified file 'Music/Models/Artist.m'
217--- Music/Models/Artist.m 2013-01-02 23:49:22 +0000
218+++ Music/Models/Artist.m 2013-01-02 23:49:23 +0000
219@@ -20,16 +20,17 @@
220
221 @synthesize index;
222
223-+ (RKManagedObjectMapping *)objectMapping {
224++ (RKEntityMapping *)objectMapping {
225 RKObjectManager *manager = [RKObjectManager sharedManager];
226
227- RKManagedObjectMapping *mapping = [RKManagedObjectMapping mappingForClass:[self class] inManagedObjectStore:manager.objectStore];
228- [mapping mapKeyPath:@"id" toAttribute:@"artistId"];
229- [mapping mapKeyPath:@"artist" toAttribute:@"name"];
230- [mapping mapKeyPath:@"artist_url" toAttribute:@"url"];
231- [mapping mapKeyPath:@"artist_art_url" toAttribute:@"artUrl"];
232-
233- mapping.primaryKeyAttribute = @"artistId";
234+ RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"Artist" inManagedObjectStore:manager.managedObjectStore];
235+ [mapping setIdentificationAttributes:@[@"artistId"]];
236+ [mapping addAttributeMappingsFromDictionary:@{
237+ @"id" : @"artistId",
238+ @"artist": @"name",
239+ @"artist_url": @"url",
240+ @"artist_art_url": @"artUrl",
241+ }];
242 return mapping;
243 }
244
245
246=== modified file 'Music/Models/Playlist.m'
247--- Music/Models/Playlist.m 2013-01-02 23:49:22 +0000
248+++ Music/Models/Playlist.m 2013-01-02 23:49:23 +0000
249@@ -19,16 +19,17 @@
250
251 @synthesize index;
252
253-+ (RKManagedObjectMapping *)objectMapping {
254++ (RKEntityMapping *)objectMapping {
255 RKObjectManager *manager = [RKObjectManager sharedManager];
256
257- RKManagedObjectMapping *mapping = [RKManagedObjectMapping mappingForClass:[self class] inManagedObjectStore:manager.objectStore];
258- [mapping mapKeyPath:@"id" toAttribute:@"playlistId"];
259- [mapping mapKeyPath:@"playlist_url" toAttribute:@"url"];
260- [mapping mapKeyPath:@"name" toAttribute:@"title"];
261- [mapping mapKeyPath:@"song_count" toAttribute:@"songCount"];
262-
263- mapping.primaryKeyAttribute = @"playlistId";
264+ RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"Playlist" inManagedObjectStore:manager.managedObjectStore];
265+ [mapping setIdentificationAttributes:@[@"playlistId"]];
266+ [mapping addAttributeMappingsFromDictionary:@{
267+ @"id": @"playlistId",
268+ @"playlist_url": @"url",
269+ @"name": @"title",
270+ @"song_count": @"songCount",
271+ }];
272 return mapping;
273 }
274
275
276=== modified file 'Music/Models/Song.m'
277--- Music/Models/Song.m 2013-01-02 23:49:22 +0000
278+++ Music/Models/Song.m 2013-01-02 23:49:23 +0000
279@@ -33,35 +33,35 @@
280
281 @synthesize index;
282
283-@synthesize album = _album;
284-@synthesize artist = _artist;
285-@synthesize albumArtist = _albumArtist;
286+@dynamic album;
287+@dynamic artist;
288+@dynamic albumArtist;
289
290-+ (RKManagedObjectMapping *)objectMapping {
291++ (RKEntityMapping *)objectMapping {
292 RKObjectManager *manager = [RKObjectManager sharedManager];
293
294- RKManagedObjectMapping *mapping = [RKManagedObjectMapping mappingForClass:[self class] inManagedObjectStore:manager.objectStore];
295-
296- [mapping mapKeyPath:@"song_url" toAttribute:@"url"];
297- [mapping mapKeyPath:@"song_art_url" toAttribute:@"artUrl"];
298- [mapping mapKeyPath:@"suffix" toAttribute:@"suffix"];
299- [mapping mapKeyPath:@"track" toAttribute:@"track"];
300- [mapping mapKeyPath:@"title" toAttribute:@"title"];
301- [mapping mapKeyPath:@"album_artist_id" toAttribute:@"albumArtistId"];
302- [mapping mapKeyPath:@"bit_rate" toAttribute:@"bitRate"];
303- [mapping mapKeyPath:@"id" toAttribute:@"songId"];
304- [mapping mapKeyPath:@"duration" toAttribute:@"duration"];
305- [mapping mapKeyPath:@"disc_number" toAttribute:@"discNumber"];
306- [mapping mapKeyPath:@"song_stream_url" toAttribute:@"streamUrl"];
307- [mapping mapKeyPath:@"content_type" toAttribute:@"contentType"];
308- [mapping mapKeyPath:@"year" toAttribute:@"year"];
309- [mapping mapKeyPath:@"genre" toAttribute:@"genre"];
310- [mapping mapKeyPath:@"path" toAttribute:@"path"];
311- [mapping mapKeyPath:@"artist_id" toAttribute:@"artistId"];
312- [mapping mapKeyPath:@"album_id" toAttribute:@"albumId"];
313- [mapping mapKeyPath:@"size" toAttribute:@"size"];
314-
315- mapping.primaryKeyAttribute = @"songId";
316+ RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"Song" inManagedObjectStore:manager.managedObjectStore];
317+ [mapping setIdentificationAttributes:@[@"songId"]];
318+ [mapping addAttributeMappingsFromDictionary:@{
319+ @"song_url": @"url",
320+ @"song_art_url": @"artUrl",
321+ @"suffix": @"suffix",
322+ @"track": @"track",
323+ @"title": @"title",
324+ @"album_artist_id": @"albumArtistId",
325+ @"bit_rate": @"bitRate",
326+ @"id": @"songId",
327+ @"duration": @"duration",
328+ @"disc_number": @"discNumber",
329+ @"song_stream_url": @"streamUrl",
330+ @"content_type": @"contentType",
331+ @"year": @"year",
332+ @"genre": @"genre",
333+ @"path": @"path",
334+ @"artist_id": @"artistId",
335+ @"album_id": @"albumId",
336+ @"size": @"size"
337+ }];
338 return mapping;
339 }
340
341@@ -81,24 +81,66 @@
342 return _index;
343 }
344
345-- (void)setArtistId:(NSString *)artistId {
346- _artist = [Artist findByPrimaryKey:artistId inContext:self.managedObjectContext];
347+- (void)setArtistId:(NSString *)_artistId {
348+ NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
349+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:self.managedObjectContext];
350+ [fetchRequest setEntity:entity];
351+
352+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(artistId = %@)", _artistId];
353+ [fetchRequest setPredicate:predicate];
354+
355+ NSError *error;
356+ NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
357+ if (error || [results count] != 1) {
358+ abort();
359+ }
360+
361+ Artist *_artist = [results objectAtIndex:0];
362+ [self setPrimitiveValue:_artist forKey:@"artist"];
363 }
364
365 - (NSString *)artistId {
366 return self.artist.artistId;
367 }
368
369-- (void)setAlbumId:(NSString *)albumId {
370- _album = [Album findByPrimaryKey:albumId inContext:self.managedObjectContext];
371+- (void)setAlbumId:(NSString *)_albumId {
372+ NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
373+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"Album" inManagedObjectContext:self.managedObjectContext];
374+ [fetchRequest setEntity:entity];
375+
376+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(albumId = %@)", _albumId];
377+ [fetchRequest setPredicate:predicate];
378+
379+ NSError *error;
380+ NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
381+ if (error || [results count] != 1) {
382+ abort();
383+ }
384+
385+ Artist *_artist = [results objectAtIndex:0];
386+ [self setPrimitiveValue:_artist forKey:@"album"];
387 }
388
389 - (NSString *)albumId {
390 return self.album.albumId;
391 }
392
393-- (void)setAlbumArtistId:(NSString *)albumArtistId {
394- _albumArtist = [Artist findByPrimaryKey:albumArtistId inContext:self.managedObjectContext];
395+- (void)setAlbumArtistId:(NSString *)_albumArtistId {
396+ NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
397+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:self.managedObjectContext];
398+ [fetchRequest setEntity:entity];
399+
400+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(artistId = %@)", _albumArtistId];
401+ [fetchRequest setPredicate:predicate];
402+
403+ NSError *error;
404+ NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
405+ if (error || [results count] != 1) {
406+ abort();
407+ }
408+
409+ Artist *_artist = [results objectAtIndex:0];
410+ [self setPrimitiveValue:_artist forKey:@"albumArtist"];
411 }
412
413 - (NSString *)albumArtistId {
414
415=== modified file 'Music/Models/UOModel.h'
416--- Music/Models/UOModel.h 2013-01-02 23:49:22 +0000
417+++ Music/Models/UOModel.h 2013-01-02 23:49:23 +0000
418@@ -8,10 +8,10 @@
419
420 #import <CoreData/CoreData.h>
421
422-@class RKManagedObjectMapping;
423+@class RKEntityMapping;
424
425 @interface UOModel : NSManagedObject
426 + (void)registerMapping;
427-+ (RKManagedObjectMapping *)objectMapping;
428++ (RKEntityMapping *)objectMapping;
429 + (NSString *)keyPath;
430 @end
431
432=== modified file 'Music/Models/UOModel.m'
433--- Music/Models/UOModel.m 2013-01-02 23:49:22 +0000
434+++ Music/Models/UOModel.m 2013-01-02 23:49:23 +0000
435@@ -13,13 +13,18 @@
436
437 + (void)registerMapping {
438 RKObjectManager *manager = [RKObjectManager sharedManager];
439- [manager.mappingProvider setMapping:[self objectMapping] forKeyPath:[self keyPath]];
440+
441+ RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[self objectMapping]
442+ pathPattern:nil
443+ keyPath:[self keyPath]
444+ statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
445+ [manager addResponseDescriptor:responseDescriptor];
446 }
447
448-+ (RKManagedObjectMapping *)objectMapping {
449++ (RKEntityMapping *)objectMapping {
450 RKObjectManager *manager = [RKObjectManager sharedManager];
451
452- RKManagedObjectMapping *mapping = [RKManagedObjectMapping mappingForClass:[self class] inManagedObjectStore:manager.objectStore];
453+ RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"" inManagedObjectStore:manager.managedObjectStore];
454 return mapping;
455 }
456
457
458=== added file 'Music/Utilities/UOWebServiceController.h'
459--- Music/Utilities/UOWebServiceController.h 1970-01-01 00:00:00 +0000
460+++ Music/Utilities/UOWebServiceController.h 2013-01-02 23:49:23 +0000
461@@ -0,0 +1,39 @@
462+//
463+// UOWebServiceController.h
464+// U1Music
465+//
466+// Created by Paul Hummer on 12/12/12.
467+// Copyright (c) 2012 Canonical. All rights reserved.
468+//
469+
470+#import <Foundation/Foundation.h>
471+#import <RestKit/RestKit.h>
472+
473+#define ALBUMS_LAST_UPDATED @"albumsLastUpdated"
474+#define ARTISTS_LAST_UPDATED @"artistsLastUpdated"
475+#define PLAYLISTS_LAST_UPDATED @"playlistsLastUpdated"
476+#define SONGS_LAST_UPDATED @"songsLastUpdated"
477+
478+@protocol UOWebServiceDelegate <NSObject>
479+
480+- (void)webserviceUpdateComplete;
481+- (void)webserviceUpdateError:(NSError *)error;
482+
483+@end
484+
485+@interface UOWebServiceController : NSObject
486++ (UOWebServiceController *)controller;
487+
488+- (void)updateAlbums:(id<UOWebServiceDelegate>)delegate;
489+- (void)updateAlbums:(id<UOWebServiceDelegate>)delegate clearCache:(BOOL)clearCache;
490+
491+- (void)updateArtists:(id<UOWebServiceDelegate>)delegate;
492+- (void)updateArtists:(id<UOWebServiceDelegate>)delegate clearCache:(BOOL)clearCache;
493+
494+- (void)updatePlaylists:(id<UOWebServiceDelegate>)delegate;
495+- (void)updatePlaylists:(id<UOWebServiceDelegate>)delegate clearCache:(BOOL)clearCache;
496+
497+- (void)updateSongs:(id<UOWebServiceDelegate>)delegate;
498+- (void)updateSongs:(id<UOWebServiceDelegate>)delegate clearCache:(BOOL)clearCache;
499+
500+@end
501
502=== added file 'Music/Utilities/UOWebServiceController.m'
503--- Music/Utilities/UOWebServiceController.m 1970-01-01 00:00:00 +0000
504+++ Music/Utilities/UOWebServiceController.m 2013-01-02 23:49:23 +0000
505@@ -0,0 +1,204 @@
506+//
507+// UOWebServiceController.m
508+// U1Music
509+//
510+// Created by Paul Hummer on 12/12/12.
511+// Copyright (c) 2012 Canonical. All rights reserved.
512+//
513+
514+#import "UOWebServiceController.h"
515+#import <RestKit/RestKit.h>
516+#import <RestKit/CoreData.h>
517+#import "UONetworkStatusCoordinator.h"
518+#import "UOAuthManager.h"
519+#import "AppDelegate.h"
520+
521+#import "Album.h"
522+#import "Artist.h"
523+#import "Playlist.h"
524+#import "Song.h"
525+
526+@interface UOWebServiceController ()
527+- (void)updateAlbumsWithBlock:(void(^)(void))completionBlock;
528+- (void)updateArtistsWithBlock:(void(^)(void))completionBlock;
529+- (void)updateSongsWithBlock:(void(^)(void))completionBlock;
530+@end
531+
532+@implementation UOWebServiceController
533+
534+- (id)init {
535+ self = [super init];
536+ [self initRestKit];
537+ return self;
538+}
539+
540+#pragma mark - Static methods
541+
542++ (UOWebServiceController *)controller {
543+ static UOWebServiceController *instance = nil;
544+ static dispatch_once_t once;
545+ dispatch_once(&once, ^{
546+ instance = [[UOWebServiceController alloc] init];
547+ });
548+ return instance;
549+}
550+
551+#pragma mark - Network operations
552+
553+- (void)updateAlbums:(id<UOWebServiceDelegate>)delegate clearCache:(BOOL)clearCache {
554+ if (clearCache || [self needsUpdating:ALBUMS_LAST_UPDATED]) {
555+ [self updateArtistsWithBlock:^(void) {
556+ [[RKObjectManager sharedManager] getObjectsAtPath:@"albums/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
557+ [delegate webserviceUpdateComplete];
558+ } failure:^(RKObjectRequestOperation *operation, NSError *error) {
559+ [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ALBUMS_LAST_UPDATED];
560+ [delegate webserviceUpdateError:error];
561+ }];
562+ }];
563+ }
564+}
565+
566+- (void)updateAlbums:(id<UOWebServiceDelegate>)delegate {
567+ [self updateAlbums:delegate clearCache:NO];
568+}
569+
570+- (void)updateAlbumsWithBlock:(void (^)(void))completionBlock {
571+ if ([self needsUpdating:ALBUMS_LAST_UPDATED]) {
572+ [self updateArtistsWithBlock:^(void) {
573+ [[RKObjectManager sharedManager] getObjectsAtPath:@"albums/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
574+ [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:ALBUMS_LAST_UPDATED];
575+ completionBlock();
576+ } failure:^(RKObjectRequestOperation *operation, NSError *error) {
577+ [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ALBUMS_LAST_UPDATED];
578+ }];
579+ }];
580+ } else {
581+ completionBlock();
582+ }
583+}
584+
585+- (void)updateArtists:(id<UOWebServiceDelegate>)delegate clearCache:(BOOL)clearCache {
586+ if (clearCache || [self needsUpdating:ARTISTS_LAST_UPDATED]) {
587+ [[RKObjectManager sharedManager] getObjectsAtPath:@"artists/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
588+ [delegate webserviceUpdateComplete];
589+ } failure:^(RKObjectRequestOperation *operation, NSError *error) {
590+ [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTISTS_LAST_UPDATED];
591+ [delegate webserviceUpdateError:error];
592+ }];
593+ }
594+}
595+
596+- (void)updateArtists:(id<UOWebServiceDelegate>)delegate {
597+ [self updateArtists:delegate clearCache:NO];
598+}
599+
600+- (void)updateArtistsWithBlock:(void(^)(void))completionBlock {
601+ if ([self needsUpdating:ARTISTS_LAST_UPDATED]) {
602+ [[RKObjectManager sharedManager] getObjectsAtPath:@"artists/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
603+ [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:ARTISTS_LAST_UPDATED];
604+ completionBlock();
605+ } failure:^(RKObjectRequestOperation *operation, NSError *error) {
606+ [[NSUserDefaults standardUserDefaults] setObject:nil forKey:ARTISTS_LAST_UPDATED];
607+ }];
608+ } else {
609+ completionBlock();
610+ }
611+}
612+
613+- (void)updatePlaylists:(id<UOWebServiceDelegate>)delegate clearCache:(BOOL)clearCache {
614+ if (clearCache || [self needsUpdating:PLAYLISTS_LAST_UPDATED]) {
615+ [self updateSongsWithBlock:^(void) {
616+ [[RKObjectManager sharedManager] getObjectsAtPath:@"playlists/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
617+ [delegate webserviceUpdateComplete];
618+ } failure:^(RKObjectRequestOperation *operation, NSError *error) {
619+ [[NSUserDefaults standardUserDefaults] setObject:nil forKey:PLAYLISTS_LAST_UPDATED];
620+ [delegate webserviceUpdateError:error];
621+ }];
622+ }];
623+ }
624+}
625+
626+- (void)updatePlaylists:(id<UOWebServiceDelegate>)delegate {
627+ [self updatePlaylists:delegate clearCache:NO];
628+}
629+
630+- (void)updateSongs:(id<UOWebServiceDelegate>)delegate clearCache:(BOOL)clearCache {
631+ if (clearCache || [self needsUpdating:SONGS_LAST_UPDATED]) {
632+ [self updateAlbumsWithBlock:^(void) {
633+ [[RKObjectManager sharedManager] getObjectsAtPath:@"songs/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
634+ [delegate webserviceUpdateComplete];
635+ } failure:^(RKObjectRequestOperation *operation, NSError *error) {
636+ [[NSUserDefaults standardUserDefaults] setObject:nil forKey:SONGS_LAST_UPDATED];
637+ [delegate webserviceUpdateError:error];
638+ }];
639+ }];
640+ }
641+}
642+
643+- (void)updateSongs:(id<UOWebServiceDelegate>)delegate {
644+ [self updateSongs:delegate clearCache:NO];
645+}
646+
647+- (void)updateSongsWithBlock:(void (^)(void))completionBlock {
648+ if ([self needsUpdating:SONGS_LAST_UPDATED]) {
649+ [self updateAlbumsWithBlock:^(void) {
650+ [[RKObjectManager sharedManager] getObjectsAtPath:@"songs/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
651+ [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:SONGS_LAST_UPDATED];
652+ completionBlock();
653+ } failure:^(RKObjectRequestOperation *operation, NSError *error) {
654+ [[NSUserDefaults standardUserDefaults] setObject:nil forKey:SONGS_LAST_UPDATED];
655+ }];
656+ }];
657+ } else {
658+ completionBlock();
659+ }
660+}
661+
662+#pragma mark - Private methods
663+
664+- (BOOL)needsUpdating:(NSString *)key {
665+ int interval = -(60*60*24*5); // 5 days ago
666+ NSDate *freshInterval = [NSDate dateWithTimeIntervalSinceNow:interval];
667+ NSDate *lastUpdatedAt = [[NSUserDefaults standardUserDefaults] objectForKey:key];
668+
669+ BOOL updateNeeded = (lastUpdatedAt == nil || [freshInterval compare:lastUpdatedAt] == NSOrderedDescending);
670+ if (updateNeeded) {
671+ [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:key];
672+ }
673+ return updateNeeded;
674+}
675+
676+- (void)initRestKit {
677+
678+ NSURL *baseUrl = [NSURL URLWithString:@"https://one.ubuntu.com/api/music/v2/"];
679+ RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:baseUrl];
680+
681+ /* Handle Plaintext auth */
682+ NSURLRequest *echo = [UOAuthManager oauthRequestForPath:[baseUrl path]];
683+ [objectManager.HTTPClient setDefaultHeader:@"Authorization" value:[echo valueForHTTPHeaderField:@"Authorization"]];
684+ [objectManager.HTTPClient setDefaultHeader:@"Accept-Encoding" value:[echo valueForHTTPHeaderField:@"Accept-Encoding"]];
685+ [objectManager.HTTPClient setDefaultHeader:@"User-Agent" value:[echo valueForHTTPHeaderField:@"User-Agent"]];
686+
687+ /* Show animated network I/O indicator when doing network I/O */
688+ [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
689+
690+ /* Set up managed object store */
691+ NSManagedObjectModel *managedObjectModel = [[AppDelegate delegate] managedObjectModel];
692+ RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
693+
694+ /* Why the hell is RestKit not smart enough to do this automatically? Annoy. */
695+ NSError *error;
696+ [managedObjectStore addSQLitePersistentStoreAtPath:[RKApplicationDataDirectory() stringByAppendingPathComponent:@"UOMusic.sqlite"] fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
697+ if (error) {
698+ abort();
699+ }
700+ [objectManager setManagedObjectStore:managedObjectStore];
701+ [managedObjectStore createManagedObjectContexts];
702+
703+ [Album registerMapping];
704+ [Artist registerMapping];
705+ [Playlist registerMapping];
706+ [Song registerMapping];
707+}
708+
709+@end
710
711=== modified file 'Music/View Controllers/AlbumViewController.h'
712--- Music/View Controllers/AlbumViewController.h 2012-11-18 16:01:56 +0000
713+++ Music/View Controllers/AlbumViewController.h 2013-01-02 23:49:23 +0000
714@@ -11,5 +11,5 @@
715 @class Album;
716
717 @interface AlbumViewController : UIViewController
718-@property (nonatomic, retain) Album *album;
719+@property (nonatomic, retain) NSString *albumId;
720 @end
721
722=== modified file 'Music/View Controllers/AlbumViewController.m'
723--- Music/View Controllers/AlbumViewController.m 2013-01-02 23:49:22 +0000
724+++ Music/View Controllers/AlbumViewController.m 2013-01-02 23:49:23 +0000
725@@ -11,10 +11,12 @@
726 #import <RestKit/RestKit.h>
727 #import "SongCell.h"
728 #import "Song.h"
729-#import "UONetworkStatusCoordinator.h"
730+#import "UOWebServiceController.h"
731
732-@interface AlbumViewController () <RKObjectLoaderDelegate, UITableViewDataSource> {
733+@interface AlbumViewController () <UITableViewDataSource, UOWebServiceDelegate> {
734 NSArray *tableData;
735+
736+ Album *album;
737 }
738 @property (weak, nonatomic) IBOutlet UIImageView *albumArt;
739 @property (weak, nonatomic) IBOutlet UILabel *albumTitle;
740@@ -24,18 +26,26 @@
741 @end
742
743 @implementation AlbumViewController
744+@synthesize albumId = _albumId;
745
746 - (void)viewDidLoad {
747 [self.tableView setDataSource:self];
748 }
749
750 - (void)viewWillAppear:(BOOL)animated {
751- [_albumTitle setText:_album.title];
752- //[_albumDescription setText:_album.artist];
753- tableData = [NSArray array];
754- [self.tableView reloadData];
755-
756- [self update];
757+ [self fetchAlbum];
758+
759+
760+ [_albumTitle setText:album.title];
761+ [_albumDescription setText:album.artist.name];
762+
763+ if ([album.songs count]) {
764+ NSArray *sortDescriptors = [NSArray arrayWithObjects:[[NSSortDescriptor alloc] initWithKey:@"track" ascending:NO], nil];
765+ tableData = [album.songs sortedArrayUsingDescriptors:sortDescriptors];
766+ [self.tableView reloadData];
767+ } else {
768+ [[UOWebServiceController controller] updateSongs:self];
769+ }
770 }
771
772 - (void)viewDidUnload {
773@@ -62,24 +72,41 @@
774
775 #pragma mark - Private methods
776
777-- (void)update {
778- [UONetworkStatusCoordinator addNetworkActivity];
779-
780- NSString *endpoint = [NSString stringWithFormat:@"/albums/%@/", _album.albumId];
781- [[RKObjectManager sharedManager] loadObjectsAtResourcePath:endpoint delegate:self];
782-}
783-
784-#pragma mark - RKObjectLoaderDelegate methods
785-
786-- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
787- [UONetworkStatusCoordinator removeNetworkActivity];
788+- (void)fetchAlbum {
789+ NSManagedObjectContext *managedObjectContext = [[[RKObjectManager sharedManager] managedObjectStore] persistentStoreManagedObjectContext];
790+
791+ NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
792+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"Album" inManagedObjectContext:managedObjectContext];
793+ [fetchRequest setEntity:entity];
794+
795+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(albumId = %@)", _albumId];
796+ [fetchRequest setPredicate:predicate];
797+
798+ NSError *error;
799+ NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
800+ if (error || [results count] != 1) {
801+ abort();
802+ }
803+ album = [results objectAtIndex:0];
804+}
805+
806+#pragma mark - UOWebServiceDelegate methods
807+
808+- (void)webserviceUpdateComplete {
809+ NSManagedObjectContext *managedObjectContext = [[[RKObjectManager sharedManager] managedObjectStore] persistentStoreManagedObjectContext];
810+ [managedObjectContext refreshObject:album mergeChanges:NO];
811+
812+ NSArray *sortDescriptors = [NSArray arrayWithObjects:[[NSSortDescriptor alloc] initWithKey:@"dearticlizedTitle" ascending:YES], nil];
813+ tableData = [album.songs sortedArrayUsingDescriptors:sortDescriptors];
814+
815+ [self.tableView reloadData];
816+}
817+
818+- (void)webserviceUpdateError:(NSError *)error {
819+ if ([error code] == 2) { // Network is down.
820+ return;
821+ }
822 NSLog(@"Error: %@", [error localizedDescription]);
823 }
824
825-- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
826- [UONetworkStatusCoordinator removeNetworkActivity];
827- tableData = [objects copy];
828- [self.tableView reloadData];
829-}
830-
831 @end
832
833=== modified file 'Music/View Controllers/AlbumsViewController.m'
834--- Music/View Controllers/AlbumsViewController.m 2013-01-02 23:49:22 +0000
835+++ Music/View Controllers/AlbumsViewController.m 2013-01-02 23:49:23 +0000
836@@ -12,6 +12,7 @@
837 #import "Album.h"
838 #import "AlbumCell.h"
839 #import "AlbumViewController.h"
840+#import "UOWebServiceController.h"
841
842 @interface AlbumsViewController ()
843
844@@ -21,8 +22,10 @@
845
846 #pragma mark - UOIndexedViewControllers methods
847
848-- (NSString *)endpoint {
849- return @"/albums/";
850+- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
851+ Album *album = [self.fetchedResultsController objectAtIndexPath:indexPath];
852+ AlbumCell *albumCell = (AlbumCell *)cell;
853+ [albumCell setAlbum:album];
854 }
855
856 - (NSString *)entityName {
857@@ -30,7 +33,7 @@
858 }
859
860 - (NSString *)lastUpdatedKey {
861- return @"albumsLastUpdated";
862+ return ALBUMS_LAST_UPDATED;
863 }
864
865 - (NSArray *)sortDescriptors {
866@@ -39,10 +42,8 @@
867 nil];
868 }
869
870-- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
871- Album *album = [self.fetchedResultsController objectAtIndexPath:indexPath];
872- AlbumCell *albumCell = (AlbumCell *)cell;
873- [albumCell setAlbum:album];
874+- (void)update {
875+ [[UOWebServiceController controller] updateAlbums:self];
876 }
877
878 #pragma mark - Storyboard methods
879@@ -53,7 +54,7 @@
880 NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
881 Album *album = [self.fetchedResultsController objectAtIndexPath:indexPath];
882
883- albumViewController.album = album;
884+ albumViewController.albumId = album.albumId;
885 }
886
887 @end
888
889=== modified file 'Music/View Controllers/ArtistViewController.h'
890--- Music/View Controllers/ArtistViewController.h 2012-11-18 15:47:37 +0000
891+++ Music/View Controllers/ArtistViewController.h 2013-01-02 23:49:23 +0000
892@@ -11,5 +11,5 @@
893 @class Artist;
894
895 @interface ArtistViewController : UIViewController
896-@property (nonatomic, retain) Artist *artist;
897+@property (nonatomic, retain) NSString *artistId;
898 @end
899
900=== modified file 'Music/View Controllers/ArtistViewController.m'
901--- Music/View Controllers/ArtistViewController.m 2013-01-02 23:49:22 +0000
902+++ Music/View Controllers/ArtistViewController.m 2013-01-02 23:49:23 +0000
903@@ -14,9 +14,13 @@
904 #import "Album.h"
905 #import "AlbumCell.h"
906 #import "AlbumViewController.h"
907+#import "UOWebServiceController.h"
908+#import "AppDelegate.h"
909
910-@interface ArtistViewController () <RKObjectLoaderDelegate,UITableViewDataSource> {
911+@interface ArtistViewController () <UITableViewDataSource, UOWebServiceDelegate> {
912 NSArray *tableData;
913+
914+ Artist *artist;
915 }
916 @property (weak, nonatomic) IBOutlet UIImageView *albumArt;
917 @property (weak, nonatomic) IBOutlet UILabel *artistName;
918@@ -26,20 +30,26 @@
919 @end
920
921 @implementation ArtistViewController
922-@synthesize artist = _artist;
923+@synthesize artistId = _artistId;
924
925 - (void)viewDidLoad {
926 [self.tableView setDataSource:self];
927 }
928
929 - (void)viewWillAppear:(BOOL)animated {
930- [_artistName setText:_artist.name];
931- // TODO: rockstar - fix this once we have that data again.
932- [_artistDescription setText:@"5 songs"];
933- tableData = [NSArray array];
934+ [[UOWebServiceController controller] updateAlbums:self];
935+ [self fetchArtist];
936+
937+ [_artistName setText:artist.name];
938+
939+ NSString *pluralizedNoun = @"albums";
940+ if ([artist.albums count] == 1) {
941+ pluralizedNoun = @"album";
942+ }
943+ [_artistDescription setText:[NSString stringWithFormat:@"%d %@", [artist.albums count], pluralizedNoun]];
944+ NSArray *sortDescriptors = [NSArray arrayWithObjects:[[NSSortDescriptor alloc] initWithKey:@"dearticlizedTitle" ascending:YES], nil];
945+ tableData = [artist.albums sortedArrayUsingDescriptors:sortDescriptors];
946 [self.tableView reloadData];
947-
948- [self update];
949 }
950
951 - (void)viewDidUnload {
952@@ -64,33 +74,50 @@
953 return cell;
954 }
955
956-#pragma mark - Private methods
957-
958-- (void)update {
959- [UONetworkStatusCoordinator addNetworkActivity];
960-
961- NSString *endpoint = [NSString stringWithFormat:@"/artists/%@/", _artist.artistId];
962- [[RKObjectManager sharedManager] loadObjectsAtResourcePath:endpoint delegate:self];
963-}
964-
965-#pragma mark - RKObjectLoaderDelegate methods
966-
967-- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
968- [UONetworkStatusCoordinator removeNetworkActivity];
969- NSLog(@"Error: %@", [error localizedDescription]);
970-}
971-
972-- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
973- [UONetworkStatusCoordinator removeNetworkActivity];
974- tableData = [objects copy];
975- [self.tableView reloadData];
976-}
977-
978 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
979 AlbumViewController *albumViewController = [segue destinationViewController];
980 NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
981 Album *album = [tableData objectAtIndex:indexPath.row];
982- albumViewController.album = album;
983+ albumViewController.albumId = album.albumId;
984+}
985+
986+#pragma mark - UOWebServiceDelegate methods
987+
988+- (void)webserviceUpdateComplete {
989+ NSManagedObjectContext *managedObjectContext = [[[RKObjectManager sharedManager] managedObjectStore] persistentStoreManagedObjectContext];
990+ [managedObjectContext refreshObject:artist mergeChanges:NO];
991+
992+ NSArray *sortDescriptors = [NSArray arrayWithObjects:[[NSSortDescriptor alloc] initWithKey:@"dearticlizedTitle" ascending:YES], nil];
993+ tableData = [artist.albums sortedArrayUsingDescriptors:sortDescriptors];
994+
995+ [self.tableView reloadData];
996+}
997+
998+- (void)webserviceUpdateError:(NSError *)error {
999+ if ([error code] == 2) { // Network is down.
1000+ return;
1001+ }
1002+ NSLog(@"Error: %@", [error localizedDescription]);
1003+}
1004+
1005+#pragma mark - Private methods
1006+
1007+- (void)fetchArtist {
1008+ NSManagedObjectContext *managedObjectContext = [[[RKObjectManager sharedManager] managedObjectStore] persistentStoreManagedObjectContext];
1009+
1010+ NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
1011+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
1012+ [fetchRequest setEntity:entity];
1013+
1014+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(artistId = %@)", _artistId];
1015+ [fetchRequest setPredicate:predicate];
1016+
1017+ NSError *error;
1018+ NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
1019+ if (error || [results count] != 1) {
1020+ abort();
1021+ }
1022+ artist = [results objectAtIndex:0];
1023 }
1024
1025 @end
1026
1027=== modified file 'Music/View Controllers/ArtistsViewController.m'
1028--- Music/View Controllers/ArtistsViewController.m 2013-01-02 23:49:22 +0000
1029+++ Music/View Controllers/ArtistsViewController.m 2013-01-02 23:49:23 +0000
1030@@ -15,6 +15,7 @@
1031
1032 #import "UONetworkStatusCoordinator.h"
1033 #import "ArtistViewController.h"
1034+#import "UOWebServiceController.h"
1035
1036 @interface ArtistsViewController ()
1037 @end
1038@@ -23,8 +24,11 @@
1039
1040 #pragma mark - UOIndexedViewController methods
1041
1042-- (NSString *)endpoint {
1043- return @"/artists/";
1044+- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
1045+ Artist *artist = [self.fetchedResultsController objectAtIndexPath:indexPath];
1046+ ArtistCell *artistCell = (ArtistCell *)cell;
1047+
1048+ [artistCell setArtist:artist];
1049 }
1050
1051 - (NSString *)entityName {
1052@@ -32,8 +36,7 @@
1053 }
1054
1055 - (NSString *)lastUpdatedKey {
1056-
1057- return @"artistsLastUpdated";
1058+return ARTISTS_LAST_UPDATED;
1059 }
1060
1061 - (NSArray *)sortDescriptors {
1062@@ -42,11 +45,8 @@
1063 nil];
1064 }
1065
1066-- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
1067- Artist *artist = [self.fetchedResultsController objectAtIndexPath:indexPath];
1068- ArtistCell *artistCell = (ArtistCell *)cell;
1069-
1070- [artistCell setArtist:artist];
1071+- (void)update {
1072+ [[UOWebServiceController controller] updateArtists:self];
1073 }
1074
1075 #pragma mark - Storyboard methods
1076@@ -56,7 +56,7 @@
1077 NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
1078 Artist *artist = [self.fetchedResultsController objectAtIndexPath:indexPath];
1079
1080- artistViewController.artist = artist;
1081+ artistViewController.artistId = artist.artistId;
1082 }
1083
1084 @end
1085
1086=== modified file 'Music/View Controllers/PlaylistsViewController.m'
1087--- Music/View Controllers/PlaylistsViewController.m 2013-01-02 23:49:22 +0000
1088+++ Music/View Controllers/PlaylistsViewController.m 2013-01-02 23:49:23 +0000
1089@@ -11,6 +11,7 @@
1090 #import "Playlist.h"
1091 #import "PlaylistCell.h"
1092 #import "NSString+Extras.h"
1093+#import "UOWebServiceController.h"
1094
1095 @interface PlaylistsViewController ()
1096
1097@@ -20,8 +21,10 @@
1098
1099 #pragma mark - UOIndexedViewController methods
1100
1101-- (NSString *)endpoint {
1102- return @"/playlists/";
1103+- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
1104+ PlaylistCell *playlistCell = (PlaylistCell *)cell;
1105+ Playlist *playlist = [self.fetchedResultsController objectAtIndexPath:indexPath];
1106+ [playlistCell setPlaylist:playlist];
1107 }
1108
1109 - (NSString *)entityName {
1110@@ -29,7 +32,7 @@
1111 }
1112
1113 - (NSString *)lastUpdatedKey {
1114- return @"playlistsLastUpdated";
1115+ return PLAYLISTS_LAST_UPDATED;
1116 }
1117
1118 - (NSArray *)sortDescriptors {
1119@@ -38,10 +41,8 @@
1120 nil];
1121 }
1122
1123-- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
1124- PlaylistCell *playlistCell = (PlaylistCell *)cell;
1125- Playlist *playlist = [self.fetchedResultsController objectAtIndexPath:indexPath];
1126- [playlistCell setPlaylist:playlist];
1127+- (void)update {
1128+ [[UOWebServiceController controller] updatePlaylists:self];
1129 }
1130
1131 @end
1132
1133=== modified file 'Music/View Controllers/SongsViewController.m'
1134--- Music/View Controllers/SongsViewController.m 2013-01-02 23:49:22 +0000
1135+++ Music/View Controllers/SongsViewController.m 2013-01-02 23:49:23 +0000
1136@@ -11,6 +11,7 @@
1137 #import "Song.h"
1138 #import "SongCell.h"
1139 #import "NSString+Extras.h"
1140+#import "UOWebServiceController.h"
1141
1142 @interface SongsViewController ()
1143
1144@@ -20,16 +21,12 @@
1145
1146 #pragma mark - UOIndexedViewControllers methods
1147
1148-- (NSString *)endpoint {
1149- return @"/songs/";
1150-}
1151-
1152 - (NSString *)entityName {
1153 return @"Song";
1154 }
1155
1156 - (NSString *)lastUpdatedKey {
1157- return @"songsLastUpdated";
1158+ return SONGS_LAST_UPDATED;
1159 }
1160
1161 - (NSArray *)sortDescriptors {
1162@@ -44,4 +41,8 @@
1163 [songCell setSong:song];
1164 }
1165
1166+- (void)viewWillAppear:(BOOL)animated {
1167+ [[UOWebServiceController controller] updateSongs:self];
1168+}
1169+
1170 @end
1171
1172=== modified file 'Music/View Controllers/UOIndexedViewController.h'
1173--- Music/View Controllers/UOIndexedViewController.h 2013-01-02 23:49:22 +0000
1174+++ Music/View Controllers/UOIndexedViewController.h 2013-01-02 23:49:23 +0000
1175@@ -8,8 +8,9 @@
1176
1177 #import <UIKit/UIKit.h>
1178 #import <RestKit/RestKit.h>
1179+#import "UOWebServiceController.h"
1180
1181-@interface UOIndexedViewController : UITableViewController <NSFetchedResultsControllerDelegate, RKObjectLoaderDelegate> {
1182+@interface UOIndexedViewController : UITableViewController <NSFetchedResultsControllerDelegate,UOWebServiceDelegate> {
1183 NSMutableDictionary *groupedTableData;
1184 NSArray *indexes;
1185 BOOL indexed;
1186@@ -26,5 +27,4 @@
1187
1188 - (NSFetchedResultsController *)fetchedResultsController;
1189
1190-- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error;
1191 @end
1192
1193=== modified file 'Music/View Controllers/UOIndexedViewController.m'
1194--- Music/View Controllers/UOIndexedViewController.m 2013-01-02 23:49:22 +0000
1195+++ Music/View Controllers/UOIndexedViewController.m 2013-01-02 23:49:23 +0000
1196@@ -8,11 +8,8 @@
1197
1198 #import "UOIndexedViewController.h"
1199 #import <RestKit/RestKit.h>
1200-#import "UONetworkStatusCoordinator.h"
1201 #import "AppDelegate.h"
1202
1203-#define ARTISTS_LAST_UPDATED @"artistsLastUpdateAt"
1204-
1205 @interface UOIndexedViewController () {
1206 NSFetchedResultsController *_fetchedResultsController;
1207 }
1208@@ -21,15 +18,6 @@
1209
1210 @implementation UOIndexedViewController
1211
1212-- (id)initWithStyle:(UITableViewStyle)style
1213-{
1214- self = [super initWithStyle:style];
1215- if (self) {
1216- // Custom initialization
1217- }
1218- return self;
1219-}
1220-
1221 #pragma mark - Abstract methods
1222
1223 - (NSString *)endpoint {
1224@@ -56,11 +44,7 @@
1225 return;
1226 }
1227
1228-#pragma mark - Data retrieval methods
1229-
1230 - (void)update {
1231- [UONetworkStatusCoordinator addNetworkActivity];
1232- [[RKObjectManager sharedManager] loadObjectsAtResourcePath:self.endpoint delegate:self];
1233 }
1234
1235 #pragma mark - UIViewController lifecycle
1236@@ -71,16 +55,7 @@
1237 }
1238
1239 - (void)viewWillAppear:(BOOL)animated {
1240- NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
1241- /* In general, using static time difference intervals is bad. However, a precision of "7 days ago"
1242- is probably good enough for our needs.
1243- */
1244- NSDate *oneWeekAgo = [NSDate dateWithTimeIntervalSinceNow:-604800];
1245- NSDate *lastUpdatedAt = [userDefaults objectForKey:[self lastUpdatedKey]];
1246- if (lastUpdatedAt == nil || [oneWeekAgo compare:lastUpdatedAt] == NSOrderedDescending ) {
1247- NSLog(@"Data is stale. Calling update");
1248- [self update];
1249- }
1250+ [self update];
1251 }
1252
1253 - (void)didReceiveMemoryWarning
1254@@ -117,8 +92,22 @@
1255 return [self.fetchedResultsController sectionIndexTitles];
1256 }
1257
1258+#pragma mark - UOWebServiceDelegate methods
1259+
1260+- (void)webserviceUpdateComplete {
1261+ _fetchedResultsController = nil;
1262+ [self.tableView reloadData];
1263+}
1264+
1265+- (void)webserviceUpdateError:(NSError *)error {
1266+ if ([error code] == 2) { // Network is down.
1267+ return;
1268+ }
1269+ NSLog(@"Error: %@", [error localizedDescription]);
1270+}
1271+
1272 #pragma mark - RKObjectLoaderDelegate methods
1273-
1274+/*
1275 - (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
1276 [UONetworkStatusCoordinator removeNetworkActivity];
1277 if ([error code] == 2) { // Network is down.
1278@@ -134,6 +123,7 @@
1279 [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:[self lastUpdatedKey]];
1280 [self.tableView reloadData];
1281 }
1282+ */
1283
1284 #pragma mark - Fetched results controller
1285
1286@@ -142,18 +132,12 @@
1287 return _fetchedResultsController;
1288 }
1289
1290- NSManagedObjectContext *managedObjectContext = [AppDelegate delegate].managedObjectContext;
1291-
1292- NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
1293- NSEntityDescription *entityDescription = [NSEntityDescription entityForName:[self entityName] inManagedObjectContext:managedObjectContext];
1294- [fetchRequest setEntity:entityDescription];
1295-
1296+ NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[self entityName]];
1297 [fetchRequest setFetchBatchSize:20];
1298-
1299 [fetchRequest setSortDescriptors:[self sortDescriptors]];
1300-
1301- _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
1302- managedObjectContext:[RKManagedObjectStore defaultObjectStore].managedObjectContextForCurrentThread
1303+
1304+ _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
1305+ managedObjectContext:[[AppDelegate delegate] managedObjectContext]
1306 sectionNameKeyPath:[self sectionNameKeyPath]
1307 cacheName:nil];
1308 _fetchedResultsController.delegate = self;
1309
1310=== modified file 'U1Music.xcodeproj/project.pbxproj'
1311--- U1Music.xcodeproj/project.pbxproj 2013-01-02 23:49:22 +0000
1312+++ U1Music.xcodeproj/project.pbxproj 2013-01-02 23:49:23 +0000
1313@@ -32,7 +32,6 @@
1314 52206BD01655355000A3A0A8 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 52206BCF1655354F00A3A0A8 /* SettingsViewController.m */; };
1315 52206BE61655BE8700A3A0A8 /* SettingsAuthenticationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 52206BE51655BE8700A3A0A8 /* SettingsAuthenticationViewController.m */; };
1316 52206BE91655BF4900A3A0A8 /* SettingsAboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 52206BE81655BF4900A3A0A8 /* SettingsAboutViewController.m */; };
1317- 52220B9E1672A75F0078D5DC /* RKRequest+Plaintext.m in Sources */ = {isa = PBXBuildFile; fileRef = 52220B9D1672A75F0078D5DC /* RKRequest+Plaintext.m */; };
1318 523B3CE215B5D64F004394F4 /* grabber.png in Resources */ = {isa = PBXBuildFile; fileRef = 523B3CE015B5D64F004394F4 /* grabber.png */; };
1319 523B3CE315B5D64F004394F4 /* grabber@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 523B3CE115B5D64F004394F4 /* grabber@2x.png */; };
1320 523B3CF915B73BA0004394F4 /* download-grey.png in Resources */ = {isa = PBXBuildFile; fileRef = 523B3CF515B73BA0004394F4 /* download-grey.png */; };
1321@@ -83,6 +82,7 @@
1322 52AC3D811604513E00B4785D /* uncached.png in Resources */ = {isa = PBXBuildFile; fileRef = 52AC3D651604513E00B4785D /* uncached.png */; };
1323 52AC3D821604513E00B4785D /* uncached@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 52AC3D661604513E00B4785D /* uncached@2x.png */; };
1324 52AC3D841604539000B4785D /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 52AC3D831604539000B4785D /* Default-568h@2x.png */; };
1325+ 52B7A030167986BC00243D7D /* UOWebServiceController.m in Sources */ = {isa = PBXBuildFile; fileRef = 52B7A02F167986BC00243D7D /* UOWebServiceController.m */; };
1326 536D620B1144495400DFCE56 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 536D620A1144495400DFCE56 /* SystemConfiguration.framework */; };
1327 537DE2D9113F008C00875852 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 537DE2D8113F008C00875852 /* CoreFoundation.framework */; };
1328 53F675D8113B092C00822059 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53F675D7113B092C00822059 /* MediaPlayer.framework */; };
1329@@ -152,13 +152,6 @@
1330 remoteGlobalIDString = 25160E78145651060060A5C5;
1331 remoteInfo = RestKitFrameworkTests;
1332 };
1333- 52206B9D16550AEE00A3A0A8 /* PBXContainerItemProxy */ = {
1334- isa = PBXContainerItemProxy;
1335- containerPortal = 52206B8A16550AED00A3A0A8 /* RestKit.xcodeproj */;
1336- proxyType = 2;
1337- remoteGlobalIDString = 259C301615128079003066A2;
1338- remoteInfo = RestKitResources;
1339- };
1340 528515931604F16D004A1F7C /* PBXContainerItemProxy */ = {
1341 isa = PBXContainerItemProxy;
1342 containerPortal = 5285158E1604F16B004A1F7C /* UbuntuOneAuthKit.xcodeproj */;
1343@@ -219,8 +212,6 @@
1344 52206BE51655BE8700A3A0A8 /* SettingsAuthenticationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SettingsAuthenticationViewController.m; path = "Music/View Controllers/SettingsAuthenticationViewController.m"; sourceTree = SOURCE_ROOT; };
1345 52206BE71655BF4900A3A0A8 /* SettingsAboutViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SettingsAboutViewController.h; path = "Music/View Controllers/SettingsAboutViewController.h"; sourceTree = SOURCE_ROOT; };
1346 52206BE81655BF4900A3A0A8 /* SettingsAboutViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SettingsAboutViewController.m; path = "Music/View Controllers/SettingsAboutViewController.m"; sourceTree = SOURCE_ROOT; };
1347- 52220B9C1672A75F0078D5DC /* RKRequest+Plaintext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RKRequest+Plaintext.h"; path = "Music/Categories/RKRequest+Plaintext.h"; sourceTree = SOURCE_ROOT; };
1348- 52220B9D1672A75F0078D5DC /* RKRequest+Plaintext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RKRequest+Plaintext.m"; path = "Music/Categories/RKRequest+Plaintext.m"; sourceTree = SOURCE_ROOT; };
1349 523B3CDC15B4C42F004394F4 /* SongUITableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SongUITableView.h; sourceTree = "<group>"; };
1350 523B3CDD15B4C42F004394F4 /* SongUITableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SongUITableView.m; sourceTree = "<group>"; };
1351 523B3CE015B5D64F004394F4 /* grabber.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = grabber.png; sourceTree = "<group>"; };
1352@@ -327,6 +318,8 @@
1353 52AC3D651604513E00B4785D /* uncached.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = uncached.png; sourceTree = "<group>"; };
1354 52AC3D661604513E00B4785D /* uncached@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "uncached@2x.png"; sourceTree = "<group>"; };
1355 52AC3D831604539000B4785D /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; };
1356+ 52B7A02E167986BC00243D7D /* UOWebServiceController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UOWebServiceController.h; path = Music/Utilities/UOWebServiceController.h; sourceTree = SOURCE_ROOT; };
1357+ 52B7A02F167986BC00243D7D /* UOWebServiceController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UOWebServiceController.m; path = Music/Utilities/UOWebServiceController.m; sourceTree = SOURCE_ROOT; };
1358 536D620A1144495400DFCE56 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
1359 537DE2D8113F008C00875852 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
1360 53F675D7113B092C00822059 /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
1361@@ -666,7 +659,6 @@
1362 52206B9816550AEE00A3A0A8 /* RestKitTests.octest */,
1363 52206B9A16550AEE00A3A0A8 /* RestKit.framework */,
1364 52206B9C16550AEE00A3A0A8 /* RestKitFrameworkTests.octest */,
1365- 52206B9E16550AEE00A3A0A8 /* RestKitResources.bundle */,
1366 );
1367 name = Products;
1368 sourceTree = "<group>";
1369@@ -736,6 +728,8 @@
1370 children = (
1371 5244A7FE1656A10200882601 /* UONetworkStatusCoordinator.h */,
1372 5244A7FF1656A10200882601 /* UONetworkStatusCoordinator.m */,
1373+ 52B7A02E167986BC00243D7D /* UOWebServiceController.h */,
1374+ 52B7A02F167986BC00243D7D /* UOWebServiceController.m */,
1375 );
1376 name = Utilities;
1377 sourceTree = "<group>";
1378@@ -745,8 +739,6 @@
1379 children = (
1380 5244A8031657245A00882601 /* NSString+Extras.h */,
1381 5244A8041657245A00882601 /* NSString+Extras.m */,
1382- 52220B9C1672A75F0078D5DC /* RKRequest+Plaintext.h */,
1383- 52220B9D1672A75F0078D5DC /* RKRequest+Plaintext.m */,
1384 );
1385 name = Categories;
1386 sourceTree = "<group>";
1387@@ -1378,13 +1370,6 @@
1388 remoteRef = 52206B9B16550AEE00A3A0A8 /* PBXContainerItemProxy */;
1389 sourceTree = BUILT_PRODUCTS_DIR;
1390 };
1391- 52206B9E16550AEE00A3A0A8 /* RestKitResources.bundle */ = {
1392- isa = PBXReferenceProxy;
1393- fileType = wrapper.cfbundle;
1394- path = RestKitResources.bundle;
1395- remoteRef = 52206B9D16550AEE00A3A0A8 /* PBXContainerItemProxy */;
1396- sourceTree = BUILT_PRODUCTS_DIR;
1397- };
1398 528515941604F16D004A1F7C /* UbuntuOneAuthKit.a */ = {
1399 isa = PBXReferenceProxy;
1400 fileType = archive.ar;
1401@@ -1531,7 +1516,7 @@
1402 5244A81A1657C63700882601 /* PlaylistCell.m in Sources */,
1403 5244A83A165B528800882601 /* UOMusic.xcdatamodeld in Sources */,
1404 5244A83D165C627900882601 /* UOModel.m in Sources */,
1405- 52220B9E1672A75F0078D5DC /* RKRequest+Plaintext.m in Sources */,
1406+ 52B7A030167986BC00243D7D /* UOWebServiceController.m in Sources */,
1407 );
1408 runOnlyForDeploymentPostprocessing = 0;
1409 };
1410
1411=== modified file 'U1Music_Prefix.pch'
1412--- U1Music_Prefix.pch 2012-09-25 21:28:26 +0000
1413+++ U1Music_Prefix.pch 2013-01-02 23:49:23 +0000
1414@@ -10,15 +10,9 @@
1415
1416 #ifdef __OBJC__
1417 #import <Foundation/Foundation.h>
1418+ #import <MobileCoreServices/MobileCoreServices.h>
1419+ #import <SystemConfiguration/SystemConfiguration.h>
1420 #import <UIKit/UIKit.h>
1421- #import <CoreData/CoreData.h>
1422- #import "Globals.h"
1423- #import "MOC.h"
1424- #import "NSManagedObjectContext+Additions.h"
1425- #import "NSString+Extras.h"
1426+
1427 #import "TestFlight.h"
1428- #define RELEASE_SAFELY(__obj) [__obj release], __obj = nil;
1429-
1430- #define UBUNTU_ONE_SERVICE_NAME @"com.ubuntu.one"
1431- #define UBUNTU_ONE_DUMMY_USER_NAME @"_UBUNTU_ONE_USER_NAME"
1432 #endif

Subscribers

People subscribed via source and target branches