+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ NSString *context([sheet context]);
+
+ if ([context isEqualToString:@"remove"]) {
+ switch (button) {
+ case 1:
+ [self cancel];
+ break;
+ case 2:
+ if (substrate_)
+ Finish_ = 2;
+ [delegate_ confirm];
+ break;
+ default:
+ _assert(false);
+ }
+
+ [sheet dismiss];
+ } else if ([context isEqualToString:@"unable"]) {
+ [self cancel];
+ [sheet dismiss];
+ } else
+ [super alertSheet:sheet buttonClicked:button];
+}
+
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+ [super webView:sender didClearWindowObject:window forFrame:frame];
+ [window setValue:changes_ forKey:@"changes"];
+ [window setValue:issues_ forKey:@"issues"];
+ [window setValue:sizes_ forKey:@"sizes"];
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+
+ NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *downgrading = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *removing = [NSMutableArray arrayWithCapacity:16];
+
+ bool remove(false);
+
+ pkgDepCache::Policy *policy([database_ policy]);
+
+ pkgCacheFile &cache([database_ cache]);
+ NSArray *packages = [database_ packages];
+ for (Package *package in packages) {
+ pkgCache::PkgIterator iterator = [package iterator];
+ pkgDepCache::StateCache &state(cache[iterator]);
+
+ NSString *name([package name]);
+
+ if (state.NewInstall())
+ [installing addObject:name];
+ else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
+ [reinstalling addObject:name];
+ else if (state.Upgrade())
+ [upgrading addObject:name];
+ else if (state.Downgrade())
+ [downgrading addObject:name];
+ else if (state.Delete()) {
+ if ([package essential])
+ remove = true;
+ [removing addObject:name];
+ } else continue;
+
+ substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
+ substrate_ |= DepSubstrate(iterator.CurrentVer());
+ }
+
+ if (!remove)
+ essential_ = nil;
+ else if (Advanced_ || true) {
+ NSString *parenthetical(CYLocalize("PARENTHETICAL"));
+
+ essential_ = [[UIActionSheet alloc]
+ initWithTitle:CYLocalize("REMOVING_ESSENTIALS")
+ buttons:[NSArray arrayWithObjects:
+ [NSString stringWithFormat:parenthetical, CYLocalize("CANCEL_OPERATION"), CYLocalize("SAFE")],
+ [NSString stringWithFormat:parenthetical, CYLocalize("FORCE_REMOVAL"), CYLocalize("UNSAFE")],
+ nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"remove"
+ ];
+
+#ifndef __OBJC2__
+ [essential_ setDestructiveButton:[[essential_ buttons] objectAtIndex:0]];
+#endif
+ [essential_ setBodyText:CYLocalize("REMOVING_ESSENTIALS_EX")];
+ } else {
+ essential_ = [[UIActionSheet alloc]
+ initWithTitle:CYLocalize("UNABLE_TO_COMPLY")
+ buttons:[NSArray arrayWithObjects:CYLocalize("OKAY"), nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"unable"
+ ];
+
+ [essential_ setBodyText:CYLocalize("UNABLE_TO_COMPLY_EX")];
+ }
+
+ changes_ = [[NSArray alloc] initWithObjects:
+ installing,
+ reinstalling,
+ upgrading,
+ downgrading,
+ removing,
+ nil];
+
+ issues_ = [database_ issues];
+ if (issues_ != nil)
+ issues_ = [issues_ retain];
+
+ sizes_ = [[NSArray alloc] initWithObjects:
+ SizeString([database_ fetcher].FetchNeeded()),
+ SizeString([database_ fetcher].PartialPresent()),
+ SizeString([database_ cache]->UsrSize()),
+ nil];
+
+ [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"confirm" ofType:@"html"]]];
+ } return self;
+}
+
+- (NSString *) backButtonTitle {
+ return CYLocalize("CONFIRM");
+}
+
+- (NSString *) leftButtonTitle {
+ return [NSString stringWithFormat:CYLocalize("SLASH_DELIMITED"), CYLocalize("CANCEL"), CYLocalize("QUEUE")];
+}
+
+- (id) rightButtonTitle {
+ return issues_ != nil ? nil : [super rightButtonTitle];
+}
+
+- (id) _rightButtonTitle {
+#if AlwaysReload || IgnoreInstall
+ return [super _rightButtonTitle];
+#else
+ return CYLocalize("CONFIRM");
+#endif
+}
+
+- (void) _leftButtonClicked {
+ [self cancel];
+}
+
+#if !AlwaysReload
+- (void) _rightButtonClicked {
+#if IgnoreInstall
+ return [super _rightButtonClicked];
+#endif
+ if (essential_ != nil)
+ [essential_ popupAlertAnimated:YES];
+ else {
+ if (substrate_)
+ Finish_ = 2;
+ [delegate_ confirm];
+ }
+}
+#endif
+
+@end
+/* }}} */
+
+/* Progress Data {{{ */
+@interface ProgressData : NSObject {
+ SEL selector_;
+ id target_;
+ id object_;
+}
+
+- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
+
+- (SEL) selector;
+- (id) target;
+- (id) object;
+@end
+
+@implementation ProgressData
+
+- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
+ if ((self = [super init]) != nil) {
+ selector_ = selector;
+ target_ = target;
+ object_ = object;
+ } return self;
+}
+
+- (SEL) selector {
+ return selector_;
+}
+
+- (id) target {
+ return target_;
+}
+
+- (id) object {
+ return object_;
+}
+
+@end
+/* }}} */
+/* Progress View {{{ */
+@interface ProgressView : UIView <
+ ConfigurationDelegate,
+ ProgressDelegate
+> {
+ _transient Database *database_;
+ UIView *view_;
+ UIView *background_;
+ UITransitionView *transition_;
+ UIView *overlay_;
+ UINavigationBar *navbar_;
+ UIProgressBar *progress_;
+ UITextView *output_;
+ UITextLabel *status_;
+ UIPushButton *close_;
+ id delegate_;
+ BOOL running_;
+ SHA1SumValue springlist_;
+ SHA1SumValue notifyconf_;
+ SHA1SumValue sandplate_;
+}
+
+- (void) transitionViewDidComplete:(UITransitionView*)view fromView:(UIView*)from toView:(UIView*)to;
+
+- (id) initWithFrame:(struct CGRect)frame database:(Database *)database delegate:(id)delegate;
+- (void) setContentView:(UIView *)view;
+- (void) resetView;
+
+- (void) _retachThread;
+- (void) _detachNewThreadData:(ProgressData *)data;
+- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title;
+
+- (BOOL) isRunning;
+
+@end
+
+@protocol ProgressViewDelegate
+- (void) progressViewIsComplete:(ProgressView *)sender;
+@end
+
+@implementation ProgressView
+
+- (void) dealloc {
+ [transition_ setDelegate:nil];
+ [navbar_ setDelegate:nil];
+
+ [view_ release];
+ if (background_ != nil)
+ [background_ release];
+ [transition_ release];
+ [overlay_ release];
+ [navbar_ release];
+ [progress_ release];
+ [output_ release];
+ [status_ release];
+ [close_ release];
+ [super dealloc];
+}
+
+- (void) transitionViewDidComplete:(UITransitionView*)view fromView:(UIView*)from toView:(UIView*)to {
+ if (bootstrap_ && from == overlay_ && to == view_)
+ exit(0);
+}
+
+- (id) initWithFrame:(struct CGRect)frame database:(Database *)database delegate:(id)delegate {
+ if ((self = [super initWithFrame:frame]) != nil) {
+ database_ = database;
+ delegate_ = delegate;
+
+ transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
+ [transition_ setDelegate:self];
+
+ overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]];
+
+ if (bootstrap_)
+ [overlay_ setBackgroundColor:[UIColor blackColor]];
+ else {
+ background_ = [[UIView alloc] initWithFrame:[self bounds]];
+ [background_ setBackgroundColor:[UIColor blackColor]];
+ [self addSubview:background_];
+ }
+
+ [self addSubview:transition_];
+
+ CGSize navsize = [UINavigationBar defaultSize];
+ CGRect navrect = {{0, 0}, navsize};
+
+ navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
+ [overlay_ addSubview:navbar_];
+
+ [navbar_ setBarStyle:1];
+ [navbar_ setDelegate:self];
+
+ UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:nil] autorelease];
+ [navbar_ pushNavigationItem:navitem];
+
+ CGRect bounds = [overlay_ bounds];
+ CGSize prgsize = [UIProgressBar defaultSize];
+
+ CGRect prgrect = {{
+ (bounds.size.width - prgsize.width) / 2,
+ bounds.size.height - prgsize.height - 20
+ }, prgsize};
+
+ progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
+ [progress_ setStyle:0];
+
+ status_ = [[UITextLabel alloc] initWithFrame:CGRectMake(
+ 10,
+ bounds.size.height - prgsize.height - 50,
+ bounds.size.width - 20,
+ 24
+ )];
+
+ [status_ setColor:[UIColor whiteColor]];
+ [status_ setBackgroundColor:[UIColor clearColor]];
+
+ [status_ setCentersHorizontally:YES];
+ //[status_ setFont:font];
+ _trace();
+
+ output_ = [[UITextView alloc] initWithFrame:CGRectMake(
+ 10,
+ navrect.size.height + 20,
+ bounds.size.width - 20,
+ bounds.size.height - navsize.height - 62 - navrect.size.height
+ )];
+ _trace();
+
+ //[output_ setTextFont:@"Courier New"];
+ [output_ setTextSize:12];
+
+ [output_ setTextColor:[UIColor whiteColor]];
+ [output_ setBackgroundColor:[UIColor clearColor]];
+
+ [output_ setMarginTop:0];
+ [output_ setAllowsRubberBanding:YES];
+ [output_ setEditable:NO];
+
+ [overlay_ addSubview:output_];
+
+ close_ = [[UIPushButton alloc] initWithFrame:CGRectMake(
+ 10,
+ bounds.size.height - prgsize.height - 50,
+ bounds.size.width - 20,
+ 32 + prgsize.height
+ )];
+
+ [close_ setAutosizesToFit:NO];
+ [close_ setDrawsShadow:YES];
+ [close_ setStretchBackground:YES];
+ [close_ setEnabled:YES];
+
+ UIFont *bold = [UIFont boldSystemFontOfSize:22];
+ [close_ setTitleFont:bold];
+
+ [close_ addTarget:self action:@selector(closeButtonPushed) forEvents:kUIControlEventMouseUpInside];
+ [close_ setBackground:[UIImage applicationImageNamed:@"green-up.png"] forState:0];
+ [close_ setBackground:[UIImage applicationImageNamed:@"green-dn.png"] forState:1];
+ } return self;
+}
+
+- (void) setContentView:(UIView *)view {
+ view_ = [view retain];
+}
+
+- (void) resetView {
+ [transition_ transition:6 toView:view_];
+}
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ NSString *context([sheet context]);
+
+ if ([context isEqualToString:@"error"])
+ [sheet dismiss];
+ else if ([context isEqualToString:@"conffile"]) {
+ FILE *input = [database_ input];
+
+ switch (button) {
+ case 1:
+ fprintf(input, "N\n");
+ fflush(input);
+ break;
+ case 2:
+ fprintf(input, "Y\n");
+ fflush(input);
+ break;
+ default:
+ _assert(false);
+ }
+
+ [sheet dismiss];
+ }
+}
+
+- (void) closeButtonPushed {
+ running_ = NO;
+
+ switch (Finish_) {
+ case 0:
+ [self resetView];
+ break;
+
+ case 1:
+ [delegate_ suspendWithAnimation:YES];
+ break;
+
+ case 2:
+ system("launchctl stop com.apple.SpringBoard");
+ break;
+
+ case 3:
+ system("launchctl unload "SpringBoard_"; launchctl load "SpringBoard_);
+ break;
+
+ case 4:
+ system("reboot");
+ break;
+ }
+}
+
+- (void) _retachThread {
+ UINavigationItem *item = [navbar_ topItem];
+ [item setTitle:CYLocalize("COMPLETE")];
+
+ [overlay_ addSubview:close_];
+ [progress_ removeFromSuperview];
+ [status_ removeFromSuperview];
+
+ [delegate_ progressViewIsComplete:self];
+
+ if (Finish_ < 4) {
+ FileFd file(SandboxTemplate_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ if (!(sandplate_ == sha1.Result()))
+ Finish_ = 4;
+ }
+
+ if (Finish_ < 4) {
+ FileFd file(NotifyConfig_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ if (!(notifyconf_ == sha1.Result()))
+ Finish_ = 4;
+ }
+
+ if (Finish_ < 3) {
+ FileFd file(SpringBoard_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ if (!(springlist_ == sha1.Result()))
+ Finish_ = 3;
+ }
+
+ switch (Finish_) {
+ case 0: [close_ setTitle:CYLocalize("RETURN_TO_CYDIA")]; break;
+ case 1: [close_ setTitle:CYLocalize("CLOSE_CYDIA")]; break;
+ case 2: [close_ setTitle:CYLocalize("RESTART_SPRINGBOARD")]; break;
+ case 3: [close_ setTitle:CYLocalize("RELOAD_SPRINGBOARD")]; break;
+ case 4: [close_ setTitle:CYLocalize("REBOOT_DEVICE")]; break;
+ }
+
+#define Cache_ "/User/Library/Caches/com.apple.mobile.installation.plist"
+
+ if (NSMutableDictionary *cache = [[NSMutableDictionary alloc] initWithContentsOfFile:@ Cache_]) {
+ [cache autorelease];
+
+ NSFileManager *manager = [NSFileManager defaultManager];
+ NSError *error = nil;
+
+ id system = [cache objectForKey:@"System"];
+ if (system == nil)
+ goto error;
+
+ struct stat info;
+ if (stat(Cache_, &info) == -1)
+ goto error;
+
+ [system removeAllObjects];
+
+ if (NSArray *apps = [manager contentsOfDirectoryAtPath:@"/Applications" error:&error]) {
+ for (NSString *app in apps)
+ if ([app hasSuffix:@".app"]) {
+ NSString *path = [@"/Applications" stringByAppendingPathComponent:app];
+ NSString *plist = [path stringByAppendingPathComponent:@"Info.plist"];
+ if (NSMutableDictionary *info = [[NSMutableDictionary alloc] initWithContentsOfFile:plist]) {
+ [info autorelease];
+ if ([info objectForKey:@"CFBundleIdentifier"] != nil) {
+ [info setObject:path forKey:@"Path"];
+ [info setObject:@"System" forKey:@"ApplicationType"];
+ [system addInfoDictionary:info];
+ }
+ }
+ }
+ } else goto error;
+
+ [cache writeToFile:@Cache_ atomically:YES];
+
+ if (chown(Cache_, info.st_uid, info.st_gid) == -1)
+ goto error;
+ if (chmod(Cache_, info.st_mode) == -1)
+ goto error;
+
+ if (false) error:
+ lprintf("%s\n", error == nil ? strerror(errno) : [[error localizedDescription] UTF8String]);
+ }
+
+ notify_post("com.apple.mobile.application_installed");
+
+ [delegate_ setStatusBarShowsProgress:NO];
+}
+
+- (void) _detachNewThreadData:(ProgressData *)data { _pooled
+ [[data target] performSelector:[data selector] withObject:[data object]];
+ [data release];
+
+ [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES];
+}
+
+- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title {
+ UINavigationItem *item = [navbar_ topItem];
+ [item setTitle:title];
+
+ [status_ setText:nil];
+ [output_ setText:@""];
+ [progress_ setProgress:0];
+
+ [close_ removeFromSuperview];
+ [overlay_ addSubview:progress_];
+ [overlay_ addSubview:status_];
+
+ [delegate_ setStatusBarShowsProgress:YES];
+ running_ = YES;
+
+ {
+ FileFd file(SandboxTemplate_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ sandplate_ = sha1.Result();
+ }
+
+ {
+ FileFd file(NotifyConfig_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ notifyconf_ = sha1.Result();
+ }
+
+ {
+ FileFd file(SpringBoard_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ springlist_ = sha1.Result();
+ }
+
+ [transition_ transition:6 toView:overlay_];
+
+ [NSThread
+ detachNewThreadSelector:@selector(_detachNewThreadData:)
+ toTarget:self
+ withObject:[[ProgressData alloc]
+ initWithSelector:selector
+ target:target
+ object:object
+ ]
+ ];
+}
+
+- (void) repairWithSelector:(SEL)selector {
+ [self
+ detachNewThreadSelector:selector
+ toTarget:database_
+ withObject:nil
+ title:CYLocalize("REPAIRING")
+ ];
+}
+
+- (void) setConfigurationData:(NSString *)data {
+ [self
+ performSelectorOnMainThread:@selector(_setConfigurationData:)
+ withObject:data
+ waitUntilDone:YES
+ ];
+}
+
+- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
+ Package *package = id == nil ? nil : [database_ packageWithName:id];
+
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:(package == nil ? id : [package name])
+ buttons:[NSArray arrayWithObjects:CYLocalize("OKAY"), nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"error"
+ ] autorelease];
+
+ [sheet setBodyText:error];
+ [sheet popupAlertAnimated:YES];
+}
+
+- (void) setProgressTitle:(NSString *)title {
+ [self
+ performSelectorOnMainThread:@selector(_setProgressTitle:)
+ withObject:title
+ waitUntilDone:YES
+ ];
+}
+
+- (void) setProgressPercent:(float)percent {
+ [self
+ performSelectorOnMainThread:@selector(_setProgressPercent:)
+ withObject:[NSNumber numberWithFloat:percent]
+ waitUntilDone:YES
+ ];
+}
+
+- (void) startProgress {
+}
+
+- (void) addProgressOutput:(NSString *)output {
+ [self
+ performSelectorOnMainThread:@selector(_addProgressOutput:)
+ withObject:output
+ waitUntilDone:YES
+ ];
+}
+
+- (bool) isCancelling:(size_t)received {
+ return false;
+}
+
+- (void) _setConfigurationData:(NSString *)data {
+ static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
+
+ _assert(conffile_r(data));
+
+ NSString *ofile = conffile_r[1];
+ //NSString *nfile = conffile_r[2];
+
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:CYLocalize("CONFIGURATION_UPGRADE")
+ buttons:[NSArray arrayWithObjects:
+ CYLocalize("KEEP_OLD_COPY"),
+ CYLocalize("ACCEPT_NEW_COPY"),
+ // XXX: CYLocalize("SEE_WHAT_CHANGED"),
+ nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"conffile"
+ ] autorelease];
+
+ [sheet setBodyText:[NSString stringWithFormat:@"%@\n\n%@", CYLocalize("CONFIGURATION_UPGRADE_EX"), ofile]];
+ [sheet popupAlertAnimated:YES];
+}
+
+- (void) _setProgressTitle:(NSString *)title {
+ NSMutableArray *words([[title componentsSeparatedByString:@" "] mutableCopy]);
+ for (size_t i(0), e([words count]); i != e; ++i) {
+ NSString *word([words objectAtIndex:i]);
+ if (Package *package = [database_ packageWithName:word])
+ [words replaceObjectAtIndex:i withObject:[package name]];
+ }
+
+ [status_ setText:[words componentsJoinedByString:@" "]];
+}
+
+- (void) _setProgressPercent:(NSNumber *)percent {
+ [progress_ setProgress:[percent floatValue]];
+}
+
+- (void) _addProgressOutput:(NSString *)output {
+ [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]];
+ CGSize size = [output_ contentSize];
+ CGRect rect = {{0, size.height}, {size.width, 0}};
+ [output_ scrollRectToVisible:rect animated:YES];
+}
+
+- (BOOL) isRunning {
+ return running_;
+}
+
+@end
+/* }}} */
+
+/* Package Cell {{{ */
+@interface PackageCell : UITableCell {
+ UIImage *icon_;
+ NSString *name_;
+ NSString *description_;
+ bool commercial_;
+ NSString *source_;
+ UIImage *badge_;
+ bool cached_;
+ Package *package_;
+#ifdef USE_BADGES
+ UITextLabel *status_;
+#endif
+}
+
+- (PackageCell *) init;
+- (void) setPackage:(Package *)package;
+
++ (int) heightForPackage:(Package *)package;
+
+@end
+
+@implementation PackageCell
+
+- (void) clearPackage {
+ if (icon_ != nil) {
+ [icon_ release];
+ icon_ = nil;
+ }
+
+ if (name_ != nil) {
+ [name_ release];
+ name_ = nil;
+ }
+
+ if (description_ != nil) {
+ [description_ release];
+ description_ = nil;
+ }
+
+ if (source_ != nil) {
+ [source_ release];
+ source_ = nil;
+ }
+
+ if (badge_ != nil) {
+ [badge_ release];
+ badge_ = nil;
+ }
+
+ [package_ release];
+ package_ = nil;
+}
+
+- (void) dealloc {
+ [self clearPackage];
+#ifdef USE_BADGES
+ [status_ release];
+#endif
+ [super dealloc];
+}
+
+- (PackageCell *) init {
+ if ((self = [super init]) != nil) {
+#ifdef USE_BADGES
+ status_ = [[UITextLabel alloc] initWithFrame:CGRectMake(48, 68, 280, 20)];
+ [status_ setBackgroundColor:[UIColor clearColor]];
+ [status_ setFont:small];
+#endif
+ } return self;
+}
+
+- (void) setPackage:(Package *)package {
+ [self clearPackage];
+ [package parse];
+
+ Source *source = [package source];
+
+ icon_ = [[package icon] retain];
+ name_ = [[package name] retain];
+ description_ = [[package shortDescription] retain];
+ commercial_ = [package isCommercial];
+
+ package_ = [package retain];
+
+ NSString *label = nil;
+ bool trusted = false;
+
+ if (source != nil) {
+ label = [source label];
+ trusted = [source trusted];
+ } else if ([[package id] isEqualToString:@"firmware"])
+ label = CYLocalize("APPLE");
+ else
+ label = [NSString stringWithFormat:CYLocalize("SLASH_DELIMITED"), CYLocalize("UNKNOWN"), CYLocalize("LOCAL")];
+
+ NSString *from(label);
+
+ NSString *section = [package simpleSection];
+ if (section != nil && ![section isEqualToString:label]) {
+ section = [[NSBundle mainBundle] localizedStringForKey:section value:nil table:@"Sections"];
+ from = [NSString stringWithFormat:CYLocalize("PARENTHETICAL"), from, section];
+ }
+
+ from = [NSString stringWithFormat:CYLocalize("FROM"), from];
+ source_ = [from retain];
+
+ if (NSString *purpose = [package primaryPurpose])
+ if ((badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]) != nil)
+ badge_ = [badge_ retain];
+
+#ifdef USE_BADGES
+ if (NSString *mode = [package mode]) {
+ [badge_ setImage:[UIImage applicationImageNamed:
+ [mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"] ? @"removing.png" : @"installing.png"
+ ]];
+
+ [status_ setText:[NSString stringWithFormat:CYLocalize("QUEUED_FOR"), CYLocalize(mode)]];
+ [status_ setColor:[UIColor colorWithCGColor:Blueish_]];
+ } else if ([package half]) {
+ [badge_ setImage:[UIImage applicationImageNamed:@"damaged.png"]];
+ [status_ setText:CYLocalize("PACKAGE_DAMAGED")];
+ [status_ setColor:[UIColor redColor]];
+ } else {
+ [badge_ setImage:nil];
+ [status_ setText:nil];
+ }
+#endif
+
+ cached_ = false;
+}
+
+- (void) drawRect:(CGRect)rect {
+ if (!cached_) {
+ UIColor *color;
+
+ if (NSString *mode = [package_ mode]) {
+ bool remove([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]);
+ color = remove ? RemovingColor_ : InstallingColor_;
+ } else
+ color = [UIColor whiteColor];
+
+ [self setBackgroundColor:color];
+ cached_ = true;
+ }
+
+ [super drawRect:rect];
+}
+
+- (void) drawBackgroundInRect:(CGRect)rect withFade:(float)fade {
+ if (fade == 0) {
+ CGContextRef context(UIGraphicsGetCurrentContext());
+ [[self backgroundColor] set];
+ CGRect back(rect);
+ back.size.height -= 1;
+ CGContextFillRect(context, back);
+ }
+
+ [super drawBackgroundInRect:rect withFade:fade];
+}
+
+- (void) drawContentInRect:(CGRect)rect selected:(BOOL)selected {
+ if (icon_ != nil) {
+ CGRect rect;
+ rect.size = [icon_ size];
+
+ rect.size.width /= 2;
+ rect.size.height /= 2;
+
+ rect.origin.x = 25 - rect.size.width / 2;
+ rect.origin.y = 25 - rect.size.height / 2;
+
+ [icon_ drawInRect:rect];
+ }
+
+ if (badge_ != nil) {
+ CGSize size = [badge_ size];
+
+ [badge_ drawAtPoint:CGPointMake(
+ 36 - size.width / 2,
+ 36 - size.height / 2
+ )];
+ }
+
+ if (selected)
+ UISetColor(White_);
+
+ if (!selected)
+ UISetColor(commercial_ ? Purple_ : Black_);
+ [name_ drawAtPoint:CGPointMake(48, 8) forWidth:240 withFont:Font18Bold_ ellipsis:2];
+ [source_ drawAtPoint:CGPointMake(58, 29) forWidth:225 withFont:Font12_ ellipsis:2];
+
+ if (!selected)
+ UISetColor(commercial_ ? Purplish_ : Gray_);
+ [description_ drawAtPoint:CGPointMake(12, 46) forWidth:280 withFont:Font14_ ellipsis:2];
+
+ [super drawContentInRect:rect selected:selected];
+}
+
+- (void) setSelected:(BOOL)selected withFade:(BOOL)fade {
+ cached_ = false;
+ [super setSelected:selected withFade:fade];
+}
+
++ (int) heightForPackage:(Package *)package {
+ return 73;
+}
+
+@end
+/* }}} */
+/* Section Cell {{{ */
+@interface SectionCell : UISimpleTableCell {
+ NSString *section_;
+ NSString *name_;
+ NSString *count_;
+ UIImage *icon_;
+ _UISwitchSlider *switch_;
+ BOOL editing_;
+}
+
+- (id) init;
+- (void) setSection:(Section *)section editing:(BOOL)editing;
+
+@end
+
+@implementation SectionCell
+
+- (void) clearSection {
+ if (section_ != nil) {
+ [section_ release];
+ section_ = nil;
+ }
+
+ if (name_ != nil) {
+ [name_ release];
+ name_ = nil;
+ }
+
+ if (count_ != nil) {
+ [count_ release];
+ count_ = nil;
+ }
+}
+
+- (void) dealloc {
+ [self clearSection];
+ [icon_ release];
+ [switch_ release];
+ [super dealloc];
+}
+
+- (id) init {
+ if ((self = [super init]) != nil) {
+ icon_ = [[UIImage applicationImageNamed:@"folder.png"] retain];
+
+ switch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(218, 9, 60, 25)];
+ [switch_ addTarget:self action:@selector(onSwitch:) forEvents:kUIControlEventMouseUpInside];
+ } return self;
+}
+
+- (void) onSwitch:(id)sender {
+ NSMutableDictionary *metadata = [Sections_ objectForKey:section_];
+ if (metadata == nil) {
+ metadata = [NSMutableDictionary dictionaryWithCapacity:2];
+ [Sections_ setObject:metadata forKey:section_];
+ }
+
+ Changed_ = true;
+ [metadata setObject:[NSNumber numberWithBool:([switch_ value] == 0)] forKey:@"Hidden"];
+}
+
+- (void) setSection:(Section *)section editing:(BOOL)editing {
+ if (editing != editing_) {
+ if (editing_)
+ [switch_ removeFromSuperview];
+ else
+ [self addSubview:switch_];
+ editing_ = editing;
+ }
+
+ [self clearSection];
+
+ if (section == nil) {
+ name_ = [CYLocalize("ALL_PACKAGES") retain];
+ count_ = nil;
+ } else {
+ section_ = [section localized];
+ if (section_ != nil)
+ section_ = [section_ retain];
+ name_ = [(section_ == nil ? CYLocalize("NO_SECTION") : section_) retain];
+ count_ = [[NSString stringWithFormat:@"%d", [section count]] retain];
+
+ if (editing_)
+ [switch_ setValue:(isSectionVisible(section_) ? 1 : 0) animated:NO];
+ }
+}
+
+- (void) drawContentInRect:(CGRect)rect selected:(BOOL)selected {
+ [icon_ drawInRect:CGRectMake(8, 7, 32, 32)];
+
+ if (selected)
+ UISetColor(White_);
+
+ if (!selected)
+ UISetColor(Black_);
+ [name_ drawAtPoint:CGPointMake(48, 9) forWidth:(editing_ ? 164 : 250) withFont:Font22Bold_ ellipsis:2];
+
+ CGSize size = [count_ sizeWithFont:Font14_];
+
+ UISetColor(White_);
+ if (count_ != nil)
+ [count_ drawAtPoint:CGPointMake(13 + (29 - size.width) / 2, 16) withFont:Font12Bold_];
+
+ [super drawContentInRect:rect selected:selected];
+}
+
+@end
+/* }}} */
+
+/* File Table {{{ */
+@interface FileTable : RVPage {
+ _transient Database *database_;
+ Package *package_;
+ NSString *name_;
+ NSMutableArray *files_;
+ UITable *list_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+- (void) setPackage:(Package *)package;
+
+@end
+
+@implementation FileTable
+
+- (void) dealloc {
+ if (package_ != nil)
+ [package_ release];
+ if (name_ != nil)
+ [name_ release];
+ [files_ release];
+ [list_ release];
+ [super dealloc];
+}
+
+- (int) numberOfRowsInTable:(UITable *)table {
+ return files_ == nil ? 0 : [files_ count];
+}
+
+- (float) table:(UITable *)table heightForRow:(int)row {
+ return 24;
+}
+
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
+ if (reusing == nil) {
+ reusing = [[[UIImageAndTextTableCell alloc] init] autorelease];
+ UIFont *font = [UIFont systemFontOfSize:16];
+ [[(UIImageAndTextTableCell *)reusing titleTextLabel] setFont:font];
+ }
+ [(UIImageAndTextTableCell *)reusing setTitle:[files_ objectAtIndex:row]];
+ return reusing;
+}
+
+- (BOOL) table:(UITable *)table canSelectRow:(int)row {
+ return NO;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+
+ files_ = [[NSMutableArray arrayWithCapacity:32] retain];
+
+ list_ = [[UITable alloc] initWithFrame:[self bounds]];
+ [self addSubview:list_];
+
+ UITableColumn *column = [[[UITableColumn alloc]
+ initWithTitle:CYLocalize("NAME")
+ identifier:@"name"
+ width:[self frame].size.width
+ ] autorelease];
+
+ [list_ setDataSource:self];
+ [list_ setSeparatorStyle:1];
+ [list_ addTableColumn:column];
+ [list_ setDelegate:self];
+ [list_ setReusesTableCells:YES];
+ } return self;
+}
+
+- (void) setPackage:(Package *)package {
+ if (package_ != nil) {
+ [package_ autorelease];
+ package_ = nil;
+ }
+
+ if (name_ != nil) {
+ [name_ release];
+ name_ = nil;
+ }
+
+ [files_ removeAllObjects];
+
+ if (package != nil) {
+ package_ = [package retain];
+ name_ = [[package id] retain];
+
+ if (NSArray *files = [package files])
+ [files_ addObjectsFromArray:files];
+
+ if ([files_ count] != 0) {
+ if ([[files_ objectAtIndex:0] isEqualToString:@"/."])
+ [files_ removeObjectAtIndex:0];
+ [files_ sortUsingSelector:@selector(compareByPath:)];
+
+ NSMutableArray *stack = [NSMutableArray arrayWithCapacity:8];
+ [stack addObject:@"/"];
+
+ for (int i(0), e([files_ count]); i != e; ++i) {
+ NSString *file = [files_ objectAtIndex:i];
+ while (![file hasPrefix:[stack lastObject]])
+ [stack removeLastObject];
+ NSString *directory = [stack lastObject];
+ [stack addObject:[file stringByAppendingString:@"/"]];
+ [files_ replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"%*s%@",
+ ([stack count] - 2) * 3, "",
+ [file substringFromIndex:[directory length]]
+ ]];
+ }
+ }
+ }
+
+ [list_ reloadData];
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [list_ resetViewAnimated:animated];
+}
+
+- (void) reloadData {
+ [self setPackage:[database_ packageWithName:name_]];
+ [self reloadButtons];
+}
+
+- (NSString *) title {
+ return CYLocalize("INSTALLED_FILES");
+}
+
+- (NSString *) backButtonTitle {
+ return CYLocalize("FILES");
+}
+
+@end
+/* }}} */
+/* Package View {{{ */
+@interface PackageView : BrowserView {
+ _transient Database *database_;
+ Package *package_;
+ NSString *name_;
+ bool commercial_;
+ NSMutableArray *buttons_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+- (void) setPackage:(Package *)package;
+
+@end
+
+@implementation PackageView
+
+- (void) dealloc {
+ if (package_ != nil)
+ [package_ release];
+ if (name_ != nil)
+ [name_ release];
+ [buttons_ release];
+ [super dealloc];
+}
+
+- (void) release {
+ if ([self retainCount] == 1)
+ [delegate_ setPackageView:self];
+ [super release];
+}
+
+/* XXX: this is not safe at all... localization of /fail/ */
+- (void) _clickButtonWithName:(NSString *)name {
+ if ([name isEqualToString:CYLocalize("CLEAR")])
+ [delegate_ clearPackage:package_];
+ else if ([name isEqualToString:CYLocalize("INSTALL")])
+ [delegate_ installPackage:package_];
+ else if ([name isEqualToString:CYLocalize("REINSTALL")])
+ [delegate_ installPackage:package_];
+ else if ([name isEqualToString:CYLocalize("REMOVE")])
+ [delegate_ removePackage:package_];
+ else if ([name isEqualToString:CYLocalize("UPGRADE")])
+ [delegate_ installPackage:package_];
+ else _assert(false);
+}
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ NSString *context([sheet context]);
+
+ if ([context isEqualToString:@"modify"]) {
+ int count = [buttons_ count];
+ _assert(count != 0);
+ _assert(button <= count + 1);
+
+ if (count != button - 1)
+ [self _clickButtonWithName:[buttons_ objectAtIndex:(button - 1)]];
+
+ [sheet dismiss];
+ } else
+ [super alertSheet:sheet buttonClicked:button];
+}
+
+- (void) webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
+ return [super webView:sender didFinishLoadForFrame:frame];
+}
+
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+ [super webView:sender didClearWindowObject:window forFrame:frame];
+ [window setValue:package_ forKey:@"package"];
+}
+
+- (bool) _allowJavaScriptPanel {
+ return commercial_;
+}
+
+#if !AlwaysReload
+- (void) __rightButtonClicked {
+ int count = [buttons_ count];
+ _assert(count != 0);
+
+ if (count == 1)
+ [self _clickButtonWithName:[buttons_ objectAtIndex:0]];
+ else {
+ NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:(count + 1)];
+ [buttons addObjectsFromArray:buttons_];
+ [buttons addObject:CYLocalize("CANCEL")];
+
+ [delegate_ slideUp:[[[UIActionSheet alloc]
+ initWithTitle:nil
+ buttons:buttons
+ defaultButtonIndex:([buttons count] - 1)
+ delegate:self
+ context:@"modify"
+ ] autorelease]];
+ }
+}
+
+- (void) _rightButtonClicked {
+ if (commercial_)
+ [super _rightButtonClicked];
+ else
+ [self __rightButtonClicked];
+}
+#endif
+
+- (id) _rightButtonTitle {
+ int count = [buttons_ count];
+ return count == 0 ? nil : count != 1 ? CYLocalize("MODIFY") : [buttons_ objectAtIndex:0];
+}
+
+- (NSString *) backButtonTitle {
+ return @"Details";
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+ buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
+ [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"package" ofType:@"html"]]];
+ } return self;
+}
+
+- (void) setPackage:(Package *)package {
+ if (package_ != nil) {
+ [package_ autorelease];
+ package_ = nil;
+ }
+
+ if (name_ != nil) {
+ [name_ release];
+ name_ = nil;
+ }
+
+ [buttons_ removeAllObjects];
+
+ if (package != nil) {
+ [package parse];
+
+ package_ = [package retain];
+ name_ = [[package id] retain];
+ commercial_ = [package isCommercial];
+
+ if ([package_ mode] != nil)
+ [buttons_ addObject:CYLocalize("CLEAR")];
+ if ([package_ source] == nil);
+ else if ([package_ upgradableAndEssential:NO])
+ [buttons_ addObject:CYLocalize("UPGRADE")];
+ else if ([package_ installed] == nil)
+ [buttons_ addObject:CYLocalize("INSTALL")];
+ else
+ [buttons_ addObject:CYLocalize("REINSTALL")];
+ if ([package_ installed] != nil)
+ [buttons_ addObject:CYLocalize("REMOVE")];
+
+ if (special_ != NULL) {
+ CGRect frame([webview_ frame]);
+ frame.size.width = 320;
+ frame.size.height = 0;
+ [webview_ setFrame:frame];
+
+ [scroller_ scrollPointVisibleAtTopLeft:CGPointZero];
+
+ WebThreadLock();
+ [[[webview_ webView] windowScriptObject] setValue:package_ forKey:@"package"];
+
+ [self setButtonTitle:nil withStyle:nil toFunction:nil];
+
+ [self setFinishHook:nil];
+ [self setPopupHook:nil];
+ WebThreadUnlock();
+
+ //[self yieldToSelector:@selector(callFunction:) withObject:special_];
+ [super callFunction:special_];
+ }
+ }
+
+ [self reloadButtons];
+}
+
+- (bool) isLoading {
+ return commercial_ ? [super isLoading] : false;
+}
+
+- (void) reloadData {
+ [self setPackage:[database_ packageWithName:name_]];
+}
+
+@end
+/* }}} */
+/* Package Table {{{ */
+@interface PackageTable : RVPage {
+ _transient Database *database_;
+ NSString *title_;
+ NSMutableArray *packages_;
+ NSMutableArray *sections_;
+ UISectionList *list_;
+}