Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Major changes all over the place

All files are written to disk directly.
When data is requested, files are mapped into memory.
Information about download status is stored as extended file attributes.
Added python test server, so we can easily simulate funny behaviour.
  • Loading branch information...
commit e8d0af5b20e4e49f52d1721ab2034ba7f8da36cc 1 parent 2f835af
@nsc nsc authored
View
4 3rdparty/ZipArchive.m
@@ -92,7 +92,7 @@ -(BOOL) addFileToZip:(NSString*) file newname:(NSString*) newname;
}
else
{
- data = [ NSData dataWithContentsOfFile:file];
+ data = [NSData dataWithContentsOfMappedFile:file];
uLong crcValue = crc32( 0L,NULL, 0L );
crcValue = crc32( crcValue, (const Bytef*)[data bytes], [data length] );
ret = zipOpenNewFileInZip3( _zipFile,
@@ -116,7 +116,7 @@ -(BOOL) addFileToZip:(NSString*) file newname:(NSString*) newname;
}
if( data==nil )
{
- data = [ NSData dataWithContentsOfFile:file];
+ data = [NSData dataWithContentsOfMappedFile:file];
}
unsigned int dataLen = [data length];
ret = zipWriteInFileInZip( _zipFile, (const void*)[data bytes], dataLen);
View
4 AFCache+PrivateExtensions.h
@@ -41,5 +41,9 @@
- (uint32_t)hash:(NSString*)str;
//- (void)removeObjectForURLString: (NSString *) URLString fileOnly:(BOOL) fileOnly;
- (void)removeCacheEntryWithFilePath:(NSString*)filePath fileOnly:(BOOL) fileOnly;
+- (NSFileHandle*)createFileForItem:(AFCacheableItem*)cacheableItem;
+- (void)downloadItem:(AFCacheableItem*)item;
+- (void)registerItem:(AFCacheableItem*)item;
+- (void)signalItemsForURL:(NSURL*)url usingSelector:(SEL)selector;
@end
View
5 AFCache.h
@@ -40,6 +40,9 @@
#define USE_ASSERTS true
+extern const char* kAFCacheContentLengthFileAttribute;
+extern const char* kAFCacheDownloadingFileAttribute;
+
enum {
kAFCacheInvalidateEntry = 1 << 9,
// kAFCacheUseLocalMirror = 2 << 9, deprecated, don't redefine id 2 for compatibility reasons
@@ -56,6 +59,7 @@ enum {
NSString *dataPath;
NSMutableDictionary *cacheInfoStore;
NSMutableDictionary *pendingConnections;
+ NSMutableDictionary *clientItems;
BOOL _offline;
int requestCounter;
double maxItemFileSize;
@@ -66,6 +70,7 @@ enum {
@property (nonatomic, copy) NSString *dataPath;
@property (nonatomic, retain) NSMutableDictionary *cacheInfoStore;
@property (nonatomic, retain) NSMutableDictionary *pendingConnections;
+@property (nonatomic, readonly) NSDictionary *clientItems;
@property (nonatomic, assign) double maxItemFileSize;
@property (nonatomic, assign) double diskCacheDisplacementTresholdSize;
View
216 AFCache.m
@@ -29,6 +29,7 @@
#include <netdb.h>
#include <uuid/uuid.h>
#import <SystemConfiguration/SCNetworkReachability.h>
+#include <sys/xattr.h>
#import "ZipArchive.h"
#import "AFRegexString.h"
@@ -38,12 +39,16 @@
ManifestKeyExpires = 2,
};
+const char* kAFCacheContentLengthFileAttribute = "de.artifacts.contentLength";
+const char* kAFCacheDownloadingFileAttribute = "de.artifacts.downloading";
+
@implementation AFCache
static AFCache *sharedAFCacheInstance = nil;
static NSString *STORE_ARCHIVE_FILENAME = @ "urlcachestore";
@synthesize cacheEnabled, dataPath, cacheInfoStore, pendingConnections, maxItemFileSize, diskCacheDisplacementTresholdSize;
+@synthesize clientItems;
#pragma mark init methods
@@ -63,13 +68,16 @@ - (int)requestsPending {
return [pendingConnections count];
}
+// The method reinitialize really initializes the cache.
+// This is usefull for testing, when you want to, uh, reinitialize
- (void)reinitialize {
cacheEnabled = YES;
- //maxItemFileSize = kAFCacheDefaultMaxFileSize;
+ maxItemFileSize = kAFCacheDefaultMaxFileSize;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
self.dataPath = [[paths objectAtIndex: 0] stringByAppendingPathComponent: STORE_ARCHIVE_FILENAME];
NSString *filename = [dataPath stringByAppendingPathComponent: kAFCacheExpireInfoDictionaryFilename];
-
+ clientItems = [[NSMutableDictionary alloc] init];
+
NSDictionary *archivedExpireDates = [NSKeyedUnarchiver unarchiveObjectWithFile: filename];
if (!archivedExpireDates) {
#if AFCACHE_LOGGING_ENABLED
@@ -86,7 +94,7 @@ - (void)reinitialize {
self.pendingConnections = [[NSMutableDictionary alloc] init];
- NSError *error;
+ NSError *error = nil;
/* check for existence of cache directory */
if ([[NSFileManager defaultManager] fileExistsAtPath: dataPath]) {
#if AFCACHE_LOGGING_ENABLED
@@ -166,6 +174,33 @@ - (void) packageArchiveDidFinishLoading: (AFCacheableItem *) cacheableItem {
}
}
+- (void)setContentLengthForFile:(NSString*)filename
+{
+ const char* cfilename = [filename fileSystemRepresentation];
+
+ NSError* err = nil;
+ NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filename error:&err];
+ if (nil != err)
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"Could not get file attributes for %@", filename);
+#endif
+ return;
+ }
+ uint64_t fileSize = [attrs fileSize];
+ if (0 != setxattr(cfilename,
+ kAFCacheContentLengthFileAttribute,
+ &fileSize,
+ sizeof(fileSize),
+ 0, 0))
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"Could not et content length for file %@", filename);
+#endif
+ return;
+ }
+}
+
- (void)consumePackageArchive:(AFCacheableItem*)cacheableItem {
NSString *urlCacheStorePath = self.dataPath;
NSString *pathToZip = [NSString stringWithFormat:@"%@/%@", urlCacheStorePath, [cacheableItem filename]];
@@ -175,7 +210,7 @@ - (void)consumePackageArchive:(AFCacheableItem*)cacheableItem {
[zip UnzipCloseFile];
[zip release];
NSString *pathToManifest = [NSString stringWithFormat:@"%@/%@", urlCacheStorePath, @"manifest.afcache"];
- NSError *error;
+ NSError *error = nil;
NSString *manifest = [NSString stringWithContentsOfFile:pathToManifest encoding:NSASCIIStringEncoding error:&error];
NSArray *entries = [manifest componentsSeparatedByString:@"\n"];
AFCacheableItemInfo *info;
@@ -206,7 +241,9 @@ - (void)consumePackageArchive:(AFCacheableItem*)cacheableItem {
URL = [values objectAtIndex:ManifestKeyURL];
key = [self filenameForURLString:URL];
- [cacheInfoStore setObject:info forKey:key];
+ [cacheInfoStore setObject:info forKey:key];
+ [self setContentLengthForFile:[urlCacheStorePath stringByAppendingPathComponent:key]];
+
[info release];
}
[[NSFileManager defaultManager] removeItemAtPath:pathToZip error:&error];
@@ -265,24 +302,34 @@ - (AFCacheableItem *)cachedObjectForURL: (NSURL *) url
item.delegate = aDelegate;
item.url = internalURL;
item.tag = requestCounter;
-
- NSURLRequest *theRequest = [NSURLRequest requestWithURL: internalURL
- cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
- timeoutInterval: 45];
-
- item.info.requestTimestamp = [NSDate timeIntervalSinceReferenceDate];
- NSURLConnection *connection = [NSURLConnection connectionWithRequest: theRequest delegate: item];
- [pendingConnections setObject: connection forKey: internalURL];
+
+ NSString* key = [self filenameForURL:internalURL];
+ [cacheInfoStore setObject:item.info forKey:key];
+
+ [self downloadItem:item];
+ return item;
} else {
// object found in cache.
// now check if it is fresh enough to serve it from disk.
// pretend it's fresh when cache is offline
if ([self isOffline] == YES) {
- [aDelegate performSelector: aSelector withObject: item];
- return item;
+ // return item and call delegate only if fully loaded
+ if (nil != item.data) {
+ [aDelegate performSelector: aSelector withObject: item];
+ return item;
+ }
+
+ return nil;
}
+ // Check if item is fully loaded already
+ if (nil == item.data)
+ {
+ [self downloadItem:item];
+ return item;
+ }
+
// Item is fresh, so call didLoad selector and return the cached item.
if ([item isFresh]) {
item.cacheStatus = kCacheStatusFresh;
@@ -307,7 +354,8 @@ - (AFCacheableItem *)cachedObjectForURL: (NSURL *) url
}
//item.info.requestTimestamp = [NSDate timeIntervalSinceReferenceDate];
NSURLConnection *connection = [NSURLConnection connectionWithRequest: theRequest delegate: item];
- [pendingConnections setObject: connection forKey: internalURL];
+ [pendingConnections setObject: connection forKey: internalURL];
+ [self registerItem:item];
}
}
@@ -394,7 +442,9 @@ - (NSString *)filenameForURL: (NSURL *) url {
}
- (NSString *)filenameForURLString: (NSString *) URLString {
+#ifndef AFCACHE_NO_MAINTAINER_WARNINGS
#warning TODO cleanup
+#endif
if ([URLString hasPrefix:@"data:"]) return nil;
NSString *filepath = [URLString stringByRegex:@".*://" substitution:@""];
NSString *filepath1 = [filepath stringByRegex:@":[0-9]?*/" substitution:@""];
@@ -456,52 +506,68 @@ - (void)removeCacheEntryWithFilePath:(NSString*)filePath fileOnly:(BOOL) fileOnl
#pragma mark internal core methods
- (void)setObject: (AFCacheableItem *) cacheableItem forURL: (NSURL *) url {
- NSError *error;
- NSString *key = [self filenameForURL:url];
+ NSError *error = nil;
+// NSString *key = [self filenameForURL:url];
#ifndef AFCACHE_NO_MAINTAINER_WARNINGS
#warning TODO clean up filenameForURL, filePathForURL methods...
#endif
NSString *filePath = [self filePathForURL: url];
+
+ /* reset the file's modification date to indicate that the URL has been checked */
+ NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys: [NSDate date], NSFileModificationDate, nil];
+ if (![[NSFileManager defaultManager] setAttributes: dict ofItemAtPath: filePath error: &error]) {
+ NSLog(@ "Failed to reset modification date for cache item %@", filePath);
+ }
+ [dict release];
+ [self archive];
+}
+
+- (NSFileHandle*)createFileForItem:(AFCacheableItem*)cacheableItem
+{
+ NSError* error = nil;
+ NSString *filePath = [self filePathForURL: cacheableItem.url];
+ NSFileHandle* fileHandle = nil;
// remove file if exists
if ([[NSFileManager defaultManager] fileExistsAtPath: filePath] == YES) {
[self removeCacheEntryWithFilePath:filePath fileOnly:YES];
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"removing %@", filePath);
+#endif
}
// create directory if not exists
NSString *pathToDirectory = [filePath stringByDeletingLastPathComponent];
if (![[NSFileManager defaultManager] fileExistsAtPath:pathToDirectory] == YES) {
[[NSFileManager defaultManager] createDirectoryAtPath:pathToDirectory withIntermediateDirectories:YES attributes:nil error:&error];
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"creating directory %@", pathToDirectory);
+#endif
}
// write file
- if (cacheableItem.data.length < maxItemFileSize || cacheableItem.isPackageArchive) {
+ if (cacheableItem.contentLength < maxItemFileSize || cacheableItem.isPackageArchive) {
/* file doesn't exist, so create it */
- [[NSFileManager defaultManager] createFileAtPath: filePath
- contents: cacheableItem.data
- attributes: nil];
+ [[NSFileManager defaultManager] createFileAtPath: filePath
+ contents: cacheableItem.data
+ attributes: nil];
+
+ fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
#ifdef AFCACHE_LOGGING_ENABLED
- NSLog(@"created file at path %@", filePath);
+ NSLog(@"created file at path %@ (%d)", filePath, [fileHandle fileDescriptor]);
#endif
}
else {
NSLog(@ "AFCache: item size exceeds maxItemFileSize (%f). Won't write file to disk", maxItemFileSize);
-// [cacheInfoStore removeObjectForKey: [url absoluteString]];
- [cacheInfoStore removeObjectForKey: key];
- return;
+ // [cacheInfoStore removeObjectForKey: [url absoluteString]];
+ [cacheInfoStore removeObjectForKey: [self filenameForURL:cacheableItem.url]];
}
-
-
- /* reset the file's modification date to indicate that the URL has been checked */
- NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys: [NSDate date], NSFileModificationDate, nil];
-
- if (![[NSFileManager defaultManager] setAttributes: dict ofItemAtPath: filePath error: &error]) {
- NSLog(@ "Failed to reset modification date for cache item %@", filePath);
- }
- [dict release];
- [self archive];
+
+ return fileHandle;
}
+// If the file exists on disk we return a new AFCacheableItem for it,
+// but it may be only half loaded yet.
- (AFCacheableItem *)cacheableItemFromCacheStore: (NSURL *) URL {
if ([[URL absoluteString] hasPrefix:@"data:"]) return nil;
NSString *key = [self filenameForURL:URL];
@@ -511,7 +577,8 @@ - (AFCacheableItem *)cacheableItemFromCacheStore: (NSURL *) URL {
NSLog(@"checking for file at path %@", filePath);
#endif
if ([[NSFileManager defaultManager] fileExistsAtPath: filePath]) {
- NSLog(@"Cache hit for URL: %@", [URL absoluteString]);
+
+ NSLog(@"Cache hit for URL: %@", [URL absoluteString]);
AFCacheableItemInfo *info = [cacheInfoStore objectForKey: key];
if (!info) {
#ifdef AFCACHE_LOGGING_ENABLED
@@ -523,24 +590,21 @@ - (AFCacheableItem *)cacheableItemFromCacheStore: (NSURL *) URL {
return nil;
}
- NSMutableData *data = [[NSMutableData alloc] initWithContentsOfFile: filePath];
AFCacheableItem *cacheableItem = [[AFCacheableItem alloc] init];
cacheableItem.cache = self;
cacheableItem.url = URL;
- cacheableItem.data = data;
cacheableItem.info = info;
- cacheableItem.cacheStatus = ([cacheableItem isFresh])?kCacheStatusFresh:kCacheStatusStale;
- cacheableItem.contentLength = [data length];
+ [cacheableItem validateCacheStatus];
if ([self isOffline]) {
cacheableItem.loadedFromOfflineCache = YES;
cacheableItem.cacheStatus = kCacheStatusFresh;
}
// NSAssert(cacheableItem.info!=nil, @"AFCache internal inconsistency (cacheableItemFromCacheStore): Info must not be nil. This is a software bug.");
- [data release];
return [cacheableItem autorelease];
}
NSLog(@"Cache miss for URL: %@.", [URL absoluteString]);
+
return nil;
}
@@ -556,6 +620,62 @@ - (void)removeReferenceToConnection: (NSURLConnection *) connection {
}
}
+- (void)registerItem:(AFCacheableItem*)item
+{
+ NSMutableArray* items = [clientItems objectForKey:item.url];
+ if (nil == items)
+ {
+ items = [NSMutableArray arrayWithObject:item];
+ [clientItems setObject:items forKey:item.url];
+ return;
+ }
+
+ [items addObject:item];
+}
+
+- (void)signalItemsForURL:(NSURL*)url usingSelector:(SEL)selector
+{
+ NSArray* items = [clientItems objectForKey:url];
+ for (AFCacheableItem* item in items)
+ {
+ id delegate = item.delegate;
+ if ([delegate respondsToSelector:selector])
+ {
+ [delegate performSelector:selector withObject:item];
+ }
+ }
+}
+
+// Download item if we need to.
+- (void)downloadItem:(AFCacheableItem*)item
+{
+ [self registerItem:item];
+
+ NSString* filePath = [self filePathForURL:item.url];
+ if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
+ {
+ // check if we are downloading already
+ if (nil != [pendingConnections objectForKey:item.url])
+ {
+ // don't start another connection
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"We are downloading already. Don't start another connection for %@", item.url);
+#endif
+ return;
+ }
+ }
+
+ item.fileHandle = [self createFileForItem:item];
+
+ NSURLRequest *theRequest = [NSURLRequest requestWithURL: item.url
+ cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval: 45];
+
+ item.info.requestTimestamp = [NSDate timeIntervalSinceReferenceDate];
+ NSURLConnection *connection = [NSURLConnection connectionWithRequest: theRequest delegate: item];
+ [pendingConnections setObject: connection forKey: item.url];
+}
+
#pragma mark serialization methods
- (BOOL)fillCacheWithArchiveFromURL:(NSURL *)url
@@ -612,7 +732,13 @@ - (BOOL)fillCacheWithArchiveFromURL:(NSURL *)url
- (BOOL)hasCachedItemForURL:(NSURL *)url
{
- return nil != [self cacheableItemFromCacheStore:url];
+ AFCacheableItem* item = [self cacheableItemFromCacheStore:url];
+ if (nil != item)
+ {
+ return nil != item.data;
+ }
+
+ return NO;
}
#pragma mark offline methods
@@ -627,7 +753,8 @@ - (BOOL)isOffline {
/*
* Returns whether we currently have a working connection
- *
+ * Note: This should be done asynchronously, i.e. use
+ * SCNetworkReachabilityScheduleWithRunLoop and let it update our information.
*/
- (BOOL)isConnectedToNetwork {
// Create zero addy
@@ -693,6 +820,7 @@ - (id)autorelease {
- (void)dealloc {
[pendingConnections release];
[cacheInfoStore release];
+ [clientItems release];
[dataPath release];
[super dealloc];
}
View
136 AFCache.xcodeproj/project.pbxproj
@@ -137,6 +137,29 @@
05E06DD71201730D00589399 /* ZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E06DD31201730D00589399 /* ZipArchive.m */; };
05E06DD81201730D00589399 /* ZipArchive.h in Headers */ = {isa = PBXBuildFile; fileRef = 05E06DD21201730D00589399 /* ZipArchive.h */; };
05E06DD91201730D00589399 /* ZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E06DD31201730D00589399 /* ZipArchive.m */; };
+ 341DA581121431AC004D2F2D /* testafcache-debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 341DA580121431AC004D2F2D /* testafcache-debug.xcconfig */; };
+ 341DA588121431D8004D2F2D /* AFCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 059A6DC311F889E500A252AC /* AFCache.m */; };
+ 341DA589121431D8004D2F2D /* AFURLCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 050853CA11F8AA36004A5D2A /* AFURLCache.m */; };
+ 341DA58A121431D9004D2F2D /* DateParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 050852A111F8A419004A5D2A /* DateParser.m */; };
+ 341DA58B121431DA004D2F2D /* AFCacheableItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 059A6DC611F889E500A252AC /* AFCacheableItem.m */; };
+ 341DA58C121431DB004D2F2D /* AFCacheableItemInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 059A6DCA11F889E500A252AC /* AFCacheableItemInfo.m */; };
+ 341DA58D121431E0004D2F2D /* AFCacheableItem+MetaDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 059A6DC811F889E500A252AC /* AFCacheableItem+MetaDescription.m */; };
+ 341DA58E121431E1004D2F2D /* AFCachePackager.m in Sources */ = {isa = PBXBuildFile; fileRef = 059A6DCC11F889E500A252AC /* AFCachePackager.m */; };
+ 341DA58F121431F9004D2F2D /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E06CAC12016F1500589399 /* SystemConfiguration.framework */; };
+ 341DA590121431FA004D2F2D /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E06CA412016EE600589399 /* libz.dylib */; };
+ 341DA591121431FA004D2F2D /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 059A6F1E11F88C4C00A252AC /* CoreFoundation.framework */; };
+ 341DA592121431FC004D2F2D /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 059A6E3811F88BB100A252AC /* AppKit.framework */; };
+ 341DA593121431FC004D2F2D /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 059A6DF011F88A6400A252AC /* SystemConfiguration.framework */; };
+ 341DA595121431FF004D2F2D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
+ 341DA59612143208004D2F2D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; };
+ 341DA59712143212004D2F2D /* AFRegexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 050309641201AEF600E846EC /* AFRegexString.m */; };
+ 341DA59812143213004D2F2D /* ZipArchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E06DD31201730D00589399 /* ZipArchive.m */; };
+ 341DA59912143215004D2F2D /* ioapi.c in Sources */ = {isa = PBXBuildFile; fileRef = 05E06C5A12016DF700589399 /* ioapi.c */; };
+ 341DA59A12143215004D2F2D /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 05E06C5F12016DF700589399 /* unzip.c */; };
+ 341DA59B12143216004D2F2D /* mztools.c in Sources */ = {isa = PBXBuildFile; fileRef = 05E06C5D12016DF700589399 /* mztools.c */; };
+ 341DA59C12143217004D2F2D /* zip.c in Sources */ = {isa = PBXBuildFile; fileRef = 05E06C6112016DF700589399 /* zip.c */; };
+ 341DA5A012143255004D2F2D /* testafcache_main.m in Sources */ = {isa = PBXBuildFile; fileRef = 341DA59F12143255004D2F2D /* testafcache_main.m */; };
+ 341DA5E9121435EE004D2F2D /* TestController.m in Sources */ = {isa = PBXBuildFile; fileRef = 341DA5E8121435EE004D2F2D /* TestController.m */; };
8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; };
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
/* End PBXBuildFile section */
@@ -241,7 +264,7 @@
059A6DCB11F889E500A252AC /* AFCachePackager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFCachePackager.h; sourceTree = "<group>"; };
059A6DCC11F889E500A252AC /* AFCachePackager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFCachePackager.m; sourceTree = "<group>"; };
059A6DF011F88A6400A252AC /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
- 059A6E1C11F88B1600A252AC /* afcpkg */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "compiled.mach-o.executable"; path = afcpkg; sourceTree = BUILT_PRODUCTS_DIR; };
+ 059A6E1C11F88B1600A252AC /* afcpkg */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = afcpkg; sourceTree = BUILT_PRODUCTS_DIR; };
059A6E3811F88BB100A252AC /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
059A6F1E11F88C4C00A252AC /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
059A6F5F11F88DA800A252AC /* afcpkg_main.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = afcpkg_main.h; sourceTree = "<group>"; };
@@ -277,8 +300,14 @@
089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
32DBCF5E0370ADEE00C91783 /* AFCache_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFCache_Prefix.pch; sourceTree = "<group>"; };
+ 341DA57C12143186004D2F2D /* testafcache */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = testafcache; sourceTree = BUILT_PRODUCTS_DIR; };
+ 341DA580121431AC004D2F2D /* testafcache-debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "testafcache-debug.xcconfig"; sourceTree = "<group>"; };
+ 341DA59F12143255004D2F2D /* testafcache_main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = testafcache_main.m; sourceTree = "<group>"; };
+ 341DA5E7121435EE004D2F2D /* TestController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestController.h; sourceTree = "<group>"; };
+ 341DA5E8121435EE004D2F2D /* TestController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestController.m; sourceTree = "<group>"; };
+ 349DB80A12148F23000C403D /* testserver.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = testserver.py; sourceTree = "<group>"; };
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
- 8DC2EF5B0486A6940098B216 /* AFCache */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.framework; path = AFCache; sourceTree = BUILT_PRODUCTS_DIR; };
+ 8DC2EF5B0486A6940098B216 /* AFCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AFCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D2F7E79907B2D74100F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
/* End PBXFileReference section */
@@ -320,6 +349,20 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 341DA57A12143186004D2F2D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 341DA58F121431F9004D2F2D /* SystemConfiguration.framework in Frameworks */,
+ 341DA590121431FA004D2F2D /* libz.dylib in Frameworks */,
+ 341DA591121431FA004D2F2D /* CoreFoundation.framework in Frameworks */,
+ 341DA592121431FC004D2F2D /* AppKit.framework in Frameworks */,
+ 341DA593121431FC004D2F2D /* SystemConfiguration.framework in Frameworks */,
+ 341DA595121431FF004D2F2D /* Cocoa.framework in Frameworks */,
+ 341DA59612143208004D2F2D /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
8DC2EF560486A6940098B216 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -336,11 +379,12 @@
034768DFFF38A50411DB9C8B /* Products */ = {
isa = PBXGroup;
children = (
- 8DC2EF5B0486A6940098B216 /* AFCache */,
+ 8DC2EF5B0486A6940098B216 /* AFCache.framework */,
059A6E1C11F88B1600A252AC /* afcpkg */,
0508536C11F8A935004A5D2A /* libAFCache.a */,
05E0688311FF3E9500589399 /* libAFCacheOSXStatic.a */,
0503099D1201B1A400E846EC /* AFCacheDemoiOS.app */,
+ 341DA57C12143186004D2F2D /* testafcache */,
);
name = Products;
sourceTree = "<group>";
@@ -379,6 +423,7 @@
05E0692011FF6AE500589399 /* AFCacheStatic-OSX-debug.xcconfig */,
05E069D412000F2B00589399 /* createFatLib-iOS.xcconfig */,
050309B91201B23700E846EC /* AFCacheDemoiOS-debug.xcconfig */,
+ 341DA580121431AC004D2F2D /* testafcache-debug.xcconfig */,
);
name = config;
sourceTree = "<group>";
@@ -404,8 +449,12 @@
059A6DEA11F889F400A252AC /* tests */ = {
isa = PBXGroup;
children = (
+ 349DB80A12148F23000C403D /* testserver.py */,
05E06A16120016BA00589399 /* LogicTests.h */,
05E06A17120016BA00589399 /* LogicTests.m */,
+ 341DA59F12143255004D2F2D /* testafcache_main.m */,
+ 341DA5E7121435EE004D2F2D /* TestController.h */,
+ 341DA5E8121435EE004D2F2D /* TestController.m */,
);
name = tests;
sourceTree = "<group>";
@@ -678,6 +727,22 @@
productReference = 05E0688311FF3E9500589399 /* libAFCacheOSXStatic.a */;
productType = "com.apple.product-type.library.static";
};
+ 341DA57B12143186004D2F2D /* testafcache */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 341DA582121431AD004D2F2D /* Build configuration list for PBXNativeTarget "testafcache" */;
+ buildPhases = (
+ 341DA57912143186004D2F2D /* Sources */,
+ 341DA57A12143186004D2F2D /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = testafcache;
+ productName = testafcache;
+ productReference = 341DA57C12143186004D2F2D /* testafcache */;
+ productType = "com.apple.product-type.tool";
+ };
8DC2EF4F0486A6940098B216 /* AFCacheOSX */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "AFCacheOSX" */;
@@ -694,7 +759,7 @@
name = AFCacheOSX;
productInstallPath = "$(HOME)/Library/Frameworks";
productName = AFCache;
- productReference = 8DC2EF5B0486A6940098B216 /* AFCache */;
+ productReference = 8DC2EF5B0486A6940098B216 /* AFCache.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
@@ -717,6 +782,7 @@
05E069D012000F0700589399 /* createFatLib-iOS */,
05E069EA1200104200589399 /* Build All */,
0503099C1201B1A400E846EC /* AFCacheDemoiOS */,
+ 341DA57B12143186004D2F2D /* testafcache */,
);
};
/* End PBXProject section */
@@ -742,6 +808,7 @@
05E0676911FF24B900589399 /* AFCache-OSX-debug.xcconfig in Resources */,
05E0676E11FF250800589399 /* afcpkg-debug.xcconfig in Resources */,
05E06C8312016DF700589399 /* ChangeLogUnzip in Resources */,
+ 341DA581121431AC004D2F2D /* testafcache-debug.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -822,6 +889,28 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 341DA57912143186004D2F2D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 341DA588121431D8004D2F2D /* AFCache.m in Sources */,
+ 341DA589121431D8004D2F2D /* AFURLCache.m in Sources */,
+ 341DA58A121431D9004D2F2D /* DateParser.m in Sources */,
+ 341DA58B121431DA004D2F2D /* AFCacheableItem.m in Sources */,
+ 341DA58C121431DB004D2F2D /* AFCacheableItemInfo.m in Sources */,
+ 341DA58D121431E0004D2F2D /* AFCacheableItem+MetaDescription.m in Sources */,
+ 341DA58E121431E1004D2F2D /* AFCachePackager.m in Sources */,
+ 341DA59712143212004D2F2D /* AFRegexString.m in Sources */,
+ 341DA59812143213004D2F2D /* ZipArchive.m in Sources */,
+ 341DA59912143215004D2F2D /* ioapi.c in Sources */,
+ 341DA59A12143215004D2F2D /* unzip.c in Sources */,
+ 341DA59B12143216004D2F2D /* mztools.c in Sources */,
+ 341DA59C12143217004D2F2D /* zip.c in Sources */,
+ 341DA5A012143255004D2F2D /* testafcache_main.m in Sources */,
+ 341DA5E9121435EE004D2F2D /* TestController.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
8DC2EF540486A6940098B216 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1192,6 +1281,36 @@
};
name = Release;
};
+ 341DA57E12143186004D2F2D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 341DA580121431AC004D2F2D /* testafcache-debug.xcconfig */;
+ buildSettings = {
+ };
+ name = Debug;
+ };
+ 341DA57F12143186004D2F2D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_ENABLE_FIX_AND_CONTINUE = NO;
+ GCC_MODEL_TUNING = G5;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
+ INSTALL_PATH = /usr/local/bin;
+ OTHER_LDFLAGS = (
+ "-framework",
+ Foundation,
+ "-framework",
+ AppKit,
+ );
+ PREBINDING = NO;
+ PRODUCT_NAME = testafcache;
+ ZERO_LINK = NO;
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1267,6 +1386,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 341DA582121431AD004D2F2D /* Build configuration list for PBXNativeTarget "testafcache" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 341DA57E12143186004D2F2D /* Debug */,
+ 341DA57F12143186004D2F2D /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
};
rootObject = 0867D690FE84028FC02AAC07 /* Project object */;
View
4 AFCachePackager.m
@@ -15,14 +15,14 @@ - (AFCacheableItem*)newCacheableItemFromFileAtPath:(NSString*)path
lastModified:(NSDate*)lastModified
expireDate:(NSDate*)expireDate
{
- AFCacheableItemInfo *info = [[AFCacheableItemInfo alloc] init];
+ AFCacheableItemInfo *info = [[[AFCacheableItemInfo alloc] init] autorelease];
info.lastModified = lastModified;
info.expireDate = expireDate;
AFCacheableItem *item = [[AFCacheableItem alloc] init];
item.url = URL;
item.info = info;
[info release];
- item.data = [NSData dataWithContentsOfFile:path];
+ item.data = [NSData dataWithContentsOfMappedFile:path];
if (!item.data)
{
[item release];
View
12 AFCacheableItem.h
@@ -39,12 +39,13 @@ enum kCacheStatus {
kCacheStatusNotModified = 4,
kCacheStatusRevalidationPending = 5,
kCacheStatusStale = 6,
+ kCacheStatusDownloading = 7, // item is not fully downloaded
};
@interface AFCacheableItem : NSObject {
NSURL *url;
NSString *mimeType;
- NSMutableData *data;
+ NSData *data;
AFCache *cache;
id <AFCacheableItemDelegate> delegate;
BOOL persistable;
@@ -65,11 +66,12 @@ enum kCacheStatus {
AFCacheableItemInfo *info;
int tag; // for debugging and testing purposes
BOOL isPackageArchive;
- NSUInteger contentLength;
+ uint64_t contentLength;
+ NSFileHandle* fileHandle;
}
@property (nonatomic, retain) NSURL *url;
-@property (nonatomic, retain) NSMutableData *data;
+@property (nonatomic, retain) NSData *data;
@property (nonatomic, retain) NSString *mimeType;
@property (nonatomic, assign) AFCache *cache;
@property (nonatomic, retain) id <AFCacheableItemDelegate> delegate;
@@ -85,7 +87,8 @@ enum kCacheStatus {
@property (nonatomic, assign) int tag;
@property (nonatomic, assign) id userData;
@property (nonatomic, assign) BOOL isPackageArchive;
-@property (nonatomic, assign) NSUInteger contentLength;
+@property (nonatomic, assign) uint64_t contentLength;
+@property (nonatomic, retain) NSFileHandle* fileHandle;
- (void)connection: (NSURLConnection *) connection didReceiveData: (NSData *) data;
- (void)connectionDidFinishLoading: (NSURLConnection *) connection;
@@ -93,6 +96,7 @@ enum kCacheStatus {
- (void)connection: (NSURLConnection *) connection didFailWithError: (NSError *) error;
- (BOOL)isFresh;
- (BOOL)isCachedOnDisk;
+- (void)validateCacheStatus;
- (NSString *)filename;
- (NSString *)asString;
View
236 AFCacheableItem.m
@@ -22,32 +22,88 @@
#import "AFCache+PrivateExtensions.h"
#import "AFCache.h"
#import "DateParser.h"
+#include <sys/xattr.h>
+
+@interface AFCacheableItem()
+- (void)setDownloadStartedFileAttributes;
+- (void)setDownloadFinishedFileAttributes;
+- (BOOL)isDownloading;
+- (uint64_t)getContentLengthFromFile;
+@end
@implementation AFCacheableItem
@synthesize url, data, mimeType, persistable, ignoreErrors;
@synthesize cache, delegate, connectionDidFinishSelector, connectionDidFailSelector, error;
-@synthesize info, validUntil, cacheStatus, loadedFromOfflineCache, tag, userData, isPackageArchive, contentLength;
+@synthesize info, validUntil, cacheStatus, loadedFromOfflineCache, tag, userData, isPackageArchive, contentLength, fileHandle;
- (id) init {
self = [super init];
if (self != nil) {
- data = [[NSMutableData alloc] init];
+ data = nil;
persistable = true;
connectionDidFinishSelector = @selector(connectionDidFinish:);
connectionDidFailSelector = @selector(connectionDidFail:);
self.cacheStatus = kCacheStatusNew;
- self.info = [[AFCacheableItemInfo alloc] init];
+ self.info = [[[AFCacheableItemInfo alloc] init] autorelease];
}
return self;
}
+- (void)appendData:(NSData*)newData
+{
+ [fileHandle seekToEndOfFile];
+ [fileHandle writeData:newData];
+}
+
+- (NSData*)data
+{
+ if (nil == data)
+ {
+ NSString* filePath = [self.cache filePath:self.filename];
+ if (![[NSFileManager defaultManager] fileExistsAtPath:filePath])
+ {
+ return nil;
+ }
+
+ NSError* err = nil;
+ NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&err];
+ if (nil != err)
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"Error getting file attributes: %@", err);
+#endif
+ return nil;
+ }
+
+ uint64_t fileSize = [attr fileSize];
+ if (self.contentLength == 0 || fileSize != self.contentLength)
+ {
+ uint64_t realContentLength = [self getContentLengthFromFile];
+
+ if (realContentLength == 0 || realContentLength != fileSize)
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"item not ready: %@", self.filename);
+#endif
+ return nil;
+ }
+ }
+
+ data = [[NSData dataWithContentsOfMappedFile:filePath] retain];
+ }
+
+ return data;
+}
+
- (void)connection: (NSURLConnection *) connection didReceiveData: (NSData *) receivedData {
- [self.data appendData: receivedData];
+ [self appendData:receivedData];
if (self.isPackageArchive) {
- if ([delegate respondsToSelector:@selector(packageArchiveDidReceiveData:)]) {
- [delegate performSelector:@selector(packageArchiveDidReceiveData:) withObject:self];
- }
+ [self.cache signalItemsForURL:self.url usingSelector:@selector(packageArchiveDidReceiveData:)];
+
+// if ([delegate respondsToSelector:@selector(packageArchiveDidReceiveData:)]) {
+// [delegate performSelector:@selector(packageArchiveDidReceiveData:) withObject:self];
+// }
}
}
@@ -92,7 +148,7 @@ - (void)connection: (NSURLConnection *) connection didReceiveResponse: (NSURLRes
self.info.responseTimestamp = [now timeIntervalSinceReferenceDate];
}
- [self.data setLength: 0];
+// [self.data setLength: 0];
// Calulate expiration time for newly fetched object to determine
// until when we may cache it.
@@ -119,7 +175,9 @@ - (void)connection: (NSURLConnection *) connection didReceiveResponse: (NSURLRes
NSString *contentLengthHeader = [headers objectForKey: @"Content-Length"];
self.contentLength = [contentLengthHeader integerValue];
-
+
+ [self setDownloadStartedFileAttributes];
+
// parse 'Age', 'Date', 'Last-Modified', 'Expires' headers and use
// a date formatter capable of parsing the date string using
// three different formats:
@@ -214,9 +272,25 @@ - (void)connection: (NSURLConnection *) connection didReceiveResponse: (NSURLRes
*/
- (void)connectionDidFinishLoading: (NSURLConnection *) connection {
NSError *err = nil;
- if ([self.data length] == 0) err = [NSError errorWithDomain: @"Request returned no data" code: 99 userInfo: nil];
+ // note: No longer an error, because the data is written directly to disk
+ //if ([self.data length] == 0) err = [NSError errorWithDomain: @"Request returned no data" code: 99 userInfo: nil];
if (url == nil) err = [NSError errorWithDomain: @"URL is nil" code: 99 userInfo: nil];
-
+
+ // do we have a correct contentLength?
+ NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:[self.cache filePath:self.filename] error:&err];
+ if (nil == err)
+ {
+ uint64_t fileSize = [attr fileSize];
+ if (fileSize != self.contentLength)
+ {
+ self.contentLength = fileSize;
+ }
+ }
+ [self setDownloadFinishedFileAttributes];
+ [fileHandle closeFile];
+ [fileHandle release];
+ fileHandle = nil;
+
// Log any error. Maybe someone might read it ;)
if (err != nil) {
NSLog(@"Error: %@", [err localizedDescription]);
@@ -238,9 +312,10 @@ - (void)connectionDidFinishLoading: (NSURLConnection *) connection {
if (isPackageArchive) {
[cache performSelector:@selector(packageArchiveDidFinishLoading:) withObject:self];
} else {
- if ([delegate respondsToSelector: connectionDidFinishSelector]) {
- [delegate performSelector: connectionDidFinishSelector withObject: self];
- }
+ [self.cache signalItemsForURL:self.url usingSelector:connectionDidFinishSelector];
+// if ([delegate respondsToSelector: connectionDidFinishSelector]) {
+// [delegate performSelector: connectionDidFinishSelector withObject: self];
+// }
}
}
@@ -249,17 +324,25 @@ - (void)connectionDidFinishLoading: (NSURLConnection *) connection {
*/
- (void)connection: (NSURLConnection *) connection didFailWithError: (NSError *) anError;
{
+ [fileHandle closeFile];
+ [fileHandle release];
+ fileHandle = nil;
+
[cache removeReferenceToConnection: connection];
self.error = anError;
[cache.cacheInfoStore removeObjectForKey:[url absoluteString]];
if (self.isPackageArchive) {
- if ([delegate respondsToSelector: @selector(packageArchiveDidFailLoading:)]) {
- [self.delegate performSelector: @selector(packageArchiveDidFailLoading:) withObject: self];
- }
- } else {
- if ([delegate respondsToSelector: connectionDidFailSelector]) {
- [self.delegate performSelector: connectionDidFailSelector withObject: self];
- }
+ [self.cache signalItemsForURL:self.url usingSelector:@selector(packageArchiveDidFailLoading:)];
+
+// if ([delegate respondsToSelector: @selector(packageArchiveDidFailLoading:)]) {
+// [self.delegate performSelector: @selector(packageArchiveDidFailLoading:) withObject: self];
+// }
+ } else {
+ [self.cache signalItemsForURL:self.url usingSelector:connectionDidFailSelector];
+
+// if ([delegate respondsToSelector: connectionDidFailSelector]) {
+// [self.delegate performSelector: connectionDidFailSelector withObject: self];
+// }
}
}
@@ -314,6 +397,115 @@ - (BOOL)isFresh {
return fresh;
}
+- (void)validateCacheStatus
+{
+ if ([self isDownloading])
+ {
+ self.cacheStatus = kCacheStatusDownloading;
+ }
+ else if (nil != self.data)
+ {
+ self.cacheStatus = [self isFresh] ? kCacheStatusFresh : kCacheStatusStale;
+ return;
+ }
+}
+
+- (void)setDownloadStartedFileAttributes
+{
+ int fd = [self.fileHandle fileDescriptor];
+ if (fd > 0)
+ {
+ if (0 != fsetxattr(fd,
+ kAFCacheContentLengthFileAttribute,
+ &contentLength,
+ sizeof(uint64_t),
+ 0, 0))
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"Could not set contentLength attribute on %@", [self filename]);
+#endif
+ }
+
+ unsigned int downloading = 1;
+ if (0 != fsetxattr(fd,
+ kAFCacheDownloadingFileAttribute,
+ &downloading,
+ sizeof(downloading),
+ 0, 0))
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"Could not set downloading attribute on %@", [self filename]);
+#endif
+ }
+
+ }
+}
+
+- (void)setDownloadFinishedFileAttributes
+{
+ int fd = [self.fileHandle fileDescriptor];
+ if (fd > 0)
+ {
+ if (0 != fsetxattr(fd,
+ kAFCacheContentLengthFileAttribute,
+ &contentLength,
+ sizeof(uint64_t),
+ 0, 0))
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"Could not set contentLength attribute on %@", [self filename]);
+#endif
+ }
+
+ if (0 != fremovexattr(fd, kAFCacheDownloadingFileAttribute, 0))
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"Could not remove downloading attribute on %@", [self filename]);
+#endif
+ }
+
+ }
+}
+
+- (BOOL)isDownloading
+{
+ unsigned int downloading = 0;
+ if (sizeof(downloading) != getxattr([[self.cache filePathForURL:self.url] fileSystemRepresentation],
+ kAFCacheDownloadingFileAttribute,
+ &downloading,
+ sizeof(downloading),
+ 0, 0))
+ {
+ return NO;
+ }
+
+ return [[self.cache pendingConnections] objectForKey:self.url] != nil;
+}
+
+- (uint64_t)getContentLengthFromFile
+{
+ if ([self isDownloading])
+ {
+ return 0LL;
+ }
+
+ uint64_t realContentLength = 0LL;
+ if (sizeof(realContentLength) != getxattr([[self.cache filePathForURL:self.url] fileSystemRepresentation],
+ kAFCacheContentLengthFileAttribute,
+ &realContentLength,
+ sizeof(realContentLength),
+ 0, 0))
+ {
+#ifdef AFCACHE_LOGGING_ENABLED
+ NSLog(@"Could not get content lenth attribute from file %@. This may be bad.",
+ [self.cache filePathForURL:self.url]);
+#endif
+ return 0LL;
+ }
+
+ return realContentLength;
+}
+
- (NSString *)filename {
return [cache filenameForURL: url];
}
@@ -341,7 +533,7 @@ - (NSString*)description {
[s appendString:@", "];
[s appendFormat:@"cacheStatus: %d", cacheStatus];
[s appendString:@", "];
- [s appendFormat:@"body content size: %d\n", [data length]];
+ [s appendFormat:@"body content size: %d\n", [self.data length]];
[s appendString:[info description]];
[s appendString:@"\n"];
if (loadedFromOfflineCache) {
View
7 CHANGES
@@ -1,5 +1,12 @@
Current version is Rev.0.6.6
+2010/08/12:
+- Major overhaul so that
+ 1.) all files are written to disk directly
+ 2.) when data is requested, files are mapped into memory
+ 3.) information about download status is stored as extended file attributes
+- added python test server, so we can easily simulate funny behaviour
+
2010/08/10, v.0.6.6:
- added some functionality to the packager
- testet packaging process on real-world project
View
19 TestController.h
@@ -0,0 +1,19 @@
+//
+// TestController.h
+// AFCache
+//
+// Created by neonico on 8/12/10.
+// Copyright 2010 __MyCompanyName__. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface TestController : NSObject
+{
+
+}
+
+- (void)test;
+
+@end
View
50 TestController.m
@@ -0,0 +1,50 @@
+//
+// TestController.m
+// AFCache
+//
+// Created by neonico on 8/12/10.
+// Copyright 2010 __MyCompanyName__. All rights reserved.
+//
+
+#import "TestController.h"
+#import "AFCache.h"
+#import "TestController.h"
+
+
+@implementation TestController
+
+
+
+- (void)fetchFile
+{
+ AFCache* cache = [AFCache sharedInstance];
+ [cache cachedObjectForURL:[NSURL URLWithString:@"http://localhost:49000/file?numBytes=100&delay=0.5&blockSize=10"]
+ delegate:self
+ selector:@selector(didLoad:)
+ options:0];
+}
+
+
+
+- (void)test
+{
+ AFCache* cache = [AFCache sharedInstance];
+ [cache invalidateAll];
+
+ [NSTimer scheduledTimerWithTimeInterval:0.4
+ target:self
+ selector:@selector(fetchFile)
+ userInfo:nil
+ repeats:YES];
+}
+
+
+
+- (void)didLoad:(AFCacheableItem*)item
+{
+ NSLog(@"item did load %@", item.url);
+}
+
+
+
+@end
View
14 testafcache-debug.xcconfig
@@ -0,0 +1,14 @@
+//
+// testafcache-debug.xcconfig
+// AFCache
+//
+// Created by neonico on 8/12/10.
+// Copyright 2010 __MyCompanyName__. All rights reserved.
+//
+
+PRODUCT_NAME = testafcache
+GCC_PREPROCESSOR_DEFINITIONS = AFCACHE_LOGGING_ENABLED
+
+COPY_PHASE_STRIP = NO
+GCC_DYNAMIC_NO_PIC = NO
+GCC_OPTIMIZATION_LEVEL = 0
View
28 testafcache_main.m
@@ -0,0 +1,28 @@
+/*
+ * testafcache_main.m
+ * AFCache
+ *
+ * Created by neonico on 8/12/10.
+ * Copyright 2010 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+
+#import <Foundation/Foundation.h>
+#import "TestController.h"
+
+int main(int argc, char* argv[])
+{
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+
+ TestController* controller = [[TestController alloc] init];
+ [controller test];
+
+ for (;;)
+ {
+ [[NSRunLoop currentRunLoop] run];
+ }
+
+ [pool release];
+}
View
79 testserver.py
@@ -0,0 +1,79 @@
+#! /usr/bin/env python
+
+import time
+import BaseHTTPServer
+import urlparse
+
+HOST_NAME = 'localhost'
+PORT_NUMBER = 49000
+
+def makeBestType(s):
+ try:
+ return int(s)
+ except:
+ pass
+
+ try:
+ return float(s)
+ except:
+ pass
+
+ return s
+
+def sendFile(s, numBytes=100, delay=0.0, blockSize=100):
+ s.send_response(200)
+ s.send_header("Content-type", "text/html")
+ s.send_header("Content-Length", "%d" % numBytes)
+ s.end_headers()
+
+ sentBytes = 0
+
+ while sentBytes < numBytes:
+ time.sleep(delay)
+ actualBytes = (sentBytes + blockSize)
+ if actualBytes > numBytes:
+ actualBytes = numBytes
+
+ s.wfile.write("a" * (actualBytes - sentBytes))
+ s.wfile.flush()
+
+ sentBytes = actualBytes
+ print sentBytes
+
+responses = {
+ "/file" : sendFile
+}
+
+class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ def do_HEAD(self):
+ self.send_response(200)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+
+ def do_GET(self):
+ """Respond to a GET request."""
+
+ if self.headers.has_key('If-Modified-Since'):
+ self.send_response(304)
+ return
+
+ components = urlparse.urlsplit(self.path)
+ path = components[2]
+ params = components[3].split('&')
+ print params
+ parameters = {}
+ for p in params:
+ k, v = p.split('=')
+ v = makeBestType(v)
+ parameters[k] = v
+ if responses.has_key(path):
+ responses[path](self, **parameters)
+
+if __name__ == '__main__':
+ server_class = BaseHTTPServer.HTTPServer
+ httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ pass
+ httpd.server_close()
Please sign in to comment.
Something went wrong with that request. Please try again.