diff --git a/AFCache.podspec b/AFCache.podspec index 367f1e8..95bc844 100644 --- a/AFCache.podspec +++ b/AFCache.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AFCache" - s.version = "0.11.5" + s.version = "0.12.0" s.summary = "AFCache is an HTTP disk cache for use on iPhone/iPad and OSX." s.description = <<-DESC diff --git a/AFCache.xcodeproj/project.pbxproj b/AFCache.xcodeproj/project.pbxproj index 586b6f1..4a08993 100644 --- a/AFCache.xcodeproj/project.pbxproj +++ b/AFCache.xcodeproj/project.pbxproj @@ -78,8 +78,10 @@ C7503D47198640AA0032E451 /* AFDownloadOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = C7503D44198640AA0032E451 /* AFDownloadOperation.h */; }; C7503D48198640AA0032E451 /* AFDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C7503D45198640AA0032E451 /* AFDownloadOperation.m */; }; C7503D49198640AA0032E451 /* AFDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C7503D45198640AA0032E451 /* AFDownloadOperation.m */; }; - E33490CD199E24B200DE6D82 /* NSFileHandle+AFCache.h in Headers */ = {isa = PBXBuildFile; fileRef = E33490CB199E24B200DE6D82 /* NSFileHandle+AFCache.h */; }; - E33490CE199E24B200DE6D82 /* NSFileHandle+AFCache.m in Sources */ = {isa = PBXBuildFile; fileRef = E33490CC199E24B200DE6D82 /* NSFileHandle+AFCache.m */; }; + C765AB591CEB39F200A47B4A /* AFCache+FileAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = C765AB571CEB39F200A47B4A /* AFCache+FileAttributes.h */; }; + C765AB5A1CEB39F200A47B4A /* AFCache+FileAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = C765AB581CEB39F200A47B4A /* AFCache+FileAttributes.m */; }; + C765AB5D1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = C765AB5B1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.h */; }; + C765AB5E1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = C765AB5C1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.m */; }; E369E20919B0711700EAC9FE /* AFCache+DeprecatedAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = E369E20719B0711700EAC9FE /* AFCache+DeprecatedAPI.h */; }; E369E20A19B0711700EAC9FE /* AFCache+DeprecatedAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = E369E20819B0711700EAC9FE /* AFCache+DeprecatedAPI.m */; }; /* End PBXBuildFile section */ @@ -194,8 +196,10 @@ C73C71CB19816F13008EDA23 /* AFRequestConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFRequestConfiguration.m; path = src/shared/AFRequestConfiguration.m; sourceTree = ""; }; C7503D44198640AA0032E451 /* AFDownloadOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFDownloadOperation.h; path = src/shared/AFDownloadOperation.h; sourceTree = ""; }; C7503D45198640AA0032E451 /* AFDownloadOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFDownloadOperation.m; path = src/shared/AFDownloadOperation.m; sourceTree = ""; }; - E33490CB199E24B200DE6D82 /* NSFileHandle+AFCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSFileHandle+AFCache.h"; path = "src/shared/NSFileHandle+AFCache.h"; sourceTree = ""; }; - E33490CC199E24B200DE6D82 /* NSFileHandle+AFCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSFileHandle+AFCache.m"; path = "src/shared/NSFileHandle+AFCache.m"; sourceTree = ""; }; + C765AB571CEB39F200A47B4A /* AFCache+FileAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "AFCache+FileAttributes.h"; path = "src/shared/AFCache+FileAttributes.h"; sourceTree = ""; }; + C765AB581CEB39F200A47B4A /* AFCache+FileAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "AFCache+FileAttributes.m"; path = "src/shared/AFCache+FileAttributes.m"; sourceTree = ""; }; + C765AB5B1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "AFCacheableItem+FileAttributes.h"; path = "src/shared/AFCacheableItem+FileAttributes.h"; sourceTree = ""; }; + C765AB5C1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "AFCacheableItem+FileAttributes.m"; path = "src/shared/AFCacheableItem+FileAttributes.m"; sourceTree = ""; }; E369E20719B0711700EAC9FE /* AFCache+DeprecatedAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "AFCache+DeprecatedAPI.h"; path = "src/shared/AFCache+DeprecatedAPI.h"; sourceTree = ""; }; E369E20819B0711700EAC9FE /* AFCache+DeprecatedAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "AFCache+DeprecatedAPI.m"; path = "src/shared/AFCache+DeprecatedAPI.m"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -354,9 +358,13 @@ children = ( 05C9BADD132A291B0087CEA1 /* AFCache.h */, 05C9BADE132A291B0087CEA1 /* AFCache.m */, + C765AB571CEB39F200A47B4A /* AFCache+FileAttributes.h */, + C765AB581CEB39F200A47B4A /* AFCache+FileAttributes.m */, 05C9BAE3132A291B0087CEA1 /* AFCache+PrivateAPI.h */, 05C9BAE4132A291B0087CEA1 /* AFCacheableItem.h */, 05C9BAE5132A291B0087CEA1 /* AFCacheableItem.m */, + C765AB5B1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.h */, + C765AB5C1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.m */, 05C9BAE8132A291B0087CEA1 /* AFCacheableItemInfo.h */, 05C9BAE9132A291B0087CEA1 /* AFCacheableItemInfo.m */, C73C71CA19816F13008EDA23 /* AFRequestConfiguration.h */, @@ -391,7 +399,6 @@ isa = PBXGroup; children = ( E369E20619B070FB00EAC9FE /* Deprecated */, - E33490CA199E182A00DE6D82 /* Categories */, 05238D15151B6E5C0015D70E /* util */, 05238D14151B6E470015D70E /* core */, 05238D13151B6E2D0015D70E /* MimeTypes */, @@ -493,15 +500,6 @@ name = "Supporting Files"; sourceTree = ""; }; - E33490CA199E182A00DE6D82 /* Categories */ = { - isa = PBXGroup; - children = ( - E33490CB199E24B200DE6D82 /* NSFileHandle+AFCache.h */, - E33490CC199E24B200DE6D82 /* NSFileHandle+AFCache.m */, - ); - name = Categories; - sourceTree = ""; - }; E369E20619B070FB00EAC9FE /* Deprecated */ = { isa = PBXGroup; children = ( @@ -520,6 +518,7 @@ files = ( 05C9BB6F132A30E60087CEA1 /* AFCacheLib.h in Headers */, 046BEFAB152D180A00FE16B8 /* AFCache.h in Headers */, + C765AB5D1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.h in Headers */, 046BEFAC152D180A00FE16B8 /* AFCacheableItem.h in Headers */, 046BEFAD152D180A00FE16B8 /* AFCacheableItemInfo.h in Headers */, 046BEFAE152D180A00FE16B8 /* AFMediaTypeParser.h in Headers */, @@ -530,7 +529,7 @@ C73C71CC19816F13008EDA23 /* AFRequestConfiguration.h in Headers */, 04007352153242D400335735 /* AFCache+Mimetypes.h in Headers */, E369E20919B0711700EAC9FE /* AFCache+DeprecatedAPI.h in Headers */, - E33490CD199E24B200DE6D82 /* NSFileHandle+AFCache.h in Headers */, + C765AB591CEB39F200A47B4A /* AFCache+FileAttributes.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -594,7 +593,7 @@ name = AFCacheTests; productName = AFCacheTests; productReference = 050D30C6132A276A003809FC /* AFCacheTests.octest */; - productType = "com.apple.product-type.bundle"; + productType = "com.apple.product-type.bundle.ocunit-test"; }; 05FA23391357515400050BCB /* afcpkg */ = { isa = PBXNativeTarget; @@ -649,7 +648,7 @@ name = AFCacheOSXStaticTests; productName = AFCacheOSXStaticTests; productReference = 05FA23691357539C00050BCB /* AFCacheOSXStaticTests.octest */; - productType = "com.apple.product-type.bundle"; + productType = "com.apple.product-type.bundle.ocunit-test"; }; /* End PBXNativeTarget section */ @@ -778,6 +777,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C765AB5E1CEB3BD500A47B4A /* AFCacheableItem+FileAttributes.m in Sources */, + C765AB5A1CEB39F200A47B4A /* AFCache+FileAttributes.m in Sources */, 05C9BAF3132A291B0087CEA1 /* AFCache.m in Sources */, 05C9BAF5132A291B0087CEA1 /* AFCache+Mimetypes.m in Sources */, 05C9BAFA132A291B0087CEA1 /* AFCacheableItem.m in Sources */, @@ -789,7 +790,6 @@ C7503D48198640AA0032E451 /* AFDownloadOperation.m in Sources */, 05C9BB05132A291B0087CEA1 /* DateParser.m in Sources */, 05C9BB19132A29370087CEA1 /* AFRegexString.m in Sources */, - E33490CE199E24B200DE6D82 /* NSFileHandle+AFCache.m in Sources */, 05C491FB150F9CB1009EDA8F /* AFMediaTypeParser.m in Sources */, 05C491FF150F9CBA009EDA8F /* AFHTTPURLProtocol.m in Sources */, C73C71CE19816F13008EDA23 /* AFRequestConfiguration.m in Sources */, diff --git a/src/shared/AFCache+FileAttributes.h b/src/shared/AFCache+FileAttributes.h new file mode 100644 index 0000000..9ad4857 --- /dev/null +++ b/src/shared/AFCache+FileAttributes.h @@ -0,0 +1,13 @@ +// +// AFCache+FileAttributes.h +// AFCache +// +// Created by Sebastian Grimme on 17.05.16. +// Copyright © 2016 Artifacts - Fine Software Development. All rights reserved. +// + +#import + +@interface AFCache (FileAttributes) +- (uint64_t)setContentLengthForFileAtPath:(NSString*)filePath; +@end diff --git a/src/shared/AFCache+FileAttributes.m b/src/shared/AFCache+FileAttributes.m new file mode 100644 index 0000000..24dd5ce --- /dev/null +++ b/src/shared/AFCache+FileAttributes.m @@ -0,0 +1,33 @@ +// +// AFCache+FileAttributes.m +// AFCache +// +// Created by Sebastian Grimme on 17.05.16. +// Copyright © 2016 Artifacts - Fine Software Development. All rights reserved. +// + +#import "AFCache+FileAttributes.h" + +#import "AFCacheableItem+FileAttributes.h" +#import "AFCache+PrivateAPI.h" +#import "AFCache_Logging.h" +#include + +@implementation AFCache (FileAttributes) + +- (uint64_t)setContentLengthForFileAtPath:(NSString*)filePath { + NSError* err = nil; + NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&err]; + if (err) { + AFLog(@"Could not get file attributes for %@", filename); + return 0; + } + uint64_t fileSize = [attrs fileSize]; + if (0 != setxattr(filePath.fileSystemRepresentation, kAFCacheContentLengthFileAttribute, &fileSize, sizeof(fileSize), 0, 0)) { + AFLog(@"Could not set content length for file %@", filename); + return 0; + } + return fileSize; +} + +@end diff --git a/src/shared/AFCache+Packaging.h b/src/shared/AFCache+Packaging.h index d78c2ac..866ef55 100755 --- a/src/shared/AFCache+Packaging.h +++ b/src/shared/AFCache+Packaging.h @@ -13,11 +13,14 @@ // TODO: Is this a real category? It relays on the existence of properties (e.g. packageArchiveQueue) that are only used by this category @interface AFCache (Packaging) - - (BOOL)importCacheableItem:(AFCacheableItem*)cacheableItem withData:(NSData*)theData; -- (AFCacheableItem *)importObjectForURL:(NSURL *)url data:(NSData *)data; -- (AFCacheableItem *)requestPackageArchive: (NSURL *) url delegate: (id) aDelegate; -- (AFCacheableItem *)requestPackageArchive: (NSURL *) url delegate: (id) aDelegate username: (NSString*) username password: (NSString*) password; +- (BOOL)importCacheableItem:(AFCacheableItem*)cacheableItem dataWithFileAtURL:(NSURL*)URL; +- (AFCacheableItem*)importObjectForURL:(NSURL*)url data:(NSData*)data; +- (AFCacheableItem*)importObjectForURL:(NSURL*)url dataWithFileAtURL:(NSURL*)URL; + +- (AFCacheableItem *)requestPackageArchive:(NSURL*)url delegate:(id)aDelegate; +- (AFCacheableItem *)requestPackageArchive:(NSURL*)url delegate:(id)aDelegate username:(NSString*)username password:(NSString*)password; + - (void)packageArchiveDidFinishLoading: (AFCacheableItem *) cacheableItem; - (NSString*)userDataPathForPackageArchiveKey:(NSString*)archiveKey; - (AFPackageInfo*)packageInfoForURL:(NSURL*)url; diff --git a/src/shared/AFCache+Packaging.m b/src/shared/AFCache+Packaging.m index 04ca1c0..e9b7774 100755 --- a/src/shared/AFCache+Packaging.m +++ b/src/shared/AFCache+Packaging.m @@ -6,9 +6,10 @@ // Copyright 2010 Artifacts - Fine Software Development. All rights reserved. // -#include #import "AFCache+PrivateAPI.h" +#import "AFCache+FileAttributes.h" #import "AFCacheableItem+Packaging.h" +#import "AFCacheableItem+FileAttributes.h" #import "ZipArchive.h" #import "DateParser.h" #import "AFPackageInfo.h" @@ -226,7 +227,7 @@ - (AFPackageInfo*)newPackageInfoByImportingCacheManifestAtPath:(NSString*)manife NSLog(@"No filename given for entry in line %d: %@", line, entry); } - uint64_t contentLength = [self setContentLengthForFile:[urlCacheStorePath stringByAppendingPathComponent: filename]]; + uint64_t contentLength = [self setContentLengthForFileAtPath:[urlCacheStorePath stringByAppendingPathComponent: filename]]; info.contentLength = contentLength; @@ -252,26 +253,6 @@ - (AFPackageInfo*)newPackageInfoByImportingCacheManifestAtPath:(NSString*)manife return packageInfo; } -// TODO: Move to NSFileHandle+AFCache, merge with method #flagAsDownloadStartedWithContentLength: -- (uint64_t)setContentLengthForFile:(NSString*)filename -{ - const char* cfilename = [filename fileSystemRepresentation]; - - NSError* err = nil; - NSDictionary* attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filename error:&err]; - if (err) { - AFLog(@"Could not get file attributes for %@", filename); - return 0; - } - uint64_t fileSize = [attrs fileSize]; - if (0 != setxattr(cfilename, kAFCacheContentLengthFileAttribute, &fileSize, sizeof(fileSize), 0, 0)) { - AFLog(@"Could not set content length for file %@", filename); - return 0; - } - - return fileSize; -} - - (void)storeCacheInfo:(NSDictionary*)dictionary { @synchronized(self) { for (NSString* key in dictionary) { @@ -293,26 +274,64 @@ - (void)performUnarchivingFailedWithItem:(AFCacheableItem*)cacheableItem cacheableItem.info.packageArchiveStatus = kAFCachePackageArchiveStatusUnarchivingFailed; } +#pragma mark - import + // import and optionally overwrite a cacheableitem. might fail if a download with the very same url is in progress. - (BOOL)importCacheableItem:(AFCacheableItem*)cacheableItem withData:(NSData*)theData { - if (cacheableItem==nil || [self isQueuedOrDownloadingURL:cacheableItem.url]) return NO; - [cacheableItem setDataAndFile:theData]; + if (cacheableItem == nil || [self isQueuedOrDownloadingURL:cacheableItem.url]) { + return NO; + } + + [cacheableItem setDataAndFile:theData]; [self.cachedItemInfos setObject:cacheableItem.info forKey:[cacheableItem.url absoluteString]]; [self archive]; + return YES; } -- (AFCacheableItem *)importObjectForURL:(NSURL *)url data:(NSData *)data -{ +- (BOOL)importCacheableItem:(AFCacheableItem*)cacheableItem dataWithFileAtURL:(NSURL *)URL { + if (cacheableItem == nil || [self isQueuedOrDownloadingURL:cacheableItem.url]) { + return NO; + } + + NSString *fullPathForCacheableItem = [self fullPathForCacheableItem:cacheableItem]; + + NSError *error = nil; + BOOL didMoveItemAtPath = [[NSFileManager defaultManager] moveItemAtPath:URL.path toPath:fullPathForCacheableItem error:&error]; + if (!didMoveItemAtPath) { + return NO; + } + + NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:fullPathForCacheableItem] options:NSDataReadingMappedIfSafe error:nil]; + cacheableItem.data = data; + cacheableItem.info.contentLength = [data length]; + + [self.cachedItemInfos setObject:cacheableItem.info forKey:[cacheableItem.url absoluteString]]; + [self archive]; + + return YES; +} + +- (AFCacheableItem *)importObjectForURL:(NSURL *)url data:(NSData *)data { AFCacheableItem *cachedItem = [self cacheableItemFromCacheStore:url]; if (cachedItem) { return cachedItem; } else { AFCacheableItem *item = [[AFCacheableItem alloc] initWithURL:url lastModified:[NSDate date] expireDate:nil]; - [self importCacheableItem:item withData:data]; - + return item; + } +} + +- (AFCacheableItem *)importObjectForURL:(NSURL *)url dataWithFileAtURL:(NSURL*)URL { + AFCacheableItem *cachedItem = [self cacheableItemFromCacheStore:url]; + if (cachedItem) { + return cachedItem; + } + else { + AFCacheableItem *item = [[AFCacheableItem alloc] initWithURL:url lastModified:[NSDate date] expireDate:nil]; + [self importCacheableItem:item dataWithFileAtURL:URL]; return item; } } diff --git a/src/shared/AFCache+PrivateAPI.h b/src/shared/AFCache+PrivateAPI.h index cec4184..930f5ac 100755 --- a/src/shared/AFCache+PrivateAPI.h +++ b/src/shared/AFCache+PrivateAPI.h @@ -27,12 +27,11 @@ - (void)updateModificationDataAndTriggerArchiving:(AFCacheableItem *)obj; - - (void)setConnectedToNetwork:(BOOL)connected; - (void)reinitialize; - (void)removeCacheEntryWithFilePath:(NSString*)filePath fileOnly:(BOOL) fileOnly; -- (NSFileHandle*)createFileForItem:(AFCacheableItem*)cacheableItem; +- (NSOutputStream*)createOutputStreamForItem:(AFCacheableItem*)cacheableItem; - (void)addItemToDownloadQueue:(AFCacheableItem*)item; - (BOOL)isQueuedURL:(NSURL*)url; - (BOOL)_fileExistsOrPendingForCacheableItem:(AFCacheableItem*)item; @@ -45,20 +44,14 @@ @end @interface AFCacheableItem (PrivateAPI) - - (BOOL)isQueuedOrDownloading; -- (BOOL)hasDownloadFileAttribute; - (BOOL)hasValidContentLength; -- (uint64_t)getContentLengthFromFile; // Making synthesized getter and setter for private property public for private API - (void)setHasReturnedCachedItemBeforeRevalidation:(BOOL)value; - (BOOL)hasReturnedCachedItemBeforeRevalidation; - @end @interface AFCacheableItemInfo (PrivateAPI) - - (NSString*)newUniqueFilename; - @end diff --git a/src/shared/AFCache.h b/src/shared/AFCache.h index fe981bd..70cb1e9 100755 --- a/src/shared/AFCache.h +++ b/src/shared/AFCache.h @@ -65,8 +65,6 @@ #define AFCachingURLHeader @"X-AFCache" #define AFCacheInternalRequestHeader @"X-AFCache-IntReq" -extern const char* kAFCacheContentLengthFileAttribute; -extern const char* kAFCacheDownloadingFileAttribute; extern const double kAFCacheInfiniteFileSize; enum { diff --git a/src/shared/AFCache.m b/src/shared/AFCache.m index 33baf16..cc1a502 100755 --- a/src/shared/AFCache.m +++ b/src/shared/AFCache.m @@ -30,6 +30,7 @@ #import "AFRegexString.h" #import "AFCache_Logging.h" #import "AFDownloadOperation.h" +#import "AFCacheableItem+FileAttributes.h" #import @@ -39,9 +40,6 @@ #define ASSERT_NO_CONNECTION_WHEN_IN_OFFLINE_MODE_FOR_URL(url) do{}while(0) #endif - -const char* kAFCacheContentLengthFileAttribute = "de.artifacts.contentLength"; -const char* kAFCacheDownloadingFileAttribute = "de.artifacts.downloading"; const double kAFCacheInfiniteFileSize = 0.0; const double kAFCacheArchiveDelay = 30.0; // archive every 30s @@ -82,13 +80,11 @@ + (AFCache *)sharedInstance { #pragma mark init methods - (id)initWithContext:(NSString*)context { - if (!context && sharedAFCacheInstance) - { + if (!context && sharedAFCacheInstance) { return [AFCache sharedInstance]; } self = [super init]; - if (self) { #if TARGET_OS_IPHONE @@ -141,24 +137,24 @@ - (void)initialize { NSString *appId = [@"afcache" stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]; _dataPath = [[[paths objectAtIndex: 0] stringByAppendingPathComponent: appId] copy]; } - + [self deserializeState]; /* check for existence of cache directory */ - if ([[NSFileManager defaultManager] fileExistsAtPath: _dataPath]) { + if ([[NSFileManager defaultManager] fileExistsAtPath:_dataPath]) { AFLog(@ "Successfully unarchived cache store"); } else { NSError *error = nil; - if (![[NSFileManager defaultManager] createDirectoryAtPath: _dataPath - withIntermediateDirectories: YES - attributes: nil - error: &error]) { + if (![[NSFileManager defaultManager] createDirectoryAtPath:_dataPath + withIntermediateDirectories:YES + attributes:nil + error:&error]) { AFLog(@ "Failed to create cache directory at path %@: %@", _dataPath, [error description]); } else { - NSString* dataPath = _dataPath; + NSString *dataPath = _dataPath; if ([[dataPath pathComponents] containsObject:@"Library"]) { while (![[dataPath lastPathComponent] isEqualToString:@"Library"] && ![[dataPath lastPathComponent] isEqualToString:@"Caches"]) { @@ -166,10 +162,10 @@ - (void)initialize { dataPath = [dataPath stringByDeletingLastPathComponent]; } } - } } - [AFCache addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:_dataPath]];//afcache + + [AFCache addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:_dataPath]]; } - (void)dealloc { @@ -243,12 +239,12 @@ - (void)reinitialize { +(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL { #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_5_1 || TARGET_OS_MAC && MAC_OS_X_VERSION_MIN_ALLOWED < MAC_OS_X_VERSION_10_8 - assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); + if (![[NSFileManager defaultManager] fileExistsAtPath:[URL path]]) { + return NO; + } NSError *error = nil; - BOOL success = [URL setResourceValue:[NSNumber numberWithBool:YES] forKey: NSURLIsExcludedFromBackupKey error:&error]; - if (!success) { NSLog(@"Error excluding %@ from backup: %@", [URL lastPathComponent], error); } @@ -257,7 +253,6 @@ +(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL NSLog(@"ERROR: System does not support excluding files from backup"); return NO; #endif - } // remove all cache entries are not in a given set @@ -923,7 +918,12 @@ -(void)saveDictionary:(NSDictionary*)dictionary ToFile:(NSString*)fileName if (serializedData) { NSError* error = nil; - if (![serializedData writeToFile:fileName options:NSDataWritingAtomic error:&error]) +#if TARGET_OS_IPHONE + NSDataWritingOptions options = NSDataWritingAtomic | NSDataWritingFileProtectionNone; +#else + NSDataWritingOptions options = NSDataWritingAtomic; +#endif + if (![serializedData writeToFile:fileName options:options error:&error]) { NSLog(@"Error: Could not write dictionary to file '%@': Error = %@, infoStore = %@", fileName, error, dictionary); } @@ -1214,13 +1214,13 @@ - (void)updateModificationDataAndTriggerArchiving: (AFCacheableItem *) cacheable /* 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]) { + if (![[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:filePath error:&error]) { NSLog(@ "Failed to reset modification date for cache item %@", filePath); } [self archive]; } -- (NSFileHandle*)createFileForItem:(AFCacheableItem*)cacheableItem +- (NSOutputStream*)createOutputStreamForItem:(AFCacheableItem*)cacheableItem { NSString *filePath = [self fullPathForCacheableItem: cacheableItem]; @@ -1251,20 +1251,26 @@ - (NSFileHandle*)createFileForItem:(AFCacheableItem*)cacheableItem // write file if (self.maxItemFileSize == kAFCacheInfiniteFileSize || cacheableItem.info.contentLength < self.maxItemFileSize) { /* file doesn't exist, so create it */ - if (![[NSFileManager defaultManager] createFileAtPath: filePath - contents: nil - attributes: nil]) +#if TARGET_OS_IPHONE + NSDictionary *fileAttributes = @{NSFileProtectionKey:NSFileProtectionNone}; +#else + NSDictionary *fileAttributes = nil; +#endif + if (![[NSFileManager defaultManager] createFileAtPath:filePath + contents:nil + attributes:fileAttributes]) { AFLog(@"Error: could not create file \"%@\"", filePath); } - [AFCache addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:filePath]]; - NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath]; - if (!fileHandle) { - AFLog(@"Could not get file handle for file at path: %@", filePath); - } else { - AFLog(@"created file at path %@ (%d)", filePath, [fileHandle fileDescriptor]); - } - return fileHandle; + + NSURL *fileURL = [NSURL fileURLWithPath:filePath]; + + [AFCache addSkipBackupAttributeToItemAtURL:fileURL]; + + NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO]; + [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [outputStream open]; + return outputStream; } else { NSLog(@ "AFCache: item %@ \nsize exceeds maxItemFileSize (%f). Won't write file to disk",cacheableItem.url, self.maxItemFileSize); diff --git a/src/shared/AFCacheableItem+FileAttributes.h b/src/shared/AFCacheableItem+FileAttributes.h new file mode 100644 index 0000000..bf2cd19 --- /dev/null +++ b/src/shared/AFCacheableItem+FileAttributes.h @@ -0,0 +1,19 @@ +// +// AFCacheableItem+FileAttributes.h +// AFCache +// +// Created by Sebastian Grimme on 17.05.16. +// Copyright © 2016 Artifacts - Fine Software Development. All rights reserved. +// + +#import + +extern const char* kAFCacheContentLengthFileAttribute; +extern const char* kAFCacheDownloadingFileAttribute; + +@interface AFCacheableItem (FileAttributes) +- (BOOL)hasDownloadFileAttribute; +- (void)flagAsDownloadStartedWithContentLength:(uint64_t)contentLength; +- (void)flagAsDownloadFinishedWithContentLength:(uint64_t)contentLength; +- (uint64_t)getContentLengthFromFile; +@end diff --git a/src/shared/AFCacheableItem+FileAttributes.m b/src/shared/AFCacheableItem+FileAttributes.m new file mode 100644 index 0000000..30a1195 --- /dev/null +++ b/src/shared/AFCacheableItem+FileAttributes.m @@ -0,0 +1,74 @@ +// +// AFCacheableItem+FileAttributes.m +// AFCache +// +// Created by Sebastian Grimme on 17.05.16. +// Copyright © 2016 Artifacts - Fine Software Development. All rights reserved. +// + +#import "AFCacheableItem+FileAttributes.h" + +#import "AFCache+PrivateAPI.h" +#import "AFCache_Logging.h" +#import "AFCacheableItem.h" +#include + +const char* kAFCacheContentLengthFileAttribute = "de.artifacts.contentLength"; +const char* kAFCacheDownloadingFileAttribute = "de.artifacts.downloading"; + +@implementation AFCacheableItem (FileAttributes) + +- (BOOL)hasDownloadFileAttribute { + unsigned int downloading = 0; + NSString *filePath = [self.cache fullPathForCacheableItem:self]; + return sizeof(downloading) == getxattr([filePath fileSystemRepresentation], kAFCacheDownloadingFileAttribute, &downloading, sizeof(downloading), 0, 0); +} + +- (void)flagAsDownloadStartedWithContentLength:(uint64_t)contentLength { + NSString *filePath = [self.cache fullPathForCacheableItem:self]; + if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + return; + } + if (0 != setxattr(filePath.fileSystemRepresentation, kAFCacheContentLengthFileAttribute, &contentLength, sizeof(uint64_t), 0, 0)) { + AFLog(@"Could not set contentLength attribute on %@", self); + } + unsigned int downloading = 1; + if (0 != setxattr(filePath.fileSystemRepresentation, kAFCacheDownloadingFileAttribute, &downloading, sizeof(downloading), 0, 0)) { + AFLog(@"Could not set downloading attribute on %@", self); + } +} + +- (void)flagAsDownloadFinishedWithContentLength:(uint64_t)contentLength { + NSString *filePath = [self.cache fullPathForCacheableItem:self]; + if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + return; + } + if (0 != setxattr(filePath.fileSystemRepresentation, kAFCacheContentLengthFileAttribute, &contentLength, sizeof(uint64_t), 0, 0)) { + AFLog(@"Could not set contentLength attribute on %@, errno = %ld", self, (long)errno ); + } + if (0 != removexattr(filePath.fileSystemRepresentation, kAFCacheDownloadingFileAttribute, 0)) { + AFLog(@"Could not remove downloading attribute on %@, errno = %ld", self, (long)errno ); + } +} + +- (uint64_t)getContentLengthFromFile { + if ([self isQueuedOrDownloading]) { + return 0LL; + } + + NSString *filePath = [self.cache fullPathForCacheableItem:self]; + + uint64_t realContentLength = 0LL; + ssize_t const size = getxattr([filePath fileSystemRepresentation], + kAFCacheContentLengthFileAttribute, + &realContentLength, + sizeof(realContentLength), + 0, 0); + if (sizeof(realContentLength) != size) { + AFLog(@"Could not get content length attribute from file %@. This may be bad (errno = %ld", filePath, (long)errno); + return 0LL; + } + return realContentLength; +} + +@end diff --git a/src/shared/AFCacheableItem+Packaging.m b/src/shared/AFCacheableItem+Packaging.m index a5d8585..d4bddfa 100755 --- a/src/shared/AFCacheableItem+Packaging.m +++ b/src/shared/AFCacheableItem+Packaging.m @@ -7,9 +7,9 @@ // #import "AFCacheableItem+Packaging.h" +#import "AFCacheableItem+FileAttributes.h" #import "DateParser.h" #import "AFCache+PrivateAPI.h" -#import "NSFileHandle+AFCache.h" @implementation AFCacheableItem (Packaging) @@ -58,14 +58,22 @@ - (void)setDataAndFile:(NSData*)data { self.info.contentLength = [data length]; // Store data into file - NSFileHandle* fileHandle = [self.cache createFileForItem:self]; - [fileHandle seekToFileOffset:0]; - - //this will throw an exception when the disk is full - [fileHandle writeData:data]; - - [fileHandle flagAsDownloadFinishedWithContentLength:[data length]]; - [fileHandle closeFile]; + NSOutputStream *outputStream = [self.cache createOutputStreamForItem:self]; + if (outputStream.hasSpaceAvailable) { + NSInteger bytesWritten = [outputStream write:data.bytes maxLength:data.length]; + if (bytesWritten != data.length) { + if ([self.delegate respondsToSelector:@selector(cannotWriteDataForItem:)]) { + [self.delegate cannotWriteDataForItem:self]; + } + } + } else { + if ([self.delegate respondsToSelector:@selector(cannotWriteDataForItem:)]) { + [self.delegate cannotWriteDataForItem:self]; + } + } + [outputStream close]; + + [self flagAsDownloadFinishedWithContentLength:data.length]; } @end diff --git a/src/shared/AFCacheableItem.h b/src/shared/AFCacheableItem.h index cd45068..659dc76 100755 --- a/src/shared/AFCacheableItem.h +++ b/src/shared/AFCacheableItem.h @@ -133,5 +133,6 @@ typedef void (^AFCacheableItemBlock)(AFCacheableItem* item); - (void) packageArchiveDidFailLoading: (AFCacheableItem *) cacheableItem; - (void) cacheableItemDidReceiveData: (AFCacheableItem *) cacheableItem; +- (void) cannotWriteDataForItem: (AFCacheableItem*)cacheableItem; @end diff --git a/src/shared/AFCacheableItem.m b/src/shared/AFCacheableItem.m index 5e00367..89c5d73 100644 --- a/src/shared/AFCacheableItem.m +++ b/src/shared/AFCacheableItem.m @@ -19,25 +19,22 @@ */ #import "AFCacheableItem.h" +#import "AFCacheableItem+FileAttributes.h" #import "AFCache+PrivateAPI.h" #import "AFCache_Logging.h" -#include @interface AFCacheableItem () - @property NSMutableArray *completionBlocks; @property NSMutableArray *failBlocks; @property NSMutableArray *progressBlocks; - @property BOOL hasReturnedCachedItemBeforeRevalidation; - @end @implementation AFCacheableItem -- (id) init { +- (instancetype)init { self = [super init]; - if (self != nil) { + if (self) { _data = nil; _canMapData = YES; _cacheStatus = kCacheStatusNew; @@ -218,13 +215,6 @@ - (BOOL)isFresh { return fresh; } -- (BOOL)hasDownloadFileAttribute -{ - unsigned int downloading = 0; - NSString *filePath = [self.cache fullPathForCacheableItem:self]; - return sizeof(downloading) == getxattr([filePath fileSystemRepresentation], kAFCacheDownloadingFileAttribute, &downloading, sizeof(downloading), 0, 0); -} - - (BOOL)hasValidContentLength { NSString* filePath = [self.cache fullPathForCacheableItem:self]; @@ -255,31 +245,6 @@ - (BOOL)isQueuedOrDownloading return [self.cache isQueuedOrDownloadingURL:self.url]; } - -- (uint64_t)getContentLengthFromFile -{ - if ([self isQueuedOrDownloading]) - { - return 0LL; - } - NSString *filePath = [self.cache fullPathForCacheableItem:self]; - - uint64_t realContentLength = 0LL; - ssize_t const size = getxattr([filePath fileSystemRepresentation], - kAFCacheContentLengthFileAttribute, - &realContentLength, - sizeof(realContentLength), - 0, 0); - if (sizeof(realContentLength) != size ) - { - AFLog(@"Could not get content length attribute from file %@. This may be bad (errno = %ld", - filePath, (long)errno ); - return 0LL; - } - - return realContentLength; -} - - (NSString *)asString { if (!self.data) return nil; return [[NSString alloc] initWithData: self.data encoding: NSUTF8StringEncoding]; @@ -333,9 +298,8 @@ - (BOOL)isComplete { return [self isDataLoaded] && (self.info.actualLength >= self.info.contentLength); } -- (BOOL)isDataLoaded -{ - return self.info.actualLength >0;// data != nil; +- (BOOL)isDataLoaded { + return self.info.actualLength > 0; } -(uint64_t)currentContentLength{ diff --git a/src/shared/AFDownloadOperation.m b/src/shared/AFDownloadOperation.m index ed7250b..6e941f2 100644 --- a/src/shared/AFDownloadOperation.m +++ b/src/shared/AFDownloadOperation.m @@ -7,18 +7,16 @@ // #import +#import #import #import "AFDownloadOperation.h" #import "AFCache+PrivateAPI.h" #import "AFCache_Logging.h" #import "DateParser.h" -#import "NSFileHandle+AFCache.h" @interface AFDownloadOperation () - @property(nonatomic, strong) NSURLConnection *connection; -@property(nonatomic, strong) NSFileHandle *fileHandle; - +@property(nonatomic, strong) NSOutputStream *outputStream; @end @implementation AFDownloadOperation @@ -67,12 +65,13 @@ - (void)start { _isExecuting = YES; [self didChangeValueForKey:@"isExecuting"]; - self.connection = [[NSURLConnection alloc] initWithRequest:self.cacheableItem.info.request delegate:self]; + self.connection = [[NSURLConnection alloc] initWithRequest:self.cacheableItem.info.request delegate:self startImmediately:YES]; } - (void)finish { [self.connection cancel]; - [self.fileHandle closeFile]; + [self.outputStream close]; + [self.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [self willChangeValueForKey:@"isExecuting"]; [self willChangeValueForKey:@"isFinished"]; @@ -82,7 +81,7 @@ - (void)finish { [self didChangeValueForKey:@"isExecuting"]; } -#pragma mark NSURLConnectionDelegate and NSURLConnectionDataDelegate methods +#pragma mark - NSURLConnectionDelegate and NSURLConnectionDataDelegate methods /* * This method gives the delegate an opportunity to inspect the request that will be used to continue loading the @@ -90,8 +89,7 @@ - (void)finish { * transforming a request URL to its canonical form, or can happen for protocol-specific reasons, such as an HTTP * redirect. */ - -- (NSURLRequest *)connection: (NSURLConnection *)connection willSendRequest: (NSURLRequest *)request redirectResponse: (NSURLResponse *)redirectResponse { +- (NSURLRequest*)connection:(NSURLConnection*)connection willSendRequest:(NSURLRequest*)request redirectResponse:(NSURLResponse*)redirectResponse { NSMutableURLRequest *theRequest = [request mutableCopy]; if (self.cacheableItem.cache.userAgent) { @@ -104,8 +102,6 @@ - (NSURLRequest *)connection: (NSURLConnection *)connection willSendRequest: (NS // TODO: Check if this redirect code is fine here, it seemed broken in the original place and I corrected it as I thought it should be if (redirectResponse && [request URL]) { - //[theRequest setURL:[redirectResponse URL]]; - self.cacheableItem.info.responseURL = [request URL]; self.cacheableItem.info.redirectRequest = request; self.cacheableItem.info.redirectResponse = redirectResponse; @@ -125,8 +121,7 @@ - (NSURLRequest *)connection: (NSURLConnection *)connection willSendRequest: (NS * connectionDidFinishLoading: with the cached object and cancel the original request. If the object is stale, we go on * with the request. */ - -- (void)connection: (NSURLConnection *) connection didReceiveResponse: (NSURLResponse *) response { +- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response { self.cacheableItem.cache.connectedToNetwork = YES; if (self.isCancelled) { @@ -152,24 +147,27 @@ - (void)connection: (NSURLConnection *) connection didReceiveResponse: (NSURLRes } } -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { +- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data { if (self.isCancelled) { [self finish]; return; } - // Append data to the end of download file - [self.fileHandle seekToEndOfFile]; - //TODO: handle "disk full"-situation. - //TODO: use NSOutputStream - @try { - [self.fileHandle writeData:data]; - } - @catch (NSException *exception) { - NSLog(@"ERROR: DownloadOperation failed with exception : %@",exception); - [self finish]; - } - @finally { + if (self.outputStream.hasSpaceAvailable) { + NSInteger bytesWritten = [self.outputStream write:data.bytes maxLength:data.length]; + if (bytesWritten != data.length) { + if ([self.cacheableItem.delegate respondsToSelector:@selector(cannotWriteDataForItem:)]) { + [self.cacheableItem.delegate cannotWriteDataForItem:self.cacheableItem]; + } + [self finishWithError]; + return; + } + } else { + if ([self.cacheableItem.delegate respondsToSelector:@selector(cannotWriteDataForItem:)]) { + [self.cacheableItem.delegate cannotWriteDataForItem:self.cacheableItem]; + } + [self finishWithError]; + return; } self.cacheableItem.info.actualLength += [data length]; @@ -217,15 +215,15 @@ - (void)connectionDidFinishLoading: (NSURLConnection *) connection { break; default: { - NSError *err = nil; + NSError *error = nil; if (!self.cacheableItem.url) { - err = [NSError errorWithDomain:@"URL is nil" code:99 userInfo:nil]; + error = [NSError errorWithDomain:@"URL is nil" code:99 userInfo:nil]; } // Test for correct content length NSString *path = [self.cacheableItem.cache fullPathForCacheableItem:self.cacheableItem]; - NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err]; + NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; if (attr) { uint64_t fileSize = [attr fileSize]; if (fileSize != self.cacheableItem.info.contentLength) { @@ -235,9 +233,9 @@ - (void)connectionDidFinishLoading: (NSURLConnection *) connection { AFLog(@"Failed to get file attributes for file at path %@. Error: %@", path, [err description]); } - [self.fileHandle flagAsDownloadFinishedWithContentLength:self.cacheableItem.info.contentLength]; + [self.cacheableItem flagAsDownloadFinishedWithContentLength:self.cacheableItem.info.contentLength]; - if (err) { + if (error) { AFLog(@"Error while finishing download: %@", [err localizedDescription]); } else { // Only cache response if it has a validUntil date and only if we're not in offline mode. @@ -262,7 +260,7 @@ - (void)connectionDidFinishLoading: (NSURLConnection *) connection { * TODO: This comment is wrong. Item is not removed from cache here. Should it be removed as the comment says? */ -- (void)connection: (NSURLConnection *) connection didFailWithError: (NSError *) anError { +- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)anError { if (self.isCancelled) { [self finish]; return; @@ -292,10 +290,19 @@ - (void)connection: (NSURLConnection *) connection didFailWithError: (NSError *) if (sendSuccessDespiteError) { [self.cacheableItem sendSuccessSignalToClientItems]; } else { + self.cacheableItem.info.actualLength = 0; + self.cacheableItem.currentContentLength = 0; [self.cacheableItem sendFailSignalToClientItems]; } } +- (void)finishWithError { + [self finish]; + self.cacheableItem.info.actualLength = 0; + self.cacheableItem.currentContentLength = 0; + [self.cacheableItem sendFailSignalToClientItems]; +} + #pragma mark - NSURLConnectionDelegate authentication methods /* @@ -378,11 +385,11 @@ - (void)handleResponse:(NSURLResponse *)response { } if (self.cacheableItem.info.statusCode == 200) { - self.fileHandle = [self.cacheableItem.cache createFileForItem:self.cacheableItem]; + self.outputStream = [self.cacheableItem.cache createOutputStreamForItem:self.cacheableItem]; } // TODO: Isn't self.cacheableItem.info.contentLength always 0 at this moment? - [self.fileHandle flagAsDownloadStartedWithContentLength:self.cacheableItem.info.contentLength]; + [self.cacheableItem flagAsDownloadStartedWithContentLength:self.cacheableItem.info.contentLength]; if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Handle response header fields to calculate expiration time for newly fetched object to determine until when we may cache it diff --git a/src/shared/NSFileHandle+AFCache.h b/src/shared/NSFileHandle+AFCache.h deleted file mode 100644 index 722bb26..0000000 --- a/src/shared/NSFileHandle+AFCache.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// NSFileHandle+AFCache.h -// AFCache -// -// Created by Lars Blumberg on 15.08.14. -// Copyright (c) 2014 Artifacts - Fine Software Development. All rights reserved. -// - -#import - -@interface NSFileHandle (AFCache) - -- (void)flagAsDownloadStartedWithContentLength: (uint64_t)contentLength; - -- (void)flagAsDownloadFinishedWithContentLength: (uint64_t)contentLength; - -@end diff --git a/src/shared/NSFileHandle+AFCache.m b/src/shared/NSFileHandle+AFCache.m deleted file mode 100644 index 7613ffc..0000000 --- a/src/shared/NSFileHandle+AFCache.m +++ /dev/null @@ -1,46 +0,0 @@ -// -// NSFileHandle+AFCache.m -// AFCache -// -// Created by Lars Blumberg on 15.08.14. -// Copyright (c) 2014 Artifacts - Fine Software Development. All rights reserved. -// - -#include -#import -#import "NSFileHandle+AFCache.h" -#import "AFCache_Logging.h" - -//const char* kAFCacheContentLengthFileAttribute = "de.artifacts.contentLength"; -//const char* kAFCacheDownloadingFileAttribute = "de.artifacts.downloading"; - -@implementation NSFileHandle (AFCache) - -- (void)flagAsDownloadStartedWithContentLength: (uint64_t)contentLength { - int fd = [self fileDescriptor]; - if (fd <= 0) { - return; - } - if (0 != fsetxattr(fd, kAFCacheContentLengthFileAttribute, &contentLength, sizeof(uint64_t), 0, 0)) { - AFLog(@"Could not set contentLength attribute on %@", self); - } - unsigned int downloading = 1; - if (0 != fsetxattr(fd, kAFCacheDownloadingFileAttribute, &downloading, sizeof(downloading), 0, 0)) { - AFLog(@"Could not set downloading attribute on %@", self); - } -} - -- (void)flagAsDownloadFinishedWithContentLength: (uint64_t)contentLength { - int fd = [self fileDescriptor]; - if (fd <= 0) { - return; - } - if (0 != fsetxattr(fd, kAFCacheContentLengthFileAttribute, &contentLength, sizeof(uint64_t), 0, 0)) { - AFLog(@"Could not set contentLength attribute on %@, errno = %ld", self, (long)errno ); - } - if (0 != fremovexattr(fd, kAFCacheDownloadingFileAttribute, 0)) { - AFLog(@"Could not remove downloading attribute on %@, errno = %ld", self, (long)errno ); - } -} - -@end