diff --git a/AutoPkgr.xcodeproj/project.pbxproj b/AutoPkgr.xcodeproj/project.pbxproj index 276b9ca7..eec7a164 100644 --- a/AutoPkgr.xcodeproj/project.pbxproj +++ b/AutoPkgr.xcodeproj/project.pbxproj @@ -92,6 +92,11 @@ BEE42DAD1AC6E2D100599238 /* report_malformed.plist in Resources */ = {isa = PBXBuildFile; fileRef = BEE42DAB1AC6E2D100599238 /* report_malformed.plist */; }; BEE42DAE1AC6E2D100599238 /* report_none.plist in Resources */ = {isa = PBXBuildFile; fileRef = BEE42DAC1AC6E2D100599238 /* report_none.plist */; }; BEE8CF1319E0E7F400981C4B /* NSTextField+setSafeStringValue.m in Sources */ = {isa = PBXBuildFile; fileRef = BEE8CF1219E0E7F400981C4B /* NSTextField+setSafeStringValue.m */; }; + BEEFE6B71AE9D01200882C89 /* LGAutoPkgErrorHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = BEEFE6B61AE9D01200882C89 /* LGAutoPkgErrorHandler.m */; }; + BEEFE6B81AE9D01200882C89 /* LGAutoPkgErrorHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = BEEFE6B61AE9D01200882C89 /* LGAutoPkgErrorHandler.m */; }; + BEEFE6BB1AE9E8A600882C89 /* LGLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = BEEFE6B91AE9E8A600882C89 /* LGLogger.m */; }; + BEEFE6BC1AE9E8A600882C89 /* LGLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = BEEFE6B91AE9E8A600882C89 /* LGLogger.m */; }; + BEEFE6BD1AE9E9D500882C89 /* LGLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = BEEFE6B91AE9E8A600882C89 /* LGLogger.m */; }; BEFC3C761A3DF16700C789E9 /* LGUserNotifications.m in Sources */ = {isa = PBXBuildFile; fileRef = BE6815D41A0B18DE004AD310 /* LGUserNotifications.m */; }; BEFC3C771A3DF16700C789E9 /* LGGitHubJSONLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1BAD9D197A4133008192A7 /* LGGitHubJSONLoader.m */; }; BEFC3C781A3DF16700C789E9 /* LGInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = BEAA76CB19BFD635002D73EE /* LGInstaller.m */; }; @@ -278,6 +283,10 @@ BEE42DAC1AC6E2D100599238 /* report_none.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = report_none.plist; sourceTree = ""; }; BEE8CF1119E0E7F400981C4B /* NSTextField+setSafeStringValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSTextField+setSafeStringValue.h"; path = "Custom Categories/NSTextField+setSafeStringValue.h"; sourceTree = ""; }; BEE8CF1219E0E7F400981C4B /* NSTextField+setSafeStringValue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSTextField+setSafeStringValue.m"; path = "Custom Categories/NSTextField+setSafeStringValue.m"; sourceTree = ""; }; + BEEFE6B51AE9D01200882C89 /* LGAutoPkgErrorHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGAutoPkgErrorHandler.h; sourceTree = ""; }; + BEEFE6B61AE9D01200882C89 /* LGAutoPkgErrorHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGAutoPkgErrorHandler.m; sourceTree = ""; }; + BEEFE6B91AE9E8A600882C89 /* LGLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGLogger.m; sourceTree = ""; }; + BEEFE6BA1AE9E8A600882C89 /* LGLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGLogger.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 = ""; }; FF435269A548437DA5380201 /* libPods-AutoPkgr.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AutoPkgr.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -331,7 +340,7 @@ isa = PBXGroup; children = ( 1AC69556195B59ED00D2BD81 /* AutoPkgr */, - BE05CE6819DAF26B0089068B /* helper */, + BE05CE6819DAF26B0089068B /* Privileged Helper */, 1AC6954F195B59ED00D2BD81 /* Frameworks */, 1AC6954E195B59ED00D2BD81 /* Products */, 1AC69574195B59EE00D2BD81 /* AutoPkgrTests */, @@ -381,16 +390,13 @@ 1AC69556195B59ED00D2BD81 /* AutoPkgr */ = { isa = PBXGroup; children = ( - 1AC86D36195E0972006FDD2B /* Feature Classes */, + BEEFE6BE1AEA82CD00882C89 /* AutoPkg Classes */, + 1AC86D36195E0972006FDD2B /* Model Classes */, BE40CB7719D4441600A1DABD /* UI Classes */, BE40CB7819D4444D00A1DABD /* Utility Classes */, BE05CE8419DAF63D0089068B /* Helper Tool Communication */, BE39FE5419DFBB2900C1B4A1 /* Custom Categories */, - 1AC69565195B59EE00D2BD81 /* MainMenu.xib */, - 1AC9841E195CEC360071CAB3 /* LGConfigurationWindowController.xib */, - BE0BACD01A2560AE00554989 /* LGJSSDistributionPointsPrefPanel.xib */, - BE67E8741A44E10500484151 /* LGRecipeSearchPanel.xib */, - 1AC69568195B59EE00D2BD81 /* Images.xcassets */, + BEEFE6BF1AEA873800882C89 /* XIBs */, 1AC98423195DF6270071CAB3 /* Resources */, 1AC69557195B59ED00D2BD81 /* Supporting Files */, ); @@ -433,30 +439,35 @@ name = "Supporting Files"; sourceTree = ""; }; - 1AC86D36195E0972006FDD2B /* Feature Classes */ = { + 1AC86D36195E0972006FDD2B /* Model Classes */ = { isa = PBXGroup; children = ( + BE344F531ABFBEAB00500AAE /* LGAutoPkgReport.h */, + BE344F541ABFBEAB00500AAE /* LGAutoPkgReport.m */, BE025CFC19BAEF8800D36345 /* LGAutoPkgSchedule.h */, BE025CFD19BAEF8800D36345 /* LGAutoPkgSchedule.m */, - BE025CF419BAE93400D36345 /* LGAutoPkgTask.h */, - BE025CF519BAE93400D36345 /* LGAutoPkgTask.m */, 1AC98410195CE8510071CAB3 /* LGEmailer.h */, 1AC98411195CE8520071CAB3 /* LGEmailer.m */, - BE6815D31A0B18DE004AD310 /* LGUserNotifications.h */, - BE6815D41A0B18DE004AD310 /* LGUserNotifications.m */, 1A1BAD9C197A4133008192A7 /* LGGitHubJSONLoader.h */, 1A1BAD9D197A4133008192A7 /* LGGitHubJSONLoader.m */, BEAA76CA19BFD635002D73EE /* LGInstaller.h */, BEAA76CB19BFD635002D73EE /* LGInstaller.m */, 6A53625B1988BE59008A949C /* LGTestPort.h */, 6A53625C1988BE59008A949C /* LGTestPort.m */, + BE8C7D6C1A86CEF600EBD32A /* LGTools.h */, + BE8C7D6D1A86CEF600EBD32A /* LGTools.m */, + BE6815D31A0B18DE004AD310 /* LGUserNotifications.h */, + BE6815D41A0B18DE004AD310 /* LGUserNotifications.m */, + BEBF7B151A4894AC00E9967F /* LGVersioner.h */, + BEBF7B161A4894AC00E9967F /* LGVersioner.m */, ); - name = "Feature Classes"; + name = "Model Classes"; sourceTree = ""; }; 1AC98423195DF6270071CAB3 /* Resources */ = { isa = PBXGroup; children = ( + 1AC69568195B59EE00D2BD81 /* Images.xcassets */, 1A2C7FAC19CC9F8300CFF472 /* scripts */, 8BB217F519709A2C00EF8B93 /* AutoPkgrIcon.png */, 1AC98425195DF6350071CAB3 /* autopkgr.png */, @@ -479,7 +490,7 @@ name = Pods; sourceTree = ""; }; - BE05CE6819DAF26B0089068B /* helper */ = { + BE05CE6819DAF26B0089068B /* Privileged Helper */ = { isa = PBXGroup; children = ( BE05CE6919DAF26B0089068B /* main.m */, @@ -488,6 +499,7 @@ BE05CE7B19DAF5A30089068B /* LGAutoPkgrProtocol.h */, BE05CE7D19DAF5B80089068B /* Supporting Files */, ); + name = "Privileged Helper"; path = helper; sourceTree = ""; }; @@ -573,18 +585,14 @@ BEFC931C1995F0710074C938 /* LGError.m */, 1AC86D3B195E0AC6006FDD2B /* LGHostInfo.h */, 1AC86D3C195E0AC6006FDD2B /* LGHostInfo.m */, - BE8C7D6C1A86CEF600EBD32A /* LGTools.h */, - BE8C7D6D1A86CEF600EBD32A /* LGTools.m */, + BEEFE6BA1AE9E8A600882C89 /* LGLogger.h */, + BEEFE6B91AE9E8A600882C89 /* LGLogger.m */, BE0E857719D668F600B25B5E /* LGHTTPRequest.h */, BE0E857819D668F600B25B5E /* LGHTTPRequest.m */, BE053D131A8F90920021D97B /* LGPasswords.h */, BE053D141A8F90920021D97B /* LGPasswords.m */, 1A1BAD94197A0407008192A7 /* LGVersionComparator.h */, 1A1BAD95197A0407008192A7 /* LGVersionComparator.m */, - BEBF7B151A4894AC00E9967F /* LGVersioner.h */, - BEBF7B161A4894AC00E9967F /* LGVersioner.m */, - BE344F531ABFBEAB00500AAE /* LGAutoPkgReport.h */, - BE344F541ABFBEAB00500AAE /* LGAutoPkgReport.m */, ); name = "Utility Classes"; sourceTree = ""; @@ -601,6 +609,28 @@ name = "Html Categories"; sourceTree = ""; }; + BEEFE6BE1AEA82CD00882C89 /* AutoPkg Classes */ = { + isa = PBXGroup; + children = ( + BE025CF419BAE93400D36345 /* LGAutoPkgTask.h */, + BE025CF519BAE93400D36345 /* LGAutoPkgTask.m */, + BEEFE6B51AE9D01200882C89 /* LGAutoPkgErrorHandler.h */, + BEEFE6B61AE9D01200882C89 /* LGAutoPkgErrorHandler.m */, + ); + name = "AutoPkg Classes"; + sourceTree = ""; + }; + BEEFE6BF1AEA873800882C89 /* XIBs */ = { + isa = PBXGroup; + children = ( + 1AC69565195B59EE00D2BD81 /* MainMenu.xib */, + 1AC9841E195CEC360071CAB3 /* LGConfigurationWindowController.xib */, + BE0BACD01A2560AE00554989 /* LGJSSDistributionPointsPrefPanel.xib */, + BE67E8741A44E10500484151 /* LGRecipeSearchPanel.xib */, + ); + name = XIBs; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -897,6 +927,7 @@ BE0BACD11A2560AE00554989 /* LGJSSDistributionPointsPrefPanel.m in Sources */, BEC1258919F046FA006696C4 /* main.m in Sources */, BED136B71AC3BE04003EBF0F /* NSArray+html_report.m in Sources */, + BEEFE6BB1AE9E8A600882C89 /* LGLogger.m in Sources */, BED02ADC1A194F9C00714CC2 /* NSArray+filtered.m in Sources */, 1AC69564195B59EE00D2BD81 /* LGAppDelegate.m in Sources */, BEC1258619F0465C006696C4 /* LGConfigurationWindowController.m in Sources */, @@ -918,6 +949,7 @@ BE0E857919D668F600B25B5E /* LGHTTPRequest.m in Sources */, BED136B11AC262CB003EBF0F /* NSString+html_report.m in Sources */, 1A1BAD9E197A4133008192A7 /* LGGitHubJSONLoader.m in Sources */, + BEEFE6B71AE9D01200882C89 /* LGAutoPkgErrorHandler.m in Sources */, BEC1258C19F049A1006696C4 /* LGTableView.m in Sources */, 6A53625D1988BE59008A949C /* LGTestPort.m in Sources */, BEFC931D1995F0710074C938 /* LGError.m in Sources */, @@ -939,11 +971,13 @@ BEFC3C761A3DF16700C789E9 /* LGUserNotifications.m in Sources */, BED136B81AC3BE04003EBF0F /* NSArray+html_report.m in Sources */, BEFC3C771A3DF16700C789E9 /* LGGitHubJSONLoader.m in Sources */, + BEEFE6BC1AE9E8A600882C89 /* LGLogger.m in Sources */, BED136AD1AC0F993003EBF0F /* LGPasswords.m in Sources */, BED136AB1AC05B1D003EBF0F /* LGAutoPkgReport.m in Sources */, BEFC3C781A3DF16700C789E9 /* LGInstaller.m in Sources */, BEFC3C791A3DF16700C789E9 /* LGTestPort.m in Sources */, BEFC3C7C1A3DF16700C789E9 /* LGPopularRepositories.m in Sources */, + BEEFE6B81AE9D01200882C89 /* LGAutoPkgErrorHandler.m in Sources */, BEFC3C7D1A3DF16700C789E9 /* LGRecipes.m in Sources */, BE67E8771A44F02B00484151 /* LGRecipeSearch.m in Sources */, BED136AC1AC05B4C003EBF0F /* LGEmailer.m in Sources */, @@ -976,6 +1010,7 @@ files = ( BE05CE9119DB380F0089068B /* LGConstants.m in Sources */, BE1C810419E8224000EF77F3 /* NSImage+statusLight.m in Sources */, + BEEFE6BD1AE9E9D500882C89 /* LGLogger.m in Sources */, BE05CE6A19DAF26B0089068B /* main.m in Sources */, BE053D1B1A8F9DC00021D97B /* SNTCertificate.m in Sources */, BE053D181A8F9DAA0021D97B /* SNTCodesignChecker.m in Sources */, diff --git a/AutoPkgr/AutoPkgr-Info.plist b/AutoPkgr/AutoPkgr-Info.plist index 158b1f47..1078fefb 100755 --- a/AutoPkgr/AutoPkgr-Info.plist +++ b/AutoPkgr/AutoPkgr-Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.2 + 1.2.3 CFBundleSignature ???? CFBundleVersion - 751 + 780 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/AutoPkgr/Custom Categories/NSString+cleaned.h b/AutoPkgr/Custom Categories/NSString+cleaned.h index d85e3072..89e3bcfb 100644 --- a/AutoPkgr/Custom Categories/NSString+cleaned.h +++ b/AutoPkgr/Custom Categories/NSString+cleaned.h @@ -35,4 +35,6 @@ - (NSString *)truncateToLength:(NSInteger)length; +- (NSString *)truncateToNumberOfLines:(NSInteger)count; + @end diff --git a/AutoPkgr/Custom Categories/NSString+cleaned.m b/AutoPkgr/Custom Categories/NSString+cleaned.m index e01fec57..0799426b 100644 --- a/AutoPkgr/Custom Categories/NSString+cleaned.m +++ b/AutoPkgr/Custom Categories/NSString+cleaned.m @@ -47,4 +47,16 @@ - (NSString *)truncateToLength:(NSInteger)length return self; } +- (NSString *)truncateToNumberOfLines:(NSInteger)count +{ + if (self.length) { + NSArray *lines = [self componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + // If the count is less, we don't need to do anything just send self back + if (lines.count > count) { + NSIndexSet *idxSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, count)]; + return [[lines objectsAtIndexes:idxSet] componentsJoinedByString:@"\n"]; + } + } + return self; +} @end diff --git a/AutoPkgr/LGAppDelegate.m b/AutoPkgr/LGAppDelegate.m index f84d937d..2a052c90 100644 --- a/AutoPkgr/LGAppDelegate.m +++ b/AutoPkgr/LGAppDelegate.m @@ -212,12 +212,15 @@ - (void)checkNowFromMenu:(id)sender { DLog(@"Received 'Check Now' menulet command."); - [self startProgressWithMessage:@"Running selected AutoPkg recipes."]; + [self startProgressWithMessage:@"Running selected AutoPkg recipes..."]; NSString *recipeList = [LGRecipes recipeList]; BOOL updateRepos = [[LGDefaults standardUserDefaults] checkForRepoUpdatesAutomaticallyEnabled]; - _taskManager = [[LGAutoPkgTaskManager alloc] init]; - _taskManager.progressDelegate = self; + if (!_taskManager) { + _taskManager = [[LGAutoPkgTaskManager alloc] init]; + _taskManager.progressDelegate = self; + } + [_taskManager runRecipeList:recipeList updateRepo:updateRepos reply:^(NSDictionary *report, NSError *error) { @@ -295,6 +298,9 @@ - (void)startProgressWithMessage:(NSString *)message [_runUpdatesNowMenuItem setTitle:@"Cancel AutoPkg Run"]; [_runUpdatesNowMenuItem setAction:@selector(cancelRunFromMenu:)]; + + NSMenuItem *runStatus = [self.statusMenu itemAtIndex:0]; + runStatus.title = [message truncateToLength:50]; }]; } @@ -402,12 +408,14 @@ - (void)menuWillOpen:(NSMenu *)menu // despite aggressive synchronization, so we need to pull the value from // the actual preference file until a better work around is found... - NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[@"~/Library/Preferences/com.lindegroup.AutoPkgr.plist" stringByExpandingTildeInPath]]; + if (!_taskManager || _taskManager.operationCount == 0 ) { + NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[@"~/Library/Preferences/com.lindegroup.AutoPkgr.plist" stringByExpandingTildeInPath]]; - NSString *date = dict[@"LastAutoPkgRun"]; - if (date) { - NSString *status = [NSString stringWithFormat:@"Last AutoPkg Run: %@", date ?: @"Never by AutoPkgr"]; - [_progressMenuItem setTitle:status]; + NSString *date = [LGDefaults formattedDate:dict[@"LastAutoPkgRun"]]; + if (date) { + NSString *status = [NSString stringWithFormat:@"Last AutoPkg Run: %@", date ?: @"Never by AutoPkgr"]; + _progressMenuItem.title = status; + } } } diff --git a/AutoPkgr/LGAutoPkgErrorHandler.h b/AutoPkgr/LGAutoPkgErrorHandler.h new file mode 100644 index 00000000..6ee5d2d0 --- /dev/null +++ b/AutoPkgr/LGAutoPkgErrorHandler.h @@ -0,0 +1,51 @@ +// +// LGAutoPkgErrorHandler.h +// +// Created by Eldon Ahrold on 4/23/15. +// +// Copyright 2015 Eldon Ahrold +// +// 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 + +/* AutoPkg Task Verbs + */ +typedef NS_ENUM(NSInteger, LGAutoPkgVerb) { + kLGAutoPkgUndefinedVerb, + // recipe verbs + kLGAutoPkgRun, + kLGAutoPkgRecipeList, + kLGAutoPkgMakeOverride, + kLGAutoPkgSearch, + + // repo verbs + kLGAutoPkgRepoAdd, + kLGAutoPkgRepoDelete, + kLGAutoPkgRepoUpdate, + kLGAutoPkgRepoList, + + // other verbs + kLGAutoPkgVersion, +}; + +@interface LGAutoPkgErrorHandler : NSObject + +@property (nonatomic, readonly) NSPipe *pipe; +@property (nonatomic, readonly) NSString *errorString; + +- (instancetype)initWithVerb:(LGAutoPkgVerb)verb; +- (NSError *)errorWithExitCode:(NSInteger)exitCode; + +@end diff --git a/AutoPkgr/LGAutoPkgErrorHandler.m b/AutoPkgr/LGAutoPkgErrorHandler.m new file mode 100644 index 00000000..c2ae9138 --- /dev/null +++ b/AutoPkgr/LGAutoPkgErrorHandler.m @@ -0,0 +1,218 @@ +// +// LGAutoPkgErrorHandler.m +// +// Created by Eldon Ahrold on 4/23/15. +// +// Copyright 2015 Eldon Ahrold +// +// 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 "LGAutoPkgErrorHandler.h" +#import "LGLogger.h" + +static NSString *errorMessageFromAutoPkgVerb(LGAutoPkgVerb verb) +{ + NSString *localizedBaseString; + NSString *message; + + switch (verb) { + 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; + } + + message = NSLocalizedString([localizedBaseString stringByAppendingString:@"Description"], + @"NSLocalizedDescriptionKey"); + return message; +} + +NSString *maskPasswordInString(NSString *string) +{ + NSError *error; + NSMutableString *retractedString = [string mutableCopy]; + + NSString *baseAll = @"a-zA-Z0-9~`!#.,$%^&*()-_{}<>?"; + NSString *pattern = [NSString stringWithFormat:@"([%@]+:[%@]+(?=@))", baseAll, baseAll]; + + NSRegularExpression *exp = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; + + NSRange range = NSMakeRange(0, string.length); + NSArray *matches = [exp matchesInString:string options:0 range:range]; + for (NSTextCheckingResult *match in matches) { + NSString *ms = [string substringWithRange:match.range]; + NSArray *array = [ms componentsSeparatedByString:@":"]; + + // Make sure to re-range the retracted string each loop, since it gets modified. + NSRange r_range = NSMakeRange(0, retractedString.length); + [retractedString replaceOccurrencesOfString:[array lastObject] + withString:@"*******" + options:NSCaseInsensitiveSearch + range:r_range]; + } + + return [retractedString copy]; +} + +@implementation LGAutoPkgErrorHandler { + LGAutoPkgVerb _verb; + NSMutableOrderedSet *_errorStrings; + NSPipe *_pipe; +} + +- (void)dealloc +{ + _pipe.fileHandleForReading.readabilityHandler = nil; + _pipe = nil; + DevLog(@"Dealloc Error Handler"); +} + +- (instancetype)initWithVerb:(LGAutoPkgVerb)verb +{ + if (self = [super init]) { + _verb = verb; + + NSPipe *pipe = [NSPipe pipe]; + _pipe = pipe; + + [pipe.fileHandleForReading setReadabilityHandler:^(NSFileHandle *fh) { + NSData *data = fh.availableData; + if (data) { + NSString *str = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + if (!_errorStrings) { + _errorStrings = [[NSMutableOrderedSet alloc] init]; + } + + [_errorStrings addObject:str]; + } + }]; + } + return self; +} + +- (NSPipe *)standardError +{ + return _pipe; +} + +- (NSString *)errorString +{ + NSArray *errors = nil; + NSString *errorString = nil; + + if ((errors = _errorStrings.array)) { + NSArray *filters = @[ @"not (SELF BEGINSWITH[CD] 'Failed.')" ]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:[filters componentsJoinedByString:@" AND "]]; + + errorString = [[_errorStrings.array filteredArrayUsingPredicate:predicate] componentsJoinedByString:@"\n"]; + }; + return errorString; +} + +- (NSError *)errorWithExitCode:(NSInteger)exitCode +{ + NSError *error = nil; + + NSString *standardErrString = self.errorString; + if (standardErrString.length) { + NSString *errorMsg = errorMessageFromAutoPkgVerb(_verb); + NSString *errorDetails = maskPasswordInString(standardErrString); + + // If the error message looks like a Python exception log it, but trim it up for UI. + NSPredicate *exceptionPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS 'Traceback'"]; + if ([exceptionPredicate evaluateWithObject:errorDetails]) { + NSArray *splitExceptionFromError = [errorDetails componentsSeparatedByString:@"Traceback (most recent call last):"]; + + // The exception should in theory always be last. + NSString *fullExceptionMessage = [splitExceptionFromError lastObject]; + NSLog(@"(FULL AUTOPKG TRACEBACK) %@", fullExceptionMessage); + + NSArray *array = [fullExceptionMessage componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + + NSPredicate *noEmptySpaces = [NSPredicate predicateWithFormat:@"not (SELF == '')"]; + NSString *exceptionDetails = [[array filteredArrayUsingPredicate:noEmptySpaces] lastObject]; + + NSMutableString *recombinedErrorDetails = [[NSMutableString alloc] init]; + if (splitExceptionFromError.count > 1) { + // If something came before, put that information back into the errorDetails. + [recombinedErrorDetails appendString:[splitExceptionFromError firstObject]]; + } + + [recombinedErrorDetails appendFormat:@"A Python exception occurred during the execution of autopkg, see the system log for more details.\n\n[ERROR] %@", exceptionDetails]; + + errorDetails = [NSString stringWithString:recombinedErrorDetails]; + + // Otherwise continue... + } else { + // AutoPkg's rc on a failed repo-update / add / delete is 0, but we want it reported back to the UI so set it to -1. + if (_verb == kLGAutoPkgRepoUpdate || _verb == kLGAutoPkgRepoDelete || _verb == kLGAutoPkgRepoAdd) { + if (errorDetails.length) { + exitCode = -1; + } + } + // autopkg run exits 255 if no recipe specified + else if (_verb == kLGAutoPkgRun && exitCode == 255) { + errorDetails = @"No recipes specified."; + } + } + + // Otherwise we can just use the termination status + if (exitCode != 0) { + error = [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier] + code:exitCode + userInfo:@{ NSLocalizedDescriptionKey : errorMsg, + NSLocalizedRecoverySuggestionErrorKey : errorDetails ?: @"" }]; + + // If Debugging is enabled, log the error message + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"debug"]) { + NSLog(@"Error [%ld] %@ \n %@", (long)exitCode, errorMsg, errorDetails); + } + } + } + + return error; +} + +@end diff --git a/AutoPkgr/LGAutoPkgSchedule.m b/AutoPkgr/LGAutoPkgSchedule.m index b1d7d87c..0451c1af 100644 --- a/AutoPkgr/LGAutoPkgSchedule.m +++ b/AutoPkgr/LGAutoPkgSchedule.m @@ -73,8 +73,8 @@ + (void)startAutoPkgSchedule:(BOOL)start interval:(NSInteger)interval isForced:( if (!error) { NSDate *date = [NSDate dateWithTimeIntervalSinceNow:runInterval]; NSDateFormatter *fomatter = [NSDateFormatter new]; - [fomatter setDateStyle:NSDateFormatterMediumStyle]; - [fomatter setTimeStyle:NSDateFormatterMediumStyle]; + [fomatter setDateStyle:NSDateFormatterShortStyle]; + [fomatter setTimeStyle:NSDateFormatterShortStyle]; NSLog(@"Next scheduled AutoPkg run will occur at %@",[fomatter stringFromDate:date]); } [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; diff --git a/AutoPkgr/LGAutoPkgTask.h b/AutoPkgr/LGAutoPkgTask.h index 87936e19..58700be7 100644 --- a/AutoPkgr/LGAutoPkgTask.h +++ b/AutoPkgr/LGAutoPkgTask.h @@ -20,12 +20,14 @@ // #import + #import "LGAutoPkgr.h" #import "LGProgressDelegate.h" @class LGAutoPkgTaskManager; @class LGAutoPkgTask; @class LGAutoPkgTaskResponseObject; + /** * Constant to access recipe key in autopkg search or recipe-list */ @@ -155,6 +157,11 @@ extern NSString *const kLGAutoPkgRepoURLKey; */ @property (copy, nonatomic, readonly) NSString *standardErrString; +/** + * Exit code from autopkg + */ +@property (nonatomic, assign) int exitCode; + /** * 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; diff --git a/AutoPkgr/LGAutoPkgTask.m b/AutoPkgr/LGAutoPkgTask.m index 7682b2d9..7b7b92f0 100644 --- a/AutoPkgr/LGAutoPkgTask.m +++ b/AutoPkgr/LGAutoPkgTask.m @@ -18,6 +18,8 @@ // #import "LGAutoPkgTask.h" +#import "LGAutoPkgErrorHandler.h" + #import "LGRecipes.h" #import "LGVersionComparator.h" #import "AHProxySettings.h" @@ -57,6 +59,8 @@ @interface LGAutoPkgTask () @property (copy, nonatomic, readwrite) NSMutableData *standardOutData; @property (copy, nonatomic, readwrite) NSString *standardOutString; @property (copy, nonatomic, readwrite) NSString *standardErrString; +@property (strong, nonatomic) LGAutoPkgErrorHandler *errorHandler; + @property (strong, nonatomic) LGVersioner *versioner; // Results objects @@ -163,6 +167,7 @@ - (void)repoUpdate:(void (^)(NSError *))reply @implementation LGAutoPkgTask { BOOL _isExecuting; BOOL _isFinished; + BOOL _userCanceled; } - (NSString *)taskDescription @@ -220,6 +225,8 @@ - (void)start - (void)cancel { [self.taskLock lock]; + + _userCanceled = YES; if (self.task && self.task.isRunning) { DLog(@"Canceling %@", self.taskDescription); [self.task terminate]; @@ -315,9 +322,9 @@ - (void)didCompleteTaskExecution [self.task.standardError fileHandleForReading].readabilityHandler = nil; } - if (!_error) { + if (!_error && !_userCanceled) { [self.taskLock lock]; - self.error = [LGError errorWithTaskError:self.task verb:_verb]; + self.error = [_errorHandler errorWithExitCode:self.task.terminationStatus]; [self.taskLock unlock]; } @@ -344,7 +351,7 @@ - (void)launch - (void)launchInBackground:(void (^)(NSError *))reply { LGAutoPkgTaskManager *bgQueue = [LGAutoPkgTaskManager new]; - DLog(@"bgQueue: %@", bgQueue.name); + DevLog(@"bgQueue: %@", bgQueue.name); self.replyErrorBlock = reply; [bgQueue addOperation:self]; } @@ -396,15 +403,17 @@ - (NSString *)version return _version; } -#pragma mark - Task config helpers #pragma mark - Task config helpers - (void)configureFileHandles { + // Set up stdout NSPipe *standardOutput = [NSPipe pipe]; self.task.standardOutput = standardOutput; - NSPipe *standardError = [NSPipe pipe]; - self.task.standardError = standardError; + // Set up stderr + // The Error handler class creates a the pipe to process the stderr messages. + _errorHandler = [[LGAutoPkgErrorHandler alloc] initWithVerb:_verb]; + self.task.standardError = _errorHandler.pipe; if (_verb == kLGAutoPkgRun || _verb == kLGAutoPkgRepoUpdate) { if (self.AUTOPKG_VERSION_0_4_0) { @@ -414,7 +423,8 @@ - (void)configureFileHandles if (_verb == kLGAutoPkgRun) { _versioner = [[LGVersioner alloc] init]; - progressPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] 'Processing'"]; + progressPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES '^Processing.*\\.\\.\\.'"]; + total = [self recipeListCount]; } else if (_verb == kLGAutoPkgRepoUpdate) { progressPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] '.git'"]; @@ -423,43 +433,48 @@ - (void)configureFileHandles BOOL verbose = [[NSUserDefaults standardUserDefaults] boolForKey:@"verboseAutoPkgRun"]; [[standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *handle) { - NSString *message = [[NSString alloc]initWithData:[handle availableData] encoding:NSUTF8StringEncoding]; - - [_versioner parseString:message]; - if ([progressPredicate evaluateWithObject:message]) { - NSString *fullMessage; - if (_verb == kLGAutoPkgRepoUpdate) { - fullMessage = [NSString stringWithFormat:@"Updating %@", [message lastPathComponent]]; - } else { - int cntStr = (int)round(count) + 1; - int totStr = (int)round(total); - fullMessage = [[NSString stringWithFormat:@"(%d/%d) %@", cntStr, totStr, message] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - } + NSData *data = handle.availableData; + if (data.length) { + NSString *message = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; + + [_versioner parseString:message]; + if ([progressPredicate evaluateWithObject:message]) { + NSString *fullMessage; + if (_verb == kLGAutoPkgRepoUpdate) { + fullMessage = [NSString stringWithFormat:@"Updating %@", [message lastPathComponent]]; + } else { + int cntStr = (int)round(count) + 1; + int totStr = (int)round(total); + fullMessage = [[NSString stringWithFormat:@"(%d/%d) %@", cntStr, totStr, message] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + } - double progress = ((count/total) * 100); + double progress = ((count/total) * 100); - LGAutoPkgTaskResponseObject *response = [[LGAutoPkgTaskResponseObject alloc] init]; - response.progressMessage = fullMessage; - response.progress = progress; - count++; + LGAutoPkgTaskResponseObject *response = [[LGAutoPkgTaskResponseObject alloc] init]; + response.progressMessage = fullMessage; + response.progress = progress; + count++; - [(NSObject *)_taskStatusDelegate performSelectorOnMainThread:@selector(didReceiveStatusUpdate:) withObject:response waitUntilDone:NO]; + [(NSObject *)_taskStatusDelegate performSelectorOnMainThread:@selector(didReceiveStatusUpdate:) withObject:response waitUntilDone:NO]; - // If verboseAutoPkgRun is not enabled, log the limited message here. - if (!verbose) { + // If verboseAutoPkgRun is not enabled, log the limited message here. + if (!verbose) { + NSLog(@"%@",message); + } + } + // If verboseAutoPkgRun is enabled, log everything generated by autopkg run -v. + if (verbose) { NSLog(@"%@",message); } } - // If verboseAutoPkgRun is enabled, log everything generated by autopkg run -v. - if (verbose) { - NSLog(@"%@",message); - } }]; } } else { + // In order to prevent maxing out the stdout buffer collect the data progressively + // even thought the data returned is usually small. [[standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *fh) { NSData *data = [fh availableData]; - if ([data length]) { + if (data.length) { if (!_standardOutData) { _standardOutData = [[NSMutableData alloc] init ]; } @@ -524,18 +539,22 @@ - (void)addEnvironmentVariable:(NSString *)variable forKey:(NSString *)key } #pragma mark - Output / Results +- (int)exitCode +{ + if (!self.task.isRunning) { + return self.task.terminationStatus; + } + + return NSTaskTerminationReasonUncaughtSignal; +} + - (NSString *)standardErrString { [self.taskLock lock]; if (!_standardErrString && !self.task.isRunning) { - NSData *data; - if ([self.task.standardError isKindOfClass:[NSPipe class]]) { - data = [[self.task.standardError fileHandleForReading] readDataToEndOfFile]; - if (data) { - _standardErrString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; - } - } + _standardErrString = _errorHandler.errorString; } + [self.taskLock unlock]; return _standardErrString; } @@ -919,23 +938,32 @@ + (LGAutoPkgTask *)addRepoTask:(NSString *)repo + (BOOL)instanceIsRunning { NSTask *task = [NSTask new]; + NSMutableData *data = [[NSMutableData alloc] init]; task.launchPath = @"/bin/ps"; task.arguments = @[ @"-e", @"-o", @"command=" ]; task.standardOutput = [NSPipe pipe]; - task.standardError = task.standardOutput; + + [[task.standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *fh) { + NSData *newData; + if ((newData = fh.availableData)) { + [data appendData:newData]; + } + }]; [task launch]; [task waitUntilExit]; - NSData *outputData = [[task.standardOutput fileHandleForReading] readDataToEndOfFile]; - NSString *outputString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; + if (data.length) { + NSString *outputString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", autopkg()]; - NSArray *runningProcs = [outputString componentsSeparatedByString:@"\n"]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS %@", autopkg()]; - if ([[runningProcs filteredArrayUsingPredicate:predicate] count]) { - return YES; + NSArray *runningProcs = [outputString componentsSeparatedByString:@"\n"]; + + if ([[runningProcs filteredArrayUsingPredicate:predicate] count]) { + return YES; + } } return NO; } diff --git a/AutoPkgr/LGAutoPkgr.h b/AutoPkgr/LGAutoPkgr.h index d08a4f08..f4a03d01 100644 --- a/AutoPkgr/LGAutoPkgr.h +++ b/AutoPkgr/LGAutoPkgr.h @@ -24,6 +24,7 @@ #import "LGConstants.h" #import "LGError.h" +#import "LGLogger.h" #import "LGHostInfo.h" #import "LGDefaults+JSSImporter.h" #import "NSString+cleaned.h" diff --git a/AutoPkgr/LGConfigurationWindowController.m b/AutoPkgr/LGConfigurationWindowController.m index 0835e421..3cffd2bd 100644 --- a/AutoPkgr/LGConfigurationWindowController.m +++ b/AutoPkgr/LGConfigurationWindowController.m @@ -615,7 +615,7 @@ - (IBAction)checkAppsNow:(id)sender [_cancelAutoPkgRunButton setHidden:NO]; [_progressDetailsMessage setHidden:NO]; - [_progressDelegate startProgressWithMessage:@"Running selected AutoPkg recipes."]; + [_progressDelegate startProgressWithMessage:@"Running selected AutoPkg recipes..."]; [_taskManager runRecipeList:recipeList updateRepo:NO @@ -671,7 +671,6 @@ - (IBAction)changeSendEmailNotificationsWhenNewVersionsAreFound:(id)sender BOOL authEnabled = _defaults.SMTPAuthenticationEnabled && enabled; - [_smtpTLSEnabledButton setEnabled:authEnabled]; [_smtpUsername setEnabled:authEnabled]; [_smtpPassword setEnabled:authEnabled]; } @@ -689,7 +688,6 @@ - (IBAction)changeSmtpAuthentication:(id)sender [_smtpUsername setEnabled:enabled]; [_smtpPassword setEnabled:enabled]; - [_smtpTLSEnabledButton setEnabled:enabled]; } - (IBAction)changeTLSButtonState:(NSButton *)sender; @@ -852,8 +850,14 @@ - (void)stopProgress:(NSError *)error if (error) { SEL selector = nil; - NSAlert *alert = [NSAlert alertWithError:error]; - [alert addButtonWithTitle:@"OK"]; + NSString *truncatedString = [error.localizedRecoverySuggestion truncateToNumberOfLines:25]; + if (![truncatedString isEqualToString:error.localizedRecoverySuggestion]) { + truncatedString = [NSString stringWithFormat:@"%@\nMore details have been logged to the system.log", truncatedString]; + NSLog(@"%@", error.localizedRecoverySuggestion); + } + + NSAlert *alert = [NSAlert alertWithMessageText:error.localizedDescription defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", truncatedString ]; + // If AutoPkg exits -1 it may be misconfigured if (error.code == kLGErrorAutoPkgConfig) { [alert addButtonWithTitle:@"Try to repair settings"]; diff --git a/AutoPkgr/LGConfigurationWindowController.xib b/AutoPkgr/LGConfigurationWindowController.xib index cd78049c..3459393b 100644 --- a/AutoPkgr/LGConfigurationWindowController.xib +++ b/AutoPkgr/LGConfigurationWindowController.xib @@ -1,8 +1,8 @@ - + - + @@ -59,7 +59,7 @@ - + @@ -110,7 +110,7 @@ - + @@ -1252,7 +1252,7 @@ - + @@ -1491,7 +1491,7 @@ - + diff --git a/AutoPkgr/LGDefaults.h b/AutoPkgr/LGDefaults.h index 59ad1f9a..2dbf61a9 100644 --- a/AutoPkgr/LGDefaults.h +++ b/AutoPkgr/LGDefaults.h @@ -89,4 +89,7 @@ typedef NS_ENUM(NSInteger, LGReportItems) { #pragma Class Methods + (BOOL)fixRelativePathsInAutoPkgDefaults:(NSError **)error neededFixing:(NSInteger *)neededFixing; + ++ (NSString *)formattedDate:(NSDate *)date; + @end diff --git a/AutoPkgr/LGDefaults.m b/AutoPkgr/LGDefaults.m index 971939fe..2df89d08 100644 --- a/AutoPkgr/LGDefaults.m +++ b/AutoPkgr/LGDefaults.m @@ -114,23 +114,22 @@ - (void)setSMTPTo:(NSArray *)SMTPTo #pragma mark - Info - (id)LastAutoPkgRun { - return [self objectForKey:NSStringFromSelector(@selector(LastAutoPkgRun))]; + id date = [self objectForKey:NSStringFromSelector(@selector(LastAutoPkgRun))]; + + NSString *setVal; + if ([date isKindOfClass:[NSDate class]]){ + setVal = [[self class] formattedDate:date]; + + } else if ([date isKindOfClass:[NSString class]] ) { + setVal = date; + } + + return setVal; } - (void)setLastAutoPkgRun:(id)LastAutoPkgRun { - NSString *setVal; - if ([LastAutoPkgRun isKindOfClass:[NSDate class]]){ - NSDateFormatter *fomatter = [NSDateFormatter new]; - [fomatter setDateStyle:NSDateFormatterMediumStyle]; - [fomatter setTimeStyle:NSDateFormatterMediumStyle]; - setVal = [fomatter stringFromDate:LastAutoPkgRun]; - } else if ([LastAutoPkgRun isKindOfClass:[NSString class]] ) { - setVal = LastAutoPkgRun; - NSLog(@"Setting date as string"); - } - - [self setObject:setVal forKey:NSStringFromSelector(@selector(LastAutoPkgRun))]; + [self setObject:LastAutoPkgRun forKey:NSStringFromSelector(@selector(LastAutoPkgRun))]; } - (LGReportItems)reportedItemFlags { @@ -316,6 +315,26 @@ - (void)setAutoPkgDomainObject:(id)object forKey:(NSString *)key } #pragma mark - Class Methods ++ (NSString *)formattedDate:(NSDate *)date +{ + if ([date isKindOfClass:[NSDate class]]) { + NSDateFormatter *formatter = [NSDateFormatter new]; + + // Set the date style... + + formatter.timeStyle = NSDateFormatterShortStyle; + formatter.dateStyle = NSDateFormatterShortStyle; + + // Set up relative date formatting... + formatter.doesRelativeDateFormatting = YES; + formatter.locale = [NSLocale currentLocale]; + formatter.timeZone = [NSTimeZone defaultTimeZone]; + + return [formatter stringFromDate:date]; + } + return nil; +} + + (BOOL)fixRelativePathsInAutoPkgDefaults:(NSError *__autoreleasing *)error neededFixing:(NSInteger *)neededFixing { LGDefaults *defaults = [LGDefaults new]; diff --git a/AutoPkgr/LGError.h b/AutoPkgr/LGError.h index ae35cc15..e60f53c9 100644 --- a/AutoPkgr/LGError.h +++ b/AutoPkgr/LGError.h @@ -21,7 +21,6 @@ #import -void DLog(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2); #pragma mark - AutoPkgr specific Error codes typedef NS_ENUM(NSInteger, LGErrorCodes) { @@ -64,24 +63,6 @@ typedef NS_ENUM(NSInteger, LGErrorAutoPkgCodes) { kLGErrorAutoPkgNoRecipes = 255, }; -typedef NS_ENUM(NSInteger, LGAutoPkgVerb) { - kLGAutoPkgUndefinedVerb, - // recipe verbs - kLGAutoPkgRun, - kLGAutoPkgRecipeList, - kLGAutoPkgMakeOverride, - kLGAutoPkgSearch, - - // repo verbs - kLGAutoPkgRepoAdd, - kLGAutoPkgRepoDelete, - kLGAutoPkgRepoUpdate, - kLGAutoPkgRepoList, - - // other verbs - kLGAutoPkgVersion, -}; - @interface LGError : NSObject #ifdef _APPKITDEFINES_H @@ -112,28 +93,8 @@ typedef NS_ENUM(NSInteger, LGAutoPkgVerb) { */ + (NSError *)errorWithCode:(LGErrorCodes)code; -#pragma mark - NSTask Error -/** - * Populate an NSError using a completed NSTask - * - * @param task Completed NSTask Object - * @param verb Cooresponding Action Word Describing the AutoPkgr task process - * @param error __autoreleasing NSError object - * - * @return NO if error occurred 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:(LGAutoPkgVerb)verb error:(NSError **)error; -/** - * Generated NSError Object from and AutoPkgr NSTask - * - * @param task Completed NSTask Object - * @param verb Cooresponding Action Word Describing the AutoPkgr task process - * - * @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:(LGAutoPkgVerb)verb; ++ (NSError *)errorFromTask:(NSTask *)task; + #pragma mark - NSURLConnection response Error + (BOOL)errorWithResponse:(NSHTTPURLResponse *)response error:(NSError **)error; diff --git a/AutoPkgr/LGError.m b/AutoPkgr/LGError.m index 6b02d634..2ccea4ba 100644 --- a/AutoPkgr/LGError.m +++ b/AutoPkgr/LGError.m @@ -21,20 +21,7 @@ #import "LGError.h" #import "LGConstants.h" -#import - -// Debug Logging Method -void DLog(NSString *format, ...) -{ - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"debug"]) { - if (format) { - va_list args; - va_start(args, format); - NSLogv([@"[DEBUG] " stringByAppendingString:format], args); - va_end(args); - } - } -} +#import "LGLogger.h" static NSDictionary *userInfoFromCode(LGErrorCodes code) { @@ -103,52 +90,6 @@ void DLog(NSString *format, ...) }; } -static NSString *errorMessageFromAutoPkgVerb(LGAutoPkgVerb verb) -{ - NSString *localizedBaseString; - NSString *message; - - switch (verb) { - 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; - } - - message = NSLocalizedString([localizedBaseString stringByAppendingString:@"Description"], - @"NSLocalizedDescriptionKey"); - return message; -} - static NSDictionary *userInfoFromHTTPResponse(NSHTTPURLResponse *response) { NSString *localizedBaseString; @@ -200,33 +141,6 @@ void DLog(NSString *format, ...) }; } -NSString *maskPasswordInString(NSString *string) -{ - NSError *error; - NSMutableString *retractedString = [string mutableCopy]; - - NSString *baseAll = @"a-zA-Z0-9~`!#.,$%^&*()-_{}<>?"; - NSString *pattern = [NSString stringWithFormat:@"([%@]+:[%@]+(?=@))", baseAll, baseAll]; - - NSRegularExpression *exp = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; - - NSRange range = NSMakeRange(0, string.length); - NSArray *matches = [exp matchesInString:string options:0 range:range]; - for (NSTextCheckingResult *match in matches) { - NSString *ms = [string substringWithRange:match.range]; - NSArray *array = [ms componentsSeparatedByString:@":"]; - - // Make sure to re-range the retracted string each loop, since it gets modified. - NSRange r_range = NSMakeRange(0, retractedString.length); - [retractedString replaceOccurrencesOfString:[array lastObject] - withString:@"*******" - options:NSCaseInsensitiveSearch - range:r_range]; - } - - return [retractedString copy]; -} - @implementation LGError #ifdef _APPKITDEFINES_H + (void)presentErrorWithCode:(LGErrorCodes)code @@ -274,17 +188,7 @@ + (NSError *)errorWithCode:(LGErrorCodes)code #pragma mark - AutoPkg Task Errors -+ (BOOL)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgVerb)verb error:(NSError **)error -{ - NSError *taskError = [self errorWithTaskError:task verb:verb]; - if (error && taskError) { - *error = taskError; - } - // If no error object was created, or the error code is 0 return YES, otherwise NO. - return taskError ? taskError.code == kLGErrorSuccess : YES; -} - -+ (NSError *)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgVerb)verb ++ (NSError *)errorFromTask:(NSTask *)task { // if task is running if ([task isRunning]) { @@ -292,59 +196,19 @@ + (NSError *)errorWithTaskError:(NSTask *)task verb:(LGAutoPkgVerb)verb } if (task.terminationReason == NSTaskTerminationReasonUncaughtSignal) { - DLog(@"AutoPkg run canceled by user."); return nil; } NSError *error; - NSString *errorMsg = errorMessageFromAutoPkgVerb(verb); NSString *errorDetails; + NSInteger taskError = task.terminationStatus; + NSString *errorMsg = @"An error occurred."; if ([task.standardError isKindOfClass:[NSPipe class]]) { NSData *errData = [[task.standardError fileHandleForReading] readDataToEndOfFile]; - NSString *rawDetails; - - if (errData && (rawDetails = [[NSString alloc] initWithData:errData encoding:NSASCIIStringEncoding])) { - errorDetails = maskPasswordInString(rawDetails); - } - } - - // If the error message looks like a Python exception log it, but trim it up for UI. - NSPredicate *exceptionPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS 'Traceback'"]; - if ([exceptionPredicate evaluateWithObject:errorDetails]) { - NSArray *splitExceptionFromError = [errorDetails componentsSeparatedByString:@"Traceback (most recent call last):"]; - - // The exception should in theory always be last. - NSString *fullExceptionMessage = [splitExceptionFromError lastObject]; - NSLog(@"(FULL AUTOPKG TRACEBACK) %@", fullExceptionMessage); - - NSArray *array = [fullExceptionMessage componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - - NSPredicate *noEmptySpaces = [NSPredicate predicateWithFormat:@"not (SELF == '')"]; - NSString *exceptionDetails = [[array filteredArrayUsingPredicate:noEmptySpaces] lastObject]; - - NSMutableString *recombinedErrorDetails = [[NSMutableString alloc] init]; - if (splitExceptionFromError.count > 1) { - // If something came before, put that information back into the errorDetails. - [recombinedErrorDetails appendString:[splitExceptionFromError firstObject]]; - } - - [recombinedErrorDetails appendFormat:@"A Python exception occurred during the execution of autopkg, see the console log for more details.\n\n[ERROR] %@", exceptionDetails]; - - errorDetails = [NSString stringWithString:recombinedErrorDetails]; - - // Otherwise continue... - } else { - // AutoPkg's rc on a failed repo-update / add / delete is 0, but we want it reported back to the UI so set it to -1. - if (verb == kLGAutoPkgRepoUpdate || verb == kLGAutoPkgRepoDelete || verb == kLGAutoPkgRepoAdd) { - if (errorDetails && ![errorDetails isEqualToString:@""]) { - taskError = kLGErrorAutoPkgConfig; - } - } - // autopkg run exits 255 if no recipe specified - else if (verb == kLGAutoPkgRun && task.terminationStatus == kLGErrorAutoPkgNoRecipes) { - errorDetails = @"No recipes specified."; + if (errData) { + errorDetails = [[NSString alloc] initWithData:errData encoding:NSASCIIStringEncoding]; } } diff --git a/AutoPkgr/LGHTTPRequest.m b/AutoPkgr/LGHTTPRequest.m index 3aed50d4..60bb8111 100644 --- a/AutoPkgr/LGHTTPRequest.m +++ b/AutoPkgr/LGHTTPRequest.m @@ -44,9 +44,11 @@ - (void)retrieveDistributionPoints:(NSString *)server NSURL *url = [NSURL URLWithString:distPointAddress]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + [request setValue:@"application/xml" forHTTPHeaderField:@"Accept"]; request.timeoutInterval = 5.0; // Set up the operation + AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; // Add the credential if specified @@ -76,8 +78,13 @@ - (void)retrieveDistributionPoints:(NSString *)server [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { NSError *error = nil; + NSDictionary *responseDictionary = [self xmlToDictionary:responseObject]; + if (!responseDictionary) error = [LGError errorWithCode:kLGErrorJSSXMLSerializerError]; + + DLog(@"Serialized Dictionary: [%@] %@", [responseDictionary class],responseDictionary); + reply(responseDictionary,error); [self resetCache]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { @@ -146,12 +153,18 @@ - (void)promptForCertTrust:(NSURLAuthenticationChallenge *)challenge - (NSDictionary *)xmlToDictionary:(id)xmlObject; { NSDictionary *dictionary = nil; + NSError *error; + if (xmlObject) { XMLDictionaryParser *xmlParser = [[XMLDictionaryParser alloc] init]; if ([xmlObject isKindOfClass:[NSXMLParser class]]) { dictionary = [xmlParser dictionaryWithParser:xmlObject]; } else if ([xmlObject isKindOfClass:[NSData class]]) { - dictionary = [xmlParser dictionaryWithData:xmlObject]; + if((dictionary = [xmlParser dictionaryWithData:xmlObject]) == nil) + // If the data doesn't parse as XML also try to parse as JSON. + if((dictionary = [NSJSONSerialization JSONObjectWithData:xmlObject options:0 error:&error]) == nil){ + DLog(@"%@", error); + } } else if ([xmlObject isKindOfClass:[NSString class]]) { dictionary = [xmlParser dictionaryWithString:xmlObject]; } diff --git a/AutoPkgr/LGJSSImporter.m b/AutoPkgr/LGJSSImporter.m index 59bbbb93..bbe2b4bd 100644 --- a/AutoPkgr/LGJSSImporter.m +++ b/AutoPkgr/LGJSSImporter.m @@ -130,13 +130,10 @@ - (IBAction)reloadJSSServerInformation:(id)sender [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self stopStatusUpdate:error]; - id distPoints = distributionPoints[@"distribution_point"]; - if (distPoints) { - NSArray *cleanedArray = [self evaluateJSSRepoDictionaries:distPoints]; - if (cleanedArray) { - _defaults.JSSRepos = cleanedArray; - [_jssDistributionPointTableView reloadData]; - } + NSArray *cleanedArray = [self evaluateJSSRepoDictionaries:distributionPoints]; + if (cleanedArray) { + _defaults.JSSRepos = cleanedArray; + [_jssDistributionPointTableView reloadData]; } }]; }]; @@ -300,8 +297,18 @@ - (void)checkReachability }]; } -- (NSArray *)evaluateJSSRepoDictionaries:(id)distPoints +- (NSArray *)evaluateJSSRepoDictionaries:(NSDictionary *)distributionPoints { + + id distPoints; + + // If the object was parsed as an XML object the key we're looking for is + // distribution_point. If the object is a JSON object the key is distribution_points + if ((distPoints = distributionPoints[@"distribution_point"]) == nil && + (distPoints = distributionPoints[@"distribution_points"]) == nil) { + return nil; + } + NSArray *dictArray; NSMutableArray *newRepos; @@ -318,8 +325,10 @@ - (NSArray *)evaluateJSSRepoDictionaries:(id)distPoints // If the "type" key is not set for the DP then it's auto detected via the server // and we'll strip them out here. LGDefaults *defaults = [LGDefaults standardUserDefaults]; + NSPredicate *customDistPointsPredicate = [NSPredicate predicateWithFormat:@"not %K == nil", kLGJSSDistPointTypeKey]; NSArray *customDistPoints = [defaults.JSSRepos filteredArrayUsingPredicate:customDistPointsPredicate]; + newRepos = [[NSMutableArray alloc] initWithArray:customDistPoints]; if (dictArray) { diff --git a/AutoPkgr/LGLogger.h b/AutoPkgr/LGLogger.h new file mode 100644 index 00000000..16ebae91 --- /dev/null +++ b/AutoPkgr/LGLogger.h @@ -0,0 +1,12 @@ +// +// LGLogger.h +// AutoPkgr +// +// Created by Eldon on 4/23/15. +// Copyright (c) 2015 The Linde Group, Inc. All rights reserved. +// + +#import + +void DLog(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2); +void DevLog(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2); \ No newline at end of file diff --git a/AutoPkgr/LGLogger.m b/AutoPkgr/LGLogger.m new file mode 100644 index 00000000..1f4383d0 --- /dev/null +++ b/AutoPkgr/LGLogger.m @@ -0,0 +1,34 @@ +// +// LGLogger.c +// AutoPkgr +// +// Created by Eldon on 4/23/15. +// Copyright (c) 2015 The Linde Group, Inc. All rights reserved. +// + +#include "LGLogger.h" + +// Debug Logging Method +void DLog(NSString *format, ...) +{ + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"debug"]) { + if (format) { + va_list args; + va_start(args, format); + NSLogv([@"[DEBUG] " stringByAppendingString:format], args); + va_end(args); + } + } +} + +void DevLog(NSString *format, ...) +{ +#if DEBUG + if (format) { + va_list args; + va_start(args, format); + NSLogv([@"[DEVEL] " stringByAppendingString:format], args); + va_end(args); + } +#endif +} \ No newline at end of file diff --git a/AutoPkgr/main.m b/AutoPkgr/main.m index 977b0282..b9f7ac61 100755 --- a/AutoPkgr/main.m +++ b/AutoPkgr/main.m @@ -35,7 +35,7 @@ int main(int argc, const char *argv[]) NSLog(@"Running AutoPkgr in background..."); __block LGEmailer *emailer = [[LGEmailer alloc] init]; - + __block BOOL completionMessageSent = NO; BOOL update = [args boolForKey:kLGCheckForRepoUpdatesAutomaticallyEnabled]; LGAutoPkgTaskManager *manager = [[LGAutoPkgTaskManager alloc] init]; @@ -63,6 +63,7 @@ int main(int argc, const char *argv[]) error:error state:kLGAutoPkgProgressComplete]; + completionMessageSent = YES; [emailer sendEmailForReport:report error:error]; }]; @@ -70,6 +71,13 @@ int main(int argc, const char *argv[]) [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; } + if (!completionMessageSent) { + [[helper.connection remoteObjectProxy] sendMessageToMainApplication:nil + progress:100 + error:nil + state:kLGAutoPkgProgressComplete]; + } + NSLog(@"AutoPkg background run complete."); return 0; diff --git a/CHANGELOG.md b/CHANGELOG.md index add75b3d..0595efe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,26 @@ All notable changes to this project will be documented in this file. This projec ## [Unreleased][unreleased] +- Nothing yet. + +## [1.2.3] - 2015-05-08 + +### Changed +- Now you can check the SSL box even if you're not using SMTP authentication. (#335) +- Relative date/time now shown in AutoPkgr menu status (e.g. "Today at 8:00 AM"). (#340) + +### Fixed +- Fixed a particularly devious bug that would cause AutoPkgr to get stuck running recipes while certain apps (usually Google Chrome) were active. (#230) +- AutoPkgr menu status should now be up to date upon first click, not just upon status change. (#340) +- Fixed a bug that caused progress indicators to show inaccurate counts when the `StopProcessingIf` processor is present in a recipe. (#333) +- Reduced the odds of error messages overflowing the on-screen alert dialog boxes. +- AutoPkgr now asks for XML when talking to your JSS, but should successfully revert to JSON if your JSS stubbornly refuses. (#287) +- Fixed a minor bug that could result in excess CPU usage. +- Various minor cosmetic changes. + +### Security +- Updated to the most recent version of the AFNetworking components. + ## [1.2.2] - 2015-04-16 @@ -230,7 +250,8 @@ All notable changes to this project will be documented in this file. This projec - Initial public release of AutoPkgr. -[unreleased]: https://github.com/lindegroup/autopkgr/compare/v1.2.2...HEAD +[unreleased]: https://github.com/lindegroup/autopkgr/compare/v1.2.3...HEAD +[1.2.3]: https://github.com/lindegroup/autopkgr/compare/v1.2.2...v1.2.3 [1.2.2]: https://github.com/lindegroup/autopkgr/compare/v1.2.1...v1.2.2 [1.2.1]: https://github.com/lindegroup/autopkgr/compare/v1.2...v1.2.1 [1.2]: https://github.com/lindegroup/autopkgr/compare/v1.1.3...v1.2 @@ -241,4 +262,4 @@ All notable changes to this project will be documented in this file. This projec [1.0.4]: https://github.com/lindegroup/autopkgr/compare/v1.0.3...v1.0.4 [1.0.3]: https://github.com/lindegroup/autopkgr/compare/v1.0.2...v1.0.3 [1.0.2]: https://github.com/lindegroup/autopkgr/compare/v1.0.1...v1.0.2 -[1.0.1]: https://github.com/lindegroup/autopkgr/compare/v1.0...v1.0.1 \ No newline at end of file +[1.0.1]: https://github.com/lindegroup/autopkgr/compare/v1.0...v1.0.1 diff --git a/Podfile b/Podfile index 160a27dc..f404c108 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ AutoPkgr = ["AutoPkgr", "AutoPkgrTests"] AutoPkgr.each { |t| target t do - pod 'AFNetworking', '~> 2.4.1' + pod 'AFNetworking', '~> 2.5.3' pod 'Sparkle', '1.8' pod 'XMLDictionary', '~> 1.4' pod 'mailcore2-osx', '~> 0.5' diff --git a/Podfile.lock b/Podfile.lock index ccafebd6..49f6aabc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,22 +1,22 @@ PODS: - - AFNetworking (2.5.2): - - AFNetworking/NSURLConnection (= 2.5.2) - - AFNetworking/NSURLSession (= 2.5.2) - - AFNetworking/Reachability (= 2.5.2) - - AFNetworking/Security (= 2.5.2) - - AFNetworking/Serialization (= 2.5.2) - - AFNetworking/UIKit (= 2.5.2) - - AFNetworking/NSURLConnection (2.5.2): + - AFNetworking (2.5.3): + - AFNetworking/NSURLConnection (= 2.5.3) + - AFNetworking/NSURLSession (= 2.5.3) + - AFNetworking/Reachability (= 2.5.3) + - AFNetworking/Security (= 2.5.3) + - AFNetworking/Serialization (= 2.5.3) + - AFNetworking/UIKit (= 2.5.3) + - AFNetworking/NSURLConnection (2.5.3): - AFNetworking/Reachability - AFNetworking/Security - AFNetworking/Serialization - - AFNetworking/NSURLSession (2.5.2): + - AFNetworking/NSURLSession (2.5.3): - AFNetworking/Reachability - AFNetworking/Security - AFNetworking/Serialization - - AFNetworking/Reachability (2.5.2) - - AFNetworking/Security (2.5.2) - - AFNetworking/Serialization (2.5.2) + - AFNetworking/Reachability (2.5.3) + - AFNetworking/Security (2.5.3) + - AFNetworking/Serialization (2.5.3) - AHKeychain (0.2.1) - AHLaunchCtl (0.4.2) - AHProxySettings (0.1.1) @@ -26,7 +26,7 @@ PODS: - XMLDictionary (1.4) DEPENDENCIES: - - AFNetworking (from `https://github.com/AFNetworking/AFNetworking.git`) + - AFNetworking (~> 2.5.3) - AHKeychain (~> 0.2.1) - AHLaunchCtl (~> 0.4.1) - AHProxySettings (~> 0.1.1) @@ -35,17 +35,8 @@ DEPENDENCIES: - Sparkle (= 1.8) - XMLDictionary (~> 1.4) -EXTERNAL SOURCES: - AFNetworking: - :git: https://github.com/AFNetworking/AFNetworking.git - -CHECKOUT OPTIONS: - AFNetworking: - :commit: 0c9216e86ec0822e7556ceaecedfe3bc2a4b486d - :git: https://github.com/AFNetworking/AFNetworking.git - SPEC CHECKSUMS: - AFNetworking: f0e3cd9309e4ea3710d557938a2710f0f1111ca2 + AFNetworking: e1d86c2a96bb5d2e7408da36149806706ee122fe AHKeychain: f797278e48ab3ddb4d62370b84583652bc071baa AHLaunchCtl: 8f8bb26326627756aa4e85609a2e1896b8dfe874 AHProxySettings: 1f882780699d7765aca9377660ba280306d30910 diff --git a/helper/LGAutoPkgrHelper.m b/helper/LGAutoPkgrHelper.m index 8611cf01..11a04f2c 100644 --- a/helper/LGAutoPkgrHelper.m +++ b/helper/LGAutoPkgrHelper.m @@ -365,7 +365,7 @@ - (void)installPackageFromPath:(NSString *)path }]; [task setTerminationHandler:^(NSTask *endTask) { - NSError *error = [LGError errorWithTaskError:endTask verb:kLGAutoPkgUndefinedVerb]; + NSError *error = [LGError errorFromTask:endTask]; reply(error); }];