load disk icons asynchronously

This commit is contained in:
Jesús A. Álvarez 2016-07-02 23:06:09 +02:00
parent be06c815f4
commit a811c30549
3 changed files with 77 additions and 31 deletions

View File

@ -531,6 +531,9 @@
@end
@implementation FileTableViewCell
{
UIImage *defaultIcon;
}
- (void)prepareForReuse {
[super prepareForReuse];
@ -540,25 +543,34 @@
self.imageView.image = nil;
self.imageView.alpha = 1.0;
self.detailTextLabel.text = nil;
if (_filePath) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:DidUpdateIconForDiskImageNotification object:_filePath];
_filePath = nil;
}
}
- (void)setFilePath:(NSString *)filePath {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
if (_filePath) {
[notificationCenter removeObserver:self name:DidUpdateIconForDiskImageNotification object:_filePath];
}
_filePath = filePath;
if (_filePath) {
[notificationCenter addObserver:self selector:@selector(didUpdateDiskIcon:) name:DidUpdateIconForDiskImageNotification object:_filePath];
}
NSString *fileName = filePath.lastPathComponent;
self.textLabel.text = self.showExtension ? fileName : fileName.stringByDeletingPathExtension;
NSDictionary *attributes = [[NSURL fileURLWithPath:filePath] resourceValuesForKeys:@[NSURLTotalFileSizeKey, NSURLFileSizeKey] error:NULL];
if (attributes && attributes[NSURLTotalFileSizeKey]) {
BOOL isDiskImage = [[AppDelegate sharedInstance].diskImageExtensions containsObject:fileName.pathExtension.lowercaseString];
if (isDiskImage) {
UIImage *icon = [UIImage imageWithIconForDiskImage:filePath];
if (icon == nil) {
NSInteger fileSize = [attributes[NSURLTotalFileSizeKey] integerValue];
NSInteger numBlocks = fileSize / 512;
icon = [UIImage imageNamed:numBlocks == 800 || numBlocks == 1600 ? @"floppy" : @"floppyV"];
}
self.imageView.image = icon;
NSInteger fileSize = [attributes[NSURLTotalFileSizeKey] integerValue];
NSInteger numBlocks = fileSize / 512;
defaultIcon = [UIImage imageNamed:numBlocks == 800 || numBlocks == 1600 ? @"floppy" : @"floppyV"];
self.imageView.image = [UIImage imageWithIconForDiskImage:filePath] ?: defaultIcon;
} else {
self.imageView.image = [UIImage imageNamed:@"document"];
defaultIcon = [UIImage imageNamed:@"document"];
self.imageView.image = defaultIcon;
}
NSString *sizeString = [NSByteCountFormatter stringFromByteCount:[attributes[NSURLTotalFileSizeKey] longLongValue] countStyle:NSByteCountFormatterCountStyleBinary];
self.detailTextLabel.text = sizeString;
@ -568,6 +580,13 @@
}
}
- (void)didUpdateDiskIcon:(NSNotification*)notification {
if ([_filePath isEqual:notification.object]) {
UIImage *icon = notification.userInfo[@"icon"];
self.imageView.image = icon ?: defaultIcon;
}
}
- (void)share:(id)sender {
UIActivityViewController *avc = [[UIActivityViewController alloc] initWithActivityItems:@[[NSURL fileURLWithPath:self.filePath]] applicationActivities:nil];
if ([avc respondsToSelector:@selector(popoverPresentationController)]) {

View File

@ -8,6 +8,8 @@
#import <UIKit/UIKit.h>
extern NSString *DidUpdateIconForDiskImageNotification;
@interface UIImage (DiskImageIcon)
+ (UIImage *)imageWithIconForDiskImage:(NSString *)path;

View File

@ -11,6 +11,10 @@
#import "res.h"
#import "mfs.h"
#import "AppDelegate.h"
#import <sys/xattr.h>
NSString *DidUpdateIconForDiskImageNotification = @"didUpdateIconForDiskImage";
static const char kDiskImageIconAttributeName[] = "net.namedfork.DiskImageIcon";
#define kDiskImageHasDC42Header 1 << 0
#define RSHORT(base, offset) ntohs(*((short *)((base) + (offset))))
@ -23,37 +27,58 @@
@end
static NSCache<NSString*,UIImage*> *diskImageIconCache = nil;
@implementation UIImage (DiskImageIcon)
+ (NSCache<NSString*,UIImage*> *)diskImageIconCache {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
diskImageIconCache = [NSCache new];
diskImageIconCache.name = @"net.namedfork.minivmac.icon-cache";
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEjectDisk:) name:[AppDelegate sharedEmulator].ejectDiskNotification object:nil];
});
return diskImageIconCache;
}
+ (void)_didEjectDisk:(NSNotification*)notification {
NSString *path = [notification.userInfo[@"path"] stringByStandardizingPath];
// reload icon
[diskImageIconCache removeObjectForKey:path];
[self imageWithIconForDiskImage:path];
[self loadIconForDiskImageAndNotify:path];
}
+ (UIImage *)imageWithIconForDiskImage:(NSString *)path {
path = path.stringByStandardizingPath;
UIImage *icon = [[self diskImageIconCache] objectForKey:path];
if (icon == nil) {
icon = [[DiskImageIconReader new] iconForDiskImage:path];
if (icon != nil) {
[diskImageIconCache setObject:icon forKey:path];
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEjectDisk:) name:[AppDelegate sharedEmulator].ejectDiskNotification object:nil];
});
// check attribute
ssize_t attrLen = getxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, NULL, 0, 0, 0);
if (attrLen == -1) {
[self loadIconForDiskImageAndNotify:path];
return nil;
}
// read data
void *attrData = malloc(attrLen);
getxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, attrData, attrLen, 0, 0);
return [UIImage imageWithData:[NSData dataWithBytesNoCopy:attrData length:attrLen freeWhenDone:YES]];
}
+ (void)loadIconForDiskImageAndNotify:(NSString *)path {
if ([NSThread isMainThread]) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[self loadIconForDiskImageAndNotify:path];
});
return;
}
// get current value
ssize_t attrLen = getxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, NULL, 0, 0, 0);
NSData *previousData = nil;
if (attrLen != -1) {
void *attrData = malloc(attrLen);
getxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, attrData, attrLen, 0, 0);
previousData = [NSData dataWithBytesNoCopy:attrData length:attrLen freeWhenDone:YES];
}
// load new icon
UIImage *icon = [[DiskImageIconReader new] iconForDiskImage:path];
NSData *newData = UIImagePNGRepresentation(icon);
if (![newData isEqualToData:previousData]) {
// save new icon and notify
setxattr(path.fileSystemRepresentation, kDiskImageIconAttributeName, newData.bytes, newData.length, 0, 0);
NSNotification *newIconNotification = [[NSNotification alloc] initWithName:DidUpdateIconForDiskImageNotification object:path userInfo:icon ? @{@"icon": icon} : nil];
[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:newIconNotification waitUntilDone:NO];
}
return icon;
}
@end