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

View File

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

View File

@ -11,6 +11,10 @@
#import "res.h" #import "res.h"
#import "mfs.h" #import "mfs.h"
#import "AppDelegate.h" #import "AppDelegate.h"
#import <sys/xattr.h>
NSString *DidUpdateIconForDiskImageNotification = @"didUpdateIconForDiskImage";
static const char kDiskImageIconAttributeName[] = "net.namedfork.DiskImageIcon";
#define kDiskImageHasDC42Header 1 << 0 #define kDiskImageHasDC42Header 1 << 0
#define RSHORT(base, offset) ntohs(*((short *)((base) + (offset)))) #define RSHORT(base, offset) ntohs(*((short *)((base) + (offset))))
@ -23,37 +27,58 @@
@end @end
static NSCache<NSString*,UIImage*> *diskImageIconCache = nil;
@implementation UIImage (DiskImageIcon) @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 { + (void)_didEjectDisk:(NSNotification*)notification {
NSString *path = [notification.userInfo[@"path"] stringByStandardizingPath]; NSString *path = [notification.userInfo[@"path"] stringByStandardizingPath];
// reload icon [self loadIconForDiskImageAndNotify:path];
[diskImageIconCache removeObjectForKey:path];
[self imageWithIconForDiskImage:path];
} }
+ (UIImage *)imageWithIconForDiskImage:(NSString *)path { + (UIImage *)imageWithIconForDiskImage:(NSString *)path {
path = path.stringByStandardizingPath; static dispatch_once_t onceToken;
UIImage *icon = [[self diskImageIconCache] objectForKey:path]; dispatch_once(&onceToken, ^{
if (icon == nil) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEjectDisk:) name:[AppDelegate sharedEmulator].ejectDiskNotification object:nil];
icon = [[DiskImageIconReader new] iconForDiskImage:path]; });
if (icon != nil) {
[diskImageIconCache setObject:icon forKey:path]; // 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 @end