Merge pull request #2898 from dreampiggy/feature_extended_metadata_disk_cache

Allows advanced user to read/write extended metadata associated with image data from disk cache
This commit is contained in:
DreamPiggy 2019-11-29 14:58:43 +08:00 committed by GitHub
commit 6d7a68e07e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 374 additions and 0 deletions

View File

@ -89,6 +89,12 @@
325C46272233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */; settings = {ATTRIBUTES = (Private, ); }; };
325C46282233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */; };
325C46292233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */; };
325F7CC623893B2E00AEDFCC /* SDFileAttributeHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */; settings = {ATTRIBUTES = (Private, ); }; };
325F7CC723893B2E00AEDFCC /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */; };
325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */; settings = {ATTRIBUTES = (Public, ); }; };
325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */; };
325F7CCC2389463D00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */; };
325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */; };
326E2F2E236F0B23006F847F /* SDAnimatedImagePlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 326E2F2C236F0B23006F847F /* SDAnimatedImagePlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
326E2F2F236F0B23006F847F /* SDAnimatedImagePlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */; };
326E2F30236F0B23006F847F /* SDAnimatedImagePlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */; };
@ -298,6 +304,7 @@
dstPath = include/SDWebImage;
dstSubfolderSpec = 16;
files = (
325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */,
326E2F36236F1E30006F847F /* SDAnimatedImagePlayer.h in Copy Headers */,
3250C9F12355E3DF0093A896 /* SDWebImageDownloaderDecryptor.h in Copy Headers */,
325427662355783C0042BAA4 /* SDWebImageDownloaderResponseModifier.h in Copy Headers */,
@ -407,6 +414,10 @@
325C461F2233A02E004CAE11 /* UIColor+HexString.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+HexString.m"; sourceTree = "<group>"; };
325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSBezierPath+RoundedCorners.h"; sourceTree = "<group>"; };
325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSBezierPath+RoundedCorners.m"; sourceTree = "<group>"; };
325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDFileAttributeHelper.h; sourceTree = "<group>"; };
325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDFileAttributeHelper.m; sourceTree = "<group>"; };
325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIImage+ExtendedCacheData.h"; path = "Core/UIImage+ExtendedCacheData.h"; sourceTree = "<group>"; };
325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ExtendedCacheData.m"; path = "Core/UIImage+ExtendedCacheData.m"; sourceTree = "<group>"; };
326E2F2C236F0B23006F847F /* SDAnimatedImagePlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImagePlayer.h; path = Core/SDAnimatedImagePlayer.h; sourceTree = "<group>"; };
326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImagePlayer.m; path = Core/SDAnimatedImagePlayer.m; sourceTree = "<group>"; };
326E2F31236F1D58006F847F /* SDDeviceHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDDeviceHelper.h; sourceTree = "<group>"; };
@ -635,6 +646,8 @@
325C461F2233A02E004CAE11 /* UIColor+HexString.m */,
325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */,
325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */,
325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */,
325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */,
329F123F223FAD3400B309FD /* SDInternalMacros.h */,
329F123E223FAD3400B309FD /* SDInternalMacros.m */,
329F1235223FAA3B00B309FD /* SDmetamacros.h */,
@ -744,6 +757,8 @@
children = (
5D5B9140188EE8DD006D06BD /* NSData+ImageContentType.h */,
5D5B9141188EE8DD006D06BD /* NSData+ImageContentType.m */,
325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */,
325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */,
A18A6CC5172DC28500419892 /* UIImage+GIF.h */,
A18A6CC6172DC28500419892 /* UIImage+GIF.m */,
329A18571FFF5DFD008C9A2F /* UIImage+Metadata.h */,
@ -849,6 +864,7 @@
3257EAFA21898AED0097B271 /* SDImageGraphics.h in Headers */,
32D3CDD121DDE87300C4DB49 /* UIImage+MemoryCacheCost.h in Headers */,
328BB6AC2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */,
325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */,
325C46272233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h in Headers */,
321B378F2083290E00C0EA77 /* SDImageLoadersManager.h in Headers */,
329A185B1FFF5DFD008C9A2F /* UIImage+Metadata.h in Headers */,
@ -906,6 +922,7 @@
32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
32F7C0712030114C00873181 /* SDImageTransformer.h in Headers */,
32E67311235765B500DB4987 /* SDDisplayLink.h in Headers */,
325F7CC623893B2E00AEDFCC /* SDFileAttributeHelper.h in Headers */,
4A2CAE2D1AB4BB7500B6BC39 /* UIImage+GIF.h in Headers */,
4A2CAE291AB4BB7500B6BC39 /* NSData+ImageContentType.h in Headers */,
328BB69E2081FED200760D6C /* SDWebImageCacheKeyFilter.h in Headers */,
@ -1098,6 +1115,7 @@
3257EAFD21898AED0097B271 /* SDImageGraphics.m in Sources */,
3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */,
325C46232233A02E004CAE11 /* UIColor+HexString.m in Sources */,
325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */,
321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */,
3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */,
3250C9F02355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */,
@ -1127,6 +1145,7 @@
321E609C1F38E8ED00405457 /* SDImageIOCoder.m in Sources */,
4A2CAE261AB4BB7000B6BC39 /* SDWebImagePrefetcher.m in Sources */,
328BB6C92082581100760D6C /* SDDiskCache.m in Sources */,
325F7CC723893B2E00AEDFCC /* SDFileAttributeHelper.m in Sources */,
3248475F201775F600AF9E5A /* SDAnimatedImageView.m in Sources */,
32D1222C2080B2EB003685A3 /* SDImageCachesManager.m in Sources */,
32B9B53F206ED4230026769D /* SDWebImageDownloaderConfig.m in Sources */,
@ -1198,6 +1217,7 @@
5376130D155AD0D5005750A4 /* SDWebImagePrefetcher.m in Sources */,
328BB6C72082581100760D6C /* SDDiskCache.m in Sources */,
3248475D201775F600AF9E5A /* SDAnimatedImageView.m in Sources */,
325F7CCC2389463D00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */,
32D1222A2080B2EB003685A3 /* SDImageCachesManager.m in Sources */,
32B9B53D206ED4230026769D /* SDWebImageDownloaderConfig.m in Sources */,
43A9186B1D8308FE00B3925F /* SDImageCacheConfig.m in Sources */,

View File

@ -54,6 +54,26 @@
*/
- (void)setData:(nullable NSData *)data forKey:(nonnull NSString *)key;
/**
Returns the extended data associated with a given key.
This method may blocks the calling thread until file read finished.
@param key A string identifying the data. If nil, just return nil.
@return The value associated with key, or nil if no value is associated with key.
*/
- (nullable NSData *)extendedDataForKey:(nonnull NSString *)key;
/**
Set extended data with a given key.
@discussion You can set any extended data to exist cache key. Without override the exist disk file data.
on UNIX, the common way for this is to use the Extended file attributes (xattr)
@param extendedData The extended data (pass nil to remove).
@param key The key with which to associate the value. If nil, this method has no effect.
*/
- (void)setExtendedData:(nullable NSData *)extendedData forKey:(nonnull NSString *)key;
/**
Removes the value of the specified key in the cache.
This method may blocks the calling thread until file delete finished.

View File

@ -8,8 +8,11 @@
#import "SDDiskCache.h"
#import "SDImageCacheConfig.h"
#import "SDFileAttributeHelper.h"
#import <CommonCrypto/CommonDigest.h>
static NSString * const SDDiskCacheExtendedAttributeName = @"com.hackemist.SDDiskCache";
@interface SDDiskCache ()
@property (nonatomic, copy) NSString *diskCachePath;
@ -95,6 +98,31 @@
}
}
- (NSData *)extendedDataForKey:(NSString *)key {
NSParameterAssert(key);
// get cache Path for image key
NSString *cachePathForKey = [self cachePathForKey:key];
NSData *extendedData = [SDFileAttributeHelper extendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
return extendedData;
}
- (void)setExtendedData:(NSData *)extendedData forKey:(NSString *)key {
NSParameterAssert(key);
// get cache Path for image key
NSString *cachePathForKey = [self cachePathForKey:key];
if (!extendedData) {
// Remove
[SDFileAttributeHelper removeExtendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
} else {
// Override
[SDFileAttributeHelper setExtendedAttribute:SDDiskCacheExtendedAttributeName value:extendedData atPath:cachePathForKey traverseLink:NO overwrite:YES error:nil];
}
}
- (void)removeDataForKey:(NSString *)key {
NSParameterAssert(key);
NSString *filePath = [self cachePathForKey:key];

View File

@ -14,6 +14,7 @@
#import "SDAnimatedImage.h"
#import "UIImage+MemoryCacheCost.h"
#import "UIImage+Metadata.h"
#import "UIImage+ExtendedCacheData.h"
@interface SDImageCache ()
@ -197,6 +198,29 @@
data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
}
[self _storeImageDataToDisk:data forKey:key];
if (image) {
// Check extended data
id extendedObject = image.sd_extendedObject;
if ([extendedObject conformsToProtocol:@protocol(NSCoding)]) {
NSData *extendedData;
if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
NSError *error;
extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject requiringSecureCoding:NO error:&error];
if (error) {
NSLog(@"NSKeyedArchiver archive failed with error: %@", error);
}
} else {
@try {
extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject];
} @catch (NSException *exception) {
NSLog(@"NSKeyedArchiver archive failed with exception: %@", exception);
}
}
if (extendedData) {
[self.diskCache setExtendedData:extendedData forKey:key];
}
}
}
}
if (completionBlock) {
@ -346,6 +370,29 @@
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options context:(SDWebImageContext *)context {
if (data) {
UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context);
if (image) {
// Check extended data
NSData *extendedData = [self.diskCache extendedDataForKey:key];
if (extendedData) {
id extendedObject;
if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
NSError *error;
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:extendedData error:&error];
unarchiver.requiresSecureCoding = NO;
extendedObject = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error];
if (error) {
NSLog(@"NSKeyedUnarchiver unarchive failed with error: %@", error);
}
} else {
@try {
extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData:extendedData];
} @catch (NSException *exception) {
NSLog(@"NSKeyedUnarchiver unarchive failed with exception: %@", exception);
}
}
image.sd_extendedObject = extendedObject;
}
}
return image;
} else {
return nil;

View File

@ -17,6 +17,10 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i
*/
@protocol SDWebImageCacheSerializer <NSObject>
/// Provide the image data associated to the image and store to disk cache
/// @param image The loaded image
/// @param data The original loaded image data
/// @param imageURL The image URL
- (nullable NSData *)cacheDataWithImage:(nonnull UIImage *)image originalData:(nullable NSData *)data imageURL:(nullable NSURL *)imageURL;
@end

View File

@ -9,6 +9,7 @@
#import "SDWebImageDefine.h"
#import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h"
#import "UIImage+ExtendedCacheData.h"
#pragma mark - Image scale
@ -112,6 +113,7 @@ inline UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage *
}
scaledImage.sd_isIncremental = image.sd_isIncremental;
scaledImage.sd_imageFormat = image.sd_imageFormat;
scaledImage.sd_extendedObject = image.sd_extendedObject;
return scaledImage;
}

