1/* UpdateController.m 2 * Checking for Updates... 3 * 4 * Copyright 2010-2012 by vhf interservice GmbH 5 * Author: Georg Fleischmann 6 * 7 * created: 2010-05-27 8 * modified: 2012-02-07 (-connectionDidFinishLoading: use -writeToFile:...encoding:error:) 9 * 2011-03-30 (-checkForUpdates: test for Prefs_DisableAutoUpdate) 10 * 11 * This program is free software; you can redistribute it and/or 12 * modify it under the terms of the vhf Public License as 13 * published by vhf interservice GmbH. Among other things, the 14 * License requires that the copyright notices and this notice 15 * be preserved on all copies. 16 * 17 * This program is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 20 * See the vhf Public License for more details. 21 * 22 * You should have received a copy of the vhf Public License along 23 * with this program; see the file LICENSE. If not, write to vhf. 24 * 25 * vhf interservice GmbH, Im Marxle 3, 72119 Altingen, Germany 26 * eMail: service@vhf.de 27 * http://www.vhf-interservice.com 28 */ 29 30#include <AppKit/AppKit.h> 31#include "UpdateController.h" 32#include "App.h" 33#include "locations.h" 34#include "messages.h" 35#include "CenonModuleMethods.h" 36#include "PreferencesMacros.h" // Prefs_DisableAutoUpdate 37#include <VHFShared/VHFStringAdditions.h> // -writeToFile:... 38 39static UpdateController *sharedInstance = nil; // default object 40static NSString *checkMarkStr = nil; // the checkmark 41 42@interface UpdateController(PrivateMethods) 43- (void)loadPanel:sender; 44@end 45@interface UpdateTableData:NSObject 46{ 47 NSMutableDictionary *dataDict; 48 int rowCount; 49} 50/* class methods */ 51+ (UpdateTableData*)tableData; 52- (int)numberOfRowsInTableView:(NSTableView*)table; 53- (id)tableView:(NSTableView*)table objectValueForTableColumn:(NSTableColumn*)column 54 row:(int)rowIndex; 55- (void)setDataDict:(NSDictionary*)newDataDict; 56- (NSDictionary*)dataDict; 57@end 58 59@interface NSFileManager(UpdateControllerMethods) 60- (BOOL)fileExistsAtWildcardPath:(NSString*)path; 61@end 62@implementation NSFileManager(UpdateControllerMethods) 63- (BOOL)fileExistsAtWildcardPath:(NSString*)path 64{ NSString *wildcard = [path lastPathComponent]; 65 NSArray *array; 66 int i, cnt, j; 67 NSArray *components = [wildcard componentsSeparatedByString:@"*"]; 68 69 if ( [components count] == 1 ) 70 return [self fileExistsAtPath:path]; 71 path = [path stringByDeletingLastPathComponent]; 72 if ( !(array = [self directoryContentsAtPath:path]) || ![components count] ) 73 return NO; 74 for ( i=0, cnt = [array count]; i<cnt; i++ ) 75 { NSString *file = [array objectAtIndex:i]; 76 NSRange searchRange = NSMakeRange(0, [file length]); 77 BOOL hit = YES; 78 79 for ( j=0; j<[components count]; j++ ) 80 { NSString *compo = [components objectAtIndex:j]; 81 NSRange range; 82 83 if ( ![compo length] ) 84 continue; 85 if ( (range = [file rangeOfString:compo options:0 range:searchRange]).length ) 86 { searchRange.location = range.location + range.length; 87 searchRange.length = [file length] - searchRange.location; 88 } 89 else 90 { hit = NO; break; } 91 } 92 if ( hit ) 93 return YES; 94 } 95 return NO; 96} 97@end 98 99/*#ifndef MAC_OS_X_VERSION_10_6 100#define MAC_OS_X_VERSION_10_6 1060 101#endif*/ 102//#if defined(__APPLE__) && MACOSX_DEPLOYMENT_TARGET <= MAC_OS_X_VERSION_10_6 103/* available OSX >= 10.6, not on GNUstep yet (2010-09-20) */ 104@interface NSWorkspace(UpdateControllerMethods) 105- (void)activateFileViewerSelectingURLs:(NSArray*)fileURLs; 106@end 107 108 109@implementation UpdateController 110 111+ (UpdateController*)sharedInstance 112{ 113 if (!sharedInstance) 114 sharedInstance = [self new]; 115 return sharedInstance; 116} 117- (id)init 118{ char checkMarkChars[4] = {0xE2, 0x9C, 0x93, 0}; // unicode 119 120 [super init]; 121 checkMarkStr = [[NSString stringWithUTF8String:checkMarkChars] retain]; 122 updateDict = nil; 123 [self loadPanel:self]; // this loads the interface and has to come first 124 return self; 125} 126 127/* show panel 128 * load interface file and display panel 129 */ 130- (void)loadPanel:sender 131{ 132 if ( !panel ) 133 { NSBundle *bundle = [NSBundle mainBundle]; 134 135 /* load panel, this establishes connections to interface outputs */ 136 if ( ![bundle loadNibFile:@"UpdatePanel" 137 externalNameTable:[NSDictionary dictionaryWithObject:self forKey:@"NSOwner"] 138 withZone:[self zone]] ) 139 NSLog(@"Cannot load Update Panel interface file"); 140 [panel setDelegate:self]; 141 [panel setFrameUsingName:@"UpdatePanel"]; 142 [panel setFrameAutosaveName:@"UpdatePanel"]; 143 144 [tableView setDelegate:self]; 145 [tableView setTarget:self]; 146 [tableView setAction:@selector(tableClick:)]; 147 //[tableView setDoubleAction:@selector(doubleClick:)]; 148 } 149} 150 151/* return Dictionary with installed modules and it's versions 152 */ 153- (NSDictionary*)installedModules 154{ NSMutableDictionary *installedDict = [NSMutableDictionary dictionary]; 155 NSArray *modules = [(App*)NSApp modules]; 156 int i; 157 158 for ( i=0; i<[modules count]; i++ ) // modules 159 { NSBundle *bundle = [modules objectAtIndex:i]; // our loaded modules 160 NSDictionary *infoDict = [bundle infoDictionary]; 161 NSString *version = [infoDict objectForKey:@"CFBundleVersion"]; 162 NSString *name = [infoDict objectForKey:@"CFBundleExecutable"]; // CAM, Astro, AstroFractal 163 NSString *date = nil, *serial = nil, *netId = nil; 164 165 if ( [[[bundle principalClass] instance] respondsToSelector:@selector(compileDate)] ) 166 date = [[[bundle principalClass] instance] compileDate]; 167 if ( [[[bundle principalClass] instance] respondsToSelector:@selector(serialNo)] ) 168 serial = [[[bundle principalClass] instance] serialNo]; 169 if ( [[[bundle principalClass] instance] respondsToSelector:@selector(netId)] ) 170 netId = [[[bundle principalClass] instance] netId]; 171 [installedDict setObject:[NSArray arrayWithObjects:version, (date) ? date : @"", 172 (serial) ? serial : @"", (netId) ? netId : @"", nil] 173 forKey:name]; 174 if ( !isAutoCheck ) 175 printf("Installed Modules: %s = %s (%s) %s %s\n", [name UTF8String], [version UTF8String], 176 (date) ? [date UTF8String] : "", (serial) ? [serial UTF8String] : "", 177 (netId) ? [netId UTF8String] : ""); 178 } 179 return installedDict; 180} 181 182/* tries to download update.plist from vhf server 183 */ 184- (void)checkForUpdates:sender 185{ NSDictionary *installedModules; 186 NSArray *keys; 187 NSURLRequest *connectionRequest; 188 NSMutableString *urlStr = [NSMutableString string]; 189 NSString *appVersion = [(App*)NSApp version]; // 3.9.2 190 NSString *appDate = [(App*)NSApp compileDate]; // 2010-06-28 191 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 192 NSString *skipVersion = [userDefaults objectForKey:@"skipUpdateVersion"]; 193 NSDate *lastUpdateCheck = [userDefaults objectForKey:@"lastUpdateCheck"]; 194 int i; 195 196 isAutoCheck = ([sender isKindOfClass:[NSMenuItem class]]) ? NO : YES; 197 if ( isAutoCheck && Prefs_DisableAutoUpdate ) 198 return; // automatic check is disabled in preferences 199 if ( isAutoCheck && lastUpdateCheck && 200 [[NSDate date] timeIntervalSinceDate:lastUpdateCheck] < 3600*24*7 ) 201 return; // not yet one week -> no check 202 203 installedModules = [self installedModules]; 204 [urlStr appendString:@"http://www.cenon.info/cgi-bin/updateCenon"]; 205 [urlStr appendFormat:@"?n=%@&v=%@&d=%@", APPNAME, appVersion, appDate]; 206 if ( [skipVersion length] ) // version the user wants to skip 207 [urlStr appendFormat:@"&sk=%@", skipVersion]; 208 //[urlStr appenFormat:@"&p="]; // TODO: pass plattform to update script 209 for (keys = [installedModules allKeys], i=0; i < [keys count]; i++) 210 { int n = i+1; 211 NSString *name = [keys objectAtIndex:i]; 212 NSArray *array = [installedModules objectForKey:name]; 213 NSString *version = [array objectAtIndex:0]; 214 NSString *date = [array objectAtIndex:1]; 215 NSString *serial = [array objectAtIndex:2]; 216 NSString *netId = [array objectAtIndex:3]; 217 218 [urlStr appendFormat:@"&n%d=%@", n, name]; 219 if ( [version length] ) [urlStr appendFormat:@"&v%d=%@", n, version]; 220 if ( [date length] ) [urlStr appendFormat:@"&d%d=%@", n, date]; 221 if ( [serial length] ) [urlStr appendFormat:@"&s%d=%@", n, serial]; 222 if ( [netId length] ) [urlStr appendFormat:@"&o%d=%@", n, netId]; // origin 223 } 224 connectionRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr] 225 cachePolicy:NSURLRequestReloadIgnoringCacheData 226 timeoutInterval:60.0]; 227 urlConnection = [[NSURLConnection alloc] initWithRequest:connectionRequest delegate:self]; 228 if ( urlConnection ) 229 { connectionData = [[NSMutableData alloc] init]; 230 [progressTitleText setStringValue:@""]; 231 [progressNameText setStringValue:@""]; 232 [progressSizeText setStringValue:@""]; 233 [progressIndicator setIndeterminate:YES]; 234 [progressIndicator startAnimation:nil]; 235 [progressPanel makeKeyAndOrderFront:self]; 236 [userDefaults setObject:[NSDate date] forKey:@"lastUpdateCheck"]; // date of this check 237 } 238#if 0 // this downloads a plist file directly (what, if it ends up in a proxy ?) 239 NSURL *url = [NSURL URLWithString:@"http://www.cenon.info/update/update.plist"]; 240 NSString *path = vhfPathWithPathComponents(vhfUserLibrary(APPNAME), @".update.plist", nil); 241 NSURLRequest *request; 242 NSURLDownload *download; 243 244 if ( checking || pkgDownload ) 245 return; 246 checking = YES; 247 request = [NSURLRequest requestWithURL:url]; 248 download = [[NSURLDownload alloc] initWithRequest:request delegate:self]; 249 if ( [sender isKindOfClass:[NSMenuItem class]] ) // we remove the skipped version 250 [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"skipUpdateVersion"]; 251 [download setDeletesFileUponFailure:YES]; 252 [download setDestination:path allowOverwrite:YES]; 253#endif 254} 255 256/* check for available updates, if available display panel 257 * created: 2010-05-27 258 * modified: 2011-02-22 (don't offer update of uninstalled versions) 259 * FIXME: this will not work with GNUstep, we need to access the infoDict correctly or add Apple info 260 */ 261- (void)checkForUpdateAndDisplayPanel 262{ static NSString *infoString = nil; // used to memorize infoLabel 263 NSMutableDictionary *dataDict = [NSMutableDictionary dictionary]; 264 NSArray *modules = [(App*)NSApp modules], *instKeys, *updateKeys; 265 NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; 266 NSString *appVersion = [(App*)NSApp version]; // "3.9.2" 267 NSString *appName = [infoDict objectForKey:@"CFBundleExecutable"]; // Cenon, APPNAME 268 NSString *appDate = [(App*)NSApp compileDate]; 269 NSMutableDictionary *installedDict = [NSMutableDictionary dictionary]; 270 NSMutableArray *checkOrder = [NSMutableArray array]; 271 int i, j; 272 NSString *updateFile; 273 NSString *skipVersion = [[NSUserDefaults standardUserDefaults] objectForKey:@"skipUpdateVersion"]; 274 NSString *newVersion; 275 BOOL updateAvailable = NO; 276 NSFileManager *fileManager = [NSFileManager defaultManager]; 277 278 [installButton setEnabled:NO]; 279 280 /* Update.plist */ 281 updateFile = vhfPathWithPathComponents(vhfUserLibrary(APPNAME), @".update.plist", nil); 282 if ( updateDict ) 283 [updateDict release]; 284 updateDict = [[NSDictionary dictionaryWithContentsOfFile:updateFile] retain]; 285 updateKeys = [updateDict allKeys]; 286 newVersion = [[updateDict objectForKey:appName] objectForKey:@"v"]; 287 if ( isAutoCheck && newVersion && skipVersion && [newVersion isEqual:skipVersion] ) 288 return; 289 290 /* Installed versions of App, Modules, Fonts, etc. */ 291 if ( !appName ) 292 appName = APPNAME; 293 //infoVersion = [[(App*)NSApp infoVersionNo] stringValue]; // ex: 3.9.1 pre 1 (2010-02-13) 294 [installedDict setObject:[NSArray arrayWithObjects:appVersion, appDate, nil] forKey:appName]; 295 [checkOrder addObject:appName]; 296 if ( !isAutoCheck ) 297 printf("Installed App: %s = %s (%s)\n", [appName UTF8String], [appVersion UTF8String], [appDate UTF8String]); 298 // FIXME: this is basically duplicating -installedModules and should be united 299 for ( i=0; i<[modules count]; i++ ) // modules 300 { NSBundle *bundle = [modules objectAtIndex:i]; // our loaded modules 301 NSDictionary *infoDict = [bundle infoDictionary]; 302 NSString *version = [infoDict objectForKey:@"CFBundleVersion"]; 303 NSString *name = [infoDict objectForKey:@"CFBundleExecutable"]; // CAM, Astro, AstroFractal 304 NSString *date = nil; 305 306 if ( [[[bundle principalClass] instance] respondsToSelector:@selector(compileDate)] ) 307 date = [[[bundle principalClass] instance] compileDate]; 308 [installedDict setObject:[NSArray arrayWithObjects:version, date, nil] forKey:name]; 309 [checkOrder addObject:name]; 310 //if ( !isAutoCheck ) 311 // printf("Installed Modules: %s = %s (%s)\n", [name UTF8String], [version UTF8String], (date) ? [date UTF8String] : ""); 312 } 313 if ( [updateDict objectForKey:@"order"] ) 314 checkOrder = [updateDict objectForKey:@"order"]; 315 /* Check for install paths of additional items given in update.plist and add their versions etc. */ 316 for ( i=0; i<[updateKeys count]; i++ ) 317 { NSString *key = [updateKeys objectAtIndex:i], *path, *pathU = nil; 318 NSDictionary *uDict = [updateDict objectForKey:key]; 319 320 if ( ![uDict isKindOfClass:[NSDictionary class]] || !(path = [uDict objectForKey:@"c"]) ) 321 continue; 322 if ( ! [path isAbsolutePath] ) 323 { pathU = vhfPathWithPathComponents(NSHomeDirectory(), path, nil); 324 path = vhfPathWithPathComponents(NSOpenStepRootDirectory(), path, nil); 325 } 326 if ( [fileManager fileExistsAtWildcardPath:path] || 327 (pathU && [fileManager fileExistsAtWildcardPath:pathU]) ) 328 { NSString *version = [uDict objectForKey:@"v"]; // TODO: version of additional installed stuff 329 NSString *date = nil; // TODO: date of stuff 330 [installedDict setObject:[NSArray arrayWithObjects:version, date, nil] forKey:key]; 331 } 332 } 333 instKeys = [installedDict allKeys]; 334 335 [dataDict setObject:[NSMutableArray array] forKey:@"install"]; 336 [dataDict setObject:[NSMutableArray array] forKey:@"name"]; 337 [dataDict setObject:[NSMutableArray array] forKey:@"version"]; 338 [dataDict setObject:[NSMutableArray array] forKey:@"size"]; 339 [dataDict setObject:[NSMutableArray array] forKey:@"price"]; 340 [dataDict setObject:[NSMutableArray array] forKey:@"updateKey"]; // our key to find way back 341 [dataDict setObject:[NSMutableArray array] forKey:@"url"]; // download link 342 343 /* Compare available updates with installed versions */ 344 for ( i=0; i<[checkOrder count]; i++ ) // CAM, AstroFractal, Astro, Cenon 345 { NSString *key = [checkOrder objectAtIndex:i]; 346 NSDictionary *uDict = [updateDict objectForKey:key]; // v, s, l, i, n, r, d 347 NSArray *iArray = [installedDict objectForKey:key]; 348 NSString *version = [uDict objectForKey:@"v"]; // package version 349 NSString *mVersion = [uDict objectForKey:@"mv"]; // module version 350 NSString *name = [uDict objectForKey:@"n"]; 351 NSString *size = [uDict objectForKey:@"s"], *price, *url; 352 BOOL installModule = NO; // install complete package or module ? 353 354 if ( ! version || ! name ) 355 continue; 356 /* is item eclipsed in rel "r" (ex: Cenon+Module.pkg eclipses Cenon.pkg alone) ? */ 357 { BOOL eclipsed = NO; 358 359 for ( j=0; j<[instKeys count]; j++ ) // all installed stuff can eclipse 360 { NSString *instKey = [instKeys objectAtIndex:j]; 361 NSDictionary *uDictI = [updateDict objectForKey:instKey]; 362 NSArray *relArray; 363 int k; 364 365 if ( ![uDictI isKindOfClass:[NSDictionary class]] ) 366 continue; 367 relArray = [uDictI objectForKey:@"r"]; 368 for (k=0; k<[relArray count]; k++ ) // it's relationships: "-" will eclipse 369 { NSString *relKey = [relArray objectAtIndex:k]; 370 371 if ( ! [relKey hasPrefix:@"-"] ) 372 continue; 373 relKey = [relKey substringFromIndex:1]; 374 if ( [key isEqual:relKey] ) // key eclipsed from relations of installed item 375 { eclipsed = YES; break; } // eclipsed 376 } 377 if (eclipsed) 378 break; 379 } 380 if ( eclipsed ) 381 continue; 382 } 383 384 /* compare installed versions with available updates */ 385 if ( iArray ) // installed item 386 { NSString *v = (mVersion) ? mVersion : version; // we compile module version 387 388 if ( [v appearanceCountOfCharacter:'.'] <= 1 ) // workaround for single dot version numbers "1.11" -> "1.1.1" 389 v = @"0.0.0"; // this is old anyway 390 if ( [[iArray objectAtIndex:0] compare:(id)v options:NSNumericSearch] == NSOrderedAscending ) // installed < version 391 updateAvailable = YES; 392 // TODO: if version is the same, compare date 393 else // installed >= version 394 continue; 395 } 396 else // 2011-02-22: not installed -> we don't offer item for installation 397 continue; 398 399 /* check, wether to install module instead of Package (ex: CenonCAM + Astro-Module) */ 400 url = ([uDict objectForKey:@"d"]) ? [uDict objectForKey:@"d"] : @""; 401 if ( mVersion && [uDict objectForKey:@"ms"] && [uDict objectForKey:@"md"] ) 402 { 403 for ( j=0; j<[instKeys count]; j++ ) // all installed stuff 404 { NSString *instKey = [instKeys objectAtIndex:j]; 405 NSDictionary *uDictI = [updateDict objectForKey:instKey]; 406 407 if ( ![uDictI isKindOfClass:[NSDictionary class]] ) 408 continue; 409 /* if something is installed, then we installed a Cenon-Package already */ 410 if ( [(NSArray*)[dataDict objectForKey:@"updateKey"] count] ) 411 { installModule = YES; 412 url = [uDict objectForKey:@"md"]; 413 version = mVersion; 414 size = [uDict objectForKey:@"ms"]; 415 break; 416 } 417 } 418 } 419 420 /* Add item */ 421 if ( [url length] ) 422 [installButton setEnabled:YES]; // enable install button, if something is there to install 423 [[dataDict objectForKey:@"install"] addObject:([url length]) ? @"1" : @"0"]; 424 [[dataDict objectForKey:@"name"] addObject:name]; // name 425 [[dataDict objectForKey:@"size"] addObject:(size) ? size : @""]; 426 [[dataDict objectForKey:@"updateKey"] addObject:key]; // key for reference 427 [[dataDict objectForKey:@"url"] addObject:url]; 428 /* License or Price */ 429 if ( [[uDict objectForKey:@"l"] hasPrefix:@"f"] ) // price or free 430 price = NSLocalizedString(@"free", NULL); 431 else 432 { NSDictionary *priceDict = [uDict objectForKey:@"p"]; 433 434 price = nil; 435 if ( priceDict ) 436 { //NSString *v = ([uDict objectForKey:@"mv"]) ? [uDict objectForKey:@"mv"] : version; 437 NSString *v = ([iArray count]) ? [iArray objectAtIndex:0] : appVersion; // version of installed module 438 NSRange range = [v rangeOfString:@"." options:NSBackwardsSearch]; 439 440 if ( range.length ) 441 { NSString *vMajor = [v substringToIndex:range.location]; // "3.9", "1.1" 442 443 if ( iArray ) // installed 444 { price = [priceDict objectForKey:vMajor]; 445 if ( [price isEqual:@"0.0"] ) 446 price = NSLocalizedString(@"free", NULL); 447 } 448 else // not installed (2011-02-22) 449 price = [priceDict objectForKey:@"0"]; 450 } 451 } 452 if ( !price ) 453 price = @""; 454 } 455 [[dataDict objectForKey:@"price"] addObject:price]; 456 if ( ! installModule && [uDict objectForKey:@"mv"] ) // version 457 version = [version stringByAppendingFormat:@" (%@)", [uDict objectForKey:@"mv"]]; 458 [[dataDict objectForKey:@"version"] addObject:version]; 459 } 460 461 462 /* Add additional items for installed items (Fonts, Ephemeris, ...) 463 */ 464 for ( i=0; i<[(NSArray*)[dataDict objectForKey:@"updateKey"] count]; i++ ) 465 { NSString *key = [[dataDict objectForKey:@"updateKey"] objectAtIndex:i]; 466 NSDictionary *uDict = [updateDict objectForKey:key]; // v, s, l, i, n, r 467 NSArray *relArray = [uDict objectForKey:@"r"]; 468 469 for ( j=0; j<[relArray count]; j++ ) 470 { NSString *key = [relArray objectAtIndex:j]; 471 NSDictionary *uDictR = [updateDict objectForKey:key]; 472 NSString *name = [uDictR objectForKey:@"n"]; 473 NSString *size = [uDictR objectForKey:@"s"]; 474 NSString *version = [uDictR objectForKey:@"v"], *price; 475 NSString *url = ([uDictR objectForKey:@"d"]) ? [uDictR objectForKey:@"d"] : @""; 476 NSArray *iArray; 477 478 if ( [[dataDict objectForKey:@"updateKey"] containsObject:key] 479 || !name || !version ) 480 continue; 481 /* check available version against installed stuff */ 482 iArray = [installedDict objectForKey:key]; 483 if ( [instKeys containsObject:key] && 484 ([iArray count] && [[iArray objectAtIndex:0] compare:(id)version options:NSNumericSearch] != NSOrderedAscending) ) 485 continue; // installed and up to date 486 /* compare installed versions with available updates */ 487 if ( iArray ) // installed item 488 { 489 if ( [[iArray objectAtIndex:0] compare:(id)version options:NSNumericSearch] == NSOrderedAscending ) // version > installed 490 updateAvailable = YES; 491 } 492 [[dataDict objectForKey:@"install"] addObject:@"0"]; 493 [[dataDict objectForKey:@"name"] addObject:name]; 494 [[dataDict objectForKey:@"version"] addObject:version]; 495 [[dataDict objectForKey:@"size"] addObject:(size) ? size : @""]; 496 [[dataDict objectForKey:@"updateKey"] addObject:key]; 497 [[dataDict objectForKey:@"url"] addObject:url]; 498 if ( [[uDictR objectForKey:@"l"] hasPrefix:@"f"] ) 499 price = NSLocalizedString(@"free", NULL); 500 else 501 { //NSDictionary *priceDict = [uDictR objectForKey:@"p"]; 502 503 price = nil; 504 /*if ( priceDict ) // TODO: prices for additional stuff 505 { NSString *v = ([uDict objectForKey:@"mv"]) ? [uDict objectForKey:@"mv"] : version; 506 NSRange range = [v rangeOfString:@"." options:NSBackwardsSearch]; 507 508 if ( range.length ) 509 { NSString *vMajor = [v substringToIndex:range.location]; // "3.9", "1.1" 510 price = [priceDict objectForKey:vMajor]; 511 } 512 }*/ 513 if ( !price ) 514 price = @""; 515 } 516 [[dataDict objectForKey:@"price"] addObject:price]; 517 } 518 } 519 520 if ( !tableData ) 521 tableData = [UpdateTableData new]; 522 [tableData setDataDict:dataDict]; 523 524 //[self loadPanel:self]; // this loads the interface and has to come first 525 526 /* update title and info labels */ 527 if ( updateAvailable ) 528 { 529 [titleLabel setStringValue:NSLocalizedString(@"A new version of Cenon is available", NULL)]; 530 if ( !infoString ) 531 infoString = [[infoLabel stringValue] retain]; 532 if ( newVersion ) 533 { NSString *str = [infoString stringByReplacing:@"V_NEW" by:newVersion all:NO]; 534 str = [str stringByReplacing:@"V_INST" by:appVersion all:NO]; 535 [infoLabel setStringValue:str]; 536 } 537 else 538 [infoLabel setStringValue:@""]; 539 } 540 else 541 { [titleLabel setStringValue:NSLocalizedString(@"Cenon is up to date", NULL)]; 542 if ( !infoString ) 543 infoString = [[infoLabel stringValue] retain]; 544 [infoLabel setStringValue:@""]; 545 } 546 547 [tableView setDataSource:tableData]; 548 [tableView reloadData]; 549 if ( updateAvailable ) 550 [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; 551 552 if ( [updateFile hasSuffix:@"update.plist"] ) 553 [[NSFileManager defaultManager] removeFileAtPath:updateFile handler:nil]; // <= 10.5 554 //[[NSFileManager defaultManager] removeItemAtPath:updateFile error:NULL]; // >= 10.6 555 556 if ( updateAvailable || !isAutoCheck ) // only display panel if update is available or manual 557 [panel makeKeyAndOrderFront:self]; 558} 559 560/* Action methods 561 * We collect the files that we have to download and start downloading the first one. 562 * When the 1st file finished, -downloadDidFinish: continues with the other files. 563 */ 564- (void)install:sender 565{ int i; 566 NSDictionary *dataDict = [tableData dataDict]; 567 NSArray *keys = [dataDict objectForKey:@"updateKey"]; 568 569 /* determine number of files to download */ 570 [downloadFiles release]; 571 downloadFiles = [[NSMutableArray array] retain]; 572 for ( i=0, fileCnt=0; i < [keys count]; i++ ) 573 { 574 if ( [[[dataDict objectForKey:@"install"] objectAtIndex:i] intValue] ) 575 { NSString *urlStr = [[dataDict objectForKey:@"url"] objectAtIndex:i]; 576 577 /* start download of file */ 578 if ( [urlStr length] ) 579 { NSURL *url = [NSURL URLWithString:urlStr]; 580 NSString *fileName = [urlStr lastPathComponent], *path = nil; 581 NSArray *array = nil; 582 583#ifdef __APPLE__ 584 if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_5) 585 array = NSSearchPathForDirectoriesInDomains(15/*NSDownloadsDirectory*/, NSUserDomainMask, YES); 586 if ( ![array count] && NSAppKitVersionNumber >= NSAppKitVersionNumber10_4) 587 array = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); 588#endif 589 if ( array && [array count] ) // 1. $HOME/Downloads/PACKAGE 590 path = [[array objectAtIndex:0] stringByAppendingPathComponent:fileName]; 591 if ( !path ) // 2. /var/folders/8L/.../-Tmp-/PACKAGE 592 path = vhfPathWithPathComponents(NSTemporaryDirectory(), fileName, nil); // this is a nonsense folder: "/var/folders/8L/arg/-Tmp-" 593 if ( !path ) // 3. /tmp/PACKAGE 594 path = vhfPathWithPathComponents(NSOpenStepRootDirectory(), @"tmp", fileName, nil); 595 fileCnt ++; 596 [downloadFiles addObject:[NSArray arrayWithObjects:url, path, nil]]; // url, path 597 } 598 else 599 NSLog(@"Update-Controller: Nothing to install for '%@'", 600 [[dataDict objectForKey:@"name"] objectAtIndex:i]); 601 } 602 } 603 604 if ( fileCnt ) 605 { int fileIx = [downloadFiles count] - fileCnt; 606 NSArray *array = [downloadFiles objectAtIndex:fileIx]; 607 NSURL *url = [array objectAtIndex:0]; 608 NSString *path = [array objectAtIndex:1]; 609 NSURLRequest *request = [NSURLRequest requestWithURL:url]; 610 NSString *titleStr; 611 612 pkgDownload = [[NSURLDownload alloc] initWithRequest:request delegate:self]; 613 [pkgDownload setDeletesFileUponFailure:YES]; 614 pkgPath = path; 615 [pkgDownload setDestination:pkgPath allowOverwrite:YES]; 616 titleStr = [NSString stringWithFormat:NSLocalizedString(@"Downloading %d %@", NULL), 617 fileCnt, (fileCnt > 1) ? NSLocalizedString(@"Items", NULL) : NSLocalizedString(@"Item", NULL)]; 618 [progressTitleText setStringValue:titleStr]; 619 [progressNameText setStringValue:[path lastPathComponent]]; 620 [progressSizeText setStringValue:@""]; 621 [progressIndicator setIndeterminate:YES]; 622 [progressIndicator startAnimation:nil]; 623 [progressPanel makeKeyAndOrderFront:self]; 624 printf("UpdateController: Download %s\n", [[url absoluteString] UTF8String]); 625 } 626 [panel orderOut:self]; 627} 628- (void)skip:sender 629{ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 630 NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; 631 NSString *appName = [infoDict objectForKey:@"CFBundleExecutable"]; // Cenon, APPNAME 632 NSString *newVersion; 633 634 if ( !appName ) 635 appName = APPNAME; 636 newVersion = [[updateDict objectForKey:appName] objectForKey:@"v"]; 637 if ( newVersion ) 638 [defaults setObject:newVersion forKey:@"skipUpdateVersion"]; 639 [panel orderOut:self]; 640} 641/* Close Update-Panel 642 */ 643- (void)cancel:sender 644{ 645 [panel orderOut:self]; 646} 647 648 649/* Cancel the download from within Progress Panel 650 */ 651- (void)cancelDownload:sender 652{ 653 if (urlConnection) 654 { [urlConnection cancel]; 655 [urlConnection release]; urlConnection = nil; 656 } 657 if (pkgDownload) 658 { 659 [pkgDownload cancel]; 660 [pkgDownload release]; pkgDownload = nil; 661 } 662 [progressPanel orderOut:self]; 663} 664 665/* click into the table - checkmark 666 * created: 2010-05-31 667 * modified: 2010-06-01 668 */ 669- (void)tableClick:sender 670{ int rowIx = [sender clickedRow]; 671 int colIx = [sender clickedColumn]; 672 673 if ( rowIx < 0 ) 674 return; 675 /* Checkmark row */ 676 if ( colIx == 0 ) // checkmark row 677 { NSString *colId = [[[sender tableColumns] objectAtIndex:colIx] identifier]; 678 NSMutableArray *colArray = [[[sender dataSource] dataDict] objectForKey:colId]; 679 NSMutableArray *keyArray = [[[sender dataSource] dataDict] objectForKey:@"updateKey"]; 680 NSString *boolStr = @""; 681 NSString *url = [[updateDict objectForKey:[keyArray objectAtIndex:rowIx]] objectForKey:@"d"]; 682 683 if ( url && // checkmark only for downloadable items 684 (![(NSString*)[colArray objectAtIndex:rowIx] length] || ![[colArray objectAtIndex:rowIx] intValue]) ) 685 boolStr = @"1"; 686 [colArray replaceObjectAtIndex:rowIx withObject:boolStr]; 687 } 688} 689 690/* NSURLConnection Delegate Methods */ 691//#if 0 692- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 693{ 694 [connectionData setLength:0]; 695 [progressTitleText setStringValue:NSLocalizedString(@"Connected to server", NULL)]; 696} 697 698- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 699{ 700 [connectionData appendData:data]; 701 [progressTitleText setStringValue:NSLocalizedString(@"Receiving data", NULL)]; 702} 703 704- (void)connectionDidFinishLoading:(NSURLConnection *)connection 705{ NSString *string = [[[NSString alloc] initWithData:connectionData 706 encoding:NSUTF8StringEncoding] autorelease]; 707 708 checking = NO; 709 if ( connection == urlConnection ) 710 urlConnection = nil; 711 [connection release]; 712 [connectionData release]; connectionData = nil; 713 [progressPanel orderOut:self]; 714 if (string && [string length] > 0) 715 { NSString *updateFile; 716 717 updateFile = vhfPathWithPathComponents(vhfUserLibrary(APPNAME), @".update.plist", nil); 718 //[string writeToFile:updateFile atomically:NO]; 719 [string writeToFile:updateFile atomically:NO 720 encoding:NSUTF8StringEncoding error:NULL]; // >= 10.5 721 [self checkForUpdateAndDisplayPanel]; 722 } 723 // TODO: show "up to date" info 724} 725 726- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 727{ 728 checking = NO; 729 if ( connection == urlConnection ) 730 urlConnection = nil; 731 [connection release]; 732 [connectionData release]; connectionData = nil; 733 [progressPanel orderOut:self]; 734} 735 736- (NSCachedURLResponse *)connection:(NSURLConnection *)connection 737 willCacheResponse:(NSCachedURLResponse *)cachedResponse 738{ 739 return nil; // Never cache 740} 741//#endif 742 743/* NSURLDownload Delegate Methods */ 744/* notification: download did finish 745 */ 746- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response 747{ 748 sizeTotal = [response expectedContentLength]; 749 sizeDownl = 0; 750} 751- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(NSUInteger)length 752{ 753 sizeDownl += length; 754 if (sizeTotal > 0) 755 { float percentComplete = ((float)sizeDownl / (float)sizeTotal) * 100.0; 756 NSString *sizeStr; 757 float div = (sizeTotal < 1024*100) ? 1024.0 : (1024.0*1024.0); // MB or KB 758 759 [progressIndicator setIndeterminate:NO]; 760 [progressIndicator setDoubleValue:percentComplete]; 761 sizeStr = [NSString stringWithFormat:((sizeTotal < 1024*100) ? @"%.1f KB %@ %.1f KB" : @"%.1f MB %@ %.1f MB"), 762 sizeDownl/div, NSLocalizedString(@"of", NULL), sizeTotal/div]; 763 [progressSizeText setStringValue:sizeStr]; 764 } 765} 766- (void)downloadDidFinish:(NSURLDownload*)download 767{ NSURLRequest *request; 768 NSURL *url; 769 770 if ( [download isKindOfClass:[NSURLDownload class]] ) 771 request = [download request]; 772 else // this is a hack to avoid to many methods 773 { request = (NSURLRequest*)download; 774 download = nil; 775 } 776 url = [request URL]; 777 778 if ( [[url absoluteString] hasSuffix:@"update.plist"] ) 779 { checking = NO; 780 [self checkForUpdateAndDisplayPanel]; 781 } 782 else if ( [[url absoluteString] hasSuffix:@".rtf"] ) // info file 783 { NSString *path, *name; 784 NSData *rtfData; 785 786 name = [[url absoluteString] lastPathComponent]; 787 path = vhfPathWithPathComponents(vhfUserLibrary(APPNAME), @".update", name, nil); 788 rtfData = [NSData dataWithContentsOfFile:path]; 789 if (rtfData) 790 [textView replaceCharactersInRange:NSMakeRange(0, [[textView string] length]) 791 withRTF:rtfData]; 792 } 793 else // package 794 { 795 fileCnt --; 796 797 if ( [[pkgPath pathExtension] isEqual:@"tar"] ) // unpack TAR archive 798 { NSString *path = [pkgPath stringByDeletingLastPathComponent]; 799 NSString *command = [NSString stringWithFormat:@"/usr/bin/tar -x -C %@ -f %@", path, pkgPath]; 800 801 system([command UTF8String]); 802 pkgPath = [pkgPath stringByDeletingPathExtension]; 803 } 804 if ( ! [[NSWorkspace sharedWorkspace] openFile:pkgPath] ) // open package in Installer 805 NSLog(@"Couldn't open file %@", pkgPath); 806 pkgDownload = nil; 807 808 if ( !fileCnt ) 809 { NSFileManager *fileManager = [NSFileManager defaultManager]; 810 NSString *path; 811 int i; 812 813 // TODO: "sudo installer -pkg %@ -target /" // man installer 814 // -dumplog ... &2 > LOGFILE 815 // -showChoiceChangesXML, -applyChoiceChangesXML <pathToXMLFile> 816 817 /* cleanup files: .update.plist, .update/rtf-files, packages */ 818 path = vhfPathWithPathComponents(vhfUserLibrary(APPNAME), @".update.plist", nil); 819 [fileManager removeFileAtPath:path handler:nil]; 820 path = vhfPathWithPathComponents(vhfUserLibrary(APPNAME), @".update", nil); 821 [fileManager removeFileAtPath:path handler:nil]; 822 823 for ( i=0, path=nil; i<[downloadFiles count]; i++) 824 { path = [[downloadFiles objectAtIndex:i] objectAtIndex:1]; 825 if ( [[path pathExtension] isEqual:@"tar"] && [fileManager fileExistsAtPath:path] ) // del TAR-file 826 [fileManager removeFileAtPath:path handler:nil]; 827 // TODO: the following must not happen before the files are really installed ! 828 /*path = [path stringByDeletingPathExtension]; 829 if ( [fileManager fileExistsAtPath:path] ) // delete PKG-file 830 [fileManager removeFileAtPath:path handler:nil];*/ 831 } 832 if ( [[NSWorkspace sharedWorkspace] respondsToSelector:@selector(activateFileViewerSelectingURLs:)] ) 833 { NSMutableArray *array = [NSMutableArray array]; 834 835 //path = [path stringByDeletingPathExtension]; 836 for ( i=0; i<[downloadFiles count]; i++) 837 { path = [[downloadFiles objectAtIndex:i] objectAtIndex:1]; 838 path = [path stringByDeletingPathExtension]; //remove ".tar" 839 [array addObject:[NSURL URLWithString:path]]; 840 } 841 [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:array]; 842 } 843 [progressPanel orderOut:self]; 844 NSRunAlertPanel(@"Update Downloaded", @"You can now install the open packages.", OK_STRING, nil, nil); 845 [downloadFiles release]; downloadFiles = nil; 846 } 847 else 848 { int fileIx = [downloadFiles count] - fileCnt; 849 NSArray *array = [downloadFiles objectAtIndex:fileIx]; 850 NSURL *url = [array objectAtIndex:0]; 851 NSString *path = [array objectAtIndex:1]; 852 NSURLRequest *request = [NSURLRequest requestWithURL:url]; 853 NSString *titleStr; 854 855 pkgDownload = [[NSURLDownload alloc] initWithRequest:request delegate:self]; 856 [pkgDownload setDeletesFileUponFailure:YES]; 857 pkgPath = path; 858 [pkgDownload setDestination:pkgPath allowOverwrite:YES]; 859 titleStr = [NSString stringWithFormat:NSLocalizedString(@"Downloading %d %@", NULL), 860 fileCnt, (fileCnt > 1) ? NSLocalizedString(@"Items", NULL) : NSLocalizedString(@"Item", NULL)]; 861 [progressTitleText setStringValue:titleStr]; 862 [progressNameText setStringValue:[path lastPathComponent]]; 863 [progressSizeText setStringValue:@""]; 864 [progressIndicator setIndeterminate:YES]; 865 [progressIndicator startAnimation:nil]; 866 [progressPanel makeKeyAndOrderFront:self]; 867 printf("UpdateController: Download %s\n", [[url absoluteString] UTF8String]); 868 } 869 } 870 if ( download == pkgDownload ) 871 pkgDownload = nil; 872 [download release]; 873} 874- (void)download:(NSURLDownload*)download didFailWithError:(NSError*)error 875{ NSURLRequest *request = [download request]; 876 NSURL *url = [request URL]; 877 //name = [dataDict objectForKey:@"name"]; 878 879 if ( [[url absoluteString] hasSuffix:@"update.plist"] ) 880 checking = NO; 881 else if ( [[url absoluteString] hasSuffix:@".rtf"] ) // info file 882 [textView replaceCharactersInRange:NSMakeRange(0, [[textView string] length]) 883 withString:@""]; 884 else // package download 885 { 886 NSLog(@"Update-Controller: Download failed for file '%@'", url); 887 [progressTitleText setStringValue:NSLocalizedString(@"Download failed !", NULL)]; 888 [downloadFiles release]; downloadFiles = nil; 889 //[progressPanel orderOut:self]; 890 } 891 if ( download == pkgDownload ) 892 pkgDownload = nil; 893 [download release]; 894} 895 896/* NSTableView Delegate methods and Notifications */ 897/* notification: selection changed 898 */ 899- (void)tableViewSelectionDidChange:(NSNotification*)notification 900{ //NSTableView *tableView = [notification object]; 901 int rowIx = [tableView selectedRow]; 902 NSString *updateKey; // key in updateDict 903 NSDictionary *uDict, *names; 904 NSURL *url; 905 NSArray *lngArray = [[NSBundle mainBundle] preferredLocalizations]; 906 NSString *lngKey, *name, *path; 907 NSURLRequest *request; 908 NSURLDownload *download; 909 NSFileManager *fileManager = [NSFileManager defaultManager]; 910 BOOL isDir; 911 912 if ( rowIx < 0 ) 913 return; 914 915 updateKey = [[[tableData dataDict] objectForKey:@"updateKey"] objectAtIndex:rowIx]; 916 uDict = [updateDict objectForKey:updateKey]; 917 names = [uDict objectForKey:@"i"]; // info file 918 //v = [uDict objectForKey:@"v"]; // version 919 if ( !names ) 920 return; 921 /* get language file */ 922 lngKey = [[updateDict objectForKey:@"lng"] objectForKey:[lngArray objectAtIndex:0]]; 923 if ( ! lngKey ) 924 lngKey = @"gb"; 925 if ( [names isKindOfClass:[NSDictionary class]] ) 926 name = [names objectForKey:lngKey]; 927 else 928 name = (NSString*)names; 929 //ext = [name pathExtension]; 930 NSLog(@"name = %@", name); 931 932 933 url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.cenon.info/update/%@", name]]; 934 path = vhfPathWithPathComponents(vhfUserLibrary(APPNAME), @".update", nil); 935 if ( ! [fileManager fileExistsAtPath:path isDirectory:&isDir] ) 936 [fileManager createDirectoryAtPath:path attributes:nil]; 937 else if ( ! isDir ) 938 NSLog(@"Cenon-Update: unexpected file at path '%@'", path); 939 path = vhfPathWithPathComponents(path, name, nil); 940 request = [NSURLRequest requestWithURL:url]; 941 if ( [[NSFileManager defaultManager] fileExistsAtPath:path] ) 942 [self downloadDidFinish:(NSURLDownload*)request]; 943 else 944 { download = [[NSURLDownload alloc] initWithRequest:request delegate:self]; 945 [download setDeletesFileUponFailure:YES]; 946 [download setDestination:path allowOverwrite:YES]; 947 } 948} 949 950@end 951 952 953/* 954 * Table Data for our tableView 955 */ 956 957@implementation UpdateTableData 958 959+ (UpdateTableData*)tableData; 960{ 961 return [[self new] autorelease]; 962} 963 964- init 965{ 966 [super init]; 967 dataDict = [NSMutableDictionary new]; 968 rowCount = 0; 969 return self; 970} 971 972- (int)numberOfRowsInTableView:(NSTableView*)table 973{ 974 return rowCount; 975} 976 977- (id)tableView:(NSTableView*)table objectValueForTableColumn:(NSTableColumn*)column row:(int)row 978{ NSString *obj = [[dataDict objectForKey:[column identifier]] objectAtIndex:row]; 979 980 if ( [[column identifier] isEqual:@"install"] ) 981 { 982 return ([obj intValue]) ? checkMarkStr : @""; 983 } 984 return obj; 985} 986 987- (void)setDataDict:(NSDictionary*)newDataDict 988{ 989 [dataDict release]; 990 dataDict = [newDataDict retain]; 991 rowCount = [(NSArray*)[dataDict objectForKey:@"name"] count]; 992} 993- (NSDictionary*)dataDict 994{ 995 return dataDict; 996} 997 998- (void)dealloc 999{ 1000 [dataDict release]; 1001 [super dealloc]; 1002} 1003 1004@end 1005