From 9d52266f823daf5cb83e3eebe1b9f57594bc9d4a Mon Sep 17 00:00:00 2001 From: matt handler Date: Wed, 20 Apr 2011 14:39:20 -0400 Subject: first commit --- Classes/HJMOHandler.m | 381 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 381 insertions(+) create mode 100755 Classes/HJMOHandler.m (limited to 'Classes/HJMOHandler.m') diff --git a/Classes/HJMOHandler.m b/Classes/HJMOHandler.m new file mode 100755 index 0000000..fb8643e --- /dev/null +++ b/Classes/HJMOHandler.m @@ -0,0 +1,381 @@ +// +// HJManagedState.m +// hjlib +// +// Copyright Hunter and Johnson 2009, 2010, 2011 +// HJCache may be used freely in any iOS or Mac application free or commercial. +// May be redistributed as source code only if all the original files are included. +// See http://www.markj.net/hjcache-iphone-image-cache/ + +#import "HJMOHandler.h" +#import "HJMOUser.h" +#import "HJObjManager.h" +#import "HJMOFileCache.h" + + +@implementation HJMOHandler + +@synthesize state; +@synthesize oid; +@synthesize url; +@synthesize urlConn; +@synthesize moData; +@synthesize moReadyDataFilename; +@synthesize moLoadingDataFile; +@synthesize managedObj; +@synthesize objManager; +@synthesize ownPolicy; + + +-(HJMOHandler*)initWithOid:(id)oid_ url:(NSURL*)url_ objManager:objManager_{ + [super init]; + state = stateNew; + self.oid = oid_; + self.url = url_ ; + self.objManager = objManager_; + if (oid==nil) { + self.oid = url_; + } + + users = [[HJWeakMutableArray alloc] initWithCapacity:1]; //it can expand automatically. + return self; +} + +-(void)dealloc { + //NSLog(@"dealloc %@",self); + [urlConn cancel]; + [self clearLoadingState]; + [users release]; + [url release]; + [moReadyDataFilename release]; + //NSLog(@"managed Obj retain count before handler dealloc %i",[managedObj retainCount]); + [managedObj release]; + [ownPolicy release]; + [oid release]; + [super dealloc]; +} + + +-(BOOL) isEqual:(id)object { + if (![object isKindOfClass:[HJMOHandler class]]) { + return NO; + } + return [oid isEqual:[(HJMOHandler*)object oid]]; +} + +-(HJMOPolicy*)policy { + if (ownPolicy) { + return ownPolicy; + } else { + return [objManager policy]; + } +} + +-(void)addUser:(id)user { + //check if already managing for this user (should not be if being used right) + if (nil==[users findObject:user]) { + [users addObject:user]; //did not already have user, so remember it (with a weak reference) + } else { + //can happen if users reused, and recycling code is lazy clearing old state, eg with UITableCellView + //NSLog(@"HJMOHandler was already managing for user"); + } + + if (user.moHandler==nil) { + //this is the normal case, so set the state + user.moHandler=self; + } else { + if (user.moHandler==self) { + //this is not what we expect, addUser has been called twice, but thats OK - do nothing + } else { + //user was pointing to another handler, this can happen when user is reused. + //have to make sure that the old handler knows it should no longer manage for this user + //otherwise it might send it callbacks, and it won't be able to become inactive (have no users) + [user.moHandler removeUser:user]; + //now can assign the current state, which will also release the old one. + user.moHandler=self; + } + } +} + +-(void) deleteFileIfExistsAtPath:(NSString*)path { + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + NSError* e = nil; + [[NSFileManager defaultManager] removeItemAtPath:path error:&e]; + if (e) { + NSLog(@"HJMOHandler error deleting file %@",path); + } + } +} + +-(void)clearLoadingState { + self.urlConn=nil; + [moLoadingDataFile closeFile]; + self.moLoadingDataFile = nil; + self.moData = nil; +} + +-(void)cancelLoading { + if (state==stateLoading) { + [urlConn cancel]; + [self clearLoadingState]; + state=stateNew; + } +} + +-(void)becameNotInUse { + //TODO is there more policy decisions here? + //[self cancelLoading]; //don't cancel loading, do that in dealloc. because object manager might be holding on to + //this handler in loadingHandlers to keep loading going +} + +-(void)removeUser:(id)user { + [users removeObject:user]; + [self retain]; //because the next line could dealloc self. + user.moHandler = nil; + if (![self isInUse]) { + [self becameNotInUse]; + } + [self autorelease]; +} + + + +-(BOOL)isInUse { + return [users count]>0; +} + +-(BOOL)isLoading { + return urlConn!=nil; +} + +-(BOOL)isReady { + return managedObj!=nil; +} + +-(void)touchFile:(NSString*)path { + HJMOFileCache* fileCache = objManager.fileCache; + if (fileCache==nil) { return; } + NSTimeInterval ageLimit = fileCache.fileAgeLimit; + if (ageLimit<=0) { return; } + + NSFileManager* fileMan = [NSFileManager defaultManager]; + + NSError* e; + NSDictionary* fsAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&e]; + double ageSeconds = -1* [[fsAttributes fileModificationDate] timeIntervalSinceNow]; + + if (ageSeconds>(ageLimit/4)) { + //to save writes, file age modification date isn't changed on every access, only if 1/4 of age limit old. + NSString *keyArray[1] = {NSFileModificationDate}; + id objectArray[1] = {[NSDate dateWithTimeIntervalSinceNow:0]}; + NSDictionary* attributes = [NSDictionary dictionaryWithObjects:objectArray forKeys:keyArray count:1]; + NSError* err; + [fileMan setAttributes:attributes ofItemAtPath:path error:&err]; + } +} + +-(void)activateNewHandlerForUser:(id)user { + HJMOFileCache* fileCache = objManager.fileCache; + if (fileCache) { + //File caching is in use + + NSString* readyFile = [fileCache readyFilePathForOid:oid]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:readyFile]) { + //NSLog(@"HJCache loading from fileCache"); + //mo is loaded as a file in file cache + self.moReadyDataFilename = readyFile; + if (self.policy.readsUpdateFileDate) { + [self touchFile:readyFile]; + } + state = stateLoaded; + [self goFromLoadedToReady]; + if (state == stateReady || state==stateLoaded) { + [objManager addHandlerToMemCache:self]; + } + return; + + } else { + //not loaded yet, so load to file because file cache in use + //NSLog(@"HJCache loading from url"); + NSString* loadingFile = [fileCache loadingFilePathForOid:oid]; + + BOOL ok = [[NSFileManager defaultManager] createFileAtPath:loadingFile contents:nil attributes:nil]; + if (!ok) { + state = stateFailed; + NSLog(@"HJMOHandler error creating loading file %@",loadingFile); + loadingFile = nil; + [self clearLoadingState]; + [self callbackFailedToUsers]; + return; + } else { + self.moLoadingDataFile = [NSFileHandle fileHandleForWritingAtPath:loadingFile]; + } + } + } + + //if file cache is in use temporary file name is prepared, either way now load from url + [self startDownloadingFromURL]; +} + + + +-(void)activateHandlerForUser:(id)user { + //stateNew, stateLoading, stateLoaded, stateReady, stateFailed + + switch (state) { + + case stateNew: + [self activateNewHandlerForUser:user]; + return; + + case stateLoading: + //handler is still loading, have to wait for it to load, so nop. + return; + + case stateLoaded: + //for some reason it didn't go to ready when it was loaded, so try again now. + [self goFromLoadedToReady]; + return; + + case stateReady: + [user managedObjReady]; + return; + + case stateFailed: + [user managedObjFailed]; + return; + + default: + //not supposed to get here + NSLog(@"HJMOHandler activateHandlerForUser error, no recognized state"); + break; + } +} + + +-(void)startDownloadingFromURL { + //NSLog(@"HJMOHandler starting download for %@",self); + HJMOPolicy* policy = [self policy]; + NSURLRequest* request = [NSURLRequest requestWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:policy.urlTimeoutTime]; + self.urlConn = [[NSURLConnection alloc] initWithRequest:request delegate:self]; + [urlConn release]; + if (urlConn==nil) { + NSLog(@"HJMOHandler nil URLConnection for %@",url); + state=stateFailed; + } else { + state=stateLoading; + //TODO if app is showing a network activity monitor in the status bar, here is where a call needs to be + // made to increment the number of active URLs + } +} + +-(void) goFromLoadedToReady { + if ([users count]==0) { + //can't go to stateReady because there's no user to do it. stay in stateLoaded. + //this is not a bug, it can happen if the object has already been deleted before its content was + //loaded over the net, eg because scrolled off the top of a table. + //NSLog(@"HJMOHandler no user object to make it ready"); + return; + } + + self.managedObj=nil; //just to be sure there's not some old one around + //pick _one_ and only one user to take mo from loaded to ready + id user = [users objectAtIndex:0]; + @try { + [user changeManagedObjStateFromLoadedToReady]; + if (managedObj!=nil) { + state = stateReady; //because it worked + [self callbackReadyToUsers]; + } + } + @catch (id exception) { + NSLog(@"%@",exception); + self.managedObj=nil; + } + @finally { + if (managedObj==nil) { + //managedObj was still nil, ie going from loaded to ready failed. go to stateFailed and clean up from caches + state = stateFailed; + self.moReadyDataFilename = nil; + self.moData=nil; + [objManager removeFromHandlerFromCaches:self]; + [self callbackFailedToUsers]; + } + } +} + +-(void) callbackReadyToUsers { + for (id user in [users objectEnumerator]) { + [user managedObjReady]; + } +} + +-(void) callbackFailedToUsers { + for (id user in [users objectEnumerator]) { + [user managedObjFailed]; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + if (state!=stateLoading) { return; } + if (!moLoadingDataFile) { + //loading direct to memory + if (moData==nil) { + self.moData = [NSMutableData dataWithCapacity:1024*10]; + } + [moData appendData:data]; + } else { + [moLoadingDataFile writeData:data]; + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + [self retain]; //ensure that self isn't released in this method when the connection is finished with it. + //NSLog(@"finishedLoading %@",self); + state = stateLoaded; + if (moLoadingDataFile) { + //was downloading to file + [moLoadingDataFile closeFile]; + self.moLoadingDataFile = nil; + self.urlConn = nil; + NSString* readyFilename = [self.objManager.fileCache loadingFinishedForOid:oid]; + if (readyFilename==nil) { + state = stateFailed; + [self callbackFailedToUsers]; + return; + } else { + self.moReadyDataFilename = readyFilename; + } + } + //TODO if app is showing a network activity monitor in the status bar, here is where a call needs to be + // made to decrement the count of active URLs + [objManager handlerFinishedDownloading:self]; + [self goFromLoadedToReady]; + if (state==stateReady || state==stateLoaded) { + [objManager addHandlerToMemCache:self]; + } + [self release]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + state = stateFailed; + NSLog(@"HJMOHandler URLConnection failed %@",error); + //TODO if app is showing a network activity monitor in the status bar, here is where a call needs to be + // made to decrement the count of active URLs + [self clearLoadingState]; + self.moReadyDataFilename = nil; + self.moData=nil; + [objManager removeFromHandlerFromCaches:self]; + [self callbackFailedToUsers]; +} + + +-(NSString*)description { + return [NSString stringWithFormat:@"HJMOHandler %@ users:%i retains:%i",oid,[users count],[self retainCount]]; +} + + +@end -- cgit v1.2.3