diff --git a/AutoPkgr.xcodeproj/project.pbxproj b/AutoPkgr.xcodeproj/project.pbxproj index fd2225b1..366943c5 100644 --- a/AutoPkgr.xcodeproj/project.pbxproj +++ b/AutoPkgr.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 1A1BAD96197A0407008192A7 /* LGVersionComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1BAD95197A0407008192A7 /* LGVersionComparator.m */; }; 1A1BAD9E197A4133008192A7 /* LGGitHubJSONLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1BAD9D197A4133008192A7 /* LGGitHubJSONLoader.m */; }; 1A979FE01960BCCC00EE9881 /* LGUnzipper.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A979FDF1960BCCC00EE9881 /* LGUnzipper.m */; }; - 1AAAA5CC196C82DB00623804 /* LGAutoPkgRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAAA5CB196C82DB00623804 /* LGAutoPkgRunner.m */; }; 1AC69551195B59ED00D2BD81 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1AC69550195B59ED00D2BD81 /* Cocoa.framework */; }; 1AC6955B195B59ED00D2BD81 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1AC69559195B59ED00D2BD81 /* InfoPlist.strings */; }; 1AC6955D195B59EE00D2BD81 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC6955C195B59ED00D2BD81 /* main.m */; }; @@ -38,6 +37,11 @@ 6A53625D1988BE59008A949C /* LGTestPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A53625C1988BE59008A949C /* LGTestPort.m */; }; 8B6A3199195B7AC1007A7A16 /* linde-logo@400px.png in Resources */ = {isa = PBXBuildFile; fileRef = 8B6A3198195B7AC1007A7A16 /* linde-logo@400px.png */; }; 8BB217F619709A2C00EF8B93 /* AutoPkgrIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BB217F519709A2C00EF8B93 /* AutoPkgrIcon.png */; }; + BE025CF619BAE93400D36345 /* LGAutoPkgTask.m in Sources */ = {isa = PBXBuildFile; fileRef = BE025CF519BAE93400D36345 /* LGAutoPkgTask.m */; }; + BE025CF719BAE93400D36345 /* LGAutoPkgTask.m in Sources */ = {isa = PBXBuildFile; fileRef = BE025CF519BAE93400D36345 /* LGAutoPkgTask.m */; }; + BE025CFE19BAEF8800D36345 /* LGAutoPkgSchedule.m in Sources */ = {isa = PBXBuildFile; fileRef = BE025CFD19BAEF8800D36345 /* LGAutoPkgSchedule.m */; }; + BE025CFF19BAEF8800D36345 /* LGAutoPkgSchedule.m in Sources */ = {isa = PBXBuildFile; fileRef = BE025CFD19BAEF8800D36345 /* LGAutoPkgSchedule.m */; }; + BE57AC2A19BA3BBC00BB17B1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BE57AC2C19BA3BBC00BB17B1 /* Localizable.strings */; }; BE87994B1995BA410082472B /* LGDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = BE87994A1995BA410082472B /* LGDefaults.m */; }; BEFC931D1995F0710074C938 /* LGError.m in Sources */ = {isa = PBXBuildFile; fileRef = BEFC931C1995F0710074C938 /* LGError.m */; }; /* End PBXBuildFile section */ @@ -116,8 +120,6 @@ 1A1BAD9D197A4133008192A7 /* LGGitHubJSONLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGGitHubJSONLoader.m; sourceTree = ""; }; 1A979FDE1960BCCC00EE9881 /* LGUnzipper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGUnzipper.h; sourceTree = ""; }; 1A979FDF1960BCCC00EE9881 /* LGUnzipper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGUnzipper.m; sourceTree = ""; }; - 1AAAA5CA196C82DB00623804 /* LGAutoPkgRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGAutoPkgRunner.h; sourceTree = ""; }; - 1AAAA5CB196C82DB00623804 /* LGAutoPkgRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGAutoPkgRunner.m; sourceTree = ""; }; 1AC6954D195B59ED00D2BD81 /* AutoPkgr.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AutoPkgr.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1AC69550195B59ED00D2BD81 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 1AC69553195B59ED00D2BD81 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -158,9 +160,15 @@ 6A53625C1988BE59008A949C /* LGTestPort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGTestPort.m; sourceTree = ""; }; 8B6A3198195B7AC1007A7A16 /* linde-logo@400px.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "linde-logo@400px.png"; sourceTree = ""; }; 8BB217F519709A2C00EF8B93 /* AutoPkgrIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AutoPkgrIcon.png; sourceTree = ""; }; + BE025CF419BAE93400D36345 /* LGAutoPkgTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGAutoPkgTask.h; sourceTree = ""; }; + BE025CF519BAE93400D36345 /* LGAutoPkgTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGAutoPkgTask.m; sourceTree = ""; }; + BE025CFC19BAEF8800D36345 /* LGAutoPkgSchedule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGAutoPkgSchedule.h; sourceTree = ""; }; + BE025CFD19BAEF8800D36345 /* LGAutoPkgSchedule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGAutoPkgSchedule.m; sourceTree = ""; }; BE34DF2B19AA21E200DC2FAF /* LGAutoPkgr.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LGAutoPkgr.h; sourceTree = ""; }; + BE57AC2B19BA3BBC00BB17B1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; BE8799491995BA410082472B /* LGDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGDefaults.h; sourceTree = ""; }; BE87994A1995BA410082472B /* LGDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGDefaults.m; sourceTree = ""; }; + BE94AA7B19BB8A78001A00D0 /* LGProgressDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LGProgressDelegate.h; sourceTree = ""; }; BEFC931B1995F0710074C938 /* LGError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGError.h; sourceTree = ""; }; BEFC931C1995F0710074C938 /* LGError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGError.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -249,14 +257,17 @@ BE34DF2B19AA21E200DC2FAF /* LGAutoPkgr.h */, 1AC69562195B59EE00D2BD81 /* LGAppDelegate.h */, 1AC69563195B59EE00D2BD81 /* LGAppDelegate.m */, + BE94AA7B19BB8A78001A00D0 /* LGProgressDelegate.h */, 1AC98410195CE8510071CAB3 /* LGEmailer.h */, 1AC98411195CE8520071CAB3 /* LGEmailer.m */, 1AC98408195CE7D60071CAB3 /* LGConstants.h */, 1AC98409195CE7D60071CAB3 /* LGConstants.m */, - 1AAAA5CA196C82DB00623804 /* LGAutoPkgRunner.h */, - 1AAAA5CB196C82DB00623804 /* LGAutoPkgRunner.m */, + BE025CF419BAE93400D36345 /* LGAutoPkgTask.h */, + BE025CF519BAE93400D36345 /* LGAutoPkgTask.m */, 1AC9841C195CEC350071CAB3 /* LGConfigurationWindowController.h */, 1AC9841D195CEC350071CAB3 /* LGConfigurationWindowController.m */, + BE025CFC19BAEF8800D36345 /* LGAutoPkgSchedule.h */, + BE025CFD19BAEF8800D36345 /* LGAutoPkgSchedule.m */, 1A979FDE1960BCCC00EE9881 /* LGUnzipper.h */, 1A979FDF1960BCCC00EE9881 /* LGUnzipper.m */, 6A0BABE7196E560000A136BB /* LGPopularRepositories.h */, @@ -281,6 +292,7 @@ children = ( 1AC69558195B59ED00D2BD81 /* AutoPkgr-Info.plist */, 1AC69559195B59ED00D2BD81 /* InfoPlist.strings */, + BE57AC2C19BA3BBC00BB17B1 /* Localizable.strings */, 1AC6955C195B59ED00D2BD81 /* main.m */, 1AC6955E195B59EE00D2BD81 /* AutoPkgr-Prefix.pch */, 1AC6955F195B59EE00D2BD81 /* Credits.rtf */, @@ -469,6 +481,7 @@ 1AC6955B195B59ED00D2BD81 /* InfoPlist.strings in Resources */, 1AC69569195B59EE00D2BD81 /* Images.xcassets in Resources */, 1AC98420195CEC360071CAB3 /* LGConfigurationWindowController.xib in Resources */, + BE57AC2A19BA3BBC00BB17B1 /* Localizable.strings in Resources */, 8B6A3199195B7AC1007A7A16 /* linde-logo@400px.png in Resources */, 1AC98427195DF6350071CAB3 /* autopkgr.png in Resources */, 1AC69561195B59EE00D2BD81 /* Credits.rtf in Resources */, @@ -494,6 +507,7 @@ 1AC86D3D195E0AC6006FDD2B /* LGHostInfo.m in Sources */, 1A1BAD96197A0407008192A7 /* LGVersionComparator.m in Sources */, 1AC98412195CE8520071CAB3 /* LGEmailer.m in Sources */, + BE025CFE19BAEF8800D36345 /* LGAutoPkgSchedule.m in Sources */, 6A0ACE34196FB349002C7FE4 /* LGApplications.m in Sources */, 1AC9841F195CEC360071CAB3 /* LGConfigurationWindowController.m in Sources */, 1A1BAD9E197A4133008192A7 /* LGGitHubJSONLoader.m in Sources */, @@ -505,8 +519,8 @@ BEFC931D1995F0710074C938 /* LGError.m in Sources */, 1AC98416195CE99E0071CAB3 /* SSKeychain.m in Sources */, 1A979FE01960BCCC00EE9881 /* LGUnzipper.m in Sources */, + BE025CF619BAE93400D36345 /* LGAutoPkgTask.m in Sources */, 6A0BABE9196E560000A136BB /* LGPopularRepositories.m in Sources */, - 1AAAA5CC196C82DB00623804 /* LGAutoPkgRunner.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -514,7 +528,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BE025CFF19BAEF8800D36345 /* LGAutoPkgSchedule.m in Sources */, 1AC6957B195B59EE00D2BD81 /* AutoPkgrTests.m in Sources */, + BE025CF719BAE93400D36345 /* LGAutoPkgTask.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -566,6 +582,15 @@ name = InfoPlist.strings; sourceTree = ""; }; + BE57AC2C19BA3BBC00BB17B1 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + BE57AC2B19BA3BBC00BB17B1 /* en */, + ); + name = Localizable.strings; + path = ..; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/AutoPkgr/AutoPkgr-Info.plist b/AutoPkgr/AutoPkgr-Info.plist index ceeac1ba..d63faebe 100644 --- a/AutoPkgr/AutoPkgr-Info.plist +++ b/AutoPkgr/AutoPkgr-Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.2 + 1.0.3 CFBundleSignature ???? CFBundleVersion - 1 + 7 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/AutoPkgr/Base.lproj/MainMenu.xib b/AutoPkgr/Base.lproj/MainMenu.xib index 1109b32b..e0e06ad0 100644 --- a/AutoPkgr/Base.lproj/MainMenu.xib +++ b/AutoPkgr/Base.lproj/MainMenu.xib @@ -682,7 +682,7 @@ - + diff --git a/AutoPkgr/LGAppDelegate.h b/AutoPkgr/LGAppDelegate.h index 5b665021..d25936e6 100644 --- a/AutoPkgr/LGAppDelegate.h +++ b/AutoPkgr/LGAppDelegate.h @@ -20,10 +20,11 @@ // #import +#import "LGProgressDelegate.h" @class LGConfigurationWindowController; -@interface LGAppDelegate : NSObject +@interface LGAppDelegate : NSObject { @private LGConfigurationWindowController *configurationWindowController; diff --git a/AutoPkgr/LGAppDelegate.m b/AutoPkgr/LGAppDelegate.m index f09588b2..76c5e527 100644 --- a/AutoPkgr/LGAppDelegate.m +++ b/AutoPkgr/LGAppDelegate.m @@ -21,6 +21,9 @@ #import "LGAppDelegate.h" #import "LGAutoPkgr.h" +#import "LGAutoPkgTask.h" +#import "LGEmailer.h" +#import "LGAutoPkgSchedule.h" #import "LGConfigurationWindowController.h" @implementation LGAppDelegate @@ -29,6 +32,10 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { LGDefaults *defaults = [LGDefaults new]; + // set self as the delegate for the time so the menu item is updated + // during timed runs. + [[LGAutoPkgSchedule sharedTimer] setProgressDelegate:self]; + // Setup the status item [self setupStatusItem]; @@ -39,24 +46,19 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification // Start the AutoPkg run timer if the user enabled it [self startAutoPkgRunTimer]; - // Update AutoPkg recipe repos when the application launches - // if the user has enabled automatic repo updates - if (defaults.checkForRepoUpdatesAutomaticallyEnabled) { - NSLog(@"Updating AutoPkg recipe repos."); - [self updateAutoPkgRecipeReposInBackgroundAtAppLaunch]; - } } - (void)startAutoPkgRunTimer { - LGAutoPkgRunner *autoPkgRunner = [[LGAutoPkgRunner alloc] init]; - [autoPkgRunner startAutoPkgRunTimer]; + [[LGAutoPkgSchedule sharedTimer] configure]; } + - (void)updateAutoPkgRecipeReposInBackgroundAtAppLaunch { - LGAutoPkgRunner *autoPkgRunner = [[LGAutoPkgRunner alloc] init]; - [autoPkgRunner invokeAutoPkgRepoUpdateInBackgroundThread]; + [LGAutoPkgTask repoUpdate:^(NSError *error) { + NSLog(@"%@", error ? error.localizedDescription:@"AutoPkg recipe repos updated."); + }]; } - (void)setupStatusItem @@ -72,10 +74,20 @@ - (void)setupStatusItem - (void)checkNowFromMenu:(id)sender { - LGAutoPkgRunner *autoPkgRunner = [[LGAutoPkgRunner alloc] init]; - [autoPkgRunner invokeAutoPkgInBackgroundThread]; + [self startProgressWithMessage:@"Starting..."]; + NSString *recipeList = [LGApplications recipeList]; + [LGAutoPkgTask runRecipeList:recipeList + progress:^(NSString *message, double taskProgress) { + [self updateProgress:message progress:taskProgress]; + } + reply:^(NSDictionary *report,NSError *error) { + [self stopProgress:error]; + LGEmailer *emailer = [LGEmailer new]; + [emailer sendEmailForReport:report error:error]; + }]; } + - (void)showConfigurationWindow:(id)sender { if (!self->configurationWindowController) { @@ -107,4 +119,29 @@ - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sende return NSTerminateNow; } +# pragma mark - Progress Protocol +-(void)startProgressWithMessage:(NSString *)message{ + __block NSMenuItem *item = [self.statusMenu itemAtIndex:0]; + [item setAction:nil]; + [item setTitle:message]; +} + +-(void)stopProgress:(NSError *)error{ + __block NSMenuItem *item = [self.statusMenu itemAtIndex:0]; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [item setTitle:@"Check Now"]; + [item setAction:@selector(checkNowFromMenu:)]; + }]; +} + +-(void)updateProgress:(NSString *)message progress:(double)progress{ + __block NSMenuItem *item = [self.statusMenu itemAtIndex:0]; + if (message.length < 50) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [item setTitle:message]; + }]; + } + NSLog(@"%@",message); +} + @end diff --git a/AutoPkgr/LGApplications.h b/AutoPkgr/LGApplications.h index 8d8c7aea..8b40b85b 100644 --- a/AutoPkgr/LGApplications.h +++ b/AutoPkgr/LGApplications.h @@ -20,7 +20,7 @@ // #import -#import "LGAutoPkgRunner.h" +#import "LGAutoPkgTask.h" @interface LGApplications : NSObject { @@ -29,7 +29,6 @@ NSArray *apps; NSArray *activeApps; NSArray *searchedApps; - LGAutoPkgRunner *pkgRunner; __weak NSSearchField *_appSearch; } @@ -39,4 +38,6 @@ @property (weak) IBOutlet NSSearchField *appSearch; ++ (NSString *)recipeList; + @end diff --git a/AutoPkgr/LGApplications.m b/AutoPkgr/LGApplications.m index c63dd795..d5ada268 100644 --- a/AutoPkgr/LGApplications.m +++ b/AutoPkgr/LGApplications.m @@ -27,20 +27,15 @@ @implementation LGApplications - (id)init { self = [super init]; - - pkgRunner = [[LGAutoPkgRunner alloc] init]; - - apps = [pkgRunner getLocalAutoPkgRecipes]; activeApps = [self getActiveApps]; searchedApps = apps; - return self; } + - (void)reload { - apps = [pkgRunner getLocalAutoPkgRecipes]; - + apps = [LGAutoPkgTask listRecipes]; [self executeAppSearch:self]; } @@ -221,8 +216,16 @@ - (void)executeAppSearch:(id)sender - (void)awakeFromNib { + [self reload]; [_appSearch setTarget:self]; [_appSearch setAction:@selector(executeAppSearch:)]; } ++ (NSString *)recipeList +{ + LGApplications *apps = [[LGApplications alloc] init]; + NSString *applicationSupportDirectory = [apps getAppSupportDirectory]; + return [applicationSupportDirectory stringByAppendingString:@"/recipe_list.txt"]; +} + @end diff --git a/AutoPkgr/LGAutoPkgRunner.h b/AutoPkgr/LGAutoPkgRunner.h deleted file mode 100644 index 2cd24317..00000000 --- a/AutoPkgr/LGAutoPkgRunner.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// LGAutoPkgRunner.h -// AutoPkgr -// -// Created by James Barclay on 7/1/14. -// -// Copyright 2014 The Linde Group, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import - -@interface LGAutoPkgRunner : NSObject - -- (NSArray *)getLocalAutoPkgRecipes; -- (NSArray *)getLocalAutoPkgRecipeRepos; -- (void)addAutoPkgRecipeRepo:(NSString *)repoURL; -- (void)removeAutoPkgRecipeRepo:(NSString *)repoURL; -- (void)updateAutoPkgRecipeRepos; -- (void)runAutoPkgWithRecipeListAndSendEmailNotificationIfConfigured:(NSString *)recipeListPath; -- (void)sendNewDowloadsEmail:(NSArray *)newDownloadsArray; -- (void)invokeAutoPkgInBackgroundThread; -- (void)invokeAutoPkgRepoUpdateInBackgroundThread; -- (void)runAutoPkgWithRecipeList; -- (void)startAutoPkgRunTimer; -- (void)setLocalMunkiRepoForAutoPkg:(NSString *)localMunkiRepo; - -@end diff --git a/AutoPkgr/LGAutoPkgRunner.m b/AutoPkgr/LGAutoPkgRunner.m deleted file mode 100644 index 259889b1..00000000 --- a/AutoPkgr/LGAutoPkgRunner.m +++ /dev/null @@ -1,472 +0,0 @@ -// -// LGAutoPkgRunner.m -// AutoPkgr -// -// Created by James Barclay on 7/1/14. -// -// Copyright 2014 The Linde Group, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "LGAutoPkgRunner.h" -#import "LGAutoPkgr.h" -#import "LGHostInfo.h" -#import "LGVersionComparator.h" -#import "LGApplications.h" -#import "LGEmailer.h" - -#import - -@implementation LGAutoPkgRunner - -- (NSArray *)getLocalAutoPkgRecipes -{ - // Set up our task, pipe, and file handle - NSTask *autoPkgRecipeListTask = [[NSTask alloc] init]; - NSPipe *autoPkgRecipeListPipe = [NSPipe pipe]; - NSFileHandle *fileHandle = [autoPkgRecipeListPipe fileHandleForReading]; - - // Set up our launch path and args - NSString *launchPath = @"/usr/bin/python"; - NSArray *args = [NSArray arrayWithObjects:@"/usr/local/bin/autopkg", @"list-recipes", nil]; - - // Configure the task - [autoPkgRecipeListTask setLaunchPath:launchPath]; - [autoPkgRecipeListTask setArguments:args]; - [autoPkgRecipeListTask setStandardOutput:autoPkgRecipeListPipe]; - - // Launch the task - [autoPkgRecipeListTask launch]; - [autoPkgRecipeListTask waitUntilExit]; - - // Read our data from the fileHandle - NSData *autoPkgRecipeListTaskData = [fileHandle readDataToEndOfFile]; - // Init our string with data from the fileHandle - NSString *autoPkgRecipeListResults = [[NSString alloc] initWithData:autoPkgRecipeListTaskData encoding:NSUTF8StringEncoding]; - // Init our array with the string separated by newlines - NSArray *autoPkgRecipeListArray = [autoPkgRecipeListResults componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - - // Strip empty strings from our array - NSMutableArray *arrayWithoutEmptyStrings = [[NSMutableArray alloc] init]; - for (NSString *recipe in autoPkgRecipeListArray) { - if (![recipe isEqualToString:@""]) { - [arrayWithoutEmptyStrings addObject:recipe]; - } - } - - return arrayWithoutEmptyStrings; -} - -- (NSArray *)getLocalAutoPkgRecipeRepos -{ - // Set up our task, pipe, and file handle - NSTask *autoPkgRepoListTask = [[NSTask alloc] init]; - NSPipe *autoPkgRepoListPipe = [NSPipe pipe]; - NSFileHandle *fileHandle = [autoPkgRepoListPipe fileHandleForReading]; - - // Set up our launch path and args - NSString *launchPath = @"/usr/bin/python"; - NSArray *args = [NSArray arrayWithObjects:@"/usr/local/bin/autopkg", @"repo-list", nil]; - - // Configure the task - [autoPkgRepoListTask setLaunchPath:launchPath]; - [autoPkgRepoListTask setArguments:args]; - [autoPkgRepoListTask setStandardOutput:autoPkgRepoListPipe]; - - // Launch the task - [autoPkgRepoListTask launch]; - [autoPkgRepoListTask waitUntilExit]; - - // Read our data from the fileHandle - NSData *autoPkgRepoListTaskData = [fileHandle readDataToEndOfFile]; - // Init our string with data from the fileHandle - NSString *autoPkgRepoListResults = [[NSString alloc] initWithData:autoPkgRepoListTaskData encoding:NSUTF8StringEncoding]; - // Init our array with the string separated by newlines - NSArray *autoPkgRepoListArray = [autoPkgRepoListResults componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - - // Strip empty strings from our array - NSMutableArray *arrayWithoutEmptyStrings = [[NSMutableArray alloc] init]; - for (NSString *repo in autoPkgRepoListArray) { - if (![repo isEqualToString:@""]) { - [arrayWithoutEmptyStrings addObject:repo]; - } - } - - return arrayWithoutEmptyStrings; -} - -- (void)addAutoPkgRecipeRepo:(NSString *)repoURL -{ - NSString *addString = [NSString stringWithFormat:@"Adding %@", repoURL]; - [[NSNotificationCenter defaultCenter] postNotificationName:kLGNotificationProgressStart - object:nil - userInfo:@{ kLGNotificationUserInfoMessage : addString }]; - // Set up task, pipe, and file handle - NSTask *autoPkgRepoAddTask = [[NSTask alloc] init]; - NSPipe *autoPkgRepoAddPipe = [NSPipe pipe]; - - // Set up launch path and args - NSString *launchPath = @"/usr/bin/python"; - NSArray *args = [NSArray arrayWithObjects:@"/usr/local/bin/autopkg", @"repo-add", [NSString stringWithFormat:@"%@", repoURL], nil]; - - // Configure the task - [autoPkgRepoAddTask setLaunchPath:launchPath]; - [autoPkgRepoAddTask setArguments:args]; - [autoPkgRepoAddTask setStandardOutput:autoPkgRepoAddPipe]; - [autoPkgRepoAddTask setStandardError:[NSPipe pipe]]; - - __weak LGAutoPkgRunner *notificationObject = self; - autoPkgRepoAddTask.terminationHandler = ^(NSTask *aTask) { - NSError *error; - NSDictionary *userInfo; - if (![LGError errorWithTaskError:aTask verb:kLGAutoPkgrRepoAdd error:&error]) { - userInfo = @{kLGNotificationUserInfoError:error}; - } - [[NSNotificationCenter defaultCenter]postNotificationName:kLGNotificationProgressStop - object:notificationObject - userInfo:userInfo]; - }; - - // Launch the task - [autoPkgRepoAddTask launch]; - [autoPkgRepoAddTask waitUntilExit]; -} - -- (void)removeAutoPkgRecipeRepo:(NSString *)repoURL -{ - [[NSNotificationCenter defaultCenter] postNotificationName:kLGNotificationProgressStart - object:nil - userInfo:@{ kLGNotificationUserInfoMessage : @"Removing Repo" }]; - - // Set up task and pipe - NSTask *autoPkgRepoRemoveTask = [[NSTask alloc] init]; - NSPipe *autoPkgRepoRemovePipe = [NSPipe pipe]; - - // Set up launch path and args - NSString *launchPath = @"/usr/bin/python"; - NSArray *args = [NSArray arrayWithObjects:@"/usr/local/bin/autopkg", @"repo-delete", [NSString stringWithFormat:@"%@", repoURL], nil]; - - // Configure the task - [autoPkgRepoRemoveTask setLaunchPath:launchPath]; - [autoPkgRepoRemoveTask setArguments:args]; - [autoPkgRepoRemoveTask setStandardOutput:autoPkgRepoRemovePipe]; - [autoPkgRepoRemoveTask setStandardError:[NSPipe pipe]]; - - __weak LGAutoPkgRunner *notificationObject = self; - autoPkgRepoRemoveTask.terminationHandler = ^(NSTask *aTask) { - NSError *error; - NSDictionary *userInfo; - if (![LGError errorWithTaskError:aTask verb:kLGAutoPkgrRepoDelete error:&error]) { - userInfo = @{kLGNotificationUserInfoError:error}; - } - [[NSNotificationCenter defaultCenter]postNotificationName:kLGNotificationProgressStop - object:notificationObject - userInfo:userInfo]; - }; - - // Launch the task - [autoPkgRepoRemoveTask launch]; - [autoPkgRepoRemoveTask waitUntilExit]; -} - -- (void)updateAutoPkgRecipeRepos -{ - // Set up task and pipe - NSTask *updateAutoPkgReposTask = [[NSTask alloc] init]; - - // Set up launch path and args - NSString *launchPath = @"/usr/bin/python"; - NSArray *args = [NSArray arrayWithObjects:@"/usr/local/bin/autopkg", @"repo-update", @"all", nil]; - - // Configure the task - [updateAutoPkgReposTask setLaunchPath:launchPath]; - [updateAutoPkgReposTask setArguments:args]; - - updateAutoPkgReposTask.standardOutput =[NSPipe pipe]; - updateAutoPkgReposTask.standardError =[NSPipe pipe]; - - updateAutoPkgReposTask.terminationHandler = ^(NSTask *aTask) { - NSDictionary *userInfo; - NSError *error; - if (![LGError errorWithTaskError:aTask verb:kLGAutoPkgrRepoUpdate error:&error]) { - userInfo = @{kLGNotificationUserInfoError:error}; - } - - [[NSNotificationCenter defaultCenter]postNotificationName:kLGNotificationUpdateReposComplete - object:nil - userInfo:userInfo]; - }; - - // Launch the task - [updateAutoPkgReposTask launch]; -} - -- (void)runAutoPkgWithRecipeListAndSendEmailNotificationIfConfigured:(NSString *)recipeListPath -{ - // Determine version so we chan properly handle --report-plist - BOOL autoPkgAboveV0_3_2; - - LGHostInfo *info = [LGHostInfo new]; - LGVersionComparator *comparator = [LGVersionComparator new]; - if ([comparator isVersion:info.getAutoPkgVersion greaterThanVersion:@"0.3.2"]) { - autoPkgAboveV0_3_2 = YES; - } - - // Set up our task, pipe, and file handle - NSTask *autoPkgRunTask = [[NSTask alloc] init]; - autoPkgRunTask.launchPath = @"/usr/bin/python"; - - autoPkgRunTask.standardError = [NSPipe pipe]; - - // Set up args based on whether report is a file or piped to stdout - NSMutableArray *args = [[NSMutableArray alloc] initWithArray:@[@"/usr/local/bin/autopkg", - @"run", - @"--recipe-list", - recipeListPath, - @"--report-plist"]]; - - if (autoPkgAboveV0_3_2) { - // Set up a pseudo terminal so stdout gets flushed and we can get status updates. - // Concept taken from http://stackoverflow.com/a/13355870 - int fdMaster, fdSlave; - - if (openpty(&fdMaster, &fdSlave, NULL, NULL, NULL) == 0) { - fcntl(fdMaster, F_SETFD, FD_CLOEXEC); - fcntl(fdSlave, F_SETFD, FD_CLOEXEC); - autoPkgRunTask.standardOutput = [[NSFileHandle alloc] initWithFileDescriptor:fdMaster closeOnDealloc:YES]; - } - - // Create a unique temp file where AutoPkg will write the plist file. - NSString *plistFile = [NSTemporaryDirectory() stringByAppendingString:[[NSProcessInfo processInfo] globallyUniqueString]]; - - // Add arg for the file path to the report-plist and turn on verbose mode. - [args addObject:plistFile]; - - // If the version of AutoPkg is > 0.3.2 we'll be able to provide lots more information - NSString *recipeListString = [NSString stringWithContentsOfFile:recipeListPath - encoding:NSASCIIStringEncoding - error:nil]; - - NSArray *recipeListArray = [recipeListString componentsSeparatedByString:@"\n"]; - - __block NSInteger installCount = 1; - NSInteger totalCount = recipeListArray.count; - - [autoPkgRunTask.standardOutput setReadabilityHandler:^(NSFileHandle *handle) { - NSString *string = [[NSString alloc] initWithData:[handle availableData] encoding:NSASCIIStringEncoding]; - - // Strip out any new line characters so it displays better - NSString *strippedString = [string stringByReplacingOccurrencesOfString:@"\n" - withString:@""]; - - // Set the detail string for the notification - NSString *detailString = [NSString stringWithFormat:@"(%ld/%ld) %@", installCount, totalCount, strippedString]; - - // Post notification - if (installCount <= totalCount) { - installCount ++; - [[NSNotificationCenter defaultCenter] postNotificationName:kLGNotificationProgressMessageUpdate - object:nil - userInfo:@{kLGNotificationUserInfoMessage:detailString, - kLGNotificationUserInfoTotalRecipeCount:@(totalCount)}]; - } - }]; - - } else { - // If still using AutoPkg v0.3.2 set up the pipe for --report-plist data - autoPkgRunTask.standardOutput = [NSPipe pipe]; - } - - autoPkgRunTask.arguments = args; - - autoPkgRunTask.terminationHandler = ^(NSTask *aTask) { - NSDictionary *userInfo = nil; - NSError *error; - if (![LGError errorWithTaskError:aTask verb:kLGAutoPkgrRun error:&error]) { - userInfo = @{kLGNotificationUserInfoError:error}; - } - - [[NSNotificationCenter defaultCenter]postNotificationName:kLGNotificationRunAutoPkgComplete - object:nil - userInfo:userInfo]; - - LGDefaults *defaults = [[LGDefaults alloc] init]; - // If the AutoPkg run exited successfully and email notifications are enabled continue - if (aTask.terminationStatus == 0 && [defaults sendEmailNotificationsWhenNewVersionsAreFoundEnabled]) { - NSDictionary *plist; - NSError *error; - - // Read our data from file if autopkg v > 0.3.2 else read from stdout filehandle - if (autoPkgAboveV0_3_2) { - // Create the plist from the temp file - plist = [NSDictionary dictionaryWithContentsOfFile:[aTask.arguments lastObject]]; - - // nil out the readability handler - [aTask.standardOutput setReadabilityHandler:nil]; - - } else { - NSData *autoPkgRunReportPlistData = [[aTask.standardOutput fileHandleForReading] readDataToEndOfFile]; - // Init our string with data from the fileHandle - NSString *autoPkgRunReportPlistString = [[NSString alloc] initWithData:autoPkgRunReportPlistData encoding:NSUTF8StringEncoding]; - if (![autoPkgRunReportPlistString isEqualToString:@""]) { - // Convert string back to data for plist serialization - NSData *plistData = [autoPkgRunReportPlistString dataUsingEncoding:NSUTF8StringEncoding]; - // Initialize plist format - NSPropertyListFormat format; - // Initialize our dict - plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:&format error:&error]; - } - } - NSLog(@"This is our plist: %@.", plist); - - if (!plist) { - NSLog(@"Could not serialize the plist. Error: %@.", error); - return; - } - - // Get arrays of new downloads/packages from the plist - NSArray *newDownloads = [plist objectForKey:@"new_downloads"]; - NSArray *newPackages = [plist objectForKey:@"new_packages"]; - if ([newDownloads count]) { - NSLog(@"New stuff was downloaded."); - NSMutableArray *newDownloadsArray = [[NSMutableArray alloc] init]; - - for (NSString *path in newDownloads) { - NSMutableDictionary *newDownloadDict = [[NSMutableDictionary alloc] init]; - // Get just the application name from the path in the new_downloads dict - NSString *app = [[path lastPathComponent] stringByDeletingPathExtension]; - // Insert the app name into the dictionary for the "app" key - [newDownloadDict setObject:app forKey:@"app"]; - [newDownloadDict setObject:@"N/A" forKey:@"version"]; - - for (NSDictionary *dct in newPackages) { - NSString *pkgPath = [dct objectForKey:@"pkg_path"]; - - if ([pkgPath rangeOfString:app options:NSCaseInsensitiveSearch].location != NSNotFound && [dct objectForKey:@"version"]) { - NSString *version = [dct objectForKey:@"version"]; - [newDownloadDict setObject:version forKey:@"version"]; - break; - } - } - [newDownloadsArray addObject:newDownloadDict]; - } - - NSLog(@"New software was downloaded. Sending an email alert."); - - LGAutoPkgRunner *sendmail = [[LGAutoPkgRunner alloc] init]; - [sendmail sendNewDowloadsEmail:newDownloadsArray]; - - } else { - NSLog(@"Nothing new was downloaded."); - } - } - [aTask.standardError fileHandleForReading].readabilityHandler = nil; - - // Setting this to nil doesn't seem right, but it prevents memory leak - [aTask setTerminationHandler:nil]; - }; - - // Launch the task - [autoPkgRunTask launch]; -} - -- (void)sendNewDowloadsEmail:(NSArray *)newDownloadsArray -{ - LGEmailer *emailer = [[LGEmailer alloc] init]; - - NSMutableArray *apps = [[NSMutableArray alloc] init]; - - for (NSDictionary *download in newDownloadsArray) { - NSString *app = [download objectForKey:@"app"]; - [apps addObject:app]; - } - - // Create the subject string - NSString *subject = [NSString stringWithFormat:@"[%@] The Following Software Is Now Available for Testing (%@)", kLGApplicationName, [apps componentsJoinedByString:@", "]]; - - // Create the message string - NSMutableString *newDownloadsString = [NSMutableString string]; - NSEnumerator *e = [newDownloadsArray objectEnumerator]; - id dictionary; - while ((dictionary = [e nextObject]) != nil) - [newDownloadsString appendFormat:@"
%@: %@", [dictionary objectForKey:@"app"], [dictionary objectForKey:@"version"]]; - NSString *message = [NSString stringWithFormat:@"The following software is now available for testing:
%@", newDownloadsString]; - - [emailer sendEmailNotification:subject message:message]; -} - -- (void)invokeAutoPkgInBackgroundThread -{ - NSOperationQueue *queue = [[NSOperationQueue alloc] init]; - // This ensures that no more than one thread will be spawned - // to run AutoPkg. - [queue setMaxConcurrentOperationCount:1]; - NSInvocationOperation *task = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(runAutoPkgWithRecipeList) object:nil]; - [queue addOperation:task]; -} - -- (void)invokeAutoPkgRepoUpdateInBackgroundThread -{ - NSOperationQueue *queue = [[NSOperationQueue alloc] init]; - // This ensures that no more than one thread will be spawned - // to run AutoPkg repo updates. - [queue setMaxConcurrentOperationCount:1]; - NSInvocationOperation *task = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(updateAutoPkgRecipeRepos) object:nil]; - [queue addOperation:task]; -} - -- (void)runAutoPkgWithRecipeList -{ - LGApplications *apps = [[LGApplications alloc] init]; - NSString *applicationSupportDirectory = [apps getAppSupportDirectory]; - NSString *recipeListFilePath = [applicationSupportDirectory stringByAppendingString:@"/recipe_list.txt"]; - __weak LGAutoPkgRunner *weakSelf = self; - [weakSelf runAutoPkgWithRecipeListAndSendEmailNotificationIfConfigured:recipeListFilePath]; -} - -- (void)startAutoPkgRunTimer -{ - LGDefaults *defaults = [[LGDefaults alloc] init]; - - if ([defaults checkForNewVersionsOfAppsAutomaticallyEnabled]) { - if ([defaults integerForKey:kLGAutoPkgRunInterval]) { - double i = [defaults integerForKey:kLGAutoPkgRunInterval]; - if (i != 0) { - NSTimeInterval ti = i * 60 * 60; // Convert hours to seconds for our time interval - [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(invokeAutoPkgInBackgroundThread) userInfo:nil repeats:YES]; - } else { - NSLog(@"i is 0 because that's what the user entered or what they entered wasn't a digit."); - } - } else { - NSLog(@"The user enabled automatic checking for app updates but they specified no interval."); - } - } -} - -- (void)setLocalMunkiRepoForAutoPkg:(NSString *)localMunkiRepo -{ - CFStringRef key = (CFSTR("MUNKI_REPO")); - // Create a CoreFoundation string reference from the - // localMunkiRepo NSString pointer, (no need to release) - CFStringRef val = (__bridge CFStringRef)localMunkiRepo; - CFStringRef appID = (CFSTR("com.github.autopkg")); - - CFPreferencesSetAppValue(key, val, appID); - - // Release our key and appID refs - CFRelease(key); - CFRelease(appID); -} - -@end diff --git a/AutoPkgr/LGAutoPkgSchedule.h b/AutoPkgr/LGAutoPkgSchedule.h new file mode 100644 index 00000000..99311b8e --- /dev/null +++ b/AutoPkgr/LGAutoPkgSchedule.h @@ -0,0 +1,18 @@ +// +// LGAutoPkgSchedule.h +// AutoPkgr +// +// Created by Eldon on 9/6/14. +// Copyright (c) 2014 The Linde Group, Inc. All rights reserved. +// + +#import +#import "LGProgressDelegate.h" + +@interface LGAutoPkgSchedule : NSObject + +@property (weak) idprogressDelegate; + ++ (LGAutoPkgSchedule *)sharedTimer; +- (void)configure; +@end diff --git a/AutoPkgr/LGAutoPkgSchedule.m b/AutoPkgr/LGAutoPkgSchedule.m new file mode 100644 index 00000000..bb4c71ba --- /dev/null +++ b/AutoPkgr/LGAutoPkgSchedule.m @@ -0,0 +1,85 @@ +// +// LGAutoPkgSchedule.m +// AutoPkgr +// +// Created by Eldon on 9/6/14. +// Copyright (c) 2014 The Linde Group, Inc. All rights reserved. +// + +#import "LGAutoPkgSchedule.h" +#import "LGAutoPkgr.h" +#import "LGAutoPkgTask.h" +#import "LGApplications.h" +#import "LGEmailer.h" + +@implementation LGAutoPkgSchedule { + NSTimer *_timer; +} + ++ (LGAutoPkgSchedule *)sharedTimer +{ + static dispatch_once_t onceToken; + static LGAutoPkgSchedule *shared; + dispatch_once(&onceToken, ^{ + shared = [[LGAutoPkgSchedule alloc] init]; + }); + return shared; +} + +- (void)configure +{ + LGDefaults *defaults = [[LGDefaults alloc] init]; + if (defaults.checkForNewVersionsOfAppsAutomaticallyEnabled) { + [self stopTimer]; + if ([defaults integerForKey:kLGAutoPkgRunInterval]) { + double i = [defaults integerForKey:kLGAutoPkgRunInterval]; + if (i != 0) { + NSTimeInterval ti = i * 60 * 60; // Convert hours to seconds for our time interval + _timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(runAutoPkg) userInfo:nil repeats:YES]; + if ([_timer isValid]) { + DLog(@"Successfully enabled AutoPkgr run timer."); + } + } else { + NSLog(@"i is 0 because that's what the user entered or what they entered wasn't a digit."); + } + } else { + NSLog(@"The user enabled automatic checking for app updates but they specified no interval."); + } + } else { + DLog(@"Successfully disabled AutoPkgr run timer."); + [self stopTimer]; + } +} + +- (void)stopTimer +{ + if ([_timer isValid]) { + [_timer invalidate]; + } + _timer = nil; +} + +- (void)runAutoPkg +{ + + LGDefaults *defaults = [[LGDefaults alloc] init]; + if (defaults.checkForNewVersionsOfAppsAutomaticallyEnabled) { + NSLog(@"Beginning scheduled run of AutoPkg"); + [_progressDelegate startProgressWithMessage:@"Starting scheduled run..."]; + NSString *recipeList = [LGApplications recipeList]; + + [LGAutoPkgTask runRecipeList:recipeList + progress:^(NSString *message, double taskProgress) { + [_progressDelegate updateProgress:message progress:taskProgress]; + } + reply:^(NSDictionary *report, NSError *error) { + NSLog(@"Scheduled run of AutoPkg complete"); + [_progressDelegate stopProgress:error]; + LGEmailer *emailer = [LGEmailer new]; + [emailer sendEmailForReport:report error:error]; + }]; + } else { + [self stopTimer]; + } +} +@end diff --git a/AutoPkgr/LGAutoPkgTask.h b/AutoPkgr/LGAutoPkgTask.h new file mode 100644 index 00000000..1bbcdac9 --- /dev/null +++ b/AutoPkgr/LGAutoPkgTask.h @@ -0,0 +1,88 @@ +// +// LGAutoPkgTask.h +// AutoPkgr +// +// Created by Eldon on 8/30/14. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "LGAutoPkgr.h" + +extern NSString *const kLGAutoPkgRecipeKey; +extern NSString *const kLGAutoPkgRecipePathKey; +extern NSString *const kLGAutoPkgRepoKey; +extern NSString *const kLGAutoPkgRepoPathKey; + +@interface LGAutoPkgTask : NSObject + +@property (copy, nonatomic) NSArray *arguments; + +/** + * The block to use for providing run status updates asynchronously. + */ +@property (copy) void (^runStatusUpdate)(NSString *message, double progress); +@property (copy, nonatomic, readonly) NSString *standardOutString; +@property (copy, nonatomic, readonly) NSString *standardErrString; + +/** + * An array of dictionaries based on the autopkg verb + * @discussion only, recipe-list, repo-list, and search will return values, all others will return nil; + */ +@property (copy, nonatomic, readonly) NSArray *results; + +/** + * Observable KVO property indicating the task has completed. + */ +@property (nonatomic, readonly) BOOL complete; + +- (BOOL)launch:(NSError **)error; +- (void)launchInBackground:(void (^)(NSError *error))reply; + +- (BOOL)cancel:(NSError **)error; + +#pragma mark - Class Methods +#pragma mark-- Run methods ++ (void)runRecipeList:(NSString *)recipeList + progress:(void (^)(NSString *message, double taskProgress))progress + reply:(void (^)(NSDictionary *report, NSError *error))reply; + ++ (void)runRecipes:(NSArray *)recipes + progress:(void (^)(NSString *message))progress + reply:(void (^)(NSError *error))reply; + ++ (void)search:(NSString *)recipe + reply:(void (^)(NSArray *results, NSError *error))reply; + ++ (void)makeOverride:(NSString *)recipe + reply:(void (^)(NSError *error))reply; + ++ (void)listRecipes:(void (^)(NSArray *recipes, NSError *error))reply; ++ (NSArray *)listRecipes; + +#pragma mark-- Repo methods ++ (void)repoAdd:(NSString *)repo + reply:(void (^)(NSError *error))reply; + ++ (void)repoRemove:(NSString *)repo + reply:(void (^)(NSError *error))reply; + ++ (void)repoUpdate:(void (^)(NSError *error))reply; + ++ (void)repoList:(void (^)(NSArray *repos, NSError *error))reply; + +#pragma mark-- Other ++ (NSString *)version; + +@end diff --git a/AutoPkgr/LGAutoPkgTask.m b/AutoPkgr/LGAutoPkgTask.m new file mode 100644 index 00000000..a771aa58 --- /dev/null +++ b/AutoPkgr/LGAutoPkgTask.m @@ -0,0 +1,468 @@ +// +// LGAutoPkgTask.m +// AutoPkgr +// +// Created by Eldon on 8/30/14. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "LGAutoPkgTask.h" +#import "LGApplications.h" +#import "LGVersionComparator.h" + +#import + +NSString *const kLGAutoPkgRecipeKey = @"recipe"; +NSString *const kLGAutoPkgRecipePathKey = @"recipe_path"; +NSString *const kLGAutoPkgRepoKey = @"repo"; +NSString *const kLGAutoPkgRepoPathKey = @"repo_path"; + +// This is a function so in the future this could be configured to +// determine autopkg path in a more robust way. +NSString *autopkg() +{ + return @"/usr/local/bin/autopkg"; +} + +@interface LGAutoPkgTask () +@property (copy, nonatomic) NSString *reportPlistFile; +@property (copy, nonatomic) NSDictionary *reportPlist; +@property (copy, nonatomic) NSString *version; +@property (nonatomic) BOOL AUTOPKG_VERSION_0_4_0; + +@end + +@implementation LGAutoPkgTask { + NSTask *_task; + NSMutableArray *_internalArgs; + LGAutoPkgVerb _verb; +} + +- (void)dealloc +{ + _task.terminationHandler = nil; + if ([_task.standardOutput isKindOfClass:[NSPipe class]]) { + [_task.standardOutput fileHandleForReading].readabilityHandler = nil; + } +} + +- (id)init +{ + self = [super init]; + if (self) { + self->_task = [[NSTask alloc] init]; + self->_task.launchPath = @"/usr/bin/python"; + self->_internalArgs = [[NSMutableArray alloc] initWithArray:@[ autopkg() ]]; + } + return self; +} + +- (BOOL)launch:(NSError *__autoreleasing *)error +{ + [_task setArguments:_internalArgs]; + + // If an instance of autopkg is running, and we're trying to + // do a run, exit + if (_verb == kLGAutoPkgRun && [[self class] instanceIsRunning]) { + return [LGError errorWithCode:kLGErrorMultipleRunsOfAutopkg + error:error]; + } + + [self setFileHandles]; + [_task launch]; + [_task waitUntilExit]; + + return [LGError errorWithTaskError:_task + verb:_verb + error:error]; +} + +- (void)launchInBackground:(void (^)(NSError *))reply +{ + NSOperationQueue *bgQueue = [NSOperationQueue new]; + [bgQueue addOperationWithBlock:^{ + NSError *error; + [self launch:&error]; + reply(error); + }]; +} + +- (BOOL)cancel:(NSError *__autoreleasing *)error +{ + [_task terminate]; + return [LGError errorWithTaskError:_task verb:_verb error:error]; +} + +- (void)setFileHandles +{ + _task.standardError = [NSPipe pipe]; + _task.standardOutput = [NSPipe pipe]; + + if (_verb == kLGAutoPkgRun) { + if (self.AUTOPKG_VERSION_0_4_0) { + __block double count = 0.0; + __block double total = [self recipeListCount]; + __weak LGAutoPkgTask *weakSelf = self; + + // To get status from autopkg set NSUnbufferedIO environment keyto YES + // Thanks to help from -- http://stackoverflow.com/questions/8251010 + _task.environment = @{ @"NSUnbufferedIO" : @"YES" }; + + NSPredicate *processingPredicate = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH[cd] 'Processing'"]; + [[_task.standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *handle) { + int cntStr = (int)round(count)+1; + int totStr = (int)round(total); + NSString *message = [[NSString alloc]initWithData:[handle availableData] encoding:NSUTF8StringEncoding]; + NSString *fullMessage; + if ([processingPredicate evaluateWithObject:message]) { + fullMessage = [NSString stringWithFormat:@"(%d/%d) %@",cntStr,totStr,message]; + } else { + fullMessage = message; + } + + if(weakSelf.runStatusUpdate){ + weakSelf.runStatusUpdate(fullMessage,((count/total)*100)); + } + + if (count < total) { + count++; + } + }]; + } + } +} + +- (void)setArguments:(NSArray *)arguments +{ + _arguments = arguments; + [_internalArgs addObjectsFromArray:arguments]; + + NSString *verbString = _arguments[0]; + if ([verbString isEqualToString:@"run"]) { + _verb = kLGAutoPkgRun; + if (self.AUTOPKG_VERSION_0_4_0) { + [_internalArgs addObject:self.reportPlistFile]; + } + } else if ([verbString isEqualToString:@"search"]) { + _verb = kLGAutoPkgSearch; + } else if ([verbString isEqualToString:@"list-recipes"]) { + _verb = kLGAutoPkgRecipeList; + } else if ([verbString isEqualToString:@"make-override"]) { + _verb = kLGAutoPkgMakeOverride; + } else if ([verbString isEqualToString:@"repo-add"]) { + _verb = kLGAutoPkgRepoAdd; + } else if ([verbString isEqualToString:@"repo-delete"]) { + _verb = kLGAutoPkgRepoDelete; + } else if ([verbString isEqualToString:@"repo-list"]) { + _verb = kLGAutoPkgRepoList; + } else if ([verbString isEqualToString:@"repo-update"]) { + _verb = kLGAutoPkgRepoUpdate; + } else if ([verbString isEqualToString:@"version"]) { + _verb = kLGAutoPkgVersion; + } +} + +- (NSString *)standardErrString +{ + NSString *standardError; + NSData *data; + if ([_task.standardError isKindOfClass:[NSPipe class]]) { + data = [[_task.standardError fileHandleForReading] readDataToEndOfFile]; + } + if (data) { + standardError = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + } + return standardError; +} + +- (NSString *)standardOutString +{ + NSString *standardOutput; + NSData *data; + if ([_task.standardOutput isKindOfClass:[NSPipe class]]) { + data = [[_task.standardOutput fileHandleForReading] readDataToEndOfFile]; + } + if (data) { + standardOutput = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + } + return standardOutput; +} + +- (NSDictionary *)reportPlist +{ + if (self.AUTOPKG_VERSION_0_4_0) { + _reportPlist = [NSDictionary dictionaryWithContentsOfFile:_reportPlistFile]; + } else { + NSString *plistString = self.standardOutString; + if (![plistString isEqualToString:@""]) { + // Convert string back to data for plist serialization + NSData *plistData = [plistString dataUsingEncoding:NSUTF8StringEncoding]; + // Initialize plist format + NSPropertyListFormat format; + // Initialize our dict + _reportPlist = [NSPropertyListSerialization propertyListWithData:plistData + options:NSPropertyListImmutable + format:&format + error:nil]; + } + } + return _reportPlist; +} + +- (NSString *)reportPlistFile +{ + if (!_reportPlistFile) { + _reportPlistFile = [NSTemporaryDirectory() stringByAppendingString:[[NSProcessInfo processInfo] globallyUniqueString]]; + } + return _reportPlistFile; +} + +- (NSArray *)results +{ + NSString *resultString = self.standardOutString; + NSArray *results = nil; + + if (resultString) { + if (_verb == kLGAutoPkgSearch) { + __block NSMutableArray *searchResults; + + NSMutableCharacterSet *skippedCharacters = [NSMutableCharacterSet whitespaceCharacterSet]; + + NSMutableCharacterSet *repoCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; + [repoCharacters formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; + + NSPredicate *nonRecipePredicate = [NSPredicate predicateWithFormat: + @"SELF BEGINSWITH 'To add' \ + or SELF BEGINSWITH '----' \ + or SELF BEGINSWITH 'Name'"]; + + [resultString enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) { + if(![nonRecipePredicate evaluateWithObject:line ]){ + NSScanner *scanner = [NSScanner scannerWithString:line]; + [scanner setCharactersToBeSkipped:skippedCharacters]; + + NSString *recipe, *repo, *path; + + [scanner scanCharactersFromSet:repoCharacters intoString:&recipe]; + [scanner scanCharactersFromSet:repoCharacters intoString:&repo]; + [scanner scanCharactersFromSet:repoCharacters intoString:&path]; + + if(recipe && repo && path){ + if(!searchResults){ + searchResults = [[NSMutableArray alloc] init]; + } + [searchResults addObject:@{kLGAutoPkgRecipeKey:[recipe stringByDeletingPathExtension], + kLGAutoPkgRepoKey:repo, + kLGAutoPkgRepoPathKey:path, + }]; + } + } + }]; + results = [NSArray arrayWithArray:searchResults]; + } else if (_verb == kLGAutoPkgRepoList || _verb == kLGAutoPkgRecipeList) { + NSArray *listResults = [resultString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + NSPredicate *noEmptyStrings = [NSPredicate predicateWithFormat:@"not (SELF == '')"]; + results = [listResults filteredArrayUsingPredicate:noEmptyStrings]; + } + } + return results; +} + +- (NSString *)version +{ + if (_version) { + return _version; + } + return [[self class] version]; +} + +- (BOOL)AUTOPKG_VERSION_0_4_0 +{ + LGVersionComparator *vc = [[LGVersionComparator alloc] init]; + return [vc isVersion:self.version greaterThanVersion:@"0.3.9"]; +} + +#pragma mark - Specialized settings +- (NSInteger)recipeListCount +{ + NSInteger count = 0; + if (_verb == kLGAutoPkgRun) { + NSString *file = [_arguments objectAtIndex:2]; + if ([[NSFileManager defaultManager] fileExistsAtPath:file]) { + NSString *fileContents = [NSString stringWithContentsOfFile:file encoding:NSASCIIStringEncoding error:nil]; + count = [[fileContents componentsSeparatedByString:@"\n"] count]; + } + } + return count; +} + +#pragma mark - Class Methods +#pragma mark-- Recipe Methods ++ (void)runRecipes:(NSArray *)recipes + progress:(void (^)(NSString *))progress + reply:(void (^)(NSError *))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + NSMutableArray *fullRecipes = [[NSMutableArray alloc] init]; + [fullRecipes addObject:@"run"]; + for (NSString *recipe in recipes) { + [fullRecipes addObject:recipe]; + } + + [fullRecipes addObjectsFromArray:@[ @"-v", @"--report-plist" ]]; + task.arguments = [NSArray arrayWithArray:fullRecipes]; + + [task setRunStatusUpdate:^(NSString *message, double progressUpdate) { + progress(message); + }]; + + [task launchInBackground:^(NSError *error) { + reply(error); + }]; +} + ++ (void)runRecipeList:(NSString *)recipeList + progress:(void (^)(NSString *, double))progress + reply:(void (^)(NSDictionary *, NSError *))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"run", @"--recipe-list", recipeList, @"--report-plist" ]; + + [task setRunStatusUpdate:^(NSString *message, double progressUpdate) { + progress(message,progressUpdate); + }]; + + [task launchInBackground:^(NSError *error) { + reply(task.reportPlist,error); + }]; +} + ++ (void)search:(NSString *)recipe reply:(void (^)(NSArray *results, NSError *error))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"search", recipe ]; + [task launchInBackground:^(NSError *error) { + NSArray *results; + if(!error){ + results = [task results]; + } + reply (results,error); + }]; +} + ++ (void)makeOverride:(NSString *)recipe reply:(void (^)(NSError *))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"make-override", recipe ]; + [task launchInBackground:^(NSError *error) { + reply(error); + }]; +} + ++ (void)listRecipes:(void (^)(NSArray *, NSError *))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"list-recipes" ]; + [task launchInBackground:^(NSError *error) { + NSArray *results; + if(!error){ + results = [task results]; + } + reply (results,error); + }]; +} + ++ (NSArray *)listRecipes +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"list-recipes" ]; + [task launch:nil]; + return task.results; +} + +#pragma mark-- Repo Methods ++ (void)repoAdd:(NSString *)repo reply:(void (^)(NSError *))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"repo-add", repo ]; + [task launchInBackground:^(NSError *error) { + reply(error); + }]; +} + ++ (void)repoRemove:(NSString *)repo reply:(void (^)(NSError *))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"repo-delete", repo ]; + [task launchInBackground:^(NSError *error) { + reply(error); + }]; +} + ++ (void)repoUpdate:(void (^)(NSError *))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"repo-update", @"all" ]; + [task launchInBackground:^(NSError *error) { + reply(error); + }]; +} + ++ (void)repoList:(void (^)(NSArray *, NSError *))reply +{ + LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; + task.arguments = @[ @"repo-list" ]; + [task launchInBackground:^(NSError *error) { + NSArray *results; + if(!error){ + results = [task results]; + } + reply (results,error); + }]; +} + +#pragma mark-- Other Methods ++ (NSString *)version +{ + LGAutoPkgTask *autoPkgTask = [[LGAutoPkgTask alloc] init]; + autoPkgTask.arguments = @[ @"version" ]; + [autoPkgTask launch:nil]; + return [autoPkgTask standardOutString]; +} + ++ (BOOL)instanceIsRunning +{ + NSTask *task = [NSTask new]; + + task.launchPath = @"/bin/ps"; + task.arguments = @[ @"-e", @"-o", @"command=" ]; + task.standardOutput = [NSPipe pipe]; + task.standardError = task.standardOutput; + + [task launch]; + [task waitUntilExit]; + + NSData *outputData = [[task.standardOutput fileHandleForReading] readDataToEndOfFile]; + NSString *outputString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", autopkg()]; + NSArray *runningProcs = [outputString componentsSeparatedByString:@"\n"]; + + if ([[runningProcs filteredArrayUsingPredicate:predicate] count]) + return YES; + + return NO; +} + +@end diff --git a/AutoPkgr/LGConfigurationWindowController.h b/AutoPkgr/LGConfigurationWindowController.h index 9f9ce7a6..0b7df064 100644 --- a/AutoPkgr/LGConfigurationWindowController.h +++ b/AutoPkgr/LGConfigurationWindowController.h @@ -80,6 +80,9 @@ @property (weak) IBOutlet NSTextField *progressMessage; @property (weak) IBOutlet NSTextField *progressDetailsMessage; +// Progress delegate (object used to send updates to status menu item) +@property (weak) idmenuProgressDelegate; + // Objects @property (strong) IBOutlet LGPopularRepositories *popRepoTableViewHandler; @property (strong) IBOutlet LGApplications *appTableViewHandler; diff --git a/AutoPkgr/LGConfigurationWindowController.m b/AutoPkgr/LGConfigurationWindowController.m index 0eb18fea..13b22e42 100644 --- a/AutoPkgr/LGConfigurationWindowController.m +++ b/AutoPkgr/LGConfigurationWindowController.m @@ -24,12 +24,14 @@ #import "LGDefaults.h" #import "LGEmailer.h" #import "LGHostInfo.h" -#import "LGAutoPkgRunner.h" +#import "LGAutoPkgTask.h" +#import "LGAutoPkgSchedule.h" +#import "LGProgressDelegate.h" #import "LGGitHubJSONLoader.h" #import "LGVersionComparator.h" #import "SSKeychain.h" -@interface LGConfigurationWindowController () { +@interface LGConfigurationWindowController () { LGDefaults *defaults; } @@ -89,6 +91,8 @@ - (id)initWithWindow:(NSWindow *)window if (self) { // Initialization code here. defaults = [LGDefaults new]; + _menuProgressDelegate = [NSApp delegate]; + NSNotificationCenter *ndc = [NSNotificationCenter defaultCenter]; [ndc addObserver:self selector:@selector(startProgressNotificationReceived:) name:kLGNotificationProgressStart object:nil]; [ndc addObserver:self selector:@selector(stopProgressNotificationReceived:) name:kLGNotificationProgressStop object:nil]; @@ -251,7 +255,7 @@ - (void)windowDidLoad NSLog(@"An error occurred when attempting to retrieve the keychain entry for %@. Error: %@", smtpUsernameString, [error localizedDescription]); } else { // Only populate the SMTP Password field if the username exists - if (smtpUsernameString != nil && ![smtpUsernameString isEqual:@""]) { + if (smtpUsernameString && password && ![smtpUsernameString isEqual:@""]) { NSLog(@"Retrieved password from keychain for account %@.", smtpUsernameString); [smtpPassword setStringValue:password]; } @@ -294,22 +298,27 @@ - (void)windowDidLoad // Enable tools buttons if directories exist BOOL isDir; - - if ([[NSFileManager defaultManager] fileExistsAtPath:defaults.autoPkgRecipeOverridesDir isDirectory:&isDir] && isDir) { - [openAutoPkgRecipeOverridesFolderButton setEnabled:YES]; - } - if ([[NSFileManager defaultManager] fileExistsAtPath:defaults.autoPkgCacheDir isDirectory:&isDir] && isDir) { - [openAutoPkgCacheFolderButton setEnabled:YES]; - } - if ([[NSFileManager defaultManager] fileExistsAtPath:defaults.autoPkgRecipeRepoDir isDirectory:&isDir] && isDir) { - [openAutoPkgRecipeReposFolderButton setEnabled:YES]; - } if ([[NSFileManager defaultManager] fileExistsAtPath:defaults.munkiRepo isDirectory:&isDir] && isDir) { [openLocalMunkiRepoFolderButton setEnabled:YES]; } + _popRepoTableViewHandler.progressDelegate = self; + // Synchronize with the defaults database [defaults synchronize]; + + // Update AutoPkg recipe repos when the application launches + // if the user has enabled automatic repo updates + if (defaults.checkForRepoUpdatesAutomaticallyEnabled) { + [_updateRepoNowButton setEnabled:NO]; + [_checkAppsNowButton setEnabled:NO]; + [_updateRepoNowButton setTitle:@"Update in Progress..."]; + [LGAutoPkgTask repoUpdate:^(NSError *error) { + [_updateRepoNowButton setEnabled:YES]; + [_updateRepoNowButton setTitle:@"Update Repos Now"]; + [_checkAppsNowButton setEnabled:YES]; + }]; + } } - (IBAction)sendTestEmail:(id)sender @@ -438,7 +447,7 @@ - (void)save NSLog(@"%@ SMTP authentication.", defaults.SMTPAuthenticationEnabled ? @"Enabling" : @"Disabling"); defaults.sendEmailNotificationsWhenNewVersionsAreFoundEnabled = [sendEmailNotificationsWhenNewVersionsAreFoundButton state]; - NSLog(@"%@ email notifications.", defaults.sendEmailNotificationsWhenNewVersionsAreFoundEnabled ? @"Enabling" : @"Disabling"); + NSLog(@"%@ email notifications.", defaults.sendEmailNotificationsWhenNewVersionsAreFoundEnabled ? @"Enabling" : @"Disabling"); defaults.checkForNewVersionsOfAppsAutomaticallyEnabled = [checkForNewVersionsOfAppsAutomaticallyButton state]; NSLog(@"%@ checking for new apps automatically.", defaults.checkForNewVersionsOfAppsAutomaticallyEnabled ? @"Enabling" : @"Disabling"); @@ -455,8 +464,6 @@ - (void)save // Synchronize with the defaults database [defaults synchronize]; - // Start the AutoPkg run timer if the user enabled it - [self startAutoPkgRunTimer]; } - (BOOL)autoPkgUpdateAvailable @@ -483,12 +490,6 @@ - (BOOL)autoPkgUpdateAvailable return NO; } -- (void)startAutoPkgRunTimer -{ - LGAutoPkgRunner *autoPkgRunner = [[LGAutoPkgRunner alloc] init]; - [autoPkgRunner startAutoPkgRunTimer]; -} - - (void)runCommandAsRoot:(NSString *)command { // Super dirty hack, but way easier than @@ -525,7 +526,7 @@ - (IBAction)installGit:(id)sender // TODO: We should probably be installing the official // Git PKG rather than dealing with the Xcode CLI tools [[NSOperationQueue mainQueue]addOperationWithBlock:^{ - NSString *alertMessage = @"After the Command Line Tools installation completes, click OK"; + NSString *alertMessage = @"After the Xcode Command Line Tools installation completes, click OK."; NSAlert *alert = [NSAlert alertWithMessageText:alertMessage defaultButton:@"OK" alternateButton:nil @@ -533,14 +534,15 @@ - (IBAction)installGit:(id)sender informativeTextWithFormat:@""]; if ([alert runModal] == NSAlertDefaultReturn) { - [installGitButton setTitle:@"Install Git"]; - LGHostInfo *hostInfo = [[LGHostInfo alloc] init]; if ([hostInfo gitInstalled]) { [installGitButton setEnabled:NO]; + [gitStatusLabel setStringValue:kLGGitInstalledLabel]; + [gitStatusIcon setImage:[NSImage imageNamed:NSImageNameStatusAvailable]]; } else { - [installGitButton setTitle:@"Install Git"]; [installGitButton setEnabled:YES]; + [gitStatusLabel setStringValue:kLGGitNotInstalledLabel]; + [gitStatusIcon setImage:[NSImage imageNamed:NSImageNameStatusUnavailable]]; alert = [NSAlert alertWithMessageText:@"There was a problem installing Git!" defaultButton:@"Go get Git!" alternateButton:@"Cancel" @@ -550,9 +552,9 @@ - (IBAction)installGit:(id)sender if ([alert runModal] == NSAlertDefaultReturn) { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://git-scm.com/downloads"]]; } - } } + [installGitButton setTitle:@"Install Git"]; }]; }; @@ -585,9 +587,14 @@ - (void)downloadAndInstallAutoPkg NSLog(@"AutoPkg installed successfully!"); [autoPkgStatusLabel setStringValue:kLGAutoPkgInstalledLabel]; [autoPkgStatusIcon setImage:[NSImage imageNamed:NSImageNameStatusAvailable]]; - [installAutoPkgButton setTitle:@"Install AutoPkg"]; [installAutoPkgButton setEnabled:NO]; + }else{ + [autoPkgStatusLabel setStringValue:kLGAutoPkgNotInstalledLabel]; + [autoPkgStatusIcon setImage:[NSImage imageNamed:NSImageNameStatusUnavailable]]; + [installAutoPkgButton setEnabled:YES]; } + + [installAutoPkgButton setTitle:@"Install AutoPkg"]; [self stopProgress:nil]; } @@ -633,7 +640,8 @@ - (IBAction)openAutoPkgRecipeReposFolder:(id)sender { BOOL isDir; NSString *autoPkgRecipeReposFolder = [defaults autoPkgRecipeRepoDir]; - + autoPkgRecipeReposFolder = autoPkgRecipeReposFolder ? autoPkgRecipeReposFolder:[@"~/Library/AutoPkg" stringByExpandingTildeInPath]; + if ([[NSFileManager defaultManager] fileExistsAtPath:autoPkgRecipeReposFolder isDirectory:&isDir] && isDir) { NSURL *autoPkgRecipeReposFolderURL = [NSURL fileURLWithPath:autoPkgRecipeReposFolder]; [[NSWorkspace sharedWorkspace] openURL:autoPkgRecipeReposFolderURL]; @@ -655,6 +663,7 @@ - (IBAction)openAutoPkgCacheFolder:(id)sender { BOOL isDir; NSString *autoPkgCacheFolder = [defaults autoPkgCacheDir]; + autoPkgCacheFolder = autoPkgCacheFolder ? autoPkgCacheFolder:[@"~/Library/AutoPkg" stringByExpandingTildeInPath]; if ([[NSFileManager defaultManager] fileExistsAtPath:autoPkgCacheFolder isDirectory:&isDir] && isDir) { NSURL *autoPkgCacheFolderURL = [NSURL fileURLWithPath:autoPkgCacheFolder]; @@ -677,7 +686,8 @@ - (IBAction)openAutoPkgRecipeOverridesFolder:(id)sender { BOOL isDir; NSString *autoPkgRecipeOverridesFolder = [defaults autoPkgRecipeOverridesDir]; - + autoPkgRecipeOverridesFolder = autoPkgRecipeOverridesFolder ? autoPkgRecipeOverridesFolder:[@"~/Library/AutoPkg" stringByExpandingTildeInPath]; + if ([[NSFileManager defaultManager] fileExistsAtPath:autoPkgRecipeOverridesFolder isDirectory:&isDir] && isDir) { NSURL *autoPkgRecipeOverridesFolderURL = [NSURL fileURLWithPath:autoPkgRecipeOverridesFolder]; [[NSWorkspace sharedWorkspace] openURL:autoPkgRecipeOverridesFolderURL]; @@ -714,6 +724,7 @@ - (IBAction)chooseLocalMunkiRepo:(id)sender // Here we can be certain the URL exists and it is a directory NSString *urlPath = [url path]; [localMunkiRepo setStringValue:urlPath]; + [openLocalMunkiRepoFolderButton setEnabled:YES]; defaults.munkiRepo = urlPath; } } @@ -812,30 +823,28 @@ - (IBAction)chooseAutoPkgRecipeOverridesDir:(id)sender - (IBAction)addAutoPkgRepoURL:(id)sender { // TODO: Input validation + success/failure notification - - LGAutoPkgRunner *autoPkgRunner = [[LGAutoPkgRunner alloc] init]; - [autoPkgRunner addAutoPkgRecipeRepo:[repoURLToAdd stringValue]]; - + NSString *repo = [repoURLToAdd stringValue]; + [self startProgressWithMessage:[NSString stringWithFormat:@"Adding %@",repo]]; + + [LGAutoPkgTask repoAdd:repo reply:^(NSError *error) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self stopProgress:error]; + [_popRepoTableViewHandler reload]; + [_appTableViewHandler reload]; + }]; + }]; [repoURLToAdd setStringValue:@""]; - - [_popRepoTableViewHandler reload]; - [_appTableViewHandler reload]; } - (IBAction)updateReposNow:(id)sender { - LGAutoPkgRunner *autoPkgRunner = [[LGAutoPkgRunner alloc] init]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateReposNowCompleteNotificationRecieved:) - name:kLGNotificationUpdateReposComplete - object:nil]; - - // TODO: Success/failure notification - [self.updateRepoNowButton setEnabled:NO]; [self startProgressWithMessage:@"Updating AutoPkg recipe repos."]; - - NSLog(@"Updating AutoPkg recipe repos."); - [autoPkgRunner invokeAutoPkgRepoUpdateInBackgroundThread]; + [self.updateRepoNowButton setEnabled:NO]; + + [LGAutoPkgTask repoUpdate:^(NSError *error) { + [self stopProgress:error]; + [self.updateRepoNowButton setEnabled:YES]; + }]; } - (void)updateReposNowCompleteNotificationRecieved:(NSNotification *)notification @@ -855,17 +864,18 @@ - (void)updateReposNowCompleteNotificationRecieved:(NSNotification *)notificatio - (IBAction)checkAppsNow:(id)sender { - LGAutoPkgRunner *autoPkgRunner = [[LGAutoPkgRunner alloc] init]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(autoPkgRunCompleteNotificationRecieved:) - name:kLGNotificationRunAutoPkgComplete - object:nil]; - - [self.checkAppsNowButton setEnabled:NO]; + NSString *recipeList = [LGApplications recipeList]; + [self startProgressWithMessage:@"Running selected AutoPkg recipes."]; - - [autoPkgRunner invokeAutoPkgInBackgroundThread]; -} + [LGAutoPkgTask runRecipeList:recipeList + progress:^(NSString *message, double taskProgress) { + [self updateProgress:message progress:taskProgress]; + + } reply:^(NSDictionary *report,NSError *error) { + [self stopProgress:error]; + LGEmailer *emailer = [LGEmailer new]; + [emailer sendEmailForReport:report error:error]; + }];} - (void)autoPkgRunCompleteNotificationRecieved:(NSNotification *)notification { @@ -940,7 +950,7 @@ - (void)controlTextDidEndEditing:(NSNotification *)notification } else if ([object isEqual:autoPkgRunInterval]) { if ([autoPkgRunInterval integerValue] != 0) { defaults.autoPkgRunInterval = [autoPkgRunInterval integerValue]; - [self startAutoPkgRunTimer]; + [[LGAutoPkgSchedule sharedTimer] configure]; } } else if ([object isEqual:smtpPassword]) { NSError *error; @@ -1010,6 +1020,7 @@ - (void)changeCheckForNewVersionsOfAppsAutomaticallyButtonState { defaults.checkForNewVersionsOfAppsAutomaticallyEnabled = [checkForNewVersionsOfAppsAutomaticallyButton state]; NSLog(@"%@ checking for new apps automatically.", defaults.checkForNewVersionsOfAppsAutomaticallyEnabled ? @"Enabling" : @"Disabling"); + [[LGAutoPkgSchedule sharedTimer] configure]; } - (void)changeCheckForRepoUpdatesAutomaticallyButtonState @@ -1055,6 +1066,7 @@ - (void)stopProgressNotificationReceived:(NSNotification *)notification - (void)startProgressWithMessage:(NSString *)message { + [_menuProgressDelegate startProgressWithMessage:message]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self.progressMessage setStringValue:message]; [self.progressIndicator setHidden:NO]; @@ -1069,6 +1081,7 @@ - (void)stopProgress:(NSError *)error { // Stop the progress panel, and if and error was sent in // do a sheet modal + [_menuProgressDelegate stopProgress:error]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self.progressPanel orderOut:self]; [self.progressIndicator setDoubleValue:0.0]; @@ -1094,6 +1107,17 @@ - (void)stopProgress:(NSError *)error }]; } +- (void)updateProgress:(NSString *)message progress:(double)progress{ + [_menuProgressDelegate updateProgress:message progress:progress]; + if (message.length < 100) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self.progressIndicator setIndeterminate:NO]; + [self.progressDetailsMessage setStringValue:message]; + [self.progressIndicator setDoubleValue:progress > 5.0 ? progress:5.0 ]; + }]; + } +} + - (void)didEndWithPreferenceRepairRequest:(NSAlert *)alert returnCode:(NSInteger)returnCode { if (returnCode == NSAlertSecondButtonReturn) { diff --git a/AutoPkgr/LGConfigurationWindowController.xib b/AutoPkgr/LGConfigurationWindowController.xib index 8dda11e2..d9e2c085 100644 --- a/AutoPkgr/LGConfigurationWindowController.xib +++ b/AutoPkgr/LGConfigurationWindowController.xib @@ -906,7 +906,7 @@ - + @@ -955,7 +955,7 @@ - + @@ -1004,7 +1004,7 @@ - + diff --git a/AutoPkgr/LGEmailer.h b/AutoPkgr/LGEmailer.h index cb1ee825..bb4c0ae8 100644 --- a/AutoPkgr/LGEmailer.h +++ b/AutoPkgr/LGEmailer.h @@ -25,6 +25,7 @@ @interface LGEmailer : NSObject - (void)sendEmailNotification:(NSString *)subject message:(NSString *)message; +- (void)sendEmailForReport:(NSDictionary *)report error:(NSError *)error; - (void)sendTestEmail; @end diff --git a/AutoPkgr/LGEmailer.m b/AutoPkgr/LGEmailer.m index ec35ea59..dc6f32fb 100644 --- a/AutoPkgr/LGEmailer.m +++ b/AutoPkgr/LGEmailer.m @@ -29,88 +29,89 @@ @implementation LGEmailer - (void)sendEmailNotification:(NSString *)subject message:(NSString *)message { LGDefaults *defaults = [[LGDefaults alloc] init]; - - BOOL TLS = [defaults SMTPTLSEnabled]; - - MCOSMTPSession *smtpSession = [[MCOSMTPSession alloc] init]; - smtpSession.hostname = [defaults objectForKey:kLGSMTPServer]; - smtpSession.port = (int)[defaults integerForKey:kLGSMTPPort]; - smtpSession.username = [defaults objectForKey:kLGSMTPUsername]; - - if (TLS) { - NSLog(@"SSL/TLS is enabled for %@.", [defaults objectForKey:kLGSMTPServer]); - // If the SMTP port is 465, use MCOConnectionTypeTLS. - // Otherwise use MCOConnectionTypeStartTLS. - if (smtpSession.port == 465) { - smtpSession.connectionType = MCOConnectionTypeTLS; + + if (defaults.sendEmailNotificationsWhenNewVersionsAreFoundEnabled) { + BOOL TLS = defaults.SMTPTLSEnabled; + + MCOSMTPSession *smtpSession = [[MCOSMTPSession alloc] init]; + smtpSession.username = defaults.SMTPUsername ? defaults.SMTPUsername:@""; + smtpSession.hostname = defaults.SMTPServer ? defaults.SMTPServer:@""; + smtpSession.port = (int)defaults.SMTPPort; + + if (TLS) { + NSLog(@"SSL/TLS is enabled for %@.", defaults.SMTPServer); + // If the SMTP port is 465, use MCOConnectionTypeTLS. + // Otherwise use MCOConnectionTypeStartTLS. + if (smtpSession.port == 465) { + smtpSession.connectionType = MCOConnectionTypeTLS; + } else { + smtpSession.connectionType = MCOConnectionTypeStartTLS; + } } else { - smtpSession.connectionType = MCOConnectionTypeStartTLS; + NSLog(@"SSL/TLS is _not_ enabled for %@.", defaults.SMTPServer); + smtpSession.connectionType = MCOConnectionTypeClear; } - } else { - NSLog(@"SSL/TLS is _not_ enabled for %@.", [defaults objectForKey:kLGSMTPServer]); - smtpSession.connectionType = MCOConnectionTypeClear; - } - - // Retrieve the SMTP password from the default - // keychain if it exists - NSError *error = nil; - NSString *smtpUsernameString = [defaults objectForKey:kLGSMTPUsername]; - - if (smtpUsernameString) { - NSString *password = [SSKeychain passwordForService:kLGApplicationName - account:smtpUsernameString - error:&error]; - - if ([error code] == SSKeychainErrorNotFound) { - NSLog(@"Keychain item not found for account %@.", smtpSession.username); - } else if([error code] == SSKeychainErrorNoPassword) { - NSLog(@"Found the keychain item for %@ but no password value was returned.", smtpUsernameString); - } else if (error != nil) { - NSLog(@"An error occurred when attempting to retrieve the keychain entry for %@. Error: %@", smtpUsernameString, [error localizedDescription]); - } else { - // Only set the SMTP session password if the username exists - if (smtpUsernameString != nil && ![smtpUsernameString isEqual:@""]) { - NSLog(@"Retrieved password from keychain for account %@.", smtpUsernameString); - smtpSession.password = password; + + // Retrieve the SMTP password from the default + // keychain if it exists + NSError *error = nil; + + if (smtpSession.username) { + NSString *password = [SSKeychain passwordForService:kLGApplicationName + account:smtpSession.username + error:&error]; + + if ([error code] == SSKeychainErrorNotFound) { + NSLog(@"Keychain item not found for account %@.", smtpSession.username); + } else if([error code] == SSKeychainErrorNoPassword) { + NSLog(@"Found the keychain item for %@ but no password value was returned.", smtpSession.username); + } else if (error != nil) { + NSLog(@"An error occurred when attempting to retrieve the keychain entry for %@. Error: %@", smtpSession.username, [error localizedDescription]); + } else { + // Only set the SMTP session password if the username exists + if (smtpSession.username != nil && ![smtpSession.username isEqual:@""]) { + NSLog(@"Retrieved password from keychain for account %@.", smtpSession.username); + smtpSession.password = password ? password:@""; + } } } - } - - MCOMessageBuilder * builder = [[MCOMessageBuilder alloc] init]; - - [[builder header] setFrom:[MCOAddress addressWithDisplayName:@"AutoPkgr Notification" - mailbox:[defaults SMTPFrom]]]; - - NSMutableArray *to = [[NSMutableArray alloc] init]; - for (NSString *toAddress in [defaults SMTPTo]) { - if (![toAddress isEqual:@""]) { - MCOAddress *newAddress = [MCOAddress addressWithMailbox:toAddress]; - [to addObject:newAddress]; + + MCOMessageBuilder * builder = [[MCOMessageBuilder alloc] init]; + [[builder header] setFrom:[MCOAddress addressWithDisplayName:@"AutoPkgr Notification" + mailbox:defaults.SMTPFrom ? defaults.SMTPFrom:@""]]; + + NSMutableArray *to = [[NSMutableArray alloc] init]; + for (NSString *toAddress in defaults.SMTPTo) { + if (![toAddress isEqual:@""]) { + MCOAddress *newAddress = [MCOAddress addressWithMailbox:toAddress]; + [to addObject:newAddress]; + } } + NSString *fullSubject = [NSString stringWithFormat:@"%@ on %@",subject,[[NSHost currentHost] name]]; + [[builder header] setTo:to]; + [[builder header] setSubject:fullSubject]; + [builder setHTMLBody:message]; + NSData * rfc822Data = [builder data]; + + MCOSMTPSendOperation *sendOperation = [smtpSession sendOperationWithData:rfc822Data]; + [sendOperation start:^(NSError *error) { + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:@{kLGNotificationUserInfoSubject:subject, + kLGNotificationUserInfoMessage:message}]; + + if (error) { + NSLog(@"Error sending email from %@: %@", smtpSession.username, error); + [userInfo setObject:error forKey:kLGNotificationUserInfoError]; + } else { + NSLog(@"Successfully sent email from %@.", smtpSession.username); + } + + [center postNotificationName:kLGNotificationEmailSent + object:self + userInfo:[NSDictionary dictionaryWithDictionary:userInfo]]; + }]; + } - - [[builder header] setTo:to]; - [[builder header] setSubject:subject]; - [builder setHTMLBody:message]; - NSData * rfc822Data = [builder data]; - - MCOSMTPSendOperation *sendOperation = [smtpSession sendOperationWithData:rfc822Data]; - [sendOperation start:^(NSError *error) { - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:@{kLGNotificationUserInfoSubject:subject, - kLGNotificationUserInfoMessage:message}]; - - if (error) { - NSLog(@"%@ Error sending email:%@", smtpSession.username, error); - [userInfo setObject:error forKey:kLGNotificationUserInfoError]; - } else { - NSLog(@"%@ Successfully sent email!", smtpSession.username); - } - - [center postNotificationName:kLGNotificationEmailSent - object:self - userInfo:[NSDictionary dictionaryWithDictionary:userInfo]]; - }]; } - (void)sendTestEmail @@ -121,7 +122,68 @@ - (void)sendTestEmail NSString *message = @"This is a test notification from AutoPkgr."; // Send the email [self sendEmailNotification:subject message:message]; + +} +- (void)sendEmailForReport:(NSDictionary *)report error:(NSError *)error +{ + // Get arrays of new downloads/packages from the plist + NSMutableString *message; + NSString *subject; + NSArray *newDownloads; + NSArray *newPackages; + + if (report) { + newDownloads = [report objectForKey:@"new_downloads"]; + newPackages = [report objectForKey:@"new_packages"]; + } + + if ([newDownloads count]) { + message = [[NSMutableString alloc] init]; + NSLog(@"New stuff was downloaded."); + + // Create the subject string + subject = [NSString stringWithFormat:@"[%@] New software avaliable for testing",kLGApplicationName]; + + // Append the the message string with report + [message appendFormat:@"The following software is now available for testing:
"]; + + for (NSString *path in newDownloads) { + // Get just the application name from the path in the new_downloads dict + NSString *app = [[path lastPathComponent] stringByDeletingPathExtension]; + + // Write the app to the string + [message appendFormat:@"%@: ",app]; + + // The default version is not detected, override later + NSString *version = @"Version not detected"; + for (NSDictionary *dct in newPackages) { + NSString *pkgPath = [dct objectForKey:@"pkg_path"]; + if ([pkgPath rangeOfString:app options:NSCaseInsensitiveSearch].location != NSNotFound && dct[@"version"]) { + version = dct[@"version"]; + break; + } + } + [message appendFormat:@"%@
",version]; + } + } else { + DLog(@"Nothing new was downloaded."); + } + + if (error) { + if(!message){ + message = [[NSMutableString alloc] init]; + } + + if (!subject){ + subject = [NSString stringWithFormat:@"[%@] Error occured while running AutoPkg",kLGApplicationName]; + } + [message appendFormat:@"
The following error occured:
%@
%@",error.localizedDescription,error.localizedRecoverySuggestion]; + } + + if (message) { + [self sendEmailNotification:subject message:message]; + } } @end diff --git a/AutoPkgr/LGError.h b/AutoPkgr/LGError.h index a9eac9f9..3664e334 100644 --- a/AutoPkgr/LGError.h +++ b/AutoPkgr/LGError.h @@ -33,26 +33,41 @@ typedef NS_ENUM(NSInteger, LGErrorCodes) { kLGErrorTestingPort, /** Error when some preferences could not be repaired, and values were removed */ kLGErrorReparingAutoPkgPrefs, + /** Error when attempting to spawn multiple instances of `autopkg run` at a time */ + kLGErrorMultipleRunsOfAutopkg, + /** Error installing Git */ + kLGErrorInstallGit, + /** Error installing/updating AutoPkg */ + kLGErrorInstallAutoPkg, + /** Error installing/updating AutoPkgr */ + kLGErrorInstallAutoPkgr, }; #pragma mark - AutoPkg specific Error codes typedef NS_ENUM(NSInteger, LGErrorAutoPkgCodes) { - /** AutoPkg most often returns -1 on misconfiguration errors */ + /** AutoPkg often returns -1 on when misconfigured */ kLGErrorAutoPkgConfig = -1, - + /** AutoPkg returns 255 if no recipe is specified */ kLGErrorAutoPkgNoRecipes = 255, }; -typedef NS_ENUM(NSInteger, LGAutoPkgrVerb) { - kLGUnknown, - kLGAutoPkgrRun, - kLGAutoPkgrRepoUpdate, - kLGAutoPkgrRepoAdd, - kLGAutoPkgrRepoDelete, - kLGAutoPkgrMakeOverride, - kLGAutoPkgrInstallGit, - kLGAutoPkgrInstallAutoPkg, +typedef NS_ENUM(NSInteger, LGAutoPkgVerb) { + kLGAutoPkgUndefinedVerb, + // recipe verbs + kLGAutoPkgRun, + kLGAutoPkgRecipeList, + kLGAutoPkgMakeOverride, + kLGAutoPkgSearch, + + // repo verbs + kLGAutoPkgRepoAdd, + kLGAutoPkgRepoDelete, + kLGAutoPkgRepoUpdate, + kLGAutoPkgRepoList, + + // other verbs + kLGAutoPkgVersion, }; @interface LGError : NSObject @@ -94,7 +109,7 @@ typedef NS_ENUM(NSInteger, LGAutoPkgrVerb) { * @return NO if error occured and the exit code is not 0, otherwise YES; * @discussion If the task is not complete this will return YES; */ -+ (BOOL)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgrVerb)verb error:(NSError **)error; ++ (BOOL)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgVerb)verb error:(NSError **)error; /** * Generated NSError Object from and AutoPkgr NSTask * @@ -104,6 +119,6 @@ typedef NS_ENUM(NSInteger, LGAutoPkgrVerb) { * @return Populated NSError Object if exit status is != kLGErrorSuccess, nil otherwise; * @discussion If the returned object will be nil if the task has not complete; */ -+ (NSError *)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgrVerb)verb; ++ (NSError *)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgVerb)verb; @end diff --git a/AutoPkgr/LGError.m b/AutoPkgr/LGError.m index 96d57740..0ca7df42 100644 --- a/AutoPkgr/LGError.m +++ b/AutoPkgr/LGError.m @@ -36,57 +36,99 @@ void DLog(NSString *format, ...) #endif } -static NSString *errorMsgFromCode(LGErrorCodes code) + +static NSDictionary *userInfoFromCode(LGErrorCodes code) { - NSString *msg; + NSString *localizedBaseString; + NSString *message; + NSString *suggestion; switch (code) { - case kLGErrorSendingEmail: - msg = @"Error sending email"; - break; - case kLGErrorTestingPort: - msg = @"Error verifying server and port"; - break; - case kLGErrorReparingAutoPkgPrefs: - msg = @"Unable to resolve some issues with the AutoPkg preferences."; - default: - break; + case kLGErrorSuccess: + localizedBaseString = @"kLGErrorSuccess"; + break; + case kLGErrorSendingEmail: + localizedBaseString = @"kLGErrorSendingEmail"; + break; + case kLGErrorTestingPort: + localizedBaseString = @"kLGErrorTestingPort"; + break; + case kLGErrorReparingAutoPkgPrefs: + localizedBaseString = @"kLGErrorReparingAutoPkgPrefs"; + break; + case kLGErrorMultipleRunsOfAutopkg: + localizedBaseString = @"kLGErrorMultipleRunsOfAutopkg"; + break; + case kLGErrorInstallGit: + localizedBaseString = @"kLGErrorInstallGit"; + break; + case kLGErrorInstallAutoPkg: + localizedBaseString = @"kLGErrorInstallAutoPkg"; + break; + case kLGErrorInstallAutoPkgr: + localizedBaseString = @"kLGErrorInstallAutoPkgr"; + break; + default: + localizedBaseString = @"kLGErrorUnknown"; + break; } - return msg; + + // Setup the localized descripton + message = NSLocalizedString([localizedBaseString stringByAppendingString:@"Description"], + @"NSLocalizedDescriptionKey"); + + // Setup the localized recovery suggestion + suggestion = NSLocalizedString([localizedBaseString stringByAppendingString:@"Suggestion"], + @"NSLocalizedRecoverySuggestionErrorKey"); + + + return @{NSLocalizedDescriptionKey:message, + NSLocalizedRecoverySuggestionErrorKey:suggestion,}; } -static NSString *errorMessageFromAutoPkgVerb(LGAutoPkgrVerb verb) +static NSString *errorMessageFromAutoPkgVerb(LGAutoPkgVerb verb) { - NSString *msg; + NSString *localizedBaseString; + NSString *message; + switch (verb) { - case kLGUnknown: - msg = @"AutoPkgr encountered an error"; - break; - case kLGAutoPkgrRun: - msg = @"Error running recipes"; - break; - case kLGAutoPkgrRepoUpdate: - msg = @"Error updating repos"; - break; - case kLGAutoPkgrRepoAdd: - msg = @"Error adding selected repo"; - break; - case kLGAutoPkgrRepoDelete: - msg = @"Error removing selected repo"; - break; - case kLGAutoPkgrMakeOverride: - msg = @"Error creating overrides file"; - break; - case kLGAutoPkgrInstallAutoPkg: - msg = @"Error installing git"; - break; - case kLGAutoPkgrInstallGit: - msg = @"Error installing/updating AutoPkg"; - break; - default: - msg = @"AutoPkgr encountered an error"; - break; + case kLGAutoPkgUndefinedVerb: + localizedBaseString = @"kLGAutoPkgUndefinedVerb"; + break; + case kLGAutoPkgRun: + localizedBaseString = @"kLGAutoPkgRun"; + break; + case kLGAutoPkgRecipeList: + localizedBaseString = @"kLGAutoPkgRecipeList"; + break; + case kLGAutoPkgMakeOverride: + localizedBaseString = @"kLGAutoPkgMakeOverride"; + break; + case kLGAutoPkgSearch: + localizedBaseString = @"kLGAutoPkgSearch"; + break; + case kLGAutoPkgRepoAdd: + localizedBaseString = @"kLGAutoPkgRepoAdd"; + break; + case kLGAutoPkgRepoDelete: + localizedBaseString = @"kLGAutoPkgRepoDelete"; + break; + case kLGAutoPkgRepoUpdate: + localizedBaseString = @"kLGAutoPkgRepoUpdate"; + break; + case kLGAutoPkgRepoList: + localizedBaseString = @"kLGAutoPkgRepoList"; + break; + case kLGAutoPkgVersion: + localizedBaseString = @"kLGAutoPkgVersion"; + break; + default: + localizedBaseString = @"kLGAutoPkgUndefinedVerb"; + break; } - return msg; + + message = NSLocalizedString([localizedBaseString stringByAppendingString:@"Description"], + @"NSLocalizedDescriptionKey"); + return message; } @implementation LGError @@ -96,20 +138,17 @@ + (void)presentErrorWithCode:(LGErrorCodes)code window:(NSWindow *)window delega NSError *error; [[self class] errorWithCode:code error:&error]; [NSApp presentError:error - modalForWindow:NULL - delegate:sender - didPresentSelector:selector - contextInfo:NULL]; + modalForWindow:NULL + delegate:sender + didPresentSelector:selector + contextInfo:NULL]; } #endif + (BOOL)errorWithCode:(LGErrorCodes)code error:(NSError *__autoreleasing *)error { NSError *err = [self errorWithCode:code]; - if (error) - *error = err; - else - DLog(@"Error: %@", err.localizedDescription); + if (error)*error = err; return (code == kLGErrorSuccess); } @@ -117,16 +156,16 @@ + (NSError *)errorWithCode:(LGErrorCodes)code { NSError *error; if (code != kLGErrorSuccess) { - NSString *errorMsg = errorMsgFromCode(code); + NSDictionary *userInfo = userInfoFromCode(code); error = [NSError errorWithDomain:kLGApplicationName code:code - userInfo:@{ NSLocalizedDescriptionKey : errorMsg }]; - DLog(@"Error [%d] %@ \n %@", code, errorMsg); + userInfo:userInfo]; + DLog(@"Error [%d]: %@ \n %@", code, userInfo[NSLocalizedDescriptionKey],userInfo[NSLocalizedRecoverySuggestionErrorKey]); } return error; } -+ (BOOL)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgrVerb)verb error:(NSError **)error ++ (BOOL)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgVerb)verb error:(NSError **)error { NSError *taskError = [self errorWithTaskError:task verb:verb]; if (error && taskError) { @@ -136,44 +175,46 @@ + (BOOL)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgrVerb)verb error:(NSErr return taskError ? taskError.code == kLGErrorSuccess : YES; } -+ (NSError *)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgrVerb)verb ++ (NSError *)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgVerb)verb { // if task is running if ([task isRunning]) { return nil; } - + NSError *error; NSString *errorMsg = errorMessageFromAutoPkgVerb(verb); NSString *errorDetails; NSInteger taskError; + + if([task.standardError isKindOfClass:[NSPipe class]]){ + NSData *errData = [[task.standardError fileHandleForReading] readDataToEndOfFile]; + if(errData){ + errorDetails = [[NSString alloc] initWithData:errData encoding:NSASCIIStringEncoding]; + } + } - NSData *errData = [[task.standardError fileHandleForReading] readDataToEndOfFile]; - - errorDetails = [[NSString alloc] initWithData:errData encoding:NSASCIIStringEncoding]; - taskError = task.terminationStatus; - DLog(@"%ld : %@", task.terminationStatus, errorDetails); - - // AutoPkg's rc on a failed repo-update / delete is 0, so check the stderr for "ERROR" string - if (verb == kLGAutoPkgrRepoUpdate || verb == kLGAutoPkgrRepoDelete) { - if ([errorDetails rangeOfString:@"ERROR"].location != NSNotFound) { + taskError = task.terminationStatus; + // AutoPkg's rc on a failed repo-update / add / delete is 0, so check the stderr for "ERROR" string + if (verb == kLGAutoPkgRepoUpdate || verb == kLGAutoPkgRepoDelete || verb == kLGAutoPkgRepoAdd) { + if (errorDetails && ![errorDetails isEqualToString:@""]) { taskError = kLGErrorAutoPkgConfig; } } // autopkg run exits 255 if no recipe speciifed - else if (verb == kLGAutoPkgrRun && task.terminationStatus == kLGErrorAutoPkgNoRecipes) { + else if (verb == kLGAutoPkgRun && task.terminationStatus == kLGErrorAutoPkgNoRecipes) { errorDetails = @"No recipes specified."; } - + // Otherwise we can just use the termination status if (taskError != 0) { error = [NSError errorWithDomain:kLGApplicationName code:taskError userInfo:@{ NSLocalizedDescriptionKey : errorMsg, NSLocalizedRecoverySuggestionErrorKey : errorDetails ? errorDetails : @"" }]; - + // If Debugging is enabled, log the error message - DLog(@"Error [%d ] %@ \n %@", taskError, errorMsg, errorDetails); + DLog(@"Error [%d] %@ \n %@", taskError, errorMsg, errorDetails); } return error; } diff --git a/AutoPkgr/LGHostInfo.h b/AutoPkgr/LGHostInfo.h index 1a8b34de..21e1500d 100644 --- a/AutoPkgr/LGHostInfo.h +++ b/AutoPkgr/LGHostInfo.h @@ -30,4 +30,5 @@ - (BOOL)gitInstalled; - (BOOL)autoPkgInstalled; ++ (NSArray *)knownGitPaths; @end diff --git a/AutoPkgr/LGHostInfo.m b/AutoPkgr/LGHostInfo.m index e9974a18..4309177b 100644 --- a/AutoPkgr/LGHostInfo.m +++ b/AutoPkgr/LGHostInfo.m @@ -21,6 +21,7 @@ #import "LGHostInfo.h" #import "LGConstants.h" +#import "LGAutoPkgr.h" @implementation LGHostInfo @@ -43,14 +44,28 @@ - (NSString *)getUserAtHostName - (BOOL)gitInstalled { - NSArray *knownGitPaths = [[NSArray alloc] initWithObjects:@"/usr/bin/git", @"/usr/local/bin/git", @"/opt/boxen/homebrew/bin/git", nil]; + NSFileManager *fm = [[NSFileManager alloc] init]; + NSArray *knownGitPaths = [[self class] knownGitPaths]; for (NSString *path in knownGitPaths) { - if ([[NSFileManager defaultManager] isExecutableFileAtPath:path]) { + if ([fm isExecutableFileAtPath:[path stringByAppendingPathComponent:@"git"]]) { + if ([path isEqualToString:knownGitPaths[0]]) { + DLog(@"Git was installed via Xcode command line tools."); + } else { + DLog(@"Found Git binary at %@", path); + } return YES; } } + NSPredicate *gitInstallPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] 'GitOSX.Installer'"]; + NSArray *receipts = [fm contentsOfDirectoryAtPath:@"/var/db/receipts" error:nil]; + + if ([receipts filteredArrayUsingPredicate:gitInstallPredicate].count) { + DLog(@"Git was installed via the official Git installer."); + return YES; + } + return NO; } @@ -89,4 +104,8 @@ - (BOOL)autoPkgInstalled return NO; } ++ (NSArray *)knownGitPaths +{ + return @[ @"/Library/Developer/CommandLineTools/usr/bin/", @"/usr/local/bin/", @"/opt/boxen/homebrew/bin/" ]; +} @end diff --git a/AutoPkgr/LGPopularRepositories.h b/AutoPkgr/LGPopularRepositories.h index c2dce276..4343c4af 100644 --- a/AutoPkgr/LGPopularRepositories.h +++ b/AutoPkgr/LGPopularRepositories.h @@ -20,9 +20,10 @@ // #import -#import "LGAutoPkgRunner.h" +#import "LGAutoPkgTask.h" #import "LGGitHubJSONLoader.h" #import "LGApplications.h" +#import "LGProgressDelegate.h" @interface LGPopularRepositories : NSObject { @@ -34,7 +35,6 @@ NSArray *popularRepos; NSArray *activeRepos; NSArray *searchedRepos; - LGAutoPkgRunner *pkgRunner; LGGitHubJSONLoader *jsonLoader; BOOL awake; } @@ -43,4 +43,6 @@ @property (weak) IBOutlet NSSearchField *repoSearch; @property (weak) IBOutlet LGApplications *appObject; +@property (weak) idprogressDelegate; + @end diff --git a/AutoPkgr/LGPopularRepositories.m b/AutoPkgr/LGPopularRepositories.m index 8503a0a7..65facd8f 100644 --- a/AutoPkgr/LGPopularRepositories.m +++ b/AutoPkgr/LGPopularRepositories.m @@ -30,7 +30,6 @@ - (id)init awake = NO; - pkgRunner = [[LGAutoPkgRunner alloc] init]; jsonLoader = [[LGGitHubJSONLoader alloc] init]; recipeRepos = [jsonLoader getAutoPkgRecipeRepos]; @@ -71,14 +70,25 @@ - (id)init return self; } +- (void)repoEditDidEndWithError:(NSError *)error withTableView:(NSTableView *)tableView{ + [[NSOperationQueue mainQueue]addOperationWithBlock:^{ + [self getAndParseLocalAutoPkgRecipeRepos]; + [_appObject reload]; + [tableView reloadData]; + [_progressDelegate stopProgress:error]; + }]; +} + - (void)reload { - [self assembleRepos]; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self assembleRepos]; + }]; } - (void)assembleRepos { - activeRepos = [self getAndParseLocalAutoPkgRecipeRepos]; + [self getAndParseLocalAutoPkgRecipeRepos]; NSMutableArray *workingPopularRepos = [NSMutableArray arrayWithArray:popularRepos]; @@ -118,22 +128,22 @@ - (NSUInteger)string:(NSString *)s inArray:(NSArray *)a return match; } -- (NSArray *)getAndParseLocalAutoPkgRecipeRepos // Strips out the local path of the cloned git repository and returns an array with only the URLs +- (void)getAndParseLocalAutoPkgRecipeRepos // Strips out the local path of the cloned git repository and returns an array with only the URLs { - NSArray *repos = [pkgRunner getLocalAutoPkgRecipeRepos]; - NSMutableArray *strippedRepos = [[NSMutableArray alloc] init]; - - NSError *error = NULL; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\((https?://.+)\\)" options:0 error:&error]; - - for (NSString *repo in repos) { - NSTextCheckingResult *result = [regex firstMatchInString:repo options:0 range:NSMakeRange(0,[repo length])]; - if ([result numberOfRanges] == 2) { - [strippedRepos addObject:[repo substringWithRange:[result rangeAtIndex:1]]]; + [LGAutoPkgTask repoList:^(NSArray *repos, NSError *error) { + NSMutableArray *strippedRepos = [[NSMutableArray alloc] init]; + + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\((https?://.+)\\)" options:0 error:&error]; + + for (NSString *repo in repos) { + NSTextCheckingResult *result = [regex firstMatchInString:repo options:0 range:NSMakeRange(0,[repo length])]; + if ([result numberOfRanges] == 2) { + [strippedRepos addObject:[repo substringWithRange:[result rangeAtIndex:1]]]; + } } - } - - return [NSArray arrayWithArray:strippedRepos]; + + activeRepos = [NSArray arrayWithArray:strippedRepos]; + }]; } - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView @@ -169,13 +179,20 @@ - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColum - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { if([[tableColumn identifier] isEqualToString:@"repoCheckbox"]) { - if ([object isEqual:@YES]) { - [pkgRunner addAutoPkgRecipeRepo:[searchedRepos objectAtIndex:row]]; + NSString *repo = [searchedRepos objectAtIndex:row]; + BOOL add = [object isEqual:@YES]; + NSString *message = [NSString stringWithFormat:@"%@ %@",add ? @"Adding":@"Removing",repo]; + [_progressDelegate startProgressWithMessage:message]; + if (add) { + [LGAutoPkgTask repoAdd:repo reply:^(NSError *error) { + [self repoEditDidEndWithError:error withTableView:tableView]; + }]; } else { - [pkgRunner removeAutoPkgRecipeRepo:[searchedRepos objectAtIndex:row]]; + [LGAutoPkgTask repoRemove:repo reply:^(NSError *error) { + [self repoEditDidEndWithError:error withTableView:tableView]; + }]; } - activeRepos = [self getAndParseLocalAutoPkgRecipeRepos]; - [_appObject reload]; + } } diff --git a/AutoPkgr/LGProgressDelegate.h b/AutoPkgr/LGProgressDelegate.h new file mode 100644 index 00000000..08c76116 --- /dev/null +++ b/AutoPkgr/LGProgressDelegate.h @@ -0,0 +1,15 @@ +// +// LGProgressDelegate.h +// AutoPkgr +// +// Created by Eldon on 9/6/14. +// Copyright (c) 2014 The Linde Group, Inc. All rights reserved. +// + +#import + +@protocol LGProgressDelegate +- (void)startProgressWithMessage:(NSString *)message; +- (void)stopProgress:(NSError *)error; +- (void)updateProgress:(NSString *)message progress:(double)progress; +@end diff --git a/README.md b/README.md index 9780ef3a..205282a7 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Usage 1. On first launch, you'll see the configuration window: ![Install](doc-images/config_tab1.png) -1. Click the button to **Install Git** if needed. +1. Click the button to **Install Git** if needed. (This will prompt you to install the Xcode Command Line Tools.) 1. Click the button to **Install AutoPkg** if needed. diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings new file mode 100644 index 00000000..15f81e52 --- /dev/null +++ b/en.lproj/Localizable.strings @@ -0,0 +1,69 @@ +/* + Localizable.strings + AutoPkgr + + Created by Eldon on 9/5/14. + Copyright (c) 2014 The Linde Group Inc. All rights reserved. +*/ + +/** + * AutoPkgr related Error code strings + * keys ending in Description are intended for NSLocalizedDescriptioncriptionKey + * keys ending in Suggestion are intended for NSLocalizedRecoverySuggestiongestionErrorKey + */ + +/** Success */ +"kLGErrorSuccessDescription" = "Success!"; +"kLGErrorSuccessSuggestion" = ""; + +/** Error when sending email fails */ +"kLGErrorSendingEmailDescription" = "Error sending email"; +"kLGErrorSendingEmailSuggestion" = "Please verify the username, password, server and port are correct. (Localized)"; + +/** Error when testing port failed */ +"kLGErrorTestingPortDescription" = "Error verifying server and port"; +"kLGErrorTestingPortSuggestion" = "Please verify server and port are correct."; + +/** Error when some preferences could not be repairedDescription" = "" and values were removed */ +"kLGErrorReparingAutoPkgPrefsDescription" = "Unable to resolve some issues with the AutoPkg preferences"; +"kLGErrorReparingAutoPkgPrefsSuggestion" = "If the problem persists please inspect the com.github.autopkg preference file manually for incorrect values."; + +/** Error when attempting to spawn multiple instances of `autopkg run` at a time */ +"kLGErrorMultipleRunsOfAutopkgDescription" = "Only one instance of AutoPkg can be run at a time"; +"kLGErrorMultipleRunsOfAutopkgSuggestion" = "A scheduled AutoPkg run may be running in the background. Please wait for it to complete."; + +/** Error installing Git */ +"kLGErrorInstallGitDescription" = "Error installing/updating Git"; +"kLGErrorInstallGitSuggestion" = "If the problem persists please try manually downloading and installing Git."; + +/** Error installing/updating AutoPkg */ +"kLGErrorInstallAutoPkgDescription" = "Error installing AutoPkg"; +"kLGErrorInstallAutoPkgSuggestion" = "If the problem persists please try manually downloading and installing AutoPkg: https://github.com/autopkg/autopkg/releases/latest"; + +/** Error installing/updating AutoPkgr */ +"kLGErrorInstallAutoPkgrDescription" = "Error updating AutoPkgr"; +"kLGErrorInstallAutoPkgrSuggestion" = "If the problem persists please try manually downloading and installing AutoPkgr: https://github.com/lindegroup/autopkgr/releases/latest"; + + + +/** + * Errors relating specifically to AutoPkg + * Recovery Suggestiongestion string is created from the task.standardError so unnecissary here + */ + +"kLGAutoPkgUndefinedVerbDescription" = "AutoPkgr encountered an error"; + +// recipe verbs +"kLGAutoPkgRunDescription" = "Error running recipes"; +"kLGAutoPkgRecipeListDescription" = "Error encountered listing recipes"; +"kLGAutoPkgMakeOverrideDescription" = "Error creating OverrideDescription file"; +"kLGAutoPkgSearchDescription" = "Error encountered searching for recipe"; + +// repo verbs +"kLGAutoPkgRepoAddDescription" = "Error adding repo"; +"kLGAutoPkgRepoDeleteDescription" = "Error removing repo"; +"kLGAutoPkgRepoUpdateDescription" = "Error updating repo"; +"kLGAutoPkgRepoListDescription" = "Error encountered listing repos"; + +// other verbs +"kLGAutoPkgVersionDescription" = "Error getting AutoPkg version";