Skip to content

Commit

Permalink
Plugin installation works
Browse files Browse the repository at this point in the history
  • Loading branch information
nate-parrott committed Nov 4, 2014
1 parent 3ab565c commit 12a5835
Show file tree
Hide file tree
Showing 62 changed files with 4,350 additions and 31 deletions.
25 changes: 25 additions & 0 deletions FlashlightApp/EasySIMBL/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ - (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
self.SIMBLOn = NO;

[self checkSpotlightVersion];

NSString *loginItemBundlePath = nil;
NSBundle *loginItemBundle = nil;
NSString *loginItemBundleVersion = nil;
Expand Down Expand Up @@ -122,6 +124,13 @@ - (IBAction)toggleUseSIMBL:(id)sender {
SIMBLLogNotice(@"SMLoginItemSetEnabled() failed!");
}
self.SIMBLOn = result;

if (!result) {
// restart spotlight after 1 sec to remove injected code:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[NSTask launchedTaskWithLaunchPath:@"/usr/bin/killall" arguments:@[@"Spotlight"]];
});
}
}
- (void)setSIMBLOn:(BOOL)SIMBLOn {
_SIMBLOn = SIMBLOn;
Expand All @@ -130,4 +139,20 @@ - (void)setSIMBLOn:(BOOL)SIMBLOn {
[self.tableView setAlphaValue:SIMBLOn ? 1 : 0.6];
}

#pragma mark Version checking
- (void)checkSpotlightVersion {
NSString *spotlightVersion = [[NSBundle bundleWithPath:[[NSWorkspace sharedWorkspace] fullPathForApplication:@"Spotlight"]] infoDictionary][@"CFBundleVersion"];
NSLog(@"DetectedSpotlightVersion: %@", spotlightVersion);
if (![spotlightVersion isEqualToString:@"911"]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSAlert *alert = [NSAlert alertWithMessageText:@"Flashlight doesn't work with your version of Spotlight." defaultButton:@"Okay" alternateButton:@"Check for updates" otherButton:nil informativeTextWithFormat:@"As a precaution, plugins won't run on unsupported versions of Spotlight, even if you enable them."];
alert.alertStyle = NSCriticalAlertStyle;
NSModalResponse resp = [alert runModal];
if (resp == NSAlertAlternateReturn) {
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/nate-parrott/flashlight"]];
}
});
}
}

@end
2 changes: 2 additions & 0 deletions FlashlightApp/EasySIMBL/PluginCellView.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

#import <Cocoa/Cocoa.h>
#import "ITSwitch.h"
@class PluginListController;

@interface PluginCellView : NSTableCellView

@property (nonatomic,weak) IBOutlet ITSwitch *switchControl;
@property (nonatomic,weak) IBOutlet PluginListController *listController;

@end
17 changes: 15 additions & 2 deletions FlashlightApp/EasySIMBL/PluginCellView.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@

#import "PluginCellView.h"
#import "PluginModel.h"
#import "PluginListController.h"

@implementation PluginCellView

- (PluginModel *)plugin {
return self.objectValue;
}

- (void)setObjectValue:(id)objectValue {
[super setObjectValue:objectValue];
PluginModel *model = (PluginModel *)objectValue;
self.switchControl.on = model.installed;
self.switchControl.on = [self plugin].installed;
[self.switchControl setEnabled:![self plugin].installing];
}

- (IBAction)toggleInstalled:(id)sender {
if ([self plugin].installed) {
[self.listController uninstallPlugin:[self plugin]];
} else {
[self.listController installPlugin:[self plugin]];
}
}

@end
18 changes: 18 additions & 0 deletions FlashlightApp/EasySIMBL/PluginInstallTask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// PluginInstallTask.h
// Flashlight
//
// Created by Nate Parrott on 11/4/14.
//
//

#import <Foundation/Foundation.h>
@class PluginModel;

@interface PluginInstallTask : NSObject

- (id)initWithPlugin:(PluginModel *)plugin;
- (void)startInstallationIntoPluginsDirectory:(NSString *)directory withCallback:(void(^)(BOOL success, NSError *error))callback; // callback comes on arbitrary thread
@property (nonatomic,readonly) PluginModel *plugin;

@end
53 changes: 53 additions & 0 deletions FlashlightApp/EasySIMBL/PluginInstallTask.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// PluginInstallTask.m
// Flashlight
//
// Created by Nate Parrott on 11/4/14.
//
//

#import "PluginInstallTask.h"
#import "PluginModel.h"
#import "zipzap.h"

@implementation PluginInstallTask


