Skip to content

Instantly share code, notes, and snippets.

@curthard89
Created January 3, 2012 11:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curthard89/1554602 to your computer and use it in GitHub Desktop.
Save curthard89/1554602 to your computer and use it in GitHub Desktop.
//
// GDataController.m
// Caffeinated
//
// Created by Curtis Hard on 03/11/2010.
// Copyright (c) 2010 GeekyGoodness. All rights reserved.
//
#import "GDataController.h"
@implementation GDataController
#define FAVICON_URL @"http://s2.googleusercontent.com/s2/favicons?domain=%@&alt=feed"
#define ARCHIVE_FOLDER [NSString stringWithFormat:@"%@",[self class]]
#define LOGGED_IN if( ! [self loggedIn] ){return;}
#define MAX_SYNC_PROCESS 20
#define MAX_USER_SYNC_NOTICE 100
@synthesize delegate, session, account, credentials, maxPreviousItems, addSubscriptionDelegate;
@synthesize maxSyncCount, maxFetchCount, continueToken, lazyLoad, lazyLoadCount, archivePath, lastChecked;
@synthesize ignoreArchivedChanges, loggedIn, emulateGoogleClient, defaultClientName, searchSubscriptionsDelegate;
@synthesize authenticating, syncing;
static GDataController * defaultController = nil;
void GDataControllerApply( Protocol * protocol, dispatch_block_t block )
{
if( [[GDataController defaultController] conformsToProtocol:protocol] )
{
dispatch_async( dispatch_get_main_queue(), block );
}
}
+ (GDataController *)defaultController
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultController = [[GDataController alloc] init];
});
return defaultController;
}
- (void)dealloc {
[self archivePreferences];
[defaultClientName release], defaultClientName = nil;
[session release], session = nil;
[requestQueue release], requestQueue = nil;
[favIconQueue release], favIconQueue = nil;
[credentials release], credentials = nil;
[archivePath release], archivePath = nil;
[account release], account = nil;
[lastChecked release], lastChecked = nil;
[itemReadUnreadQueue release], itemReadUnreadQueue = nil;
[tagQueueCounts release], tagQueueCounts = nil;
[itemRequestCounts release], itemRequestCounts = nil;
[tokenQueue release], tokenQueue = nil;
[groupArray release], groupArray = nil;
[prefs release], prefs = nil;
[processTimer invalidate];
[processTimer release], processTimer = nil;
[defaultClientName release], defaultClientName = nil;
[pageDictionary release], pageDictionary = nil;
[dateCheckDictionary release], dateCheckDictionary = nil;
[super dealloc];
}
- (id)init
{
if( ( self = [super init] ) != nil )
{
[self setMaxFetchCount:1000];
[self setMaxSyncCount:99999999]; // good luck reaching that, if you do...kudos to you
[self setMaxPreviousItems:500];
[self setLazyLoadCount:100];
itemReadUnreadQueue = [[NSMutableDictionary alloc] init];
tagQueueCounts = [[NSMutableDictionary alloc] init];
tagQueueCount = [[NSMutableDictionary alloc] init];
itemRequestCounts = [[NSMutableDictionary alloc] init];
// set up the request queue
requestQueue = [[GDataOperationQueue alloc] initWithController:self];
[requestQueue setSuspended:YES];
[requestQueue addObserver:self
forKeyPath:@"operations"
options:0
context:NULL];
[requestQueue addObserver:self
forKeyPath:@"suspended"
options:0
context:NULL];
[self addObserver:self
forKeyPath:@"authenticating"
options:0
context:NULL];
// limit to 1 item at a time, as google reader api only supports that
[requestQueue setMaxConcurrentOperationCount:1];
favIconQueue = [[NSOperationQueue alloc] init];
[favIconQueue setMaxConcurrentOperationCount:10];
// first run?
firstRun = YES;
// find last checked
lastChecked = [[NSUserDefaults standardUserDefaults] objectForKey:@"GDataControllerDateChecked"];
NSString * clientName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
clientName = [clientName stringByReplacingOccurrencesOfString:@" "
withString:@"%20"];
[self setDefaultClientName:clientName];
}
return self;
}
- (BOOL)pendingLocalChanges:(BOOL *)userNotice
{
if( [itemReadUnreadQueue count] >= MAX_USER_SYNC_NOTICE )
{
*userNotice = YES;
}
if( [itemReadUnreadQueue count] != 0 )
{
return YES;
}
NSInteger count = 0;
for( id op in [requestQueue operations] )
{
if(
[op isKindOfClass:[GDataItemEditTag class]] ||
[op isKindOfClass:[GDataSubscriptionAdd class]] ||
[op isKindOfClass:[GDataSubscriptionEditLabel class]] )
{
count++;
}
}
if( ( count * MAX_SYNC_PROCESS ) >= MAX_USER_SYNC_NOTICE )
{
*userNotice = YES;
}
return count != 0;
}
- (BOOL)hasItemsRequestInQueue
{
for( id operation in [requestQueue operations] )
{
if( [operation isKindOfClass:[GDataItems class]] )
{
return YES;
}
}
return NO;
}
- (void)archive
{
if( ! [self archivePath] )
{
return;
}
NSString * path = [NSString stringWithFormat:@"%@/%@.gr",[self archivePath],[[[self credentials] username] MD5String]];
[NSArchiver archiveRootObject:requestQueue
toFile:path];
}
- (void)reset
{
[self archivePendingChanges];
[self archivePreferences];
[prefs release], prefs = nil;
[dateCheckDictionary release], dateCheckDictionary = nil;
firstRun = YES;
requiresRefresh = NO;
[self setLoggedIn:NO];
[self setIgnoreArchivedChanges:NO];
[itemReadUnreadQueue removeAllObjects];
[session release], session = nil;
[credentials release], credentials = nil;
[account release], account = nil;
[requestQueue cancelAllOperations];
[favIconQueue cancelAllOperations];
[groupArray removeAllObjects];
[lastChecked release], lastChecked = nil;
[tagQueueCounts removeAllObjects];
[self setLastChecked:nil];
}
- (void)logout
{
if( defaultController != nil )
{
[defaultController release], defaultController = nil;
}
defaultController = [[[self class] alloc] init];
}
- (NSString *)folderIdentifierWithName:(NSString *)aName
{
return [NSString stringWithFormat:@"user/%@/label/%@",[[self account] userID],aName];
}
- (void)resumeQueues
{
LOGGED_IN
suspended = NO;
[requestQueue setSuspended:NO];
}
- (void)suspendQueues
{
LOGGED_IN
suspended = YES;
[requestQueue setSuspended:YES];
}
- (BOOL)suspended
{
return suspended;
}
- (NSString *)clientName
{
return [self emulateGoogleClient] ? @"scroll" : defaultClientName;
}
// listeners for the change in operations within the queue
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if( [keyPath isEqualToString:@"operations"] || [keyPath isEqualToString:@"authenticating"] )
{
BOOL flag = ( [[requestQueue operations] count] != 0 ) || [self authenticating];
[self setSyncing:flag];
if( [[self delegate] respondsToSelector:@selector(gDataController:isSyncing:)] )
{
[[self delegate] gDataController:self
isSyncing:flag];
}
}
}
- (void)checkItemsInQueueWithMask:(GDataItemsMask)mask
{
for( id operation in [requestQueue operations] )
{
if( [operation isKindOfClass:[GDataItems class]] )
{
return;
}
}
if( [[self delegate] respondsToSelector:@selector(gDataControllerDidFinishRequestingItems:mask:)] )
{
[[self delegate] gDataControllerDidFinishRequestingItems:self
mask:mask];
}
}
// this should be ran 1st, passing in the credentials object with
// username and password., if it fails, you will know about it
- (void)authenticateWithCredentials:(GDataCredentials *)aCredential
{
// release if already set and different
[self setAuthenticating:YES];
if( [self credentials] && [self credentials] != aCredential )
{
credentials = nil;
[credentials release];
}
// set them
[self setCredentials:aCredential];
// set up the auth
GDataAuth * auth = [[GDataAuth alloc] initWithCredentials:[self credentials]];
[auth setDelegate:self];
// request
[auth authenticate];
}
// puts a request into the queue for subscriptions
- (void)requestSubscriptions
{
LOGGED_IN
// push a subscription request in
[requestQueue addOperation:[[[GDataSubscriptions alloc] init] autorelease]];
}
- (void)requestUnreadCount:(id)sender
{
LOGGED_IN
[self processPendingChanges];
GDataUnreadCount * operation = [[[GDataUnreadCount alloc] init] autorelease];
[operation setQueuePriority:NSOperationQueuePriorityNormal];
[requestQueue addOperation:operation];
}
- (void)requestItems:(BOOL)flag
limitByDate:(BOOL)limit
{
[self requestItems:(flag?GDATA_ITEMS_TYPE_READ:GDATA_ITEMS_TYPE_UNREAD)
limitByDate:limit
startDate:nil
endDate:nil
maxCount:NSNotFound
maxFetchCount:NSNotFound];
}
- (void)setDate:(NSDate *)date
forTag:(GDataItemsTag)tag
temp:(BOOL)flag
{
NSString * type = [NSString stringWithFormat:@"%d",tag];
NSMutableDictionary * dict = nil;
if( [dateCheckDictionary objectForKey:type] != nil )
{
dict = [dateCheckDictionary objectForKey:type];
} else {
dict = [[[NSMutableDictionary alloc] init] autorelease];
}
[dict setObject:date
forKey:(flag?@"temp":@"legit")];
[dateCheckDictionary setObject:dict
forKey:type];
[prefs setObject:dateCheckDictionary
forKey:PREFS_DATE_CHECKED_KEY];
[self archivePreferences];
}
- (void)resetDates
{
[dateCheckDictionary removeAllObjects];
[prefs removeObjectForKey:PREFS_DATE_CHECKED_KEY];
[self archivePreferences];
}
- (void)pushDateForTag:(GDataItemsTag)tag
{
NSString * key = [NSString stringWithFormat:@"%d",tag];
NSMutableDictionary * dict = [dateCheckDictionary objectForKey:key];
if( dict != nil )
{
if( [dict objectForKey:@"temp"] )
{
[dict setObject:[dict objectForKey:@"temp"]
forKey:@"legit"];
[dict removeObjectForKey:@"temp"];
[dateCheckDictionary setObject:dict
forKey:key];
}
}
[self archivePreferences];
}
- (NSDate *)dateForTag:(GDataItemsTag)tag
temp:(BOOL)flag
{
return [[dateCheckDictionary objectForKey:[NSString stringWithFormat:@"%d",tag]] objectForKey:(flag?@"temp":@"legit")];;
}
- (NSInteger)timeSinceDateForTag:(GDataItemsTag)tag
toDate:(NSDate *)date;
{
NSTimeInterval time = [[self dateForTag:tag
temp:NO] timeIntervalSince1970];
NSTimeInterval newTime = [date timeIntervalSince1970];
return (NSInteger)round( newTime - time );
}
- (void)setLastChecked:(NSDate *)flag
{
if( lastChecked != nil )
{
[lastChecked release], lastChecked = nil;
}
if( flag != nil )
{
lastChecked = [flag copy];
}
}
- (NSDate *)lastChecked
{
return [[NSUserDefaults standardUserDefaults] objectForKey:@"GDataControllerDateChecked"];
}
- (void)orderItemsOperation:(GDataItems *)items
{
if( [items type] == GDATA_ITEMS_TYPE_UNREAD )
{
// push up the queue, make every read request have depency of this
[[requestQueue operations] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
if( [obj isKindOfClass:[GDataItems class]] && items != obj )
{
GDataItems * oldItems = obj;
if( [oldItems type] == GDATA_ITEMS_TYPE_READ )
{
[oldItems addDependency:items];
}
}
}];
} else if( [items type] == GDATA_ITEMS_TYPE_READ ) {
// find the last unread items request
[[requestQueue operations] enumerateObjectsWithOptions:NSEnumerationReverse
usingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
if( [obj isKindOfClass:[GDataItems class]] && items != obj )
{
GDataItems * oldItems = obj;
if( [oldItems type] == GDATA_ITEMS_TYPE_UNREAD )
{
[items addDependency:oldItems];
}
}
}];
}
}
- (void)orderProcessChangesOperation:(GDataItemEditTag *)items
{
// first we want to order it against any other edit operations in the queue
[[requestQueue operations] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
if( ! [obj isKindOfClass:[GDataItemEditTag class]] && ! [obj isExecuting] )
{
[obj addDependency:items];
}
}];
}
// puts a request in the queue for items
- (void)requestItems:(GDataItemsTag)tag
limitByDate:(BOOL)limit
startDate:(NSDate *)startDate
endDate:(NSDate *)endDate
maxCount:(NSInteger)max
maxFetchCount:(NSInteger)maxFetch
{
LOGGED_IN
[self processPendingChanges];
// push items request in
GDataItems * items = [[[GDataItems alloc] init] autorelease];
[items setFetchUID:[[NSProcessInfo processInfo] globallyUniqueString]];
[items setType:tag];
[items setQueuePriority:( tag == GDATA_ITEMS_TYPE_READ ? NSOperationQueuePriorityVeryLow : NSOperationQueuePriorityVeryHigh )];
[items setMaxCount:(maxFetch == NSNotFound?[self maxFetchCount]:maxFetch)];
[items setMaxItemCount:max];
[items setLimitByDate:limit];
[items setFirstRun:firstRun];
[items setFromDate:startDate];
[items setToDate:endDate];
NSDate * date = [NSDate date];
// is the tag unread?
if( tag == GDATA_ITEMS_TYPE_UNREAD )
{
// minus an hour, as googles craw might not of happened
NSTimeInterval time = [date timeIntervalSince1970];
time -= ( 60 * 60 ); // 1 hour
date = [NSDate dateWithTimeIntervalSince1970:time];
}
[self orderItemsOperation:items];
[self setDate:date
forTag:tag
temp:YES];
if( [[self delegate] respondsToSelector:@selector(gDataController:didRequestItems:)] )
{
[[self delegate] gDataController:self
didRequestItems:items];
}
[requestQueue addOperation:items];
// starred items will be fetched ever 20 request calls,
// or on the 0 call - so when the first request is called
if( starredInc == 20 || starredInc == 0 )
{
items = [[[GDataItems alloc] init] autorelease];
[items setFetchUID:[[NSProcessInfo processInfo] globallyUniqueString]];
[items setType:GDATA_ITEMS_TYPE_STARRED];
[items setMaxCount:NSNotFound];
[items setMaxItemCount:NSNotFound];
[items setLimitByDate:NO];
[items setQueuePriority:NSOperationQueuePriorityHigh];
[requestQueue addOperation:items];
[self setDate:[NSDate date]
forTag:GDATA_ITEMS_TYPE_STARRED
temp:YES];
if( starredInc != 0 )
{
starredInc = -1;
}
if( [[self delegate] respondsToSelector:@selector(gDataController:didRequestItems:)] )
{
[[self delegate] gDataController:self
didRequestItems:items];
}
}
starredInc++;
}
- (void)requestItemsForSubscription:(GDataSubscription *)sub
maxCount:(NSInteger)maxItem
maxFetchCount:(NSInteger)maxFetch
unread:(BOOL)flag
fromDate:(NSDate *)fromDate
allowContinuation:(BOOL)cont
{
LOGGED_IN
[self processPendingChanges];
GDataItems * operation = [[[GDataItems alloc] init] autorelease];
[operation setSubscription:sub];
[operation setType:GDATA_ITEMS_TYPE_READ];
[operation setLimitByDate:YES];
[operation setLastChecked:fromDate];
[operation setFetchUID:[[NSProcessInfo processInfo] globallyUniqueString]];
[operation setFromDate:fromDate];
[operation setMaxCount:(maxFetch == NSNotFound?[self maxFetchCount]:maxFetch)];
[operation setMaxItemCount:maxItem];
[operation setQueuePriority:NSOperationQueuePriorityNormal];
[requestQueue addOperation:operation];
}
- (NSString *)prefsFileLocation
{
NSString * path = NSUserLibraryPathWithDirectory([NSString stringWithFormat:@"%@/%@",ARCHIVE_FOLDER,[[self credentials] username]]);
NSString * file = [NSString stringWithFormat:@"%@/prefs.plist",path];
return file;
}
- (void)unarchivePreferences
{
LOGGED_IN
NSString * file = [self prefsFileLocation];
if( [[NSFileManager defaultManager] fileExistsAtPath:file] )
{
prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:file];
} else {
prefs = [[NSMutableDictionary alloc] init];
}
}
- (void)archivePreferences
{
if( prefs == nil )
{
return;
}
[prefs writeToFile:[self prefsFileLocation]
atomically:NO];
}
- (void)unarchivePendingChanges
{
LOGGED_IN
NSString * path = NSUserLibraryPathWithDirectory([NSString stringWithFormat:@"%@/%@",ARCHIVE_FOLDER,[[self credentials] username]]);
NSString * file = [NSString stringWithFormat:@"%@/changes.plist",path];
if( ! [[NSFileManager defaultManager] fileExistsAtPath:file] )
{
return;
}
NSMutableDictionary * archiveDict = [[[NSMutableDictionary alloc] initWithContentsOfFile:file] autorelease];
GGLog(@"Unarchiving %lu changes",[[archiveDict allKeys] count]);
// remove the file
NSError * error = nil;
[[NSFileManager defaultManager] removeItemAtPath:file
error:&error];
NSMutableDictionary * tempDict = [[[NSMutableDictionary alloc] init] autorelease];
// loop through each key
NSString * hashPath = NSUserLibraryPathWithDirectory([NSString stringWithFormat:@"%@/%@/hashes",ARCHIVE_FOLDER,[[self credentials] username]]);
for( NSString * key in [archiveDict allKeys] )
{
// unarchive the item
NSDictionary * dict = [archiveDict objectForKey:key];
NSString * hash = [dict objectForKey:@"item"];
// find the item
NSString * itemLoc = [NSString stringWithFormat:@"%@/%@.item",hashPath,hash];
GDataItem * item = (GDataItem *)[NSKeyedUnarchiver unarchiveObjectWithFile:itemLoc];
[tempDict setObject:item
forKey:key];
// remove the file
[[NSFileManager defaultManager] removeItemAtPath:itemLoc
error:&error];
}
// add them back in
for( NSString * key in [tempDict allKeys] )
{
[[archiveDict objectForKey:key] setObject:[tempDict objectForKey:key]
forKey:@"item"];
}
// now add them to the pending list
for( NSString * key in [archiveDict allKeys] )
{
[itemReadUnreadQueue setObject:[archiveDict objectForKey:key]
forKey:key];
}
}
- (void)archivePendingChanges
{
NSInteger count = [[itemReadUnreadQueue allKeys] count];
if( count == 0 )
{
return;
}
GGLog(@"Archiving %lu changes",count);
// archive it against the account
NSString * path = NSUserLibraryPathWithDirectory([NSString stringWithFormat:@"%@/%@",ARCHIVE_FOLDER,[[self credentials] username]]);
NSMutableDictionary * tempDict = [[[NSMutableDictionary alloc] init] autorelease];
for( NSString * key in itemReadUnreadQueue )
{
NSMutableDictionary * dict = [[[itemReadUnreadQueue objectForKey:key] mutableCopy] autorelease];
GDataItem * item = [dict objectForKey:@"item"];
NSUInteger hash = [item hash];
// replace the item with a hash
[dict setObject:[NSString stringWithFormat:@"%lu",hash]
forKey:@"item"];
// save the item into hashes
NSString * hashPath = NSUserLibraryPathWithDirectory([NSString stringWithFormat:@"%@/%@/hashes",ARCHIVE_FOLDER,[[self credentials] username]]);
[NSKeyedArchiver archiveRootObject:item
toFile:[NSString stringWithFormat:@"%@/%lu.item",hashPath,hash]];
[tempDict setObject:dict
forKey:key];
}
for( NSString * key in [tempDict allKeys] )
{
[itemReadUnreadQueue setObject:[tempDict objectForKey:key]
forKey:key];
}
path = [NSString stringWithFormat:@"%@/changes.plist",path];
[itemReadUnreadQueue writeToFile:path
atomically:YES];
}
- (void)processPendingChanges
{
LOGGED_IN
if( processTimer != nil )
{
if( [processTimer isValid] )
{
[processTimer invalidate];
}
[processTimer release], processTimer = nil;
}
if( [[itemReadUnreadQueue allKeys] count] == 0 )
{
return;
}
syncStartTime = [[NSDate date] timeIntervalSince1970];
NSString * queueIdentifier = [[NSProcessInfo processInfo] globallyUniqueString];
[tagQueueCounts setObject:[NSNumber numberWithInteger:0]
forKey:queueIdentifier];
[tagQueueCount setObject:[NSNumber numberWithInteger:0]
forKey:queueIdentifier];
NSMutableDictionary * items = [[[NSMutableDictionary alloc] init] autorelease];
// sort them
for( NSDictionary * dict in [itemReadUnreadQueue allValues] )
{
GDataItem * item = [dict objectForKey:@"item"];
NSMutableArray * array = nil;
if( ( array = [items objectForKey:[item identifier]] ) == nil )
{
array = [[[NSMutableArray alloc] init] autorelease];
[items setObject:array
forKey:[item identifier]];
}
[array addObject:dict];
}
NSMutableArray * newItems = [[[NSMutableArray alloc] init] autorelease];
// sort each group
NSSortDescriptor * sort = [[[NSSortDescriptor alloc] initWithKey:@"microtime"
ascending:YES] autorelease];
for( NSMutableArray * arr in [items allValues] )
{
NSArray * newArray = [arr sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
[newItems addObject:[newArray lastObject]];
}
GGLog(@"Found %lu changes, processing...",[newItems count]);
// do the deed
NSMutableDictionary * tags = [[[NSMutableDictionary alloc] init] autorelease];
for( NSDictionary * dict in newItems )
{
BOOL add = [[dict objectForKey:@"add"] boolValue];
NSString * tag = [[dict objectForKey:@"tag"] stringValue];
GDataItem * item = [dict objectForKey:@"item"];
if( ! [tags objectForKey:tag] )
{
[tags setObject:[[[NSMutableDictionary alloc] init] autorelease]
forKey:tag];
}
if( ! [[tags objectForKey:tag] objectForKey:@"add"] )
{
[[tags objectForKey:tag] setObject:[[[NSMutableArray alloc] init] autorelease]
forKey:@"add"];
}
if( ! [[tags objectForKey:tag] objectForKey:@"remove"] )
{
[[tags objectForKey:tag] setObject:[[[NSMutableArray alloc] init] autorelease]
forKey:@"remove"];
}
[[[tags objectForKey:tag] objectForKey:( add ? @"add" : @"remove" )] addObject:item];
}
[itemReadUnreadQueue removeAllObjects];
for( NSString * key in [tags allKeys] )
{
[self batchEditTagWithItems:[[tags objectForKey:key] objectForKey:@"add"]
tag:[key intValue]
add:YES
queueIdentifier:queueIdentifier];
[self batchEditTagWithItems:[[tags objectForKey:key] objectForKey:@"remove"]
tag:[key intValue]
add:NO
queueIdentifier:queueIdentifier];
}
}
- (void)batchEditTagWithItems:(NSArray *)array
tag:(GDataItemsTag)tag
add:(BOOL)flag
queueIdentifier:(NSString *)queueIdentifier
{
NSInteger count = [array count];
if( count == 0 )
{
return;
}
NSInteger max = MAX_SYNC_PROCESS;
NSInteger i = 0;
NSInteger tempI = 0;
NSMutableArray * arg = [[[NSMutableArray alloc] init] autorelease];
for( GDataItem * item in array )
{
[arg addObject:item];
if( tempI == ( max - 1 ) || i == ( count - 1 ) )
{
GDataItemEditTag * operation = [[[GDataItemEditTag alloc] init] autorelease];
[operation setItems:arg];
[operation setAdd:flag];
[operation setTag:tag];
[operation setQueueIdentifier:queueIdentifier];
[operation setQueuePriority:NSOperationQueuePriorityVeryHigh];
[requestQueue addOperation:operation];
[self orderProcessChangesOperation:operation];
GGLog(@"Batching items sent with count %lu",[arg count]);
arg = [[[NSMutableArray alloc] init] autorelease];
tempI = -1;
NSInteger daCount = [[tagQueueCounts objectForKey:queueIdentifier] integerValue];
daCount++;
[tagQueueCounts setObject:[NSNumber numberWithInteger:daCount]
forKey:queueIdentifier];
[tagQueueCount setObject:[NSNumber numberWithInteger:daCount]
forKey:queueIdentifier];
}
i++;
tempI++;
}
}
- (void)queueItem:(GDataItem *)item
tag:(GDataItemsTag)tag
add:(BOOL)flag
{
LOGGED_IN
if( processTimer != nil )
{
if( [processTimer isValid] )
{
[processTimer invalidate];
}
[processTimer release], processTimer = nil;
}
NSNumber * micro = [NSNumber numberWithDouble:[NSDate microtime]];
[itemReadUnreadQueue setObject:[NSDictionary dictionaryWithObjectsAndKeys:item,@"item",[NSNumber numberWithBool:flag],@"add",[NSNumber numberWithInt:tag],@"tag",micro,@"microtime", nil]
forKey:[item identifier]];
processTimer = [[NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:@selector(processPendingChanges)
userInfo:nil
repeats:NO] retain];
}
// marks item as unread
- (void)markItemAsUnread:(GDataItem *)item
{
[self queueItem:item
tag:GDATA_ITEM_TAG_READ
add:NO];
}
// marks an item as read, add to a queue then gets synced next time the
// items are requested
- (void)markItemAsRead:(GDataItem *)item
{
[self queueItem:item
tag:GDATA_ITEM_TAG_READ
add:YES];
}
// stars an item
- (void)starItem:(GDataItem *)item
{
[self queueItem:item
tag:GDATA_ITEM_TAG_STARRED
add:YES];
}
// removes the star flag against an item
- (void)unstarItem:(GDataItem *)item
{
[self queueItem:item
tag:GDATA_ITEM_TAG_STARRED
add:NO];
}
// edit tag on an item
- (void)addTag:(GDataItemsTag)tag
toItem:(GDataItem *)item
{
LOGGED_IN
GDataItemEditTag * operation = [[[GDataItemEditTag alloc] init] autorelease];
[operation setItem:item];
[operation setAdd:YES];
[operation setRemove:NO];
[operation setTag:tag];
[operation setQueuePriority:NSOperationQueuePriorityVeryHigh];
[requestQueue addOperation:operation];
}
- (void)removeTag:(GDataItemsTag)tag
fromItem:(GDataItem *)item
{
LOGGED_IN
GDataItemEditTag * operation = [[[GDataItemEditTag alloc] init] autorelease];
[operation setItem:item];
[operation setAdd:NO];
[operation setRemove:YES];
[operation setTag:tag];
[operation setQueuePriority:NSOperationQueuePriorityVeryHigh];
[requestQueue addOperation:operation];
}
// add subscription to folder
- (void)addSubscription:(GDataSubscription *)subscription
toFolder:(GDataFolder *)folder
{
LOGGED_IN
[self processPendingChanges];
GDataSubscriptionEditLabel * operation = [[[GDataSubscriptionEditLabel alloc] init] autorelease];
[operation setSubscription:subscription];
[operation setFolder:folder];
[operation setAdd:YES];
[operation setRemove:NO];
[operation setQueuePriority:NSOperationQueuePriorityVeryHigh];
[requestQueue addOperation:operation];
}
// remove subscription from folder
- (void)removeSubscription:(GDataSubscription *)subscription
fromFolder:(GDataFolder *)folder
{
LOGGED_IN
[self processPendingChanges];
GDataSubscriptionEditLabel * operation = [[[GDataSubscriptionEditLabel alloc] init] autorelease];
[operation setSubscription:subscription];
[operation setFolder:folder];
[operation setAdd:NO];
[operation setRemove:YES];
[operation setQueuePriority:NSOperationQueuePriorityHigh];
[requestQueue addOperation:operation];
}
// subscribe to a feed
- (void)subscribeToURLWithString:(NSString *)URL
{
}
// unsubscribe from a feed
- (void)unsubscribe:(GDataSubscription *)subscription
{
LOGGED_IN
[self processPendingChanges];
GDataSubscriptionEditLabel * operation = [[[GDataSubscriptionEditLabel alloc] init] autorelease];
[operation setSubscription:subscription];
[operation setUnsubscribe:YES];
[operation setQueuePriority:NSOperationQueuePriorityVeryHigh];
[requestQueue addOperation:operation];
}
- (void)requestReadItemsForSubscription:(GDataSubscription *)sub
{
LOGGED_IN
[self processPendingChanges];
GDataItems * operation = [[[GDataItems alloc] init] autorelease];
[operation setSubscription:sub];
[operation setMaxCount:[self maxFetchCount]];
[operation setType:GDATA_ITEMS_TYPE_READ];
[operation setFetchUID:[[NSProcessInfo processInfo] globallyUniqueString]];
[operation setQueuePriority:NSOperationQueuePriorityNormal];
[self orderItemsOperation:operation];
[requestQueue addOperation:operation];
}
- (void)requestItemsForSubscription:(GDataSubscription *)sub
{
LOGGED_IN
[self processPendingChanges];
GDataItems * operation = [[[GDataItems alloc] init] autorelease];
[operation setSubscription:sub];
[operation setMaxCount:[self maxFetchCount]];
[operation setType:GDATA_ITEMS_TYPE_UNREAD];
[operation setFetchUID:[[NSProcessInfo processInfo] globallyUniqueString]];
[operation setQueuePriority:NSOperationQueuePriorityNormal];
[self orderItemsOperation:operation];
[requestQueue addOperation:operation];
}
// add a feed
- (void)addSubscriptionWithURLString:(NSString *)URLString
{
LOGGED_IN
[self processPendingChanges];
GDataSubscriptionAdd * operation = [[[GDataSubscriptionAdd alloc] initWithURLString:URLString] autorelease];
[operation setQueuePriority:NSOperationQueuePriorityVeryHigh];
[operation setDelegate:[self addSubscriptionDelegate]];
[requestQueue addOperation:operation];
}
// pauses the current operations queue
- (void)pauseRequestQueue
{
[requestQueue setSuspended:YES];
}
// continues the request queue
- (void)continueRequestQueue
{
LOGGED_IN
[requestQueue setSuspended:NO];
}
// removes all objects from the request queue
- (void)clearRequestQueue
{
[requestQueue cancelAllOperations];
}
// request ids until a certain date
- (void)requestReadItemsFromNowToDate:(NSDate *)date
{
[self requestReadItemsFromNowToDate:date
useIds:YES];
}
- (void)requestIdentifiersFromNowToDate:(NSDate *)date
type:(int)type
{
LOGGED_IN;
[self processPendingChanges];
GDataItemIdentifiers * identifiers = [[[GDataItemIdentifiers alloc] initWithDate:date] autorelease];
[identifiers setQueuePriority:NSOperationQueuePriorityHigh];
[identifiers setType:type];
[requestQueue addOperation:identifiers];
}
- (void)requestReadItemsFromNowToDate:(NSDate *)date
useIds:(BOOL)flag
{
LOGGED_IN
[self processPendingChanges];
if( flag )
{
GDataItemIdentifiers * identifiers = [[[GDataItemIdentifiers alloc] initWithDate:date] autorelease];
[identifiers setQueuePriority:NSOperationQueuePriorityHigh];
[identifiers setType:GDATA_ITEMS_TYPE_READ];
[requestQueue addOperation:identifiers];
return;
}
GDataItems * items = [[[GDataItems alloc] init] autorelease];
[items setType:GDATA_ITEMS_TYPE_READ];
[items setLimitByDate:YES];
[items setToDate:date];
[requestQueue addOperation:items];
}
// refresh favicons
- (void)refreshFavicons:(NSArray *)subscriptions
force:(BOOL)flag
{
LOGGED_IN
for( GDataSubscription * subscription in subscriptions )
{
if( ! flag && [[self delegate] respondsToSelector:@selector(gDataController:checkFaviconExistanceForSubscription:)] )
{
if( [[self delegate] gDataController:self
checkFaviconExistanceForSubscription:subscription] )
{
continue;
}
}
[favIconQueue addOperationWithBlock:^(void){
NSString * url = [[subscription identifier] stringByReplacingOccurrencesOfString:@"feed/"
withString:@""];
if( [subscription htmlURL] )
{
url = [subscription htmlURL];
}
url = [[NSURL URLWithString:url] host];
// check the link href
NSError * error;
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@",url]]];
NSString * html = nil;
html = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
if( [html length] )
{
NSXMLDocument * DOM = nil;
DOM = [[[NSXMLDocument alloc] initWithXMLString:html
options:NSXMLDocumentXHTMLKind|NSXMLDocumentTidyHTML
error:&error] autorelease];
if( DOM == nil || DOM == NULL )
{
return;
}
NSArray * nodes = [[DOM rootElement] nodesForXPath:@"head/link[contains(@rel,'icon')]"
error:&error];
if( [nodes count] )
{
NSString * href = [[[nodes lastObject] attributeForName:@"href"] stringValue];
if( [href rangeOfString:@"http"].location != NSNotFound )
{
url = href;
} else {
url = [NSString stringWithFormat:@"http://%@%@",url,href];
}
} else {
url = [NSString stringWithFormat:@"http://%@/favicon.ico",url];
}
} else {
url = [NSString stringWithFormat:FAVICON_URL,url];
}
NSData * blob = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
dispatch_async( dispatch_get_main_queue(), ^{
NSData * returnData = blob;
if( ! [blob length] )
{
returnData = nil;
}
// tell the delegate we receieved the data
if( [[self delegate] conformsToProtocol:@protocol(GDataControllerDelegate)] )
{
[[self delegate] gDataController:self
didReceieveFaviconData:returnData
forSubscription:subscription];
}
});
}];
}
}
- (void)searchForSubscritionsWithTerm:(NSString *)term
maxResults:(NSInteger)results
{
NSInteger pages = ceil( results / 10 );
for( NSInteger i = 0; i < pages; i++ )
{
[self searchForSubscritionsWithTerm:term
page:i];
}
}
- (void)searchForSubscritionsWithTerm:(NSString *)term
page:(NSInteger)page
{
GDataSearchSubscriptions * operation = [[[GDataSearchSubscriptions alloc] initWithSearchTerm:term
page:page] autorelease];
[operation setQueuePriority:NSOperationQueuePriorityHigh];
[requestQueue addOperation:operation];
}
- (void)cancelAllSearchSubscriptionsRequests
{
for( id operation in [requestQueue operations] )
{
if( [operation isKindOfClass:[GDataSearchSubscriptions class]] )
{
[operation cancel];
}
}
}
#pragma mark GDataSearchSubscriptions
- (void)gDataSearchSubscriptions:(GDataSearchSubscriptions *)operation
didFindSubscriptions:(NSArray *)subscriptions
term:(NSString *)term
page:(NSInteger)page
continuation:(BOOL)flag
{
if( [subscriptions count] == 0 && ! flag )
{
return;
} else if( [subscriptions count] == 0 && flag ) {
GDataSearchSubscriptions * newSub = [[operation copy] autorelease];
[newSub setPage:(page+1)];
[requestQueue addOperation:newSub];
}
if( [[self searchSubscriptionsDelegate] conformsToProtocol:@protocol(GDataSearchSubscriptionsDelegate)] )
{
[[self searchSubscriptionsDelegate] gDataController:self
didFindSubscriptions:subscriptions
searchTerm:term
page:page
continuation:flag];
}
}
#pragma mark GDataItemEditTagDelegate
- (void)gDataItemDidEditTag:(int)tag
operation:(GDataItemEditTag *)operation
queueIdentifier:(NSString *)queueIdentifier
{
NSInteger num = [[tagQueueCounts objectForKey:queueIdentifier] integerValue];
num--;
if( num == 0 )
{
syncEndTime = [[NSDate date] timeIntervalSince1970];
[tagQueueCounts removeObjectForKey:queueIdentifier];
if( [[self delegate] respondsToSelector:@selector(gDataControllerDidSyncLocalChanges:)] )
{
[[self delegate] gDataControllerDidSyncLocalChanges:self];
}
} else {
[tagQueueCounts setObject:[NSNumber numberWithInteger:num]
forKey:queueIdentifier];
}
}
#pragma mark GDataUnreadCountDelegate
- (void)gDataUnreadCount:(GDataUnreadCount *)obj
didReceiveUnreadCounts:(NSMutableDictionary *)counts
{
NSInteger count = 0;
for( NSNumber * number in [counts allValues] )
{
count += [number integerValue];
}
float f = ((float)count / (float)[self maxFetchCount]);
count = ceilf( f );
unreadPageCount = count;
if( [[self delegate] respondsToSelector:@selector(gDataController:didReceivePageCount:)] )
{
[[self delegate] gDataController:self
didReceivePageCount:count];
}
if( [[self delegate] respondsToSelector:@selector(gDataController:didReceiveUnreadCounts:)] )
{
[[self delegate] gDataController:self
didReceiveUnreadCounts:counts];
}
}
#pragma mark GDataOperationDelegate
// if net connection is lost
- (void)gDataOperationDidLoseConnection:(GDataOperation *)operation
{
}
// if an operation fails, its most likely to a token expired
// if it does expire, grab a new one and add the operation
// back into the queue
- (void)gDataOperationFailed:(GDataOperation *)operation
requestToken:(BOOL)flag
{
if( flag )
{
// add to our failed queue
GGLog(@"Token request from class %@",[operation class]);
[self authenticateWithCredentials:[self credentials]];
[requestQueue setSuspended:YES];
[requestQueue addOperation:[[operation copy] autorelease]];
} else {
// assume no net connection
BOOL shouldSuspend = YES;
GGLog(@"Lost net connection...?");
if( [[self delegate] respondsToSelector:@selector(gDataControllerShouldSuspendQueues:)] )
{
shouldSuspend = [[self delegate] gDataControllerShouldSuspendQueues:self];
}
// suspend the queues
if( shouldSuspend )
{
[requestQueue setSuspended:YES];
[requestQueue addOperation:[[operation copy] autorelease]];
}
}
}
#pragma mark GDataAuthDelegate
- (void)gDataAuthDidAuthenticate:(GDataAuth *)auth
account:(GDataAccount *)anAccount
{
[self setLoggedIn:YES];
if( firstRun && ! [self ignoreArchivedChanges] )
{
[self unarchivePreferences];
dateCheckDictionary = [[prefs objectForKey:PREFS_DATE_CHECKED_KEY] mutableCopy];
if( ! dateCheckDictionary )
{
dateCheckDictionary = [[NSMutableDictionary alloc] init];
}
[self unarchivePendingChanges];
}
// save account info
[self setAccount:anAccount];
// save the session
[self setSession:[auth session]];
// any left over failed queues?
[requestQueue setSuspended:NO];
[auth release];
[self setAuthenticating:NO];
if( [[self delegate] conformsToProtocol:@protocol(GDataControllerDelegate)] )
{
// tell delegate were good to go
[[self delegate] gDataControllerDidAuthenticate:self];
}
}
- (void)gDataAuthDidFailToAuthenticate:(GDataAuth *)auth
{
if( [[self delegate] conformsToProtocol:@protocol(GDataControllerDelegate)] )
{
[[self delegate] gDataControllerDidFailToAuthenticate:self];
}
}
#pragma mark GDataSubscriptionsEditDelegate
- (void)gDataSubscriptionEdit:(GDataSubscriptionEditLabel *)edit
didRemoveSubscription:(GDataSubscription *)subscription
{
if( [[self delegate] conformsToProtocol:@protocol(GDataControllerDelegate)] )
{
[[self delegate] gDataController:self
didRemoveSubscription:subscription];
}
}
#pragma mark GDataItemsDelegate
- (void)gDataItems:(GDataItems *)items
didReceiveEntryCountBeforeParse:(NSUInteger)count
{
if( [[self delegate] conformsToProtocol:@protocol(GDataControllerDelegate)] )
{
[[self delegate] gDataController:self
didReceiveEntryCountBeforeParse:count];
}
}
- (void)gDataItems:(GDataItems *)itemsOperation
requiredContinuationWithString:(NSString *)string
{
if( [itemsOperation itemsCount] == 0 )
{
return;
}
if( [itemsOperation maxItemCount] != NSNotFound )
{
NSInteger curCount = 0;
if( [[itemRequestCounts allKeys] indexOfObject:[itemsOperation fetchUID]] != NSNotFound )
{
curCount = [[itemRequestCounts objectForKey:[itemsOperation fetchUID]] integerValue];
}
curCount += [itemsOperation itemsCount];
if( curCount >= [itemsOperation maxItemCount] )
{
[itemsOperation setStop:YES];
[itemRequestCounts removeObjectForKey:[itemsOperation fetchUID]];
return;
} else {
[itemRequestCounts setObject:[NSNumber numberWithInteger:curCount]
forKey:[itemsOperation fetchUID]];
}
}
currentSyncCount += [self maxFetchCount];
GDataItems * items = [[itemsOperation copy] autorelease];
[itemsOperation setQueuePriority:([itemsOperation type]==GDATA_ITEMS_TYPE_UNREAD?NSOperationQueuePriorityVeryHigh:NSOperationQueuePriorityVeryLow)];
[items setContinueToken:string];
[self orderItemsOperation:items];
[requestQueue addOperation:items];
}
- (void)GDataItemIdentifiers:(GDataItemIdentifiers *)ident
didReceieveIdentifiers:(NSArray *)identifiers
type:(int)type
{
if( [[self delegate] conformsToProtocol:@protocol(GDataControllerDelegate)] )
{
[[self delegate] gDataController:self
didReceiveItemIdentifiers:identifiers
toDate:[[[ident date] copy] autorelease]
type:type];
}
}
- (void)gDataItems:(GDataItems *)items
didReceiveList:(NSArray *)list
type:(GDataItemsTag)type
{
if( firstRun )
{
firstRun = NO;
}
if( ! [items continueToken] || [self lazyLoad] )
{
currentSyncCount = 0;
}
if( ( [items continueToken] == nil || [items stop] ) && [[self delegate] respondsToSelector:@selector(gDataController:didFinishReceivingItemsOfType:)] )
{
[self pushDateForTag:type];
[[self delegate] gDataController:self
didFinishReceivingItemsOfType:type];
}
if( [[self delegate] conformsToProtocol:@protocol(GDataControllerDelegate)] )
{
[[self delegate] gDataController:self
didReceieveItemsList:list
type:type
continued:( [items continueToken] ? YES : NO )];
}
isRefreshingItems = NO;
GDataItemsMask mask = GDataUnreadItems;
switch( [items type] )
{
case GDATA_ITEMS_TYPE_UNREAD:
mask = GDataUnreadItems;
break;
case GDATA_ITEMS_TYPE_READ:
mask = GDataReadItems;
break;
case GDATA_ITEMS_TYPE_STARRED:
mask = GDataStarredItems;
break;
}
[self checkItemsInQueueWithMask:mask];
}
#pragma mark GDataSubscriptionsDelegate
- (void)gDataSubscriptions:(GDataSubscriptions *)subscriptions
didReceiveError:(GDataError *)anError
{
}
- (void)gDataSubscriptions:(GDataSubscriptions *)subscriptions
didReceiveList:(NSArray *)array
{
[self refreshFavicons:array
force:NO];
if( [[self delegate] conformsToProtocol:@protocol(GDataControllerDelegate)] )
{
[[self delegate] gDataController:self
didReceieveSubscriptionList:array];
}
}
#pragma mark GDataSubscriptionsAddDelegate
- (void)gDataSubscriptionsAdd:(GDataSubscriptionAdd *)sub
didAddSubscription:(GDataSubscription *)subscription
delegate:(id)del
{
if( [del respondsToSelector:@selector(gDataController:didSubscribeToSubscription:)] )
{
[del gDataController:self
didSubscribeToSubscription:subscription];
[self refreshFavicons:[NSArray arrayWithObject:subscription]
force:YES];
}
}
- (void)gDataSubscriptionsAdd:(GDataSubscriptionAdd *)sub
didFailToAddSubscriptionWithURLString:(NSString *)urlString
delegate:(id)del
{
[del gDataController:self
didFailToSubscribeToURLWithString:urlString];
}
- (void)gDataItems:(GDataItems *)items
didFindImageLocations:(NSArray *)array
{
if( [[self delegate] respondsToSelector:@selector(gDataController:didFindImageLocations:)] )
{
[[self delegate] gDataController:self
didFindImageLocations:array];
}
}
- (NSInteger)countForItemsType:(GDataItemsTag)type
{
if( [[self delegate] respondsToSelector:@selector(gDataController:countForItemsType:)] )
{
return [[self delegate] gDataController:self
countForItemsType:type];
}
return -1;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment