| 1 | // |
| 2 | // AppController.m |
| 3 | // OpenMoko Flasher |
| 4 | // |
| 5 | // Created by H. Nikolaus Schaller on 01.08.07. |
| 6 | // Copyright 2007 Golden Delicious Computers GmbH&Co. KG. All rights reserved. |
| 7 | // Licensed under GPLv2 - see www.fsf.org |
| 8 | // |
| 9 | |
| 10 | #import "AppController.h" |
| 11 | |
| 12 | @implementation NSString (Reverse) |
| 13 | |
| 14 | - (NSComparisonResult) reverseCaseInsensitiveCompare:(NSString *)aString |
| 15 | { |
| 16 | NSComparisonResult r=[self caseInsensitiveCompare:aString]; |
| 17 | return -r; |
| 18 | } |
| 19 | |
| 20 | @end |
| 21 | |
| 22 | enum |
| 23 | { // this must match the tag of the popup button values |
| 24 | TYPE_UBOOT=1, |
| 25 | TYPE_2, |
| 26 | TYPE_KERNEL, |
| 27 | TYPE_SPLASH, |
| 28 | TYPE_ROOTFS |
| 29 | }; |
| 30 | |
| 31 | @implementation AppController |
| 32 | |
| 33 | - (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication |
| 34 | { |
| 35 | return YES; |
| 36 | } |
| 37 | |
| 38 | - (NSString *) cachePath:(NSString *) package; |
| 39 | { // path may be either a file or some http:// address |
| 40 | NSMutableString *r=[package mutableCopy]; |
| 41 | NSString *str; |
| 42 | [r replaceOccurrencesOfString:@"file://" withString:@"" options:0 range:NSMakeRange(0, [r length])]; |
| 43 | [r replaceOccurrencesOfString:@"http://" withString:@":" options:0 range:NSMakeRange(0, [r length])]; |
| 44 | [r replaceOccurrencesOfString:@"/" withString:@":" options:0 range:NSMakeRange(0, [r length])]; |
| 45 | str=[NSHomeDirectory() stringByAppendingFormat:@"/Library/Caches/OpenMoko Flasher/%@", r]; |
| 46 | [r autorelease]; |
| 47 | #if 0 |
| 48 | NSLog(@"cachePath %@ -> %@", package, str); |
| 49 | #endif |
| 50 | return str; |
| 51 | } |
| 52 | |
| 53 | - (NSString *) urlPath:(NSString *) cacheFile; |
| 54 | { // path may be either a file or some http:// address |
| 55 | NSMutableString *r=[cacheFile mutableCopy]; |
| 56 | [r replaceOccurrencesOfString:@":" withString:@"/" options:0 range:NSMakeRange(1, [r length]-1)]; |
| 57 | if([r replaceOccurrencesOfString:@":" withString:@"http://" options:0 range:NSMakeRange(0, 1)] == 0) |
| 58 | [r insertString:@"file://" atIndex:0]; // has no initial : |
| 59 | [r autorelease]; |
| 60 | #if 0 |
| 61 | NSLog(@"urlPath %@ -> %@", cacheFile, r); |
| 62 | #endif |
| 63 | return r; |
| 64 | } |
| 65 | |
| 66 | - (NSString *) serverFor:(NSString *) cacheFile; |
| 67 | { // path may be either a file or some http:// address |
| 68 | NSMutableArray *c=[[cacheFile componentsSeparatedByString:@"/"] mutableCopy]; |
| 69 | NSString *r; |
| 70 | [c removeLastObject]; |
| 71 | r=[c componentsJoinedByString:@"/"]; |
| 72 | [c release]; |
| 73 | #if 0 |
| 74 | NSLog(@"serverFor %@ -> %@", cacheFile, r); |
| 75 | #endif |
| 76 | return r; |
| 77 | } |
| 78 | |
| 79 | - (BOOL) downloading:(NSString *) pack; |
| 80 | { // check if we are currently downloading this package |
| 81 | NSEnumerator *e=[downloads objectEnumerator]; |
| 82 | NSURLDownload *d; |
| 83 | while((d=[e nextObject])) |
| 84 | { |
| 85 | if([[[[d request] URL] absoluteString] isEqualToString:pack]) |
| 86 | return YES; |
| 87 | } |
| 88 | return NO; |
| 89 | } |
| 90 | |
| 91 | - (BOOL) cached:(NSString *) pack; |
| 92 | { // already cached? |
| 93 | return [[NSFileManager defaultManager] fileExistsAtPath:[self cachePath:pack]]; |
| 94 | } |
| 95 | |
| 96 | - (void) loadCached; |
| 97 | { // loads all from cache |
| 98 | NSString *dir=[self cachePath:@""]; |
| 99 | NSArray *cached=[[NSFileManager defaultManager] directoryContentsAtPath:dir]; |
| 100 | NSEnumerator *e=[cached objectEnumerator]; |
| 101 | NSString *cacheFile; |
| 102 | [packages removeAllObjects]; |
| 103 | while((cacheFile=[e nextObject])) |
| 104 | { |
| 105 | NSString *path; |
| 106 | if([cacheFile hasPrefix:@"."]) |
| 107 | continue; // skip |
| 108 | if([cacheFile length] < 8) |
| 109 | continue; // too short (is some cache file) |
| 110 | path=[self urlPath:cacheFile]; |
| 111 | [packages addObject:path]; |
| 112 | if([path hasPrefix:@"file:"]) |
| 113 | continue; // don't add |
| 114 | path=[self serverFor:path]; |
| 115 | if(![repositories containsObject:path]) |
| 116 | { // add to known repositories |
| 117 | [repositories addObject:path]; |
| 118 | [repositoryView reloadData]; |
| 119 | } |
| 120 | } |
| 121 | [self filter:nil]; |
| 122 | } |
| 123 | |
| 124 | - (void) updateButtons; |
| 125 | { |
| 126 | int row=[table selectedRow]; |
| 127 | BOOL flag=row >= 0; |
| 128 | NSString *pkg=(row >= 0 && row <[filtered count])?[filtered objectAtIndex:row]:nil; |
| 129 | [loadButton setEnabled:flag && ![self cached:pkg] && ![self downloading:pkg]]; |
| 130 | // [stopButton setEnabled:flag && [self downloading:pkg]]; |
| 131 | [removeButton setEnabled:flag && ([self cached:pkg] || [self downloading:pkg])]; // can also stop a download |
| 132 | [removeButton setTitle:[self downloading:pkg]?@"Cancel":@"Uncache"]; |
| 133 | [refreshButton setEnabled:[[repositoryView stringValue] hasPrefix:@"http://"]]; |
| 134 | [flashButton setEnabled:flag && [self cached:pkg] && ![self downloading:pkg] && !flasher]; |
| 135 | [progress setHidden:!(flasher || [downloads count] > 0)]; |
| 136 | if(![progress isHidden]) |
| 137 | [progress startAnimation:nil]; |
| 138 | } |
| 139 | |
| 140 | - (void) awakeFromNib; |
| 141 | { |
| 142 | [table setDoubleAction:@selector(doubleClick:)]; |
| 143 | [self tableViewSelectionDidChange:nil]; |
| 144 | packages=[[NSMutableArray alloc] initWithCapacity:100]; |
| 145 | moreInfo=[[NSMutableDictionary alloc] initWithCapacity:100]; |
| 146 | downloads=[[NSMutableArray alloc] initWithCapacity:5]; |
| 147 | repositories=[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"Repositories"] mutableCopy]; |
| 148 | [repositoryView reloadData]; |
| 149 | [repositoryView setStringValue:[repositories objectAtIndex:1]]; // preselect first (should we remember by user defaults?) |
| 150 | #if 0 |
| 151 | NSLog(@"repositories=%@", repositories); |
| 152 | #endif |
| 153 | [[NSFileManager defaultManager] createDirectoryAtPath:[self cachePath:@""] attributes:nil]; |
| 154 | [self loadCached]; |
| 155 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(readNotification:) name:NSFileHandleReadCompletionNotification object:nil]; |
| 156 | } |
| 157 | |
| 158 | - (void) applicationDidFinishLaunching:(NSNotification *)aNotification |
| 159 | { // refresh initial repository |
| 160 | [self performSelector:@selector(refresh:) withObject:nil afterDelay:0.1]; |
| 161 | } |
| 162 | |
| 163 | - (id) comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(int)index |
| 164 | { |
| 165 | return [repositories objectAtIndex:index]; |
| 166 | } |
| 167 | |
| 168 | - (int) numberOfItemsInComboBox:(NSComboBox *)aComboBox |
| 169 | { |
| 170 | #if 0 |
| 171 | NSLog(@"items=%d", [repositories count]); |
| 172 | #endif |
| 173 | return [repositories count]; |
| 174 | } |
| 175 | |
| 176 | - (IBAction) refresh:(id) sender; // refresh list |
| 177 | { |
| 178 | NSString *repos=[repositoryView stringValue]; |
| 179 | NSURL *url=[NSURL URLWithString:repos]; |
| 180 | NSError *err; |
| 181 | NSString *str; |
| 182 | // NSAttributedString *astr; |
| 183 | // NSDictionary *attribs; |
| 184 | if(!url) |
| 185 | { |
| 186 | [self updateButtons]; |
| 187 | [self filter:nil]; |
| 188 | return; |
| 189 | } |
| 190 | [progress setHidden:NO]; |
| 191 | [progress startAnimation:nil]; |
| 192 | // astr=[[NSAttributedString alloc] initWithURL:url options:nil documentAttributes:&attribs error:&err]; |
| 193 | str=[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&err]; |
| 194 | [self loadCached]; |
| 195 | if(!str) |
| 196 | { |
| 197 | NSRunAlertPanel(@"OpenMoko Flasher", @"Can't access %@ due to %@", @"Ok", nil, nil, url, err); |
| 198 | } |
| 199 | else |
| 200 | { |
| 201 | unsigned pos=0; |
| 202 | unsigned len=[str length]; |
| 203 | while(YES) |
| 204 | { |
| 205 | // should be reworked to use NSScanner! |
| 206 | |
| 207 | NSRange f=[str rangeOfString:@"href=\"" options:NSCaseInsensitiveSearch range:NSMakeRange(pos, len-pos)]; |
| 208 | NSRange g; |
| 209 | NSString *name; |
| 210 | NSString *date; |
| 211 | if(f.location == NSNotFound) |
| 212 | break; // no more locations |
| 213 | f.location+=f.length; // advance to start of href string |
| 214 | g=[str rangeOfString:@"\">" options:NSCaseInsensitiveSearch range:NSMakeRange(f.location, len-f.location)]; // find closing location |
| 215 | if(g.location == NSNotFound) |
| 216 | break; // some error |
| 217 | name=[str substringWithRange:NSMakeRange(f.location, g.location-f.location)]; |
| 218 | if([[repositoryView stringValue] hasSuffix:@"/"]) |
| 219 | name=[[repositoryView stringValue] stringByAppendingString:name]; |
| 220 | else |
| 221 | name=[[repositoryView stringValue] stringByAppendingFormat:@"/%@", name]; // make URL package name |
| 222 | // NSLog(@"pkg=%@--", name); |
| 223 | if([name hasSuffix:@".bin"] || |
| 224 | [name hasSuffix:@".tgz"] || |
| 225 | [name hasSuffix:@".gz"] || |
| 226 | [name hasSuffix:@".jffs2"]) |
| 227 | { // candidate |
| 228 | if(![packages containsObject:name]) |
| 229 | { // new |
| 230 | [packages addObject:name]; |
| 231 | [moreInfo setObject:[NSMutableDictionary dictionaryWithCapacity:3] forKey:name]; |
| 232 | } |
| 233 | g=[str rangeOfString:@"</a> " options:NSCaseInsensitiveSearch range:NSMakeRange(g.location, len-g.location)]; // find closing location |
| 234 | if(g.location != NSNotFound) |
| 235 | { |
| 236 | int pos=g.location+g.length; |
| 237 | while(pos < [str length] && [str characterAtIndex:pos] == ' ') |
| 238 | pos++; |
| 239 | date=[str substringWithRange:NSMakeRange(pos, 17)]; |
| 240 | } |
| 241 | else |
| 242 | date=@"?"; |
| 243 | // should convert to real NSDate for better sorting |
| 244 | NSLog(@"add date=%@ name=%@", date, name); |
| 245 | [[moreInfo objectForKey:name] setObject:date forKey:@"date"]; |
| 246 | } |
| 247 | pos=g.location; |
| 248 | } |
| 249 | } |
| 250 | [self updateButtons]; |
| 251 | [self filter:nil]; |
| 252 | } |
| 253 | |
| 254 | - (IBAction) add:(id) sender; // manually add file to cache |
| 255 | { |
| 256 | NSOpenPanel *o=[NSOpenPanel openPanel]; |
| 257 | NSString *pack; |
| 258 | if([o runModalForDirectory:nil file:nil types:[NSArray arrayWithObjects:@"jffs2", @"bin", @"gz", nil]] != NSFileHandlingPanelOKButton) |
| 259 | return; // ignore |
| 260 | pack=[@"file://" stringByAppendingString:[[o filename] lastPathComponent]]; |
| 261 | if([[NSFileManager defaultManager] fileExistsAtPath:[self cachePath:pack]]) |
| 262 | NSRunAlertPanel(@"OpenMoko Flasher", @"Already exists in cache: %@", @"Ok", nil, nil, [o filename]); |
| 263 | else if(![[NSFileManager defaultManager] copyPath:[o filename] toPath:[self cachePath:pack] handler:nil]) |
| 264 | NSRunAlertPanel(@"OpenMoko Flasher", @"Can't add %@", @"Ok", nil, nil, [o filename]); |
| 265 | else |
| 266 | { |
| 267 | if(![packages containsObject:pack]) |
| 268 | [packages addObject:pack]; // cache as file:name |
| 269 | } |
| 270 | [self filter:nil]; // reload |
| 271 | } |
| 272 | |
| 273 | - (IBAction) remove:(id) sender; // remove from cache |
| 274 | { |
| 275 | NSString *path; |
| 276 | NSString *package; |
| 277 | int row=[table selectedRow]; |
| 278 | NSEnumerator *e=[downloads objectEnumerator]; |
| 279 | NSURLDownload *d; |
| 280 | if(row < 0) |
| 281 | return; |
| 282 | package=[filtered objectAtIndex:row]; |
| 283 | path=[self cachePath:package]; |
| 284 | while((d=[e nextObject])) |
| 285 | { |
| 286 | NSURL *url=[[d request] URL]; |
| 287 | if([[url absoluteString] isEqualToString:package]) |
| 288 | { |
| 289 | [d cancel]; |
| 290 | #if 1 // cancel should have deleted the file? |
| 291 | [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; |
| 292 | #endif |
| 293 | [downloads removeObject:d]; |
| 294 | NSLog(@"cancelled %@", d); |
| 295 | NSLog(@"downloads %@", downloads); |
| 296 | [self filter:nil]; // show result |
| 297 | return; |
| 298 | } |
| 299 | } |
| 300 | if(![self cached:package]) |
| 301 | return; // not cached |
| 302 | if(NSRunAlertPanel(@"OpenMoko Flasher", @"Do you really want to delete %@ from the cache?", @"Ok", @"Cancel", nil, package) != NSAlertDefaultReturn) |
| 303 | return; |
| 304 | [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; |
| 305 | [self filter:nil]; // show result |
| 306 | } |
| 307 | |
| 308 | - (IBAction) load:(id) sender; // load selected to cache |
| 309 | { |
| 310 | NSString *path; |
| 311 | NSString *package; |
| 312 | int row=[table selectedRow]; |
| 313 | if(row < 0) |
| 314 | return; |
| 315 | package=[filtered objectAtIndex:row]; |
| 316 | path=[self cachePath:package]; |
| 317 | if(![self cached:package] || ![self downloading:package]) |
| 318 | { // does not exist |
| 319 | NSURL *url=[NSURL URLWithString:package]; |
| 320 | NSURLRequest *request=[NSURLRequest requestWithURL:url]; |
| 321 | NSURLDownload *download=[[NSURLDownload alloc] initWithRequest:request delegate:self]; |
| 322 | NSLog(@"url=%@", url); |
| 323 | NSLog(@"request=%@", request); |
| 324 | [download setDestination:path allowOverwrite:YES]; |
| 325 | [download setDeletesFileUponFailure:YES]; |
| 326 | [downloads addObject:download]; // add to list |
| 327 | NSLog(@"downloads=%@", downloads); |
| 328 | [download release]; |
| 329 | [self filter:nil]; // needs a refresh to show downloading status |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | - (void) readNotification:(NSNotification *) n; |
| 334 | { |
| 335 | NSFileHandle *f=[n object]; |
| 336 | NSData *d=[[n userInfo] objectForKey:NSFileHandleNotificationDataItem]; |
| 337 | NSAttributedString *astr; |
| 338 | NSString *str; |
| 339 | if([d length] == 0) |
| 340 | { |
| 341 | NSLog(@"flashing done"); |
| 342 | [flasher waitUntilExit]; |
| 343 | if([flasher terminationStatus] == 0) |
| 344 | { // flashed ok |
| 345 | [[NSUserDefaults standardUserDefaults] setObject:[currentFile lastPathComponent] forKey:[[region selectedItem] title]]; |
| 346 | } |
| 347 | else |
| 348 | NSRunAlertPanel(@"Access OpenMoko", @"Flashing failed. Try to unplug/replug the USB connection. And, try twice.", @"Ok", nil, nil); |
| 349 | [table reloadData]; // update status |
| 350 | [flasher release]; |
| 351 | flasher=nil; |
| 352 | [currentFile release]; |
| 353 | [self updateButtons]; // stop progress indicator |
| 354 | return; // done |
| 355 | } |
| 356 | NSLog(@"received %d bytes", [d length]); |
| 357 | str=[[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; |
| 358 | astr=[[NSAttributedString alloc] initWithString:str]; |
| 359 | [str release]; |
| 360 | [[logView textStorage] appendAttributedString:astr]; |
| 361 | [astr release]; |
| 362 | [logView scrollRangeToVisible:NSMakeRange([[logView string] length], 0)]; // scroll to bottom |
| 363 | [[logView window] display]; |
| 364 | [f readInBackgroundAndNotify]; // continue |
| 365 | } |
| 366 | |
| 367 | - (int) packageType:(NSString *) file |
| 368 | { |
| 369 | if([file hasPrefix:@"u-boot-"] && [file hasSuffix:@".bin"]) |
| 370 | return TYPE_UBOOT; |
| 371 | if([file hasSuffix:@"-u-boot.bin"]) |
| 372 | return TYPE_UBOOT; |
| 373 | if([file hasSuffix:@"u-boot_env"]) |
| 374 | return TYPE_2; |
| 375 | if([file hasPrefix:@"uImage-"] && [file hasSuffix:@".bin"]) |
| 376 | return TYPE_KERNEL; |
| 377 | if([file hasSuffix:@".uImage.bin"]) |
| 378 | return TYPE_KERNEL; |
| 379 | if([file hasSuffix:@".splash.gz"]) |
| 380 | return TYPE_SPLASH; |
| 381 | if([file hasSuffix:@".jffs2"]) |
| 382 | return TYPE_ROOTFS; |
| 383 | return NSNotFound; |
| 384 | } |
| 385 | |
| 386 | - (IBAction) flash:(id) sender; // flash selected file from cache |
| 387 | { |
| 388 | NSBundle *b=[NSBundle mainBundle]; |
| 389 | NSString *path; |
| 390 | NSString *package; |
| 391 | NSPipe *pipe; |
| 392 | int type; |
| 393 | NSString *mappedType=nil; |
| 394 | int row=[table selectedRow]; |
| 395 | if(row < 0) |
| 396 | return; |
| 397 | if([self downloading:package]) |
| 398 | return; |
| 399 | package=[filtered objectAtIndex:row]; |
| 400 | type=[self packageType:[package lastPathComponent]]; |
| 401 | switch(type) |
| 402 | { |
| 403 | case TYPE_UBOOT: mappedType=@"u-boot"; break; |
| 404 | case TYPE_2: mappedType=@"2"; break; |
| 405 | case TYPE_KERNEL: mappedType=@"kernel"; break; |
| 406 | case TYPE_SPLASH: mappedType=@"splash"; break; |
| 407 | case TYPE_ROOTFS: mappedType=@"rootfs"; break; |
| 408 | default: |
| 409 | return; // unknown type |
| 410 | } |
| 411 | path=[self cachePath:package]; |
| 412 | if(!path) |
| 413 | return; // can't load |
| 414 | currentFile=[path retain]; |
| 415 | #if 1 |
| 416 | { |
| 417 | NSString *cmd=[NSString stringWithFormat:@"export DYLD_LIBRARY_PATH=\"%@:${DYLD_LIBRARY_PATH}\"; '%@' -a %@ -R -D '%@'", |
| 418 | [b resourcePath], [b pathForAuxiliaryExecutable:@"dfu-util"], mappedType, path]; |
| 419 | NSLog(@"\n%@", cmd); |
| 420 | } |
| 421 | #endif |
| 422 | if(NSRunAlertPanel(@"Access OpenMoko", @"Do you really want to flash a new file?\nThis may damage your device!\n\nTo turn on Flashing mode, press the AUX buton while switching on the OpenMoko. Leave the device in the BOOT menu. If it fails, unplug/replug USB. And try again (twice is better than once).", @"Ok", @"Cancel", nil) != NSOKButton) |
| 423 | return; |
| 424 | flasher=[[NSTask alloc] init]; |
| 425 | #if 0 |
| 426 | [flasher setLaunchPath:@"/bin/echo"]; |
| 427 | #else |
| 428 | [flasher setLaunchPath:[b pathForAuxiliaryExecutable:@"dfu-util"]]; |
| 429 | #endif |
| 430 | [flasher setArguments:[NSArray arrayWithObjects: |
| 431 | // @"dfu-util", |
| 432 | @"-a", |
| 433 | mappedType, |
| 434 | @"-R", |
| 435 | @"-D", |
| 436 | path, |
| 437 | nil]]; |
| 438 | [flasher setEnvironment:[NSDictionary dictionaryWithObjectsAndKeys: |
| 439 | [b resourcePath], @"DYLD_LIBRARY_PATH", |
| 440 | nil]]; |
| 441 | #if 1 |
| 442 | NSLog(@"%@ %@ %@", [flasher launchPath], [flasher arguments], [flasher environment]); |
| 443 | // write virtual command(s) to log window |
| 444 | #endif |
| 445 | pipe=[NSPipe pipe]; |
| 446 | [flasher setStandardOutput:pipe]; |
| 447 | [flasher setStandardError:pipe]; |
| 448 | [[pipe fileHandleForReading] readInBackgroundAndNotify]; |
| 449 | [flasher launch]; |
| 450 | [self updateButtons]; // start progress indicator |
| 451 | } |
| 452 | |
| 453 | // should be effective for every change!!! |
| 454 | |
| 455 | - (IBAction) didchange:(id) sender; |
| 456 | { |
| 457 | // check if it really did change... |
| 458 | if(sender == repositoryView) |
| 459 | { |
| 460 | [self refresh:nil]; |
| 461 | } |
| 462 | else |
| 463 | { |
| 464 | [self filter:nil]; // update filter |
| 465 | [self updateButtons]; |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | - (IBAction) filter:(id) sender; // change filter |
| 470 | { |
| 471 | [filtered release]; // clear cache |
| 472 | filtered=nil; |
| 473 | [table reloadData]; |
| 474 | } |
| 475 | |
| 476 | - (IBAction) go:(id) sender; |
| 477 | { |
| 478 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[repositoryView stringValue]]]; |
| 479 | [sender setState:NSOffState]; |
| 480 | } |
| 481 | |
| 482 | - (BOOL) validateMenuItem:(id <NSMenuItem>)menuItem |
| 483 | { |
| 484 | NSString *action=NSStringFromSelector([menuItem action]); |
| 485 | if([action isEqualToString:@"load:"]) |
| 486 | return [loadButton isEnabled]; |
| 487 | if([action isEqualToString:@"flash:"]) |
| 488 | return [flashButton isEnabled]; |
| 489 | return YES; |
| 490 | } |
| 491 | |
| 492 | // table callbacks |
| 493 | |
| 494 | - (IBAction) doubleClick:(id) sender; |
| 495 | { |
| 496 | int row=[sender clickedRow]; |
| 497 | NSString *pack; |
| 498 | if(row < 0) |
| 499 | return; |
| 500 | pack=[filtered objectAtIndex:row]; |
| 501 | if([self cached:pack]) |
| 502 | [[NSWorkspace sharedWorkspace] selectFile:[self cachePath:pack] inFileViewerRootedAtPath:@""]; |
| 503 | } |
| 504 | |
| 505 | - (int) numberOfRowsInTableView:(NSTableView *)aTableView |
| 506 | { |
| 507 | if(!filtered) |
| 508 | { |
| 509 | int tag=[[region selectedItem] tag]; |
| 510 | NSEnumerator *e=[packages objectEnumerator]; |
| 511 | NSString *package; |
| 512 | NSString *repos=[repositoryView stringValue]; |
| 513 | if([repos hasSuffix:@"/"]) |
| 514 | repos=[repos substringToIndex:[repos length]-1]; |
| 515 | filtered=[[NSMutableArray alloc] initWithCapacity:100]; |
| 516 | while((package=[e nextObject])) |
| 517 | { |
| 518 | NSString *loc=[self serverFor:package]; |
| 519 | NSString *file=[package lastPathComponent]; |
| 520 | if([repos hasPrefix:@"http://"] && ![loc isEqualToString:repos]) |
| 521 | { |
| 522 | #if 0 |
| 523 | NSLog(@"no match: %@ - %@", loc, repos); |
| 524 | #endif |
| 525 | continue; // filter by repository |
| 526 | } |
| 527 | if(tag != 0 && [self packageType:file] != tag) |
| 528 | continue; // didn't pass filter |
| 529 | [filtered addObject:package]; // did go through filter |
| 530 | } |
| 531 | [filtered sortUsingSelector:@selector(reverseCaseInsensitiveCompare:)]; |
| 532 | [self updateButtons]; // may change button status |
| 533 | } |
| 534 | return [filtered count]; |
| 535 | } |
| 536 | |
| 537 | - (id) tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex |
| 538 | { |
| 539 | NSString *ident=[aTableColumn identifier]; |
| 540 | if([ident isEqualToString:@"status"]) |
| 541 | { // get status |
| 542 | NSString *name=[filtered objectAtIndex:rowIndex]; |
| 543 | NSString *current=[[NSUserDefaults standardUserDefaults] objectForKey:[[region selectedItem] title]]; |
| 544 | if([current isEqualToString:name]) |
| 545 | return @"Flashed"; |
| 546 | if([self downloading:name]) |
| 547 | return @"Loading"; |
| 548 | if([self cached:name]) |
| 549 | return @"Cached"; |
| 550 | return @""; |
| 551 | } |
| 552 | if([ident isEqualToString:@"type"]) |
| 553 | { |
| 554 | switch([self packageType:[[filtered objectAtIndex:rowIndex] lastPathComponent]]) |
| 555 | { |
| 556 | case TYPE_UBOOT: return @"BootLD"; |
| 557 | case TYPE_KERNEL: return @"Kernel"; |
| 558 | case TYPE_SPLASH: return @"Splash"; |
| 559 | case TYPE_ROOTFS: return @"RootFS"; |
| 560 | default: @"?"; |
| 561 | } |
| 562 | } |
| 563 | if([ident isEqualToString:@"package"]) |
| 564 | { |
| 565 | if([[repositoryView stringValue] hasPrefix:@"http://"]) |
| 566 | return [[filtered objectAtIndex:rowIndex] lastPathComponent]; // show filename only |
| 567 | return [filtered objectAtIndex:rowIndex]; // show full path |
| 568 | } |
| 569 | if([ident isEqualToString:@"date"]) |
| 570 | { |
| 571 | NSString *package=[filtered objectAtIndex:rowIndex]; |
| 572 | NSDictionary *more=[moreInfo objectForKey:package]; |
| 573 | return [more objectForKey:@"date"]; |
| 574 | } |
| 575 | if([ident isEqualToString:@"size"]) |
| 576 | { |
| 577 | NSString *package=[filtered objectAtIndex:rowIndex]; |
| 578 | if([self cached:package]) |
| 579 | { // get cached file size |
| 580 | NSString *path=[self cachePath:package]; |
| 581 | NSDictionary *stat=[[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES]; |
| 582 | if(stat) |
| 583 | { // get (real) file size |
| 584 | return [[stat objectForKey:NSFileSize] description]; |
| 585 | } |
| 586 | } |
| 587 | return @"?"; |
| 588 | } |
| 589 | return @"?"; |
| 590 | } |
| 591 | |
| 592 | - (void) tableViewSelectionDidChange:(NSNotification *)aNotification |
| 593 | { |
| 594 | [self updateButtons]; |
| 595 | } |
| 596 | |
| 597 | - (void) downloadDidFinish:(NSURLDownload *)download |
| 598 | { |
| 599 | NSLog(@"download finished %@", download); |
| 600 | [downloads removeObject:download]; |
| 601 | [self filter:nil]; |
| 602 | } |
| 603 | |
| 604 | - (void)download:(NSURLDownload *)download didReceiveDataOfLength:(unsigned)length |
| 605 | { // show new file length |
| 606 | static NSDate *last; |
| 607 | if(!last || [[NSDate date] timeIntervalSinceDate:last] > 0.2) |
| 608 | { // refresh |
| 609 | [table reloadData]; |
| 610 | [last release]; |
| 611 | last=[[NSDate alloc] init]; |
| 612 | } |
| 613 | } |
| 614 | |
| 615 | - (void) download:(NSURLDownload *)download didFailWithError:(NSError *)error |
| 616 | { |
| 617 | NSLog(@"download error %@ for %@", error, download); |
| 618 | [downloads removeObject:download]; |
| 619 | [self filter:nil]; |
| 620 | } |
| 621 | |
| 622 | @end |
| 623 | |