View File

@ -10,6 +10,7 @@
#import "SDImageCache.h"
#import "SDWebImageDownloader.h"
#import "UIImage+Metadata.h"
#import "UIImage+ExtendedCacheData.h"
#import "SDWebImageError.h"
#import "SDInternalMacros.h"
@ -341,6 +342,10 @@ static id<SDImageLoader> _defaultImageLoader;
} else {
cacheData = (imageWasTransformed ? nil : downloadedData);
}
// keep the original image format and extended data
transformedImage.sd_isIncremental = downloadedImage.sd_isIncremental;
transformedImage.sd_imageFormat = downloadedImage.sd_imageFormat;
transformedImage.sd_extendedObject = downloadedImage.sd_extendedObject;
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
}

View File

@ -0,0 +1,24 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Fabrice Aneche
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
@interface UIImage (ExtendedCacheData)
/**
Read and Write the extended object and bind it to the image. Which can hold some extra metadata like Image's scale factor, URL rich link, date, etc.
The extended object should conforms to NSCoding, which we use `NSKeyedArchiver` and `NSKeyedUnarchiver` to archive it to data, and write to disk cache.
@note The disk cache preserve both of the data and extended data with the same cache key. For manual query, use the `SDDiskCache` protocol method `extendedDataForKey:` instead.
@note You can specify arbitrary object conforms to NSCoding (NSObject protocol here is used to support object using `NS_ROOT_CLASS`, which is not NSObject subclass). If you load image from disk cache, you should check the extended object class to avoid corrupted data.
@warning This object don't need to implements NSSecureCoding (but it's recommended), because we allows arbitrary class.
*/
@property (nonatomic, strong, nullable) id<NSObject, NSCoding> sd_extendedObject;
@end

View File

@ -0,0 +1,23 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Fabrice Aneche
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImage+ExtendedCacheData.h"
#import <objc/runtime.h>
@implementation UIImage (ExtendedCacheData)
- (id<NSObject, NSCoding>)sd_extendedObject {
return objc_getAssociatedObject(self, @selector(sd_extendedObject));
}
- (void)setSd_extendedObject:(id<NSObject, NSCoding>)sd_extendedObject {
objc_setAssociatedObject(self, @selector(sd_extendedObject), sd_extendedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

View File

@ -0,0 +1,18 @@
//
// This file is from https://gist.github.com/zydeco/6292773
//
// Created by Jesús A. Álvarez on 2008-12-17.
// Copyright 2008-2009 namedfork.net. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface SDFileAttributeHelper : NSObject
+ (NSArray*)extendedAttributeNamesAtPath:(NSString*)path traverseLink:(BOOL)follow error:(NSError**)err;
+ (BOOL)hasExtendedAttribute:(NSString*)name atPath:(NSString*)path traverseLink:(BOOL)follow error:(NSError**)err;
+ (NSData*)extendedAttribute:(NSString*)name atPath:(NSString*)path traverseLink:(BOOL)follow error:(NSError**)err;
+ (BOOL)setExtendedAttribute:(NSString*)name value:(NSData*)value atPath:(NSString*)path traverseLink:(BOOL)follow overwrite:(BOOL)overwrite error:(NSError**)err;
+ (BOOL)removeExtendedAttribute:(NSString*)name atPath:(NSString*)path traverseLink:(BOOL)follow error:(NSError**)err;
@end

View File

@ -0,0 +1,127 @@
//
// This file is from https://gist.github.com/zydeco/6292773
//
// Created by Jesús A. Álvarez on 2008-12-17.
// Copyright 2008-2009 namedfork.net. All rights reserved.
//
#import "SDFileAttributeHelper.h"
#import <sys/xattr.h>
@implementation SDFileAttributeHelper
+ (NSArray*)extendedAttributeNamesAtPath:(NSString*)path traverseLink:(BOOL)follow error:(NSError**)err {
int flags = follow? 0 : XATTR_NOFOLLOW;
// get size of name list
ssize_t nameBuffLen = listxattr([path fileSystemRepresentation], NULL, 0, flags);
if (nameBuffLen == -1) {
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String:strerror(errno)], @"error",
@"listxattr", @"function",
path, @":path",
[NSNumber numberWithBool:follow], @":traverseLink",
nil]
];
return nil;
} else if (nameBuffLen == 0) return [NSArray array];
// get name list
NSMutableData *nameBuff = [NSMutableData dataWithLength:nameBuffLen];
listxattr([path fileSystemRepresentation], [nameBuff mutableBytes], nameBuffLen, flags);
// convert to array
NSMutableArray * names = [NSMutableArray arrayWithCapacity:5];
char *nextName, *endOfNames = [nameBuff mutableBytes] + nameBuffLen;
for(nextName = [nameBuff mutableBytes]; nextName < endOfNames; nextName += 1+strlen(nextName))
[names addObject:[NSString stringWithUTF8String:nextName]];
return [NSArray arrayWithArray:names];
}
+ (BOOL)hasExtendedAttribute:(NSString*)name atPath:(NSString*)path traverseLink:(BOOL)follow error:(NSError**)err {
int flags = follow? 0 : XATTR_NOFOLLOW;
// get size of name list
ssize_t nameBuffLen = listxattr([path fileSystemRepresentation], NULL, 0, flags);
if (nameBuffLen == -1) {
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String:strerror(errno)], @"error",
@"listxattr", @"function",
path, @":path",
[NSNumber numberWithBool:follow], @":traverseLink",
nil]
];
return NO;
} else if (nameBuffLen == 0) return NO;
// get name list
NSMutableData *nameBuff = [NSMutableData dataWithLength:nameBuffLen];
listxattr([path fileSystemRepresentation], [nameBuff mutableBytes], nameBuffLen, flags);
// find our name
char *nextName, *endOfNames = [nameBuff mutableBytes] + nameBuffLen;
for(nextName = [nameBuff mutableBytes]; nextName < endOfNames; nextName += 1+strlen(nextName))
if (strcmp(nextName, [name UTF8String]) == 0) return YES;
return NO;
}
+ (NSData*)extendedAttribute:(NSString*)name atPath:(NSString*)path traverseLink:(BOOL)follow error:(NSError**)err {
int flags = follow? 0 : XATTR_NOFOLLOW;
// get length
ssize_t attrLen = getxattr([path fileSystemRepresentation], [name UTF8String], NULL, 0, 0, flags);
if (attrLen == -1) {
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String:strerror(errno)], @"error",
@"getxattr", @"function",
name, @":name",
path, @":path",
[NSNumber numberWithBool:follow], @":traverseLink",
nil]
];
return nil;
}
// get attribute data
NSMutableData * attrData = [NSMutableData dataWithLength:attrLen];
getxattr([path fileSystemRepresentation], [name UTF8String], [attrData mutableBytes], attrLen, 0, flags);
return attrData;
}
+ (BOOL)setExtendedAttribute:(NSString*)name value:(NSData*)value atPath:(NSString*)path traverseLink:(BOOL)follow overwrite:(BOOL)overwrite error:(NSError**)err {
int flags = (follow? 0 : XATTR_NOFOLLOW) | (overwrite? 0 : XATTR_CREATE);
if (0 == setxattr([path fileSystemRepresentation], [name UTF8String], [value bytes], [value length], 0, flags)) return YES;
// error
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String:strerror(errno)], @"error",
@"setxattr", @"function",
name, @":name",
[NSNumber numberWithUnsignedInteger:[value length]], @":value.length",
path, @":path",
[NSNumber numberWithBool:follow], @":traverseLink",
[NSNumber numberWithBool:overwrite], @":overwrite",
nil]
];
return NO;
}
+ (BOOL)removeExtendedAttribute:(NSString*)name atPath:(NSString*)path traverseLink:(BOOL)follow error:(NSError**)err {
int flags = (follow? 0 : XATTR_NOFOLLOW);
if (0 == removexattr([path fileSystemRepresentation], [name UTF8String], flags)) return YES;
// error
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String:strerror(errno)], @"error",
@"removexattr", @"function",
name, @":name",
path, @":path",
[NSNumber numberWithBool:follow], @":traverseLink",
nil]
];
return NO;
}
@end

