From a811c305491401eac1426365735ba4ecd87728aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20A=2E=20A=CC=81lvarez?= Date: Sat, 2 Jul 2016 23:06:09 +0200 Subject: [PATCH] load disk icons asynchronously --- Mini vMac/InsertDiskViewController.m | 35 ++++++++++---- Mini vMac/UIImage+DiskImageIcon.h | 2 + Mini vMac/UIImage+DiskImageIcon.m | 71 +++++++++++++++++++--------- 3 files changed, 77 insertions(+), 31 deletions(-) diff --git a/Mini vMac/InsertDiskViewController.m b/Mini vMac/InsertDiskViewController.m index ec9a378..899d48c 100644 --- a/Mini vMac/InsertDiskViewController.m +++ b/Mini vMac/InsertDiskViewController.m @@ -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)]) { diff --git a/Mini vMac/UIImage+DiskImageIcon.h b/Mini vMac/UIImage+DiskImageIcon.h index 9e1d449..80c4a8f 100644 --- a/Mini vMac/UIImage+DiskImageIcon.h +++ b/Mini vMac/UIImage+DiskImageIcon.h @@ -8,6 +8,8 @@ #import +extern NSString *DidUpdateIconForDiskImageNotification; + @interface UIImage (DiskImageIcon) + (UIImage *)imageWithIconForDiskImage:(NSString *)path; diff --git a/Mini vMac/UIImage+DiskImageIcon.m b/Mini vMac/UIImage+DiskImageIcon.m index 9c1eb08..8ab3eb3 100644 --- a/Mini vMac/UIImage+DiskImageIcon.m +++ b/Mini vMac/UIImage+DiskImageIcon.m @@ -11,6 +11,10 @@ #import "res.h" #import "mfs.h" #import "AppDelegate.h" +#import + +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 *diskImageIconCache = nil; - @implementation UIImage (DiskImageIcon) -+ (NSCache *)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