- (id)initWithPlugin:(PluginModel *)plugin {
self = [super init];
_plugin = plugin;
return self;
}
- (void)startInstallationIntoPluginsDirectory:(NSString *)directory withCallback:(void(^)(BOOL success, NSError *error))callback {
[[[NSURLSession sharedSession] dataTaskWithURL:self.plugin.zipURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSError *zipError = nil;
ZZArchive *archive = [ZZArchive archiveWithData:data error:&zipError];
if (archive && !zipError) {
for (ZZArchiveEntry *entry in archive.entries) {
zipError = nil;
NSData *entryData = [entry newDataWithError:&zipError];
if (entryData && !zipError) {
NSString *writeToPath = [directory stringByAppendingPathComponent:entry.fileName];
if (![[NSFileManager defaultManager] fileExistsAtPath:[writeToPath stringByDeletingLastPathComponent]]) {
[[NSFileManager defaultManager] createDirectoryAtPath:[writeToPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:NO];
}
[entryData writeToFile:writeToPath atomically:YES];
} else {
callback(NO, zipError);
return;
}
}
callback(YES, nil);
} else {
callback(NO, zipError);
}
});
} else {
callback(NO, error);
}
}] resume];
}

@end
8 changes: 7 additions & 1 deletion FlashlightApp/EasySIMBL/PluginListController.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@

#import <Foundation/Foundation.h>

@class PluginModel;

@interface PluginListController : NSObject

@property (nonatomic,weak) IBOutlet NSArrayController *arrayController;
@property (nonatomic,weak) IBOutlet NSTableView *tableView;

- (IBAction)reloadPluginsFromWeb:(id)sender;

- (void)installPlugin:(PluginModel *)plugin;
- (void)uninstallPlugin:(PluginModel *)plugin;

@property (nonatomic,weak) IBOutlet NSView *failedToLoadDirectoryBanner;

@end
117 changes: 99 additions & 18 deletions FlashlightApp/EasySIMBL/PluginListController.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

#import "PluginListController.h"
#import "PluginModel.h"
#import "PluginCellView.h"
#import "PluginInstallTask.h"

@interface PluginListController ()
@interface PluginListController () <NSTableViewDelegate>

@property (nonatomic) NSArray *pluginsFromWeb;
@property (nonatomic) NSArray *installedPlugins;
@property (nonatomic) NSSet *installTasksInProgress;

@property (nonatomic) dispatch_source_t dispatchSource;
@property (nonatomic) int fileDesc;
Expand All @@ -26,17 +29,41 @@ @implementation PluginListController
#pragma mark Lifecycle
- (void)awakeFromNib {
[super awakeFromNib];

self.arrayController.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"installed" ascending:NO], [NSSortDescriptor sortDescriptorWithKey:@"displayName" ascending:YES]];

[self startWatchingPluginsDir];
self.needsReloadFromDisk = YES;
[self reloadFromDiskIfNeeded];
[self reloadPluginsFromWeb:nil];
}
- (void)dealloc {
[self startWatchingPluginsDir];
}

#pragma mark UI
- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row {
((PluginCellView *)[rowView viewAtColumn:0]).listController = self;
}

#pragma mark Data
- (IBAction)reloadPluginsFromWeb:(id)sender {
[self setPluginsFromWeb:@[]];
[self.failedToLoadDirectoryBanner setHidden:YES];

NSURL *url = [NSURL URLWithString:@"https://raw.githubusercontent.com/nate-parrott/flashlight/master/PluginDirectory/index.json"];
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSMutableArray *plugins = [NSMutableArray new];
for (NSDictionary *dict in d[@"plugins"]) {
[plugins addObject:[PluginModel fromJson:dict baseURL:url]];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self setPluginsFromWeb:plugins];
if (plugins.count == 0) {
[self.failedToLoadDirectoryBanner setHidden:NO];
}
});
}] resume];
}

- (void)setPluginsFromWeb:(NSArray *)pluginsFromWeb {
Expand All @@ -49,14 +76,28 @@ - (void)setInstalledPlugins:(NSArray *)installedPlugins {
[self updateArrayController];
}

- (void)setInstallTasksInProgress:(NSSet *)installTasksInProgress {
_installTasksInProgress = installTasksInProgress;
[self updateArrayController];
}

- (void)updateArrayController {
[self.arrayController removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self.arrayController.arrangedObjects count])]];
for (PluginModel *p in self.installedPlugins) {
[self.arrayController addObject:p];

NSMutableArray *allPlugins = [NSMutableArray new];
if (self.installedPlugins) {
[allPlugins addObjectsFromArray:self.installedPlugins];
}
for (PluginModel *p in self.pluginsFromWeb) {
[self.arrayController addObject:p];
if (self.pluginsFromWeb) {
[allPlugins addObjectsFromArray:self.pluginsFromWeb];
}

NSArray *plugins = [PluginModel mergeDuplicates:allPlugins];
for (PluginModel *plugin in plugins) {
plugin.installing = [self isPluginCurrentlyBeingInstalled:plugin];
}
[self.arrayController addObjects:plugins];
[self.arrayController rearrangeObjects];
}

#pragma mark Local plugin files
Expand All @@ -69,12 +110,14 @@ - (void)startWatchingPluginsDir {
// call the passed block if the source is modified
__weak PluginListController *weakSelf = self;
dispatch_source_set_event_handler(self.dispatchSource, ^{
if (!weakSelf.needsReloadFromDisk) {
weakSelf.needsReloadFromDisk = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf reloadFromDiskIfNeeded];
});
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // work around some bug when reloading after install
if (!weakSelf.needsReloadFromDisk) {
weakSelf.needsReloadFromDisk = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf reloadFromDiskIfNeeded];
});
}
});
});

// close the file descriptor when the dispatch source is cancelled
Expand All @@ -96,12 +139,7 @@ - (void)reloadFromDiskIfNeeded {
NSMutableArray *models = [NSMutableArray new];
for (NSString *itemName in contents) {
if ([[itemName pathExtension] isEqualToString:@"bundle"]) {
NSBundle *bundle = [NSBundle bundleWithPath:[[self localPluginsPath] stringByAppendingPathComponent:itemName]];
PluginModel *model = [PluginModel new];
model.name = [bundle infoDictionary][@"CFBundleDisplayName"];
model.pluginDescription = [bundle infoDictionary][@"Description"];
model.installed = YES;
[models addObject:model];
[models addObject:[PluginModel fromPath:[[self localPluginsPath] stringByAppendingPathComponent:itemName]]];
}
}
self.installedPlugins = models;
Expand All @@ -111,4 +149,47 @@ - (NSString *)localPluginsPath {
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"FlashlightPlugins"];
}

#pragma mark (Un)?installation
- (BOOL)isPluginCurrentlyBeingInstalled:(PluginModel *)plugin {
for (PluginInstallTask *task in self.installTasksInProgress) {
if ([task.plugin.name isEqualToString:plugin.name]) {
return YES;
}
}
return NO;
}
- (void)installPlugin:(PluginModel *)plugin {
if ([self isPluginCurrentlyBeingInstalled:plugin]) return;

PluginInstallTask *task = [[PluginInstallTask alloc] initWithPlugin:plugin];
self.installTasksInProgress = self.installTasksInProgress ? [self.installTasksInProgress setByAddingObject:task] : [NSSet setWithObject:task];
[task startInstallationIntoPluginsDirectory:[self localPluginsPath] withCallback:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
/*if (!success) {
NSAlert *alert = [NSAlert new];
NSMutableString *errorMsg = [NSMutableString stringWithFormat:@"Couldn't install \"%@\".", plugin.displayName];
if (error) {
[errorMsg appendFormat:@"\n(%@)", error.localizedFailureReason];
}
alert.messageText = errorMsg;
[alert addButtonWithTitle:@"Okay"];
[alert runModal];
}*/
if (!success) {
NSAlert *alert = error ? [NSAlert alertWithError:error] : [NSAlert alertWithMessageText:@"Couldn't install plugin." defaultButton:@"Okay" alternateButton:nil otherButton:nil informativeTextWithFormat:nil];
alert.alertStyle = NSWarningAlertStyle;
[alert runModal];
}
NSMutableSet *tasks = self.installTasksInProgress.mutableCopy;
[tasks removeObject:task];
self.installTasksInProgress = tasks;
});
}];
}
- (void)uninstallPlugin:(PluginModel *)plugin {
if ([self isPluginCurrentlyBeingInstalled:plugin]) return;

[[NSFileManager defaultManager] removeItemAtPath:[[self localPluginsPath] stringByAppendingPathComponent:[plugin.name stringByAppendingPathExtension:@"bundle"]] error:nil];
}

@end
9 changes: 7 additions & 2 deletions FlashlightApp/EasySIMBL/PluginModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@

@interface PluginModel : NSObject <NSCopying>

@property (nonatomic) NSString *name;
@property (nonatomic) NSString *pluginDescription;
@property (nonatomic) NSString *name, *displayName, *pluginDescription;
@property (nonatomic) BOOL installed;
@property (nonatomic) BOOL installing;
@property (nonatomic) NSURL *zipURL;

+ (PluginModel *)fromPath:(NSString *)path;
+ (PluginModel *)fromJson:(NSDictionary *)json baseURL:(NSURL *)url;

+ (NSArray *)mergeDuplicates:(NSArray *)models;

@end
Loading

0 comments on commit 12a5835

Please sign in to comment.