View File

@ -491,6 +491,28 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
}
#endif
- (void)test47DiskCacheExtendedData {
XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache extended data read/write works"];
UIImage *image = [self testPNGImage];
NSDictionary *extendedObject = @{@"Test" : @"Object"};
image.sd_extendedObject = extendedObject;
[SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestImageKeyPNG];
[SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestImageKeyPNG];
// Write extended data
[SDImageCache.sharedImageCache storeImage:image forKey:kTestImageKeyPNG completion:^{
NSData *extendedData = [SDImageCache.sharedImageCache.diskCache extendedDataForKey:kTestImageKeyPNG];
expect(extendedData).toNot.beNil();
// Read extended data
UIImage *newImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kTestImageKeyPNG];
id newExtendedObject = newImage.sd_extendedObject;
expect(extendedObject).equal(newExtendedObject);
// Remove extended data
[SDImageCache.sharedImageCache.diskCache setExtendedData:nil forKey:kTestImageKeyPNG];
[expectation fulfill];
}];
[self waitForExpectationsWithCommonTimeout];
}
#pragma mark - SDImageCache & SDImageCachesManager
- (void)test50SDImageCacheQueryOp {
XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache query op works"];

View File

@ -11,6 +11,7 @@
#import "SDWeakProxy.h"
#import "SDDisplayLink.h"
#import "SDInternalMacros.h"
#import "SDFileAttributeHelper.h"
@interface SDUtilsTests : SDTestCase
@ -74,6 +75,38 @@
expect(duration).beLessThan(0.02);
}
- (void)testSDFileAttributeHelper {
NSData *fileData = [@"File Data" dataUsingEncoding:NSUTF8StringEncoding];
NSData *extendedData = [@"Extended Data" dataUsingEncoding:NSUTF8StringEncoding];
NSString *filePath = @"/tmp/file.dat";
[NSFileManager.defaultManager removeItemAtPath:filePath error:nil];
[fileData writeToFile:filePath atomically:YES];
BOOL exist = [NSFileManager.defaultManager fileExistsAtPath:filePath];
expect(exist).beTruthy();
NSArray *names = [SDFileAttributeHelper extendedAttributeNamesAtPath:filePath traverseLink:NO error:nil];
expect(names.count).equal(0);
NSString *attr = @"com.hackemist.test";
[SDFileAttributeHelper setExtendedAttribute:attr value:extendedData atPath:filePath traverseLink:NO overwrite:YES error:nil];
BOOL hasAttr =[SDFileAttributeHelper hasExtendedAttribute:attr atPath:filePath traverseLink:NO error:nil];
expect(hasAttr).beTruthy();
names = [SDFileAttributeHelper extendedAttributeNamesAtPath:filePath traverseLink:NO error:nil];
expect(names.count).equal(1);
expect(names.firstObject).equal(attr);
NSData *queriedData = [SDFileAttributeHelper extendedAttribute:attr atPath:filePath traverseLink:NO error:nil];
expect(extendedData).equal(queriedData);
BOOL removed = [SDFileAttributeHelper removeExtendedAttribute:attr atPath:filePath traverseLink:NO error:nil];
expect(removed).beTruthy();
hasAttr = [SDFileAttributeHelper hasExtendedAttribute:attr atPath:filePath traverseLink:NO error:nil];
expect(hasAttr).beFalsy();
}
- (void)testSDScaledImageForKey {
// Test nil
expect(SDScaledImageForKey(nil, nil)).beNil();

View File

@ -46,6 +46,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
#import <SDWebImage/UIImage+Metadata.h>
#import <SDWebImage/UIImage+MultiFormat.h>
#import <SDWebImage/UIImage+MemoryCacheCost.h>
#import <SDWebImage/UIImage+ExtendedCacheData.h>
#import <SDWebImage/SDWebImageOperation.h>
#import <SDWebImage/SDWebImageDownloader.h>
#import <SDWebImage/SDWebImageTransition.h>