From: Grant Paul Date: Sun, 15 Jan 2012 06:59:39 +0000 (-0800) Subject: Begin work to restructure confirmation controller into a separate tab. X-Git-Url: https://git.saurik.com/cydia.git/commitdiff_plain/refs/heads/new-search-simpler-sections-new-progress?ds=sidebyside Begin work to restructure confirmation controller into a separate tab. --- diff --git a/MobileCydia.mm b/MobileCydia.mm index dee717bd..1eb18808 100644 --- a/MobileCydia.mm +++ b/MobileCydia.mm @@ -67,6 +67,8 @@ #include #include +#include +#include #include @@ -687,8 +689,6 @@ static NSArray *Finishes_; #define SpringBoard_ "/System/Library/LaunchDaemons/com.apple.SpringBoard.plist" #define NotifyConfig_ "/etc/notify.conf" -static bool Queuing_; - static CYColor Blue_; static CYColor Blueish_; static CYColor Black_; @@ -4765,2514 +4765,2378 @@ static _H Diversions_; @end // }}} -/* Confirmation Controller {{{ */ -bool DepSubstrate(const pkgCache::VerIterator &iterator) { - if (!iterator.end()) - for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) { - if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends) - continue; - pkgCache::PkgIterator package(dep.TargetPkg()); - if (package.end()) - continue; - if (strcmp(package.Name(), "mobilesubstrate") == 0) - return true; - } - - return false; +/* Package Cell {{{ */ +@interface PackageCell : CyteTableViewCell < + CyteTableViewCellDelegate +> { + _H icon_; + _H name_; + _H description_; + bool commercial_; + _H source_; + _H badge_; + _H placard_; } -@protocol ConfirmationControllerDelegate -- (void) cancelAndClear:(bool)clear; -- (void) confirmWithNavigationController:(UINavigationController *)navigation; -- (void) queue; -@end - -@interface ConfirmationController : CydiaWebViewController { - _transient Database *database_; - - _H essential_; - - _H changes_; - _H issues_; - _H sizes_; - - BOOL substrate_; -} +- (PackageCell *) init; +- (void) setPackage:(Package *)package; -- (id) initWithDatabase:(Database *)database; +- (void) drawContentRect:(CGRect)rect; @end -@implementation ConfirmationController - -- (void) complete { - if (substrate_) - RestartSubstrate_ = true; - [delegate_ confirmWithNavigationController:[self navigationController]]; -} +@implementation PackageCell -- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { - NSString *context([alert context]); +- (PackageCell *) init { + CGRect frame(CGRectMake(0, 0, 320, 74)); + if ((self = [super initWithFrame:frame reuseIdentifier:@"Package"]) != nil) { + UIView *content([self contentView]); + CGRect bounds([content bounds]); - if ([context isEqualToString:@"remove"]) { - if (button == [alert cancelButtonIndex]) - [self dismissModalViewControllerAnimated:YES]; - else if (button == [alert firstOtherButtonIndex]) { - [self performSelector:@selector(complete) withObject:nil afterDelay:0]; - } + content_ = [[[CyteTableViewCellContentView alloc] initWithFrame:bounds] autorelease]; + [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [content addSubview:content_]; - [alert dismissWithClickedButtonIndex:-1 animated:YES]; - } else if ([context isEqualToString:@"unable"]) { - [self dismissModalViewControllerAnimated:YES]; - [alert dismissWithClickedButtonIndex:-1 animated:YES]; - } else { - [super alertView:alert clickedButtonAtIndex:button]; - } + [content_ setDelegate:self]; + [content_ setOpaque:YES]; + } return self; } -- (void) _doContinue { - [delegate_ cancelAndClear:NO]; - [self dismissModalViewControllerAnimated:YES]; +- (NSString *) accessibilityLabel { + return [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), (id) name_, (id) description_]; } -- (id) invokeDefaultMethodWithArguments:(NSArray *)args { - [self performSelectorOnMainThread:@selector(_doContinue) withObject:nil waitUntilDone:NO]; - return nil; -} +- (void) setPackage:(Package *)package { + icon_ = nil; + name_ = nil; + description_ = nil; + source_ = nil; + badge_ = nil; + placard_ = nil; -- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { - [super webView:view didClearWindowObject:window forFrame:frame]; + if (package == nil) + [content_ setBackgroundColor:[UIColor whiteColor]]; + else { + [package parse]; - [window setValue:[[NSDictionary dictionaryWithObjectsAndKeys: - (id) changes_, @"changes", - (id) issues_, @"issues", - (id) sizes_, @"sizes", - self, @"queue", - nil] Cydia$webScriptObjectInContext:window] forKey:@"cydiaConfirm"]; -} + Source *source = [package source]; -- (id) initWithDatabase:(Database *)database { - if ((self = [super init]) != nil) { - database_ = database; + icon_ = [package icon]; - NSMutableArray *installs([NSMutableArray arrayWithCapacity:16]); - NSMutableArray *reinstalls([NSMutableArray arrayWithCapacity:16]); - NSMutableArray *upgrades([NSMutableArray arrayWithCapacity:16]); - NSMutableArray *downgrades([NSMutableArray arrayWithCapacity:16]); - NSMutableArray *removes([NSMutableArray arrayWithCapacity:16]); + if (NSString *name = [package name]) + name_ = [NSString stringWithString:name]; - bool remove(false); + NSString *description(nil); - pkgCacheFile &cache([database_ cache]); - NSArray *packages([database_ packages]); - pkgDepCache::Policy *policy([database_ policy]); + if (description == nil && IsWildcat_) + description = [package longDescription]; + if (description == nil) + description = [package shortDescription]; - issues_ = [NSMutableArray arrayWithCapacity:4]; + if (description != nil) + description_ = [NSString stringWithString:description]; - for (Package *package in packages) { - pkgCache::PkgIterator iterator([package iterator]); - NSString *name([package id]); + commercial_ = [package isCommercial]; - if ([package broken]) { - NSMutableArray *reasons([NSMutableArray arrayWithCapacity:4]); + NSString *label = nil; + bool trusted = false; - [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys: - name, @"package", - reasons, @"reasons", - nil]]; + if (source != nil) { + label = [source label]; + trusted = [source trusted]; + } else if ([[package id] isEqualToString:@"firmware"]) + label = UCLocalize("APPLE"); + else + label = [NSString stringWithFormat:UCLocalize("SLASH_DELIMITED"), UCLocalize("UNKNOWN"), UCLocalize("LOCAL")]; - pkgCache::VerIterator ver(cache[iterator].InstVerIter(cache)); - if (ver.end()) - continue; + NSString *from(label); - for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) { - pkgCache::DepIterator start; - pkgCache::DepIterator end; - dep.GlobOr(start, end); // ++dep + NSString *section = [package simpleSection]; + if (section != nil && ![section isEqualToString:label]) { + section = [[NSBundle mainBundle] localizedStringForKey:section value:nil table:@"Sections"]; + from = [NSString stringWithFormat:UCLocalize("PARENTHETICAL"), from, section]; + } - if (!cache->IsImportantDep(end)) - continue; - if ((cache[end] & pkgDepCache::DepGInstall) != 0) - continue; + source_ = [NSString stringWithFormat:UCLocalize("FROM"), from]; - NSMutableArray *clauses([NSMutableArray arrayWithCapacity:4]); + if (NSString *purpose = [package primaryPurpose]) + badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]; - [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithUTF8String:start.DepType()], @"relationship", - clauses, @"clauses", - nil]]; + UIColor *color; + NSString *placard; - _forever { - NSString *reason, *installed((NSString *) [WebUndefined undefined]); - - pkgCache::PkgIterator target(start.TargetPkg()); - if (target->ProvidesList != 0) - reason = @"missing"; - else { - pkgCache::VerIterator ver(cache[target].InstVerIter(cache)); - if (!ver.end()) { - reason = @"installed"; - installed = [NSString stringWithUTF8String:ver.VerStr()]; - } else if (!cache[target].CandidateVerIter(cache).end()) - reason = @"uninstalled"; - else if (target->ProvidesList == 0) - reason = @"uninstallable"; - else - reason = @"virtual"; - } - - NSDictionary *version(start.TargetVer() == 0 ? [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithUTF8String:start.CompType()], @"operator", - [NSString stringWithUTF8String:start.TargetVer()], @"value", - nil]); - - [clauses addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithUTF8String:start.TargetPkg().Name()], @"package", - version, @"version", - reason, @"reason", - installed, @"installed", - nil]]; - - // yes, seriously. (wtf?) - if (start == end) - break; - ++start; - } - } + if (NSString *mode = [package mode]) { + if ([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]) { + color = RemovingColor_; + //placard = @"removing"; + } else { + color = InstallingColor_; + //placard = @"installing"; } - pkgDepCache::StateCache &state(cache[iterator]); - - static Pcre special_r("^(firmware$|gsc\\.|cy\\+)"); - - if (state.NewInstall()) - [installs addObject:name]; - // XXX: else if (state.Install()) - else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall) - [reinstalls addObject:name]; - // XXX: move before previous if - else if (state.Upgrade()) - [upgrades addObject:name]; - else if (state.Downgrade()) - [downgrades addObject:name]; - else if (!state.Delete()) - // XXX: _assert(state.Keep()); - continue; - else if (special_r(name)) - [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNull null], @"package", - [NSArray arrayWithObjects: - [NSDictionary dictionaryWithObjectsAndKeys: - @"Conflicts", @"relationship", - [NSArray arrayWithObjects: - [NSDictionary dictionaryWithObjectsAndKeys: - name, @"package", - [NSNull null], @"version", - @"installed", @"reason", - nil], - nil], @"clauses", - nil], - nil], @"reasons", - nil]]; - else { - if ([package essential]) - remove = true; - [removes addObject:name]; - } + // XXX: the removing/installing placards are not @2x + placard = nil; + } else { + color = [UIColor whiteColor]; - substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator)); - substrate_ |= DepSubstrate(iterator.CurrentVer()); + if ([package installed] != nil) + placard = @"installed"; + else + placard = nil; } - if (!remove) - essential_ = nil; - else if (Advanced_) { - NSString *parenthetical(UCLocalize("PARENTHETICAL")); + [content_ setBackgroundColor:color]; - essential_ = [[[UIAlertView alloc] - initWithTitle:UCLocalize("REMOVING_ESSENTIALS") - message:UCLocalize("REMOVING_ESSENTIALS_EX") - delegate:self - cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")] - otherButtonTitles: - [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")], - nil - ] autorelease]; + if (placard != nil) + placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/%@.png", App_, placard]]; + } - [essential_ setContext:@"remove"]; - [essential_ setNumberOfRows:2]; - } else { - essential_ = [[[UIAlertView alloc] - initWithTitle:UCLocalize("UNABLE_TO_COMPLY") - message:UCLocalize("UNABLE_TO_COMPLY_EX") - delegate:self - cancelButtonTitle:UCLocalize("OKAY") - otherButtonTitles:nil - ] autorelease]; + [self setNeedsDisplay]; + [content_ setNeedsDisplay]; +} + +- (void) drawContentRect:(CGRect)rect { + bool highlighted(highlighted_); + float width([self bounds].size.width); - [essential_ setContext:@"unable"]; + if (icon_ != nil) { + CGRect rect; + rect.size = [(UIImage *) icon_ size]; + + while (rect.size.width > 32 || rect.size.height > 32) { + rect.size.width /= 2; + rect.size.height /= 2; } - changes_ = [NSDictionary dictionaryWithObjectsAndKeys: - installs, @"installs", - reinstalls, @"reinstalls", - upgrades, @"upgrades", - downgrades, @"downgrades", - removes, @"removes", - nil]; + rect.origin.x = 25 - rect.size.width / 2; + rect.origin.y = 25 - rect.size.height / 2; - sizes_ = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:[database_ fetcher].FetchNeeded()], @"downloading", - [NSNumber numberWithInteger:[database_ fetcher].PartialPresent()], @"resuming", - nil]; + [icon_ drawInRect:rect]; + } - [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/confirm/", UI_]]]; - } return self; -} + if (badge_ != nil) { + CGRect rect; + rect.size = [(UIImage *) badge_ size]; -- (UIBarButtonItem *) leftButton { - return [[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("CANCEL") - style:UIBarButtonItemStylePlain - target:self - action:@selector(cancelButtonClicked) - ] autorelease]; -} + rect.size.width /= 2; + rect.size.height /= 2; -#if !AlwaysReload -- (void) applyRightButton { - if ([issues_ count] == 0 && ![self isLoading]) - [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("CONFIRM") - style:UIBarButtonItemStyleDone - target:self - action:@selector(confirmButtonClicked) - ] autorelease]]; - else - [[self navigationItem] setRightBarButtonItem:nil]; -} -#endif + rect.origin.x = 36 - rect.size.width / 2; + rect.origin.y = 36 - rect.size.height / 2; -- (void) cancelButtonClicked { - [delegate_ cancelAndClear:YES]; - [self dismissModalViewControllerAnimated:YES]; -} + [badge_ drawInRect:rect]; + } -#if !AlwaysReload -- (void) confirmButtonClicked { - if (essential_ != nil) - [essential_ show]; - else - [self complete]; + if (highlighted) + UISetColor(White_); + + if (!highlighted) + UISetColor(commercial_ ? Purple_ : Black_); + [name_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - (placard_ == nil ? 80 : 106)) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation]; + [source_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation]; + + if (!highlighted) + UISetColor(commercial_ ? Purplish_ : Gray_); + [description_ drawAtPoint:CGPointMake(12, 46) forWidth:(width - 46) withFont:Font14_ lineBreakMode:UILineBreakModeTailTruncation]; + + if (placard_ != nil) + [placard_ drawAtPoint:CGPointMake(width - 52, 9)]; } -#endif @end /* }}} */ +/* Section Cell {{{ */ +@interface SectionCell : CyteTableViewCell < + CyteTableViewCellDelegate +> { + _H basic_; + _H section_; + _H name_; + _H count_; + _H icon_; + _H switch_; + BOOL editing_; +} -/* Progress Data {{{ */ -@interface CydiaProgressData : NSObject { - _transient id delegate_; +- (void) setSection:(Section *)section editing:(BOOL)editing; - bool running_; - float percent_; +@end - float current_; - float total_; - float speed_; +@implementation SectionCell - _H events_; - _H title_; - - _H status_; - _H finish_; -} - -@end - -@implementation CydiaProgressData - -+ (NSArray *) _attributeKeys { - return [NSArray arrayWithObjects: - @"current", - @"events", - @"finish", - @"percent", - @"running", - @"speed", - @"title", - @"total", - nil]; -} +- (id) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { + if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) { + icon_ = [UIImage applicationImageNamed:@"folder.png"]; + switch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(218, 9, 60, 25)] autorelease]; + [switch_ addTarget:self action:@selector(onSwitch:) forEvents:UIControlEventValueChanged]; -- (NSArray *) attributeKeys { - return [[self class] _attributeKeys]; -} + UIView *content([self contentView]); + CGRect bounds([content bounds]); -+ (BOOL) isKeyExcludedFromWebScript:(const char *)name { - return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name]; -} + content_ = [[[CyteTableViewCellContentView alloc] initWithFrame:bounds] autorelease]; + [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [content addSubview:content_]; + [content_ setBackgroundColor:[UIColor whiteColor]]; -- (id) init { - if ((self = [super init]) != nil) { - events_ = [NSMutableArray arrayWithCapacity:32]; + [content_ setDelegate:self]; } return self; } -- (void) setDelegate:(id)delegate { - delegate_ = delegate; -} +- (void) onSwitch:(id)sender { + NSMutableDictionary *metadata([Sections_ objectForKey:basic_]); + if (metadata == nil) { + metadata = [NSMutableDictionary dictionaryWithCapacity:2]; + [Sections_ setObject:metadata forKey:basic_]; + } -- (void) setPercent:(float)value { - percent_ = value; + [metadata setObject:[NSNumber numberWithBool:([switch_ isOn] == NO)] forKey:@"Hidden"]; + Changed_ = true; } -- (NSNumber *) percent { - return [NSNumber numberWithFloat:percent_]; -} +- (void) setSection:(Section *)section editing:(BOOL)editing { + if (editing != editing_) { + if (editing_) + [switch_ removeFromSuperview]; + else + [self addSubview:switch_]; + editing_ = editing; + } -- (void) setCurrent:(float)value { - current_ = value; -} + basic_ = nil; + section_ = nil; + name_ = nil; + count_ = nil; -- (NSNumber *) current { - return [NSNumber numberWithFloat:current_]; -} + if (section == nil) { + name_ = UCLocalize("ALL_PACKAGES"); + count_ = nil; + } else { + basic_ = [section name]; + section_ = [section localized]; -- (void) setTotal:(float)value { - total_ = value; -} + name_ = section_ == nil || [section_ length] == 0 ? UCLocalize("NO_SECTION") : (NSString *) section_; + count_ = [NSString stringWithFormat:@"%d", [section count]]; -- (NSNumber *) total { - return [NSNumber numberWithFloat:total_]; -} + if (editing_) + [switch_ setOn:(isSectionVisible(basic_) ? 1 : 0) animated:NO]; + } -- (void) setSpeed:(float)value { - speed_ = value; -} + [self setAccessoryType:editing ? UITableViewCellAccessoryNone : UITableViewCellAccessoryDisclosureIndicator]; + [self setSelectionStyle:editing ? UITableViewCellSelectionStyleNone : UITableViewCellSelectionStyleBlue]; -- (NSNumber *) speed { - return [NSNumber numberWithFloat:speed_]; + [content_ setNeedsDisplay]; } -- (NSArray *) events { - return events_; -} +- (void) setFrame:(CGRect)frame { + [super setFrame:frame]; -- (void) removeAllEvents { - [events_ removeAllObjects]; + CGRect rect([switch_ frame]); + [switch_ setFrame:CGRectMake(frame.size.width - 102, 9, rect.size.width, rect.size.height)]; } -- (void) addEvent:(CydiaProgressEvent *)event { - [events_ addObject:event]; +- (NSString *) accessibilityLabel { + return name_; } -- (void) setTitle:(NSString *)text { - title_ = text; -} +- (void) drawContentRect:(CGRect)rect { + bool highlighted(highlighted_ && !editing_); -- (NSString *) title { - return title_; -} + [icon_ drawInRect:CGRectMake(8, 7, 32, 32)]; -- (void) setFinish:(NSString *)text { - finish_ = text; -} + if (highlighted) + UISetColor(White_); -- (NSString *) finish { - return (id) finish_ ?: [NSNull null]; -} + float width(rect.size.width); + if (editing_) + width -= 87; -- (void) setRunning:(bool)running { - running_ = running; -} + if (!highlighted) + UISetColor(Black_); + [name_ drawAtPoint:CGPointMake(48, 9) forWidth:(width - 70) withFont:Font22Bold_ lineBreakMode:UILineBreakModeTailTruncation]; -- (NSNumber *) running { - return running_ ? (NSNumber *) kCFBooleanTrue : (NSNumber *) kCFBooleanFalse; + CGSize size = [count_ sizeWithFont:Font14_]; + + UISetColor(White_); + if (count_ != nil) + [count_ drawAtPoint:CGPointMake(13 + (29 - size.width) / 2, 16) withFont:Font12Bold_]; } @end /* }}} */ -/* Progress Controller {{{ */ -@interface ProgressController : CydiaWebViewController < - ProgressDelegate + +/* File Table {{{ */ +@interface FileTable : CyteViewController < + UITableViewDataSource, + UITableViewDelegate > { _transient Database *database_; - _H progress_; - unsigned cancel_; + _H package_; + _H name_; + _H files_; + _H list_; } -- (id) initWithDatabase:(Database *)database delegate:(id)delegate; - -- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title; - -- (void) setTitle:(NSString *)title; -- (void) setCancellable:(bool)cancellable; +- (id) initWithDatabase:(Database *)database; +- (void) setPackage:(Package *)package; @end -@implementation ProgressController +@implementation FileTable -- (void) dealloc { - [database_ setProgressDelegate:nil]; - [super dealloc]; +- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return files_ == nil ? 0 : [files_ count]; } -- (UIBarButtonItem *) leftButton { - return cancel_ == 1 ? [[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("CANCEL") - style:UIBarButtonItemStylePlain - target:self - action:@selector(cancel) - ] autorelease] : nil; +/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 24.0f; +}*/ + +- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *reuseIdentifier = @"Cell"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease]; + [cell setFont:[UIFont systemFontOfSize:16]]; + } + [cell setText:[files_ objectAtIndex:indexPath.row]]; + [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; + + return cell; } -- (void) updateCancel { - [super applyLeftButton]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/files", [package_ id]]]; } -- (id) initWithDatabase:(Database *)database delegate:(id)delegate { - if ((self = [super init]) != nil) { - database_ = database; - delegate_ = delegate; +- (void) loadView { + list_ = [[[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]; + [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [list_ setRowHeight:24.0f]; + [(UITableView *) list_ setDataSource:self]; + [list_ setDelegate:self]; + [self setView:list_]; +} - [database_ setProgressDelegate:self]; +- (void) viewDidLoad { + [super viewDidLoad]; - progress_ = [[[CydiaProgressData alloc] init] autorelease]; - [progress_ setDelegate:self]; + [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")]; +} - [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/progress/", UI_]]]; +- (void) releaseSubviews { + list_ = nil; - [scroller_ setBackgroundColor:[UIColor blackColor]]; + package_ = nil; + files_ = nil; - [[self navigationItem] setHidesBackButton:YES]; + [super releaseSubviews]; +} - [self updateCancel]; +- (id) initWithDatabase:(Database *)database { + if ((self = [super init]) != nil) { + database_ = database; } return self; } -- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { - [super webView:view didClearWindowObject:window forFrame:frame]; - [window setValue:progress_ forKey:@"cydiaProgress"]; -} +- (void) setPackage:(Package *)package { + package_ = nil; + name_ = nil; -- (void) updateProgress { - [self dispatchEvent:@"CydiaProgressUpdate"]; -} + files_ = [NSMutableArray arrayWithCapacity:32]; -- (void) viewWillAppear:(BOOL)animated { - [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack]; - [super viewWillAppear:animated]; -} + if (package != nil) { + package_ = package; + name_ = [package id]; -- (void) reloadSpringBoard { - pid_t pid(ExecFork()); - if (pid == 0) { - pid_t pid(ExecFork()); - if (pid == 0) { - execl("/usr/bin/sbreload", "sbreload", NULL); - perror("sbreload"); - exit(0); - } + if (NSArray *files = [package files]) + [files_ addObjectsFromArray:files]; - exit(0); + 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]] + ]]; + } + } } - ReapZombie(pid); + [list_ reloadData]; +} - sleep(15); - system("/usr/bin/killall SpringBoard"); +- (void) reloadData { + [super reloadData]; + + [self setPackage:[database_ packageWithName:name_]]; } -- (void) close { - UpdateExternalStatus(0); +@end +/* }}} */ +/* Package Controller {{{ */ +@interface CYPackageController : CydiaWebViewController < + UIActionSheetDelegate +> { + _transient Database *database_; + _H package_; + _H name_; + bool commercial_; + _H buttons_; + _H button_; +} - if (Finish_ > 1) - [delegate_ saveState]; +- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name withReferrer:(NSString *)referrer; - switch (Finish_) { - case 0: - [delegate_ returnToCydia]; - break; +@end - case 1: - [delegate_ terminateWithSuccess]; - /*if ([delegate_ respondsToSelector:@selector(suspendWithAnimation:)]) - [delegate_ suspendWithAnimation:YES]; - else - [delegate_ suspend];*/ - break; +@implementation CYPackageController - case 2: - _trace(); - goto reload; +- (NSURL *) navigationURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@", (id) name_]]; +} - case 3: - _trace(); - goto reload; +/* XXX: this is not safe at all... localization of /fail/ */ +- (void) _clickButtonWithName:(NSString *)name { + if ([name isEqualToString:UCLocalize("CLEAR")]) + [delegate_ clearPackage:package_]; + else if ([name isEqualToString:UCLocalize("INSTALL")]) + [delegate_ installPackage:package_]; + else if ([name isEqualToString:UCLocalize("REINSTALL")]) + [delegate_ installPackage:package_]; + else if ([name isEqualToString:UCLocalize("REMOVE")]) + [delegate_ removePackage:package_]; + else if ([name isEqualToString:UCLocalize("UPGRADE")]) + [delegate_ installPackage:package_]; + else _assert(false); - reload: { - UIProgressHUD *hud([delegate_ addProgressHUD]); - [hud setText:UCLocalize("LOADING")]; - [self performSelector:@selector(reloadSpringBoard) withObject:nil afterDelay:0.5]; - return; - } + UITabBar *tabBar = [[self tabBarController] tabBar]; + UITabBarItem *lastItem = [[tabBar items] lastObject]; + CGPoint endPoint = [[[lastItem view] superview] convertPoint:[[lastItem view] frame].origin toView:nil]; + endPoint = CGPointMake(endPoint.x + [[lastItem view] frame].size.width / 2, endPoint.y + [[lastItem view] frame].size.height / 2); - case 4: - _trace(); - if (void (*SBReboot)(mach_port_t) = reinterpret_cast(dlsym(RTLD_DEFAULT, "SBReboot"))) - SBReboot(SBSSpringBoardServerPort()); - else - reboot2(RB_AUTOBOOT); - break; - } + UIBarButtonItem *rightItem = [[self navigationItem] rightBarButtonItem]; + CGPoint startPoint = [[[rightItem view] superview] convertPoint:[[rightItem view] frame].origin toView:nil]; + startPoint = CGPointMake(startPoint.x + [[rightItem view] frame].size.width / 2, startPoint.y + [[rightItem view] frame].size.height / 2); - [super close]; -} + NSLog(@"animating from %@ to %@", NSStringFromCGPoint(startPoint), NSStringFromCGPoint(endPoint)); -- (void) setTitle:(NSString *)title { - [progress_ setTitle:title]; - [self updateProgress]; -} + // Determine the animation's path. + CGPoint curvePoint1 = CGPointMake(startPoint.x - 130, startPoint.y - 10); + CGPoint curvePoint2 = CGPointMake(startPoint.x - 140, endPoint.y - 40); -- (UIBarButtonItem *) rightButton { - return [[progress_ running] boolValue] ? [super rightButton] : [[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("CLOSE") - style:UIBarButtonItemStylePlain - target:self - action:@selector(close) - ] autorelease]; + // Create the animation's path. + CGPathRef path = NULL; + CGMutablePathRef mutablepath = CGPathCreateMutable(); + CGPathMoveToPoint(mutablepath, NULL, startPoint.x, startPoint.y); + + CGPathAddCurveToPoint(mutablepath, NULL, + curvePoint1.x, curvePoint1.y, + curvePoint2.x, curvePoint2.y, + endPoint.x, endPoint.y); + + path = CGPathCreateCopy(mutablepath); + CGPathRelease(mutablepath); + + UIImageView* animatedLabel = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"packages.png"]]; + animatedLabel.tag = 12345; + [[[self view] window] addSubview:animatedLabel]; + [animatedLabel release]; + CALayer *iconViewLayer = animatedLabel.layer; + + CAKeyframeAnimation *animatedIconAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; + animatedIconAnimation.removedOnCompletion = YES; + animatedIconAnimation.duration = 0.5; + animatedIconAnimation.delegate = self; + animatedIconAnimation.path = path; + animatedIconAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + [iconViewLayer addAnimation:animatedIconAnimation forKey:@"animateIcon"]; + + // Start the icon animation. + [iconViewLayer setPosition:CGPointMake(endPoint.x, endPoint.y)]; + + [UIView beginAnimations:nil context:iconViewLayer]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(flyAnimationCompleted:finished:context:)]; + [UIView setAnimationCurve:UIViewAnimationCurveEaseIn]; + [UIView setAnimationDuration:0.5]; + [animatedLabel setTransform:CGAffineTransformMakeScale(0.3, 0.3)]; + [UIView commitAnimations]; } -- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title { - UpdateExternalStatus(1); +- (void) flyAnimationCompleted:(NSString *)animation finished:(BOOL)finished context:(void *)context { + CALayer *layer = (CALayer *) context; + [layer removeFromSuperlayer]; +} - [progress_ setRunning:true]; - [self setTitle:title]; - // implicit updateProgress +- (void) actionSheet:(UIActionSheet *)sheet clickedButtonAtIndex:(NSInteger)button { + NSString *context([sheet context]); - SHA1SumValue notifyconf; { - FileFd file; - if (!file.Open(NotifyConfig_, FileFd::ReadOnly)) - _error->Discard(); - else { - MMap mmap(file, MMap::ReadOnly); - SHA1Summation sha1; - sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); - notifyconf = sha1.Result(); + if ([context isEqualToString:@"modify"]) { + if (button != [sheet cancelButtonIndex]) { + NSString *buttonName = [buttons_ objectAtIndex:button]; + [self _clickButtonWithName:buttonName]; } - } - SHA1SumValue springlist; { - FileFd file; - if (!file.Open(SpringBoard_, FileFd::ReadOnly)) - _error->Discard(); - else { - MMap mmap(file, MMap::ReadOnly); - SHA1Summation sha1; - sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); - springlist = sha1.Result(); - } + [sheet dismissWithClickedButtonIndex:-1 animated:YES]; } +} - if (invocation != nil) { - [invocation yieldToSelector:@selector(invoke)]; - [self setTitle:@"COMPLETE"]; - } +- (bool) _allowJavaScriptPanel { + return commercial_; +} - if (Finish_ < 4) { - FileFd file; - if (!file.Open(NotifyConfig_, FileFd::ReadOnly)) - _error->Discard(); - else { - MMap mmap(file, MMap::ReadOnly); - SHA1Summation sha1; - sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); - if (!(notifyconf == sha1.Result())) - Finish_ = 4; - } - } +#if !AlwaysReload +- (void) _customButtonClicked { + int count([buttons_ count]); + if (count == 0) + return; - if (Finish_ < 3) { - FileFd file; - if (!file.Open(SpringBoard_, FileFd::ReadOnly)) - _error->Discard(); - else { - MMap mmap(file, MMap::ReadOnly); - SHA1Summation sha1; - sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); - if (!(springlist == sha1.Result())) - Finish_ = 3; - } - } + if (count == 1) + [self _clickButtonWithName:[buttons_ objectAtIndex:0]]; + else { + NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:count]; + [buttons addObjectsFromArray:buttons_]; - if (Finish_ < 2) { - if (RestartSubstrate_) - Finish_ = 2; - } + UIActionSheet *sheet = [[[UIActionSheet alloc] + initWithTitle:nil + delegate:self + cancelButtonTitle:nil + destructiveButtonTitle:nil + otherButtonTitles:nil + ] autorelease]; - RestartSubstrate_ = false; + for (NSString *button in buttons) [sheet addButtonWithTitle:button]; + if (!IsWildcat_) { + [sheet addButtonWithTitle:UCLocalize("CANCEL")]; + [sheet setCancelButtonIndex:[sheet numberOfButtons] - 1]; + } + [sheet setContext:@"modify"]; - switch (Finish_) { - case 0: [progress_ setFinish:UCLocalize("RETURN_TO_CYDIA")]; break; /* XXX: Maybe UCLocalize("DONE")? */ - case 1: [progress_ setFinish:UCLocalize("CLOSE_CYDIA")]; break; - case 2: [progress_ setFinish:UCLocalize("RESTART_SPRINGBOARD")]; break; - case 3: [progress_ setFinish:UCLocalize("RELOAD_SPRINGBOARD")]; break; - case 4: [progress_ setFinish:UCLocalize("REBOOT_DEVICE")]; break; + [delegate_ showActionSheet:sheet fromItem:[[self navigationItem] rightBarButtonItem]]; } +} - UpdateExternalStatus(Finish_ == 0 ? 0 : 2); - - [progress_ setRunning:false]; - [self updateProgress]; +// We don't want to allow non-commercial packages to do custom things to the install button, +// so it must call customButtonClicked with a custom commercial_ == 1 fallthrough. +- (void) customButtonClicked { + if (commercial_) + [super customButtonClicked]; + else + [self _customButtonClicked]; +} - [self applyRightButton]; +- (void) reloadButtonClicked { + // Don't reload a commerical package by tapping the loading button, + // but if it's not an Install button, we should forward it on. + if (![package_ uninstalled]) + [self _customButtonClicked]; } -- (void) addProgressEvent:(CydiaProgressEvent *)event { - [progress_ addEvent:event]; - [self updateProgress]; +- (void) applyLoadingTitle { + // Don't show "Loading" as the title. Ever. } -- (bool) isProgressCancelled { - return cancel_ == 2; +- (UIBarButtonItem *) rightButton { + return button_; } +#endif -- (void) cancel { - cancel_ = 2; - [self updateCancel]; +- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name withReferrer:(NSString *)referrer { + if ((self = [super init]) != nil) { + database_ = database; + buttons_ = [NSMutableArray arrayWithCapacity:4]; + name_ = name == nil ? @"" : [NSString stringWithString:name]; + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/package/%@", UI_, (id) name_]] withReferrer:referrer]; + } return self; } -- (void) setCancellable:(bool)cancellable { - unsigned cancel(cancel_); +- (void) reloadData { + [super reloadData]; - if (!cancellable) - cancel_ = 0; - else if (cancel_ == 0) - cancel_ = 1; + package_ = [database_ packageWithName:name_]; - if (cancel != cancel_) - [self updateCancel]; -} + [buttons_ removeAllObjects]; -- (void) setProgressCancellable:(NSNumber *)cancellable { - [self setCancellable:[cancellable boolValue]]; -} + if (package_ != nil) { + [(Package *) package_ parse]; -- (void) setProgressPercent:(NSNumber *)percent { - [progress_ setPercent:[percent floatValue]]; - [self updateProgress]; -} + commercial_ = [package_ isCommercial]; -- (void) setProgressStatus:(NSDictionary *)status { - if (status == nil) { - [progress_ setCurrent:0]; - [progress_ setTotal:0]; - [progress_ setSpeed:0]; - } else { - [progress_ setPercent:[[status objectForKey:@"Percent"] floatValue]]; + if ([package_ mode] != nil) + [buttons_ addObject:UCLocalize("CLEAR")]; + if ([package_ source] == nil); + else if ([package_ upgradableAndEssential:NO]) + [buttons_ addObject:UCLocalize("UPGRADE")]; + else if ([package_ uninstalled]) + [buttons_ addObject:UCLocalize("INSTALL")]; + else + [buttons_ addObject:UCLocalize("REINSTALL")]; + if (![package_ uninstalled]) + [buttons_ addObject:UCLocalize("REMOVE")]; + } - [progress_ setCurrent:[[status objectForKey:@"Current"] floatValue]]; - [progress_ setTotal:[[status objectForKey:@"Total"] floatValue]]; - [progress_ setSpeed:[[status objectForKey:@"Speed"] floatValue]]; + NSString *title; + switch ([buttons_ count]) { + case 0: title = nil; break; + case 1: title = [buttons_ objectAtIndex:0]; break; + default: title = UCLocalize("MODIFY"); break; } - [self updateProgress]; + button_ = [[[UIBarButtonItem alloc] + initWithTitle:title + style:UIBarButtonItemStylePlain + target:self + action:@selector(customButtonClicked) + ] autorelease]; +} + +- (bool) isLoading { + return commercial_ ? [super isLoading] : false; } @end /* }}} */ -/* Package Cell {{{ */ -@interface PackageCell : CyteTableViewCell < - CyteTableViewCellDelegate -> { - _H icon_; - _H name_; - _H description_; - bool commercial_; - _H source_; - _H badge_; - _H placard_; -} +/* Package List Controller {{{ */ -- (PackageCell *) init; -- (void) setPackage:(Package *)package; +// This is used to sort the filters. We want this to ensure that the +// cheapest filters run first. This is probably a premature optimization. +typedef enum { + kPackageListFilterPriorityLow, + kPackageListFilterPriorityNormal, + kPackageListFilterPriorityHigh +} PackageListFilterPriority; -- (void) drawContentRect:(CGRect)rect; +typedef struct { + SEL selector; + id object; + PackageListFilterPriority priority; + IMP implementation; +} PackageListFilter; -@end +@interface NSValue(PackageListFilter) -@implementation PackageCell ++ (id)valueWithPackageListFilter:(PackageListFilter)filter; +- (PackageListFilter)packageListFilterValue; -- (PackageCell *) init { - CGRect frame(CGRectMake(0, 0, 320, 74)); - if ((self = [super initWithFrame:frame reuseIdentifier:@"Package"]) != nil) { - UIView *content([self contentView]); - CGRect bounds([content bounds]); +@end - content_ = [[[CyteTableViewCellContentView alloc] initWithFrame:bounds] autorelease]; - [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [content addSubview:content_]; +@implementation NSValue(PackageListFilter) - [content_ setDelegate:self]; - [content_ setOpaque:YES]; - } return self; ++ (id)valueWithPackageListFilter:(PackageListFilter)filter { + return [NSValue valueWithBytes:&filter objCType:@encode(PackageListFilter)]; } -- (NSString *) accessibilityLabel { - return [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), (id) name_, (id) description_]; +- (PackageListFilter)packageListFilterValue { + PackageListFilter filter; + [self getValue:&filter]; + return filter; } -- (void) setPackage:(Package *)package { - icon_ = nil; - name_ = nil; - description_ = nil; - source_ = nil; - badge_ = nil; - placard_ = nil; +@end - if (package == nil) - [content_ setBackgroundColor:[UIColor whiteColor]]; - else { - [package parse]; +@interface FilteredPackageListDataSource : NSObject { + _transient Database *database_; + unsigned era_; + _H packages_; + _H sections_; + _H index_; + _H indices_; + _H filters_; +} - Source *source = [package source]; +- (id)objectForFilter:(NSString *)filter; +- (void)setObject:(id)object forFilter:(NSString *)filter; +- (SEL)selectorForFilter:(NSString *)filter; +- (void)setSelector:(SEL)selector forFilter:(NSString *)filter; +- (PackageListFilterPriority)priorityForFilter:(NSString *)filter; +- (void)setPriority:(PackageListFilterPriority)priority forFilter:(NSString *)filter; - icon_ = [package icon]; +- (void)addFilter:(NSString *)filter withSelector:(SEL)selector; +- (void)addFilter:(NSString *)filter withSelector:(SEL)selector priority:(PackageListFilterPriority)priority; +- (void)addFilter:(NSString *)filter withSelector:(SEL)selector priority:(PackageListFilterPriority)priority object:(id)object; +- (void)removeFilter:(NSString *)filter; - if (NSString *name = [package name]) - name_ = [NSString stringWithString:name]; +- (NSArray *)packages; - NSString *description(nil); +@end - if (description == nil && IsWildcat_) - description = [package longDescription]; - if (description == nil) - description = [package shortDescription]; +@implementation FilteredPackageListDataSource - if (description != nil) - description_ = [NSString stringWithString:description]; +- (NSArray *) packages { + return packages_; +} - commercial_ = [package isCommercial]; +- (id) initWithDatabase:(Database *)database { + if ((self = [super init]) != nil) { + database_ = database; + filters_ = [NSMutableDictionary dictionary]; + } return self; +} - NSString *label = nil; - bool trusted = false; ++ (BOOL) supportsSearch { + return YES; +} - if (source != nil) { - label = [source label]; - trusted = [source trusted]; - } else if ([[package id] isEqualToString:@"firmware"]) - label = UCLocalize("APPLE"); - else - label = [NSString stringWithFormat:UCLocalize("SLASH_DELIMITED"), UCLocalize("UNKNOWN"), UCLocalize("LOCAL")]; +- (Package *) packageAtIndexPath:(NSIndexPath *)path { +@synchronized (database_) { + if ([database_ era] != era_) + return nil; - NSString *from(label); + Section *section([sections_ objectAtIndex:[path section]]); + NSInteger row([path row]); + Package *package([packages_ objectAtIndex:([section row] + row)]); + return [[package retain] autorelease]; +} } + +- (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView { + if ([[self class] supportsSearch]) + return [[NSArray arrayWithObject:UITableViewIndexSearch] arrayByAddingObjectsFromArray:index_]; + else + return index_; +} - NSString *section = [package simpleSection]; - if (section != nil && ![section isEqualToString:label]) { - section = [[NSBundle mainBundle] localizedStringForKey:section value:nil table:@"Sections"]; - from = [NSString stringWithFormat:UCLocalize("PARENTHETICAL"), from, section]; - } +- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path { + PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]); + if (cell == nil) + cell = [[[PackageCell alloc] init] autorelease]; - source_ = [NSString stringWithFormat:UCLocalize("FROM"), from]; + Package *package([database_ packageWithName:[[self packageAtIndexPath:path] id]]); + [cell setPackage:package]; + return cell; +} - if (NSString *purpose = [package primaryPurpose]) - badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]; +- (NSInteger) numberOfSectionsInTableView:(UITableView *)list { + NSInteger count([sections_ count]); + return count == 0 ? 1 : count; +} - UIColor *color; - NSString *placard; +- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section { + if ([sections_ count] == 0 || [[sections_ objectAtIndex:section] count] == 0) + return nil; + return [[sections_ objectAtIndex:section] name]; +} - if (NSString *mode = [package mode]) { - if ([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]) { - color = RemovingColor_; - //placard = @"removing"; - } else { - color = InstallingColor_; - //placard = @"installing"; - } +- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section { + if ([sections_ count] == 0) + return 0; + return [[sections_ objectAtIndex:section] count]; +} - // XXX: the removing/installing placards are not @2x - placard = nil; +- (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { + if ([[self class] supportsSearch]) { + if (index == 0) { + [tableView setContentOffset:CGPointZero animated:NO]; + return NSNotFound; } else { - color = [UIColor whiteColor]; - - if ([package installed] != nil) - placard = @"installed"; - else - placard = nil; + return index - 1; } - - [content_ setBackgroundColor:color]; - - if (placard != nil) - placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/%@.png", App_, placard]]; } - [self setNeedsDisplay]; - [content_ setNeedsDisplay]; + return index; } -- (void) drawContentRect:(CGRect)rect { - bool highlighted(highlighted_); - float width([self bounds].size.width); +- (IMP)implementationForSelector:(SEL)selector { +@synchronized (self) { + /* XXX: this is an unsafe optimization of doomy hell */ + Method method(class_getInstanceMethod([Package class], selector)); + _assert(method != NULL); + IMP imp = method_getImplementation(method); + _assert(imp != NULL); - if (icon_ != nil) { - CGRect rect; - rect.size = [(UIImage *) icon_ size]; + return imp; +} } - while (rect.size.width > 32 || rect.size.height > 32) { - rect.size.width /= 2; - rect.size.height /= 2; - } +- (PackageListFilter)packageListFilterForFilter:(NSString *)filter { + return [[filters_ objectForKey:filter] packageListFilterValue]; +} - rect.origin.x = 25 - rect.size.width / 2; - rect.origin.y = 25 - rect.size.height / 2; +- (void)setPackageListFilter:(PackageListFilter)filter forFilter:(NSString *)filterName { + [filters_ setObject:[NSValue valueWithPackageListFilter:filter] forKey:filterName]; +} - [icon_ drawInRect:rect]; - } +- (id)objectForFilter:(NSString *)filter { + return [self packageListFilterForFilter:filter].object; +} - if (badge_ != nil) { - CGRect rect; - rect.size = [(UIImage *) badge_ size]; +- (SEL)selectorForFilter:(NSString *)filter { + return [self packageListFilterForFilter:filter].selector; +} - rect.size.width /= 2; - rect.size.height /= 2; +- (IMP)implementationForFilter:(NSString *)filter { + return [self packageListFilterForFilter:filter].implementation; +} - rect.origin.x = 36 - rect.size.width / 2; - rect.origin.y = 36 - rect.size.height / 2; +- (PackageListFilterPriority)priorityForFilter:(NSString *)filter { + return [self packageListFilterForFilter:filter].priority; +} - [badge_ drawInRect:rect]; - } +- (void)setObject:(id)object forFilter:(NSString *)filterName { + PackageListFilter filter = [self packageListFilterForFilter:filterName]; + filter.object = object; + [self setPackageListFilter:filter forFilter:filterName]; +} - if (highlighted) - UISetColor(White_); +- (void)setPriority:(PackageListFilterPriority)priority forFilter:(NSString *)filterName { + PackageListFilter filter = [self packageListFilterForFilter:filterName]; + filter.priority = priority; + [self setPackageListFilter:filter forFilter:filterName]; +} - if (!highlighted) - UISetColor(commercial_ ? Purple_ : Black_); - [name_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - (placard_ == nil ? 80 : 106)) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation]; - [source_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation]; +- (void)setImplementation:(IMP)implementation forFilter:(NSString *)filterName { + PackageListFilter filter = [self packageListFilterForFilter:filterName]; + filter.implementation = implementation; + [self setPackageListFilter:filter forFilter:filterName]; +} - if (!highlighted) - UISetColor(commercial_ ? Purplish_ : Gray_); - [description_ drawAtPoint:CGPointMake(12, 46) forWidth:(width - 46) withFont:Font14_ lineBreakMode:UILineBreakModeTailTruncation]; +- (void)setSelector:(SEL)selector forFilter:(NSString *)filterName { + PackageListFilter filter = [self packageListFilterForFilter:filterName]; + filter.selector = selector; + [self setPackageListFilter:filter forFilter:filterName]; - if (placard_ != nil) - [placard_ drawAtPoint:CGPointMake(width - 52, 9)]; + [self setImplementation:[self implementationForSelector:selector] forFilter:filterName]; } -@end -/* }}} */ -/* Section Cell {{{ */ -@interface SectionCell : CyteTableViewCell < - CyteTableViewCellDelegate -> { - _H basic_; - _H section_; - _H name_; - _H count_; - _H icon_; - _H switch_; - BOOL editing_; -} +- (void)addFilter:(NSString *)filterName { + PackageListFilter filter; + filter.object = nil; + filter.selector = NULL; + filter.implementation = NULL; + filter.priority = kPackageListFilterPriorityNormal; -- (void) setSection:(Section *)section editing:(BOOL)editing; + [self setPackageListFilter:filter forFilter:filterName]; +} -@end +- (void)addFilter:(NSString *)filter withSelector:(SEL)selector { + [self addFilter:filter]; + [self setSelector:selector forFilter:filter]; +} -@implementation SectionCell +- (void)addFilter:(NSString *)filter withSelector:(SEL)selector priority:(PackageListFilterPriority)priority { + [self addFilter:filter withSelector:selector]; + [self setPriority:priority forFilter:filter]; +} -- (id) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { - if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) { - icon_ = [UIImage applicationImageNamed:@"folder.png"]; - switch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(218, 9, 60, 25)] autorelease]; - [switch_ addTarget:self action:@selector(onSwitch:) forEvents:UIControlEventValueChanged]; +- (void)addFilter:(NSString *)filter withSelector:(SEL)selector priority:(PackageListFilterPriority)priority object:(id)object { + [self addFilter:filter withSelector:selector]; + [self setObject:object forFilter:filter]; +} - UIView *content([self contentView]); - CGRect bounds([content bounds]); +- (void)removeFilter:(NSString *)filter { + [filters_ removeObjectForKey:filter]; +} - content_ = [[[CyteTableViewCellContentView alloc] initWithFrame:bounds] autorelease]; - [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [content addSubview:content_]; - [content_ setBackgroundColor:[UIColor whiteColor]]; +- (NSMutableArray *) _reloadPackages { +@synchronized (database_) { + era_ = [database_ era]; - [content_ setDelegate:self]; - } return self; -} + NSArray *packages([database_ packages]); + NSMutableArray *filtered; -- (void) onSwitch:(id)sender { - NSMutableDictionary *metadata([Sections_ objectForKey:basic_]); - if (metadata == nil) { - metadata = [NSMutableDictionary dictionaryWithCapacity:2]; - [Sections_ setObject:metadata forKey:basic_]; - } + NSArray *filters = [filters_ allKeys]; - [metadata setObject:[NSNumber numberWithBool:([switch_ isOn] == NO)] forKey:@"Hidden"]; - Changed_ = true; -} + for (PackageListFilterPriority currentPriority = kPackageListFilterPriorityHigh; currentPriority >= kPackageListFilterPriorityLow; currentPriority = static_cast(static_cast(currentPriority) - 1)) { + for (NSString *filterName in filters) { + PackageListFilter filter = [self packageListFilterForFilter:filterName]; -- (void) setSection:(Section *)section editing:(BOOL)editing { - if (editing != editing_) { - if (editing_) - [switch_ removeFromSuperview]; - else - [self addSubview:switch_]; - editing_ = editing; - } + if (filter.priority == currentPriority) { + filtered = [NSMutableArray arrayWithCapacity:[packages count]]; - basic_ = nil; - section_ = nil; - name_ = nil; - count_ = nil; + IMP implementation; + SEL selector; + _H object; - if (section == nil) { - name_ = UCLocalize("ALL_PACKAGES"); - count_ = nil; - } else { - basic_ = [section name]; - section_ = [section localized]; + @synchronized (self) { + implementation = filter.implementation; + selector = filter.selector; + object = filter.object; + } - name_ = section_ == nil || [section_ length] == 0 ? UCLocalize("NO_SECTION") : (NSString *) section_; - count_ = [NSString stringWithFormat:@"%d", [section count]]; + if (implementation == NULL) continue; - if (editing_) - [switch_ setOn:(isSectionVisible(basic_) ? 1 : 0) animated:NO]; + _profile(PackageTable$reloadData$Filter) + for (Package *package in packages) + if ([package valid] && (*reinterpret_cast(implementation))(package, selector, object)) + [filtered addObject:package]; + _end + + packages = filtered; + } + } } - [self setAccessoryType:editing ? UITableViewCellAccessoryNone : UITableViewCellAccessoryDisclosureIndicator]; - [self setSelectionStyle:editing ? UITableViewCellSelectionStyleNone : UITableViewCellSelectionStyleBlue]; + // packages would also be valid here, but it's defined + // as an immutable array. filtered works too, so use it. + return filtered; +} } - [content_ setNeedsDisplay]; -} +- (void) reloadData { + NSArray *packages; -- (void) setFrame:(CGRect)frame { - [super setFrame:frame]; + reload: + packages = [self _reloadPackages]; - CGRect rect([switch_ frame]); - [switch_ setFrame:CGRectMake(frame.size.width - 102, 9, rect.size.width, rect.size.height)]; -} +@synchronized (database_) { + if (era_ != [database_ era]) + goto reload; -- (NSString *) accessibilityLabel { - return name_; -} + packages_ = packages; -- (void) drawContentRect:(CGRect)rect { - bool highlighted(highlighted_ && !editing_); + indices_ = [NSMutableDictionary dictionaryWithCapacity:32]; + sections_ = [NSMutableArray arrayWithCapacity:16]; - [icon_ drawInRect:CGRectMake(8, 7, 32, 32)]; + Section *section = nil; + index_ = [NSMutableArray arrayWithCapacity:32]; - if (highlighted) - UISetColor(White_); + _profile(PackageTable$reloadData$Section) + for (size_t offset(0), end([packages_ count]); offset != end; ++offset) { + Package *package; + unichar index; - float width(rect.size.width); - if (editing_) - width -= 87; + _profile(PackageTable$reloadData$Section$Package) + package = [packages_ objectAtIndex:offset]; + index = [package index]; + _end - if (!highlighted) - UISetColor(Black_); - [name_ drawAtPoint:CGPointMake(48, 9) forWidth:(width - 70) withFont:Font22Bold_ lineBreakMode:UILineBreakModeTailTruncation]; + if (section == nil || [section index] != index) { + _profile(PackageTable$reloadData$Section$Allocate) + section = [[[Section alloc] initWithIndex:index row:offset] autorelease]; + _end - CGSize size = [count_ sizeWithFont:Font14_]; + [index_ addObject:[section name]]; + //[indices_ setObject:[NSNumber numberForInt:[sections_ count]] forKey:index]; - UISetColor(White_); - if (count_ != nil) - [count_ drawAtPoint:CGPointMake(13 + (29 - size.width) / 2, 16) withFont:Font12Bold_]; -} + _profile(PackageTable$reloadData$Section$Add) + [sections_ addObject:section]; + _end + } + + [section addToCount]; + } + _end +} } @end -/* }}} */ -/* File Table {{{ */ -@interface FileTable : CyteViewController < - UITableViewDataSource, - UITableViewDelegate -> { +@interface FilteredPackageListController : CyteViewController { _transient Database *database_; - _H package_; - _H name_; - _H files_; + _H searchController_; + _H searchBar_; + _H datasource_; _H list_; + _H title_; } -- (id) initWithDatabase:(Database *)database; -- (void) setPackage:(Package *)package; +- (id) initWithDatabase:(Database *)database title:(NSString *)title; +- (void) clearData; @end -@implementation FileTable +@implementation FilteredPackageListController -- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return files_ == nil ? 0 : [files_ count]; +- (NSURL *) referrerURL { + return [self navigationURL]; } -/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - return 24.0f; -}*/ - -- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *reuseIdentifier = @"Cell"; - - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; - if (cell == nil) { - cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease]; - [cell setFont:[UIFont systemFontOfSize:16]]; - } - [cell setText:[files_ objectAtIndex:indexPath.row]]; - [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; - - return cell; +- (void) deselectWithAnimation:(BOOL)animated { + [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; } -- (NSURL *) navigationURL { - return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/files", [package_ id]]]; +- (void) viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [self deselectWithAnimation:animated]; } -- (void) loadView { - list_ = [[[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]; - [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [list_ setRowHeight:24.0f]; - [(UITableView *) list_ setDataSource:self]; - [list_ setDelegate:self]; - [self setView:list_]; +- (void) didSelectPackage:(Package *)package { + CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id] withReferrer:[[self referrerURL] absoluteString]] autorelease]); + [view setDelegate:delegate_]; + [[self navigationController] pushViewController:view animated:YES]; } -- (void) viewDidLoad { - [super viewDidLoad]; +- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path { + FilteredPackageListDataSource *source = datasource_; - [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")]; + Package *package([source packageAtIndexPath:path]); + package = [database_ packageWithName:[package id]]; + [self didSelectPackage:package]; } -- (void) releaseSubviews { - list_ = nil; - - package_ = nil; - files_ = nil; - - [super releaseSubviews]; ++ (Class) dataSourceClass { + return [FilteredPackageListDataSource class]; } -- (id) initWithDatabase:(Database *)database { +- (id) initWithDatabase:(Database *)database title:(NSString *)title { if ((self = [super init]) != nil) { database_ = database; - } return self; -} - -- (void) setPackage:(Package *)package { - package_ = nil; - name_ = nil; - - files_ = [NSMutableArray arrayWithCapacity:32]; - - if (package != nil) { - package_ = package; - name_ = [package id]; - 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) reloadData { - [super reloadData]; - - [self setPackage:[database_ packageWithName:name_]]; -} + datasource_ = [[[[[self class] dataSourceClass] alloc] initWithDatabase:database_] autorelease]; -@end -/* }}} */ -/* Package Controller {{{ */ -@interface CYPackageController : CydiaWebViewController < - UIActionSheetDelegate -> { - _transient Database *database_; - _H package_; - _H name_; - bool commercial_; - _H buttons_; - _H button_; + title_ = [title copy]; + [[self navigationItem] setTitle:title_]; + } return self; } -- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name withReferrer:(NSString *)referrer; - -@end - -@implementation CYPackageController - -- (NSURL *) navigationURL { - return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@", (id) name_]]; -} +- (void) loadView { + UIView *view([[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]); + [view setBackgroundColor:[UIColor whiteColor]]; + [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)]; + [self setView:view]; -/* XXX: this is not safe at all... localization of /fail/ */ -- (void) _clickButtonWithName:(NSString *)name { - if ([name isEqualToString:UCLocalize("CLEAR")]) - [delegate_ clearPackage:package_]; - else if ([name isEqualToString:UCLocalize("INSTALL")]) - [delegate_ installPackage:package_]; - else if ([name isEqualToString:UCLocalize("REINSTALL")]) - [delegate_ installPackage:package_]; - else if ([name isEqualToString:UCLocalize("REMOVE")]) - [delegate_ removePackage:package_]; - else if ([name isEqualToString:UCLocalize("UPGRADE")]) - [delegate_ installPackage:package_]; - else _assert(false); -} + list_ = [[[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain] autorelease]; + [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [view addSubview:list_]; -- (void) actionSheet:(UIActionSheet *)sheet clickedButtonAtIndex:(NSInteger)button { - NSString *context([sheet context]); + // XXX: is 20 the most optimal number here? + [list_ setSectionIndexMinimumDisplayRowCount:20]; + [list_ setRowHeight:73]; + [(UITableView *) list_ setDataSource:datasource_]; + [list_ setDelegate:self]; - if ([context isEqualToString:@"modify"]) { - if (button != [sheet cancelButtonIndex]) { - NSString *buttonName = [buttons_ objectAtIndex:button]; - [self _clickButtonWithName:buttonName]; - } + if ([[[self class] dataSourceClass] supportsSearch]) { + searchBar_ = [[UISearchBar alloc] init]; + [searchBar_ sizeToFit]; + searchController_ = [[UISearchDisplayController alloc] initWithSearchBar:searchBar_ contentsController:self]; + [searchController_ setDelegate:self]; + [list_ setTableHeaderView:searchBar_]; - [sheet dismissWithClickedButtonIndex:-1 animated:YES]; + [searchController_ setSearchResultsDataSource:datasource_]; + [searchController_ setSearchResultsDelegate:self]; } } -- (bool) _allowJavaScriptPanel { - return commercial_; +- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { + [datasource_ addFilter:@"search" withSelector:@selector(isUnfilteredAndSelectedForBy:) priority:kPackageListFilterPriorityLow object:searchString]; + [datasource_ reloadData]; + return YES; } -#if !AlwaysReload -- (void) _customButtonClicked { - int count([buttons_ count]); - if (count == 0) - return; +- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller { + [datasource_ removeFilter:@"search"]; + [self reloadData]; +} - if (count == 1) - [self _clickButtonWithName:[buttons_ objectAtIndex:0]]; - else { - NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:count]; - [buttons addObjectsFromArray:buttons_]; +- (void)searchDisplayController:(UISearchDisplayController *)controller willShowSearchResultsTableView:(UITableView *)tableView { + // XXX: is 20 the most optimal number here? + [tableView setSectionIndexMinimumDisplayRowCount:20]; + [tableView setRowHeight:73]; +} - UIActionSheet *sheet = [[[UIActionSheet alloc] - initWithTitle:nil - delegate:self - cancelButtonTitle:nil - destructiveButtonTitle:nil - otherButtonTitles:nil - ] autorelease]; +- (void) releaseSubviews { + list_ = nil; - for (NSString *button in buttons) [sheet addButtonWithTitle:button]; - if (!IsWildcat_) { - [sheet addButtonWithTitle:UCLocalize("CANCEL")]; - [sheet setCancelButtonIndex:[sheet numberOfButtons] - 1]; - } - [sheet setContext:@"modify"]; + [super releaseSubviews]; +} - [delegate_ showActionSheet:sheet fromItem:[[self navigationItem] rightBarButtonItem]]; - } +- (void) reloadData { + [super reloadData]; + + [datasource_ reloadData]; + [list_ reloadData]; } -// We don't want to allow non-commercial packages to do custom things to the install button, -// so it must call customButtonClicked with a custom commercial_ == 1 fallthrough. -- (void) customButtonClicked { - if (commercial_) - [super customButtonClicked]; - else - [self _customButtonClicked]; +- (void) resetScrollPosition { + [list_ scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO]; } -- (void) reloadButtonClicked { - // Don't reload a commerical package by tapping the loading button, - // but if it's not an Install button, we should forward it on. - if (![package_ uninstalled]) - [self _customButtonClicked]; +- (void) clearData { + [list_ setDataSource:nil]; + [list_ reloadData]; + + [self resetScrollPosition]; } -- (void) applyLoadingTitle { - // Don't show "Loading" as the title. Ever. +@end +/* }}} */ + +/* Home Controller {{{ */ +@interface HomeController : CydiaWebViewController { + CFRunLoopRef runloop_; + SCNetworkReachabilityRef reachability_; } -- (UIBarButtonItem *) rightButton { - return button_; +@end + +@implementation HomeController + +static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachability, SCNetworkReachabilityFlags flags, void *info) { + [(HomeController *) info dispatchEvent:@"CydiaReachabilityCallback"]; } -#endif -- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name withReferrer:(NSString *)referrer { +- (id) init { if ((self = [super init]) != nil) { - database_ = database; - buttons_ = [NSMutableArray arrayWithCapacity:4]; - name_ = name == nil ? @"" : [NSString stringWithString:name]; - [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/package/%@", UI_, (id) name_]] withReferrer:referrer]; + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/home/", UI_]]]; + [self reloadData]; + + reachability_ = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "cydia.saurik.com"); + if (reachability_ != NULL) { + SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; + SCNetworkReachabilitySetCallback(reachability_, HomeControllerReachabilityCallback, &context); + + CFRunLoopRef runloop(CFRunLoopGetCurrent()); + if (SCNetworkReachabilityScheduleWithRunLoop(reachability_, runloop, kCFRunLoopDefaultMode)) + runloop_ = runloop; + } } return self; } -- (void) reloadData { - [super reloadData]; - - package_ = [database_ packageWithName:name_]; +- (void) dealloc { + if (reachability_ != NULL && runloop_ != NULL) + SCNetworkReachabilityUnscheduleFromRunLoop(reachability_, runloop_, kCFRunLoopDefaultMode); + [super dealloc]; +} - [buttons_ removeAllObjects]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://home"]; +} - if (package_ != nil) { - [(Package *) package_ parse]; +- (void) aboutButtonClicked { + UIAlertView *alert([[[UIAlertView alloc] init] autorelease]); - commercial_ = [package_ isCommercial]; + [alert setTitle:UCLocalize("ABOUT_CYDIA")]; + [alert addButtonWithTitle:UCLocalize("CLOSE")]; + [alert setCancelButtonIndex:0]; - if ([package_ mode] != nil) - [buttons_ addObject:UCLocalize("CLEAR")]; - if ([package_ source] == nil); - else if ([package_ upgradableAndEssential:NO]) - [buttons_ addObject:UCLocalize("UPGRADE")]; - else if ([package_ uninstalled]) - [buttons_ addObject:UCLocalize("INSTALL")]; - else - [buttons_ addObject:UCLocalize("REINSTALL")]; - if (![package_ uninstalled]) - [buttons_ addObject:UCLocalize("REMOVE")]; - } + [alert setMessage: + @"Copyright \u00a9 2008-2011\n" + "SaurikIT, LLC\n" + "\n" + "Jay Freeman (saurik)\n" + "saurik@saurik.com\n" + "http://www.saurik.com/" + ]; - NSString *title; - switch ([buttons_ count]) { - case 0: title = nil; break; - case 1: title = [buttons_ objectAtIndex:0]; break; - default: title = UCLocalize("MODIFY"); break; - } + [alert show]; +} - button_ = [[[UIBarButtonItem alloc] - initWithTitle:title +- (UIBarButtonItem *) leftButton { + return [[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("ABOUT") style:UIBarButtonItemStylePlain target:self - action:@selector(customButtonClicked) + action:@selector(aboutButtonClicked) ] autorelease]; } -- (bool) isLoading { - return commercial_ ? [super isLoading] : false; -} - @end /* }}} */ -/* Package List Controller {{{ */ - -// This is used to sort the filters. We want this to ensure that the -// cheapest filters run first. This is probably a premature optimization. -typedef enum { - kPackageListFilterPriorityLow, - kPackageListFilterPriorityNormal, - kPackageListFilterPriorityHigh -} PackageListFilterPriority; +/* Refresh Bar {{{ */ +@interface RefreshBar : UINavigationBar { + _H indicator_; + _H prompt_; + _H progress_; + _H cancel_; +} -typedef struct { - SEL selector; - id object; - PackageListFilterPriority priority; - IMP implementation; -} PackageListFilter; +@end -@interface NSValue(PackageListFilter) +@implementation RefreshBar -+ (id)valueWithPackageListFilter:(PackageListFilter)filter; -- (PackageListFilter)packageListFilterValue; +- (void) positionViews { + CGRect frame = [cancel_ frame]; + frame.size = [cancel_ sizeThatFits:frame.size]; + frame.origin.x = [self frame].size.width - frame.size.width - 5; + frame.origin.y = ([self frame].size.height - frame.size.height) / 2; + [cancel_ setFrame:frame]; -@end + CGSize prgsize = {75, 100}; + CGRect prgrect = {{ + [self frame].size.width - prgsize.width - 10, + ([self frame].size.height - prgsize.height) / 2 + } , prgsize}; + [progress_ setFrame:prgrect]; -@implementation NSValue(PackageListFilter) + CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]); + unsigned indoffset = ([self frame].size.height - indsize.height) / 2; + CGRect indrect = {{indoffset, indoffset}, indsize}; + [indicator_ setFrame:indrect]; -+ (id)valueWithPackageListFilter:(PackageListFilter)filter { - return [NSValue valueWithBytes:&filter objCType:@encode(PackageListFilter)]; + CGSize prmsize = {215, indsize.height + 4}; + CGRect prmrect = {{ + indoffset * 2 + indsize.width, + unsigned([self frame].size.height - prmsize.height) / 2 - 1 + }, prmsize}; + [prompt_ setFrame:prmrect]; } -- (PackageListFilter)packageListFilterValue { - PackageListFilter filter; - [self getValue:&filter]; - return filter; +- (void) setFrame:(CGRect)frame { + [super setFrame:frame]; + [self positionViews]; } -@end - -@interface FilteredPackageListDataSource : NSObject { - _transient Database *database_; - unsigned era_; - _H packages_; - _H sections_; - _H index_; - _H indices_; - _H filters_; -} +- (id) initWithFrame:(CGRect)frame delegate:(id)delegate { + if ((self = [super initWithFrame:frame]) != nil) { + [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; -- (id)objectForFilter:(NSString *)filter; -- (void)setObject:(id)object forFilter:(NSString *)filter; -- (SEL)selectorForFilter:(NSString *)filter; -- (void)setSelector:(SEL)selector forFilter:(NSString *)filter; -- (PackageListFilterPriority)priorityForFilter:(NSString *)filter; -- (void)setPriority:(PackageListFilterPriority)priority forFilter:(NSString *)filter; + [self setBarStyle:UIBarStyleBlack]; -- (void)addFilter:(NSString *)filter withSelector:(SEL)selector; -- (void)addFilter:(NSString *)filter withSelector:(SEL)selector priority:(PackageListFilterPriority)priority; -- (void)addFilter:(NSString *)filter withSelector:(SEL)selector priority:(PackageListFilterPriority)priority object:(id)object; -- (void)removeFilter:(NSString *)filter; + UIBarStyle barstyle([self _barStyle:NO]); + bool ugly(barstyle == UIBarStyleDefault); -@end + UIProgressIndicatorStyle style = ugly ? + UIProgressIndicatorStyleMediumBrown : + UIProgressIndicatorStyleMediumWhite; -@implementation FilteredPackageListDataSource + indicator_ = [[[UIProgressIndicator alloc] initWithFrame:CGRectZero] autorelease]; + [(UIProgressIndicator *) indicator_ setStyle:style]; + [indicator_ startAnimation]; + [self addSubview:indicator_]; -- (id) initWithDatabase:(Database *)database { - if ((self = [super init]) != nil) { - database_ = database; - filters_ = [NSMutableDictionary dictionary]; - } return self; -} + prompt_ = [[[UITextLabel alloc] initWithFrame:CGRectZero] autorelease]; + [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]]; + [prompt_ setBackgroundColor:[UIColor clearColor]]; + [prompt_ setFont:[UIFont systemFontOfSize:15]]; + [self addSubview:prompt_]; -+ (BOOL) supportsSearch { - return YES; -} + progress_ = [[[UIProgressBar alloc] initWithFrame:CGRectZero] autorelease]; + [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin]; + [(UIProgressBar *) progress_ setStyle:0]; + [self addSubview:progress_]; -- (Package *) packageAtIndexPath:(NSIndexPath *)path { -@synchronized (database_) { - if ([database_ era] != era_) - return nil; + cancel_ = [[[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted] autorelease]; + [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside]; + [cancel_ setBarStyle:barstyle]; - Section *section([sections_ objectAtIndex:[path section]]); - NSInteger row([path row]); - Package *package([packages_ objectAtIndex:([section row] + row)]); - return [[package retain] autorelease]; -} } + [self positionViews]; + } return self; +} -- (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView { - if ([[self class] supportsSearch]) - return [[NSArray arrayWithObject:UITableViewIndexSearch] arrayByAddingObjectsFromArray:index_]; +- (void) setCancellable:(bool)cancellable { + if (cancellable) + [self addSubview:cancel_]; else - return index_; + [cancel_ removeFromSuperview]; } -- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path { - PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]); - if (cell == nil) - cell = [[[PackageCell alloc] init] autorelease]; - - Package *package([database_ packageWithName:[[self packageAtIndexPath:path] id]]); - [cell setPackage:package]; - return cell; +- (void) start { + [prompt_ setText:UCLocalize("UPDATING_DATABASE")]; + [progress_ setProgress:0]; } -- (NSInteger) numberOfSectionsInTableView:(UITableView *)list { - NSInteger count([sections_ count]); - return count == 0 ? 1 : count; +- (void) stop { + [self setCancellable:NO]; } -- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section { - if ([sections_ count] == 0 || [[sections_ objectAtIndex:section] count] == 0) - return nil; - return [[sections_ objectAtIndex:section] name]; +- (void) setPrompt:(NSString *)prompt { + [prompt_ setText:prompt]; } -- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section { - if ([sections_ count] == 0) - return 0; - return [[sections_ objectAtIndex:section] count]; +- (void) setProgress:(float)progress { + [progress_ setProgress:progress]; } -- (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { - if ([[self class] supportsSearch]) { - if (index == 0) { - [tableView setContentOffset:CGPointZero animated:NO]; - return NSNotFound; - } else { - return index - 1; - } - } +@end +/* }}} */ - return index; -} +/* Cydia Navigation Controller Interface {{{ */ +@interface UINavigationController (Cydia) -- (IMP)implementationForSelector:(SEL)selector { -@synchronized (self) { - /* XXX: this is an unsafe optimization of doomy hell */ - Method method(class_getInstanceMethod([Package class], selector)); - _assert(method != NULL); - IMP imp = method_getImplementation(method); - _assert(imp != NULL); +- (NSArray *) navigationURLCollection; +- (void) unloadData; - return imp; -} } +@end +/* }}} */ -- (PackageListFilter)packageListFilterForFilter:(NSString *)filter { - return [[filters_ objectForKey:filter] packageListFilterValue]; -} +/* Cydia Tab Bar Controller {{{ */ +@interface CYTabBarController : UITabBarController < + UITabBarControllerDelegate, + ProgressDelegate +> { + _transient Database *database_; + _H refreshbar_; -- (void)setPackageListFilter:(PackageListFilter)filter forFilter:(NSString *)filterName { - [filters_ setObject:[NSValue valueWithPackageListFilter:filter] forKey:filterName]; -} + bool dropped_; + bool updating_; + // XXX: ok, "updatedelegate_"?... + _transient NSObject *updatedelegate_; -- (id)objectForFilter:(NSString *)filter { - return [self packageListFilterForFilter:filter].object; + _H remembered_; + _transient UIViewController *transient_; } -- (SEL)selectorForFilter:(NSString *)filter { - return [self packageListFilterForFilter:filter].selector; -} +- (NSArray *) navigationURLCollection; +- (void) dropBar:(BOOL)animated; +- (void) beginUpdate; +- (void) raiseBar:(BOOL)animated; +- (BOOL) updating; +- (void) unloadData; -- (IMP)implementationForFilter:(NSString *)filter { - return [self packageListFilterForFilter:filter].implementation; -} +@end -- (PackageListFilterPriority)priorityForFilter:(NSString *)filter { - return [self packageListFilterForFilter:filter].priority; -} +@implementation CYTabBarController -- (void)setObject:(id)object forFilter:(NSString *)filterName { - PackageListFilter filter = [self packageListFilterForFilter:filterName]; - filter.object = object; - [self setPackageListFilter:filter forFilter:filterName]; +- (void) didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + + // presenting a UINavigationController on 2.x does not update its transitionView + // it thereby will not allow its topViewController to be unloaded by memory pressure + if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) { + UIViewController *selected([self selectedViewController]); + for (UINavigationController *controller in [self viewControllers]) + if (controller != selected) + if (UIViewController *top = [controller topViewController]) + [top unloadView]; + } } -- (void)setPriority:(PackageListFilterPriority)priority forFilter:(NSString *)filterName { - PackageListFilter filter = [self packageListFilterForFilter:filterName]; - filter.priority = priority; - [self setPackageListFilter:filter forFilter:filterName]; +- (void) setUnselectedViewController:(UIViewController *)transient { + if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) { + if (transient != nil) { + [[[self viewControllers] objectAtIndex:0] pushViewController:transient animated:YES]; + [self setSelectedIndex:0]; + } return; + } + + NSMutableArray *controllers = [[[self viewControllers] mutableCopy] autorelease]; + if (transient != nil) { + UINavigationController *navigation([[[UINavigationController alloc] init] autorelease]); + [navigation setViewControllers:[NSArray arrayWithObject:transient]]; + transient = navigation; + + if (transient_ == nil) + remembered_ = [controllers objectAtIndex:0]; + transient_ = transient; + [transient_ setTabBarItem:[remembered_ tabBarItem]]; + [controllers replaceObjectAtIndex:0 withObject:transient_]; + [self setSelectedIndex:0]; + [self setViewControllers:controllers]; + [self concealTabBarSelection]; + } else if (remembered_ != nil) { + [remembered_ setTabBarItem:[transient_ tabBarItem]]; + transient_ = transient; + [controllers replaceObjectAtIndex:0 withObject:remembered_]; + remembered_ = nil; + [self setViewControllers:controllers]; + [self revealTabBarSelection]; + } } -- (void)setImplementation:(IMP)implementation forFilter:(NSString *)filterName { - PackageListFilter filter = [self packageListFilterForFilter:filterName]; - filter.implementation = implementation; - [self setPackageListFilter:filter forFilter:filterName]; +- (UIViewController *) unselectedViewController { + return transient_; } -- (void)setSelector:(SEL)selector forFilter:(NSString *)filterName { - PackageListFilter filter = [self packageListFilterForFilter:filterName]; - filter.selector = selector; - [self setPackageListFilter:filter forFilter:filterName]; +- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController { + if ([self unselectedViewController]) + [self setUnselectedViewController:nil]; - [self setImplementation:[self implementationForSelector:selector] forFilter:filterName]; + // presenting a UINavigationController on 2.x does not update its transitionView + // if this view was unloaded, the tranitionView may currently be presenting nothing + if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) { + UINavigationController *navigation((UINavigationController *) viewController); + [navigation pushViewController:[[[UIViewController alloc] init] autorelease] animated:NO]; + [navigation popViewControllerAnimated:NO]; + } } -- (void)addFilter:(NSString *)filterName { - PackageListFilter filter; - filter.object = nil; - filter.selector = NULL; - filter.implementation = NULL; - filter.priority = kPackageListFilterPriorityNormal; +- (NSArray *) navigationURLCollection { + NSMutableArray *items([NSMutableArray array]); - [self setPackageListFilter:filter forFilter:filterName]; + // XXX: Should this deal with transient view controllers? + for (id navigation in [self viewControllers]) { + NSArray *stack = [navigation performSelector:@selector(navigationURLCollection)]; + if (stack != nil) + [items addObject:stack]; + } + + return items; } -- (void)addFilter:(NSString *)filter withSelector:(SEL)selector { - [self addFilter:filter]; - [self setSelector:selector forFilter:filter]; +- (void) dismissModalViewControllerAnimated:(BOOL)animated { + if ([self modalViewController] == nil && [self unselectedViewController] != nil) + [self setUnselectedViewController:nil]; + else + [super dismissModalViewControllerAnimated:YES]; } -- (void)addFilter:(NSString *)filter withSelector:(SEL)selector priority:(PackageListFilterPriority)priority { - [self addFilter:filter withSelector:selector]; - [self setPriority:priority forFilter:filter]; +- (void) unloadData { + [super unloadData]; + + for (UINavigationController *controller in [self viewControllers]) + [controller unloadData]; + + if (UIViewController *selected = [self selectedViewController]) + [selected reloadData]; + + if (UIViewController *unselected = [self unselectedViewController]) { + [unselected unloadData]; + [unselected reloadData]; + } } -- (void)addFilter:(NSString *)filter withSelector:(SEL)selector priority:(PackageListFilterPriority)priority object:(id)object { - [self addFilter:filter withSelector:selector]; - [self setObject:object forFilter:filter]; -} +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; -- (void)removeFilter:(NSString *)filter { - [filters_ removeObjectForKey:filter]; + [super dealloc]; } -- (NSMutableArray *) _reloadPackages { -@synchronized (database_) { - era_ = [database_ era]; +- (id) initWithDatabase:(Database *)database { + if ((self = [super init]) != nil) { + database_ = database; + [self setDelegate:self]; - NSArray *packages([database_ packages]); - NSMutableArray *filtered; + [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; - NSArray *filters = [filters_ allKeys]; + refreshbar_ = [[[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self] autorelease]; + } return self; +} - for (PackageListFilterPriority currentPriority = kPackageListFilterPriorityHigh; currentPriority >= kPackageListFilterPriorityLow; currentPriority = static_cast(static_cast(currentPriority) - 1)) { - for (NSString *filterName in filters) { - PackageListFilter filter = [self packageListFilterForFilter:filterName]; +- (void) setUpdate:(NSDate *)date { + [self beginUpdate]; +} - if (filter.priority == currentPriority) { - filtered = [NSMutableArray arrayWithCapacity:[packages count]]; +- (void) beginUpdate { + [(RefreshBar *) refreshbar_ start]; + [self dropBar:YES]; - IMP implementation; - SEL selector; - _H object; + [updatedelegate_ retainNetworkActivityIndicator]; + updating_ = true; - @synchronized (self) { - implementation = filter.implementation; - selector = filter.selector; - object = filter.object; - } + [NSThread + detachNewThreadSelector:@selector(performUpdate) + toTarget:self + withObject:nil + ]; +} - if (implementation == NULL) continue; +- (void) performUpdate { + NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]); - _profile(PackageTable$reloadData$Filter) - for (Package *package in packages) - if ([package valid] && (*reinterpret_cast(implementation))(package, selector, object)) - [filtered addObject:package]; - _end + Status status; + status.setDelegate(self); + [database_ updateWithStatus:status]; - packages = filtered; - } - } - } + [self + performSelectorOnMainThread:@selector(completeUpdate) + withObject:nil + waitUntilDone:NO + ]; - // packages would also be valid here, but it's defined - // as an immutable array. filtered works too, so use it. - return filtered; -} } + [pool release]; +} -- (void) reloadData { - NSArray *packages; +- (void) stopUpdateWithSelector:(SEL)selector { + updating_ = false; + [updatedelegate_ releaseNetworkActivityIndicator]; - reload: - packages = [self _reloadPackages]; + [self raiseBar:YES]; + [refreshbar_ stop]; -@synchronized (database_) { - if (era_ != [database_ era]) - goto reload; + [updatedelegate_ performSelector:selector withObject:nil afterDelay:0]; +} - packages_ = packages; +- (void) completeUpdate { + if (!updating_) + return; + [self stopUpdateWithSelector:@selector(reloadData)]; +} - indices_ = [NSMutableDictionary dictionaryWithCapacity:32]; - sections_ = [NSMutableArray arrayWithCapacity:16]; +- (void) cancelUpdate { + [self stopUpdateWithSelector:@selector(updateDataAndLoad)]; +} - Section *section = nil; - index_ = [NSMutableArray arrayWithCapacity:32]; +- (void) cancelPressed { + [self cancelUpdate]; +} - _profile(PackageTable$reloadData$Section) - for (size_t offset(0), end([packages_ count]); offset != end; ++offset) { - Package *package; - unichar index; +- (BOOL) updating { + return updating_; +} - _profile(PackageTable$reloadData$Section$Package) - package = [packages_ objectAtIndex:offset]; - index = [package index]; - _end +- (void) addProgressEvent:(CydiaProgressEvent *)event { + [refreshbar_ setPrompt:[event compoundMessage]]; +} - if (section == nil || [section index] != index) { - _profile(PackageTable$reloadData$Section$Allocate) - section = [[[Section alloc] initWithIndex:index row:offset] autorelease]; - _end +- (bool) isProgressCancelled { + return !updating_; +} - [index_ addObject:[section name]]; - //[indices_ setObject:[NSNumber numberForInt:[sections_ count]] forKey:index]; +- (void) setProgressCancellable:(NSNumber *)cancellable { + [refreshbar_ setCancellable:(updating_ && [cancellable boolValue])]; +} - _profile(PackageTable$reloadData$Section$Add) - [sections_ addObject:section]; - _end - } +- (void) setProgressPercent:(NSNumber *)percent { + [refreshbar_ setProgress:[percent floatValue]]; +} - [section addToCount]; - } - _end -} } +- (void) setProgressStatus:(NSDictionary *)status { + if (status != nil) + [self setProgressPercent:[status objectForKey:@"Percent"]]; +} -@end +- (void) setUpdateDelegate:(id)delegate { + updatedelegate_ = delegate; +} -@interface FilteredPackageListController : CyteViewController { - _transient Database *database_; - _H searchController_; - _H searchBar_; - _H datasource_; - _H list_; - _H title_; +- (UIView *) transitionView { + if ([self respondsToSelector:@selector(_transitionView)]) + return [self _transitionView]; + else + return MSHookIvar(self, "_viewControllerTransitionView"); } -- (id) initWithDatabase:(Database *)database title:(NSString *)title; -- (void) clearData; +- (void) dropBar:(BOOL)animated { + if (dropped_) + return; + dropped_ = true; -@end + UIView *transition([self transitionView]); + [[self view] addSubview:refreshbar_]; -@implementation FilteredPackageListController + CGRect barframe([refreshbar_ frame]); -- (NSURL *) referrerURL { - return [self navigationURL]; -} + if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_0) // XXX: _UIApplicationLinkedOnOrAfter(4) + barframe.origin.y = CYStatusBarHeight(); + else + barframe.origin.y = 0; -- (void) deselectWithAnimation:(BOOL)animated { - [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; -} + [refreshbar_ setFrame:barframe]; -- (void) viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [self deselectWithAnimation:animated]; -} + if (animated) + [UIView beginAnimations:nil context:NULL]; -- (void) didSelectPackage:(Package *)package { - CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id] withReferrer:[[self referrerURL] absoluteString]] autorelease]); - [view setDelegate:delegate_]; - [[self navigationController] pushViewController:view animated:YES]; -} + CGRect viewframe = [transition frame]; + viewframe.origin.y += barframe.size.height; + viewframe.size.height -= barframe.size.height; + [transition setFrame:viewframe]; -- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path { - FilteredPackageListDataSource *source = datasource_; + if (animated) + [UIView commitAnimations]; - Package *package([source packageAtIndexPath:path]); - package = [database_ packageWithName:[package id]]; - [self didSelectPackage:package]; + // Ensure bar has the proper width for our view, it might have changed + barframe.size.width = viewframe.size.width; + [refreshbar_ setFrame:barframe]; } -+ (Class) dataSourceClass { - return [FilteredPackageListDataSource class]; -} +- (void) raiseBar:(BOOL)animated { + if (!dropped_) + return; + dropped_ = false; -- (id) initWithDatabase:(Database *)database title:(NSString *)title { - if ((self = [super init]) != nil) { - database_ = database; + UIView *transition([self transitionView]); + [refreshbar_ removeFromSuperview]; - datasource_ = [[[[[self class] dataSourceClass] alloc] initWithDatabase:database_] autorelease]; + CGRect barframe([refreshbar_ frame]); - title_ = [title copy]; - [[self navigationItem] setTitle:title_]; - } return self; -} + if (animated) + [UIView beginAnimations:nil context:NULL]; -- (void) loadView { - UIView *view([[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]); - [view setBackgroundColor:[UIColor whiteColor]]; - [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)]; - [self setView:view]; + CGRect viewframe = [transition frame]; + viewframe.origin.y -= barframe.size.height; + viewframe.size.height += barframe.size.height; + [transition setFrame:viewframe]; - list_ = [[[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain] autorelease]; - [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [view addSubview:list_]; + if (animated) + [UIView commitAnimations]; +} - // XXX: is 20 the most optimal number here? - [list_ setSectionIndexMinimumDisplayRowCount:20]; - [list_ setRowHeight:73]; - [(UITableView *) list_ setDataSource:datasource_]; - [list_ setDelegate:self]; +- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + bool dropped(dropped_); + + if (dropped) + [self raiseBar:NO]; - if ([[[self class] dataSourceClass] supportsSearch]) { - searchBar_ = [[UISearchBar alloc] init]; - [searchBar_ sizeToFit]; - searchController_ = [[UISearchDisplayController alloc] initWithSearchBar:searchBar_ contentsController:self]; - [searchController_ setDelegate:self]; - [list_ setTableHeaderView:searchBar_]; + [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; - [searchController_ setSearchResultsDataSource:datasource_]; - [searchController_ setSearchResultsDelegate:self]; - } + if (dropped) + [self dropBar:NO]; } -- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { - [datasource_ addFilter:@"search" withSelector:@selector(isUnfilteredAndSelectedForBy:) priority:kPackageListFilterPriorityLow object:searchString]; - [datasource_ reloadData]; - return YES; +- (void) statusBarFrameChanged:(NSNotification *)notification { + if (dropped_) { + [self raiseBar:NO]; + [self dropBar:NO]; + } } -- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller { - [datasource_ removeFilter:@"search"]; - [self reloadData]; -} +@end +/* }}} */ -- (void)searchDisplayController:(UISearchDisplayController *)controller willShowSearchResultsTableView:(UITableView *)tableView { - // XXX: is 20 the most optimal number here? - [tableView setSectionIndexMinimumDisplayRowCount:20]; - [tableView setRowHeight:73]; -} +/* Cydia Navigation Controller Implementation {{{ */ +@implementation UINavigationController (Cydia) -- (void) releaseSubviews { - list_ = nil; +- (NSArray *) navigationURLCollection { + NSMutableArray *stack([NSMutableArray array]); - [super releaseSubviews]; + for (CyteViewController *controller in [self viewControllers]) { + NSString *url = [[controller navigationURL] absoluteString]; + if (url != nil) + [stack addObject:url]; + } + + return stack; } - (void) reloadData { [super reloadData]; - [datasource_ reloadData]; - [list_ reloadData]; -} + UIViewController *visible([self visibleViewController]); + if (visible != nil) + [visible reloadData]; -- (void) resetScrollPosition { - [list_ scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO]; + // on the iPad, this view controller is ALSO visible. :( + if (IsWildcat_) + if (UIViewController *top = [self topViewController]) + if (top != visible) + [top reloadData]; } -- (void) clearData { - [list_ setDataSource:nil]; - [list_ reloadData]; +- (void) unloadData { + for (CyteViewController *page in [self viewControllers]) + [page unloadData]; - [self resetScrollPosition]; + [super unloadData]; } @end /* }}} */ -/* Home Controller {{{ */ -@interface HomeController : CydiaWebViewController { - CFRunLoopRef runloop_; - SCNetworkReachabilityRef reachability_; +/* Cydia:// Protocol {{{ */ +@interface CydiaURLProtocol : NSURLProtocol { } @end -@implementation HomeController - -static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachability, SCNetworkReachabilityFlags flags, void *info) { - [(HomeController *) info dispatchEvent:@"CydiaReachabilityCallback"]; -} +@implementation CydiaURLProtocol -- (id) init { - if ((self = [super init]) != nil) { - [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/home/", UI_]]]; - [self reloadData]; ++ (BOOL) canInitWithRequest:(NSURLRequest *)request { + NSURL *url([request URL]); + if (url == nil) + return NO; - reachability_ = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "cydia.saurik.com"); - if (reachability_ != NULL) { - SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; - SCNetworkReachabilitySetCallback(reachability_, HomeControllerReachabilityCallback, &context); + NSString *scheme([[url scheme] lowercaseString]); + if (scheme != nil && [scheme isEqualToString:@"cydia"]) + return YES; + if ([[url absoluteString] hasPrefix:@"about:cydia-"]) + return YES; - CFRunLoopRef runloop(CFRunLoopGetCurrent()); - if (SCNetworkReachabilityScheduleWithRunLoop(reachability_, runloop, kCFRunLoopDefaultMode)) - runloop_ = runloop; - } - } return self; + return NO; } -- (void) dealloc { - if (reachability_ != NULL && runloop_ != NULL) - SCNetworkReachabilityUnscheduleFromRunLoop(reachability_, runloop_, kCFRunLoopDefaultMode); - [super dealloc]; ++ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request { + return request; } -- (NSURL *) navigationURL { - return [NSURL URLWithString:@"cydia://home"]; +- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request { + id client([self client]); + if (icon == nil) + [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]]; + else { + NSData *data(UIImagePNGRepresentation(icon)); + + NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]); + [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [client URLProtocol:self didLoadData:data]; + [client URLProtocolDidFinishLoading:self]; + } } -- (void) aboutButtonClicked { - UIAlertView *alert([[[UIAlertView alloc] init] autorelease]); +- (void) startLoading { + id client([self client]); + NSURLRequest *request([self request]); - [alert setTitle:UCLocalize("ABOUT_CYDIA")]; - [alert addButtonWithTitle:UCLocalize("CLOSE")]; - [alert setCancelButtonIndex:0]; + NSURL *url([request URL]); + NSString *href([url absoluteString]); + NSString *scheme([[url scheme] lowercaseString]); - [alert setMessage: - @"Copyright \u00a9 2008-2011\n" - "SaurikIT, LLC\n" - "\n" - "Jay Freeman (saurik)\n" - "saurik@saurik.com\n" - "http://www.saurik.com/" - ]; + NSString *path; - [alert show]; + if ([scheme isEqualToString:@"cydia"]) + path = [href substringFromIndex:8]; + else if ([scheme isEqualToString:@"about"]) + path = [href substringFromIndex:12]; + else _assert(false); + + NSRange slash([path rangeOfString:@"/"]); + + NSString *command; + if (slash.location == NSNotFound) { + command = path; + path = nil; + } else { + command = [path substringToIndex:slash.location]; + path = [path substringFromIndex:(slash.location + 1)]; + } + + Database *database([Database sharedInstance]); + + if ([command isEqualToString:@"package-icon"]) { + if (path == nil) + goto fail; + path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + Package *package([database packageWithName:path]); + if (package == nil) + goto fail; + [package parse]; + UIImage *icon([package icon]); + [self _returnPNGWithImage:icon forRequest:request]; + } else if ([command isEqualToString:@"uikit-image"]) { + if (path == nil) + goto fail; + path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + UIImage *icon(_UIImageWithName(path)); + [self _returnPNGWithImage:icon forRequest:request]; + } else if ([command isEqualToString:@"section-icon"]) { + if (path == nil) + goto fail; + path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, [path stringByReplacingOccurrencesOfString:@" " withString:@"_"]]]); + if (icon == nil) + icon = [UIImage applicationImageNamed:@"unknown.png"]; + [self _returnPNGWithImage:icon forRequest:request]; + } else fail: { + [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]]; + } } -- (UIBarButtonItem *) leftButton { - return [[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("ABOUT") - style:UIBarButtonItemStylePlain - target:self - action:@selector(aboutButtonClicked) - ] autorelease]; +- (void) stopLoading { } @end /* }}} */ -/* Refresh Bar {{{ */ -@interface RefreshBar : UINavigationBar { - _H indicator_; - _H prompt_; - _H progress_; - _H cancel_; +/* Section Controller {{{ */ +@interface SectionController : FilteredPackageListController { + _H indirect_; + _H cydia_; + _H section_; + _H key_; + _transient Source *source_; + std::vector< _H > promoted_; } -@end +- (id) initWithDatabase:(Database *)database section:(NSString *)section source:(Source *)source; -@implementation RefreshBar +@end -- (void) positionViews { - CGRect frame = [cancel_ frame]; - frame.size = [cancel_ sizeThatFits:frame.size]; - frame.origin.x = [self frame].size.width - frame.size.width - 5; - frame.origin.y = ([self frame].size.height - frame.size.height) / 2; - [cancel_ setFrame:frame]; +@implementation SectionController - CGSize prgsize = {75, 100}; - CGRect prgrect = {{ - [self frame].size.width - prgsize.width - 10, - ([self frame].size.height - prgsize.height) / 2 - } , prgsize}; - [progress_ setFrame:prgrect]; +- (NSURL *) referrerURL { + NSMutableString *path = [NSMutableString string]; - CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]); - unsigned indoffset = ([self frame].size.height - indsize.height) / 2; - CGRect indrect = {{indoffset, indoffset}, indsize}; - [indicator_ setFrame:indrect]; + if (source_ != nil) { + [path appendString:[NSString stringWithFormat:@"sources/%@", [key_ stringByAddingPercentEscapesIncludingReserved]]]; + } else { + [path appendString:@"sections"]; + } - CGSize prmsize = {215, indsize.height + 4}; - CGRect prmrect = {{ - indoffset * 2 + indsize.width, - unsigned([self frame].size.height - prmsize.height) / 2 - 1 - }, prmsize}; - [prompt_ setFrame:prmrect]; -} + [path appendString:@"/"]; -- (void) setFrame:(CGRect)frame { - [super setFrame:frame]; - [self positionViews]; + if (section_ != nil) { + [path appendString:[NSString stringWithFormat:@"%@", [section_ stringByAddingPercentEscapesIncludingReserved]]]; + } else { + [path appendString:@"all"]; + } + + return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/%@", UI_, path]]; } -- (id) initWithFrame:(CGRect)frame delegate:(id)delegate { - if ((self = [super initWithFrame:frame]) != nil) { - [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; +- (NSURL *) navigationURL { + NSMutableString *path = [NSMutableString string]; - [self setBarStyle:UIBarStyleBlack]; + if (source_ != nil) { + [path appendString:[NSString stringWithFormat:@"sources/%@", [key_ stringByAddingPercentEscapesIncludingReserved]]]; + } else { + [path appendString:@"sections"]; + } - UIBarStyle barstyle([self _barStyle:NO]); - bool ugly(barstyle == UIBarStyleDefault); + [path appendString:@"/"]; - UIProgressIndicatorStyle style = ugly ? - UIProgressIndicatorStyleMediumBrown : - UIProgressIndicatorStyleMediumWhite; + if (section_ != nil) { + [path appendString:[NSString stringWithFormat:@"%@", [section_ stringByAddingPercentEscapesIncludingReserved]]]; + } else { + [path appendString:@"all"]; + } - indicator_ = [[[UIProgressIndicator alloc] initWithFrame:CGRectZero] autorelease]; - [(UIProgressIndicator *) indicator_ setStyle:style]; - [indicator_ startAnimation]; - [self addSubview:indicator_]; + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://%@", path]]; +} - prompt_ = [[[UITextLabel alloc] initWithFrame:CGRectZero] autorelease]; - [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]]; - [prompt_ setBackgroundColor:[UIColor clearColor]]; - [prompt_ setFont:[UIFont systemFontOfSize:15]]; - [self addSubview:prompt_]; +- (id) initWithDatabase:(Database *)database section:(NSString *)name source:(Source *)source { + NSString *title; + if (name == nil) + title = UCLocalize("ALL_PACKAGES"); + else if (![name isEqual:@""]) + title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"]; + else + title = UCLocalize("NO_SECTION"); - progress_ = [[[UIProgressBar alloc] initWithFrame:CGRectZero] autorelease]; - [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin]; - [(UIProgressBar *) progress_ setStyle:0]; - [self addSubview:progress_]; + if ((self = [super initWithDatabase:database title:title]) != nil) { + indirect_ = [[[IndirectDelegate alloc] initWithDelegate:self] autorelease]; + cydia_ = [[[CydiaObject alloc] initWithDelegate:indirect_] autorelease]; - cancel_ = [[[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted] autorelease]; - [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; - [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside]; - [cancel_ setBarStyle:barstyle]; + [datasource_ addFilter:@"section" withSelector:@selector(isVisibleInSection:) priority:kPackageListFilterPriorityHigh object:name]; + [datasource_ addFilter:@"source" withSelector:@selector(isVisibleInSource:) priority:kPackageListFilterPriorityHigh object:source]; - [self positionViews]; + section_ = name; + source_ = source; + key_ = [source key]; } return self; } -- (void) setCancellable:(bool)cancellable { - if (cancellable) - [self addSubview:cancel_]; - else - [cancel_ removeFromSuperview]; +/*- (NSInteger) numberOfSectionsInTableView:(UITableView *)list { + return [super numberOfSectionsInTableView:list] + 1; } -- (void) start { - [prompt_ setText:UCLocalize("UPDATING_DATABASE")]; - [progress_ setProgress:0]; +- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section { + return section == 0 ? nil : [super tableView:list titleForHeaderInSection:(section - 1)]; } -- (void) stop { - [self setCancellable:NO]; +- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section { + return section == 0 ? promoted_.size() : [super tableView:list numberOfRowsInSection:(section - 1)]; } -- (void) setPrompt:(NSString *)prompt { - [prompt_ setText:prompt]; ++ (NSIndexPath *) adjustedIndexPath:(NSIndexPath *)path { + return [NSIndexPath indexPathForRow:[path row] inSection:([path section] - 1)]; } -- (void) setProgress:(float)progress { - [progress_ setProgress:progress]; +- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path { + if ([path section] != 0) + return [super tableView:table cellForRowAtIndexPath:[SectionController adjustedIndexPath:path]]; + + return promoted_[[path row]]; } -@end -/* }}} */ +- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path { + if ([path section] != 0) + return [super tableView:table didSelectRowAtIndexPath:[SectionController adjustedIndexPath:path]]; +} -/* Cydia Navigation Controller Interface {{{ */ -@interface UINavigationController (Cydia) +- (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { + NSInteger section([super tableView:tableView sectionForSectionIndexTitle:title atIndex:index]); + return section == 0 ? 0 : section + 1; +}*/ -- (NSArray *) navigationURLCollection; -- (void) unloadData; +- (void) webView:(WebView *)view decidePolicyForNewWindowAction:(NSDictionary *)action request:(NSURLRequest *)request newFrameName:(NSString *)frame decisionListener:(id)listener { + NSURL *url([request URL]); + if (url == nil) + return; -@end -/* }}} */ + if ([frame isEqualToString:@"_open"]) + [delegate_ openURL:url]; + else { + WebFrame *frame(nil); + if (NSDictionary *WebActionElement = [action objectForKey:@"WebActionElementKey"]) + frame = [WebActionElement objectForKey:@"WebElementFrame"]; + if (frame == nil) + frame = [view mainFrame]; -/* Cydia Tab Bar Controller {{{ */ -@interface CYTabBarController : UITabBarController < - UITabBarControllerDelegate, - ProgressDelegate -> { - _transient Database *database_; - _H refreshbar_; + WebDataSource *source([frame provisionalDataSource] ?: [frame dataSource]); - bool dropped_; - bool updating_; - // XXX: ok, "updatedelegate_"?... - _transient NSObject *updatedelegate_; + CyteViewController *controller([delegate_ pageForURL:url forExternal:NO withReferrer:([request valueForHTTPHeaderField:@"Referer"] ?: [[[source request] URL] absoluteString])] ?: [[[CydiaWebViewController alloc] initWithRequest:request] autorelease]); + [controller setDelegate:delegate_]; + [[self navigationController] pushViewController:controller animated:YES]; + } - _H remembered_; - _transient UIViewController *transient_; + [listener ignore]; } -- (NSArray *) navigationURLCollection; -- (void) dropBar:(BOOL)animated; -- (void) beginUpdate; -- (void) raiseBar:(BOOL)animated; -- (BOOL) updating; -- (void) unloadData; - -@end +- (NSURLRequest *) webView:(WebView *)view resource:(id)resource willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response fromDataSource:(WebDataSource *)source { + return [CydiaWebViewController requestWithHeaders:request]; +} -@implementation CYTabBarController +- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { + [CydiaWebViewController didClearWindowObject:window forFrame:frame withCydia:cydia_]; +} -- (void) didReceiveMemoryWarning { - [super didReceiveMemoryWarning]; +- (void) loadView { + [super loadView]; - // presenting a UINavigationController on 2.x does not update its transitionView - // it thereby will not allow its topViewController to be unloaded by memory pressure - if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) { - UIViewController *selected([self selectedViewController]); - for (UINavigationController *controller in [self viewControllers]) - if (controller != selected) - if (UIViewController *top = [controller topViewController]) - [top unloadView]; - } -} + // XXX: this code is horrible. I mean, wtf Jay? + if (ShowPromoted_ && [[Metadata_ objectForKey:@"ShowPromoted"] boolValue]) { + promoted_.resize(1); -- (void) setUnselectedViewController:(UIViewController *)transient { - if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) { - if (transient != nil) { - [[[self viewControllers] objectAtIndex:0] pushViewController:transient animated:YES]; - [self setSelectedIndex:0]; - } return; - } + for (unsigned i(0); i != promoted_.size(); ++i) { + CyteWebViewTableViewCell *promoted([CyteWebViewTableViewCell cellWithRequest:[NSURLRequest + requestWithURL:[Diversion divertURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/sectionhead/%u/%@", + UI_, i, section_ == nil ? @"" : [section_ stringByAddingPercentEscapesIncludingReserved]] + ]] - NSMutableArray *controllers = [[[self viewControllers] mutableCopy] autorelease]; - if (transient != nil) { - UINavigationController *navigation([[[UINavigationController alloc] init] autorelease]); - [navigation setViewControllers:[NSArray arrayWithObject:transient]]; - transient = navigation; + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:120 + ]]); - if (transient_ == nil) - remembered_ = [controllers objectAtIndex:0]; - transient_ = transient; - [transient_ setTabBarItem:[remembered_ tabBarItem]]; - [controllers replaceObjectAtIndex:0 withObject:transient_]; - [self setSelectedIndex:0]; - [self setViewControllers:controllers]; - [self concealTabBarSelection]; - } else if (remembered_ != nil) { - [remembered_ setTabBarItem:[transient_ tabBarItem]]; - transient_ = transient; - [controllers replaceObjectAtIndex:0 withObject:remembered_]; - remembered_ = nil; - [self setViewControllers:controllers]; - [self revealTabBarSelection]; + [promoted setDelegate:self]; + promoted_[i] = promoted; + } } } -- (UIViewController *) unselectedViewController { - return transient_; +- (void) setDelegate:(id)delegate { + [super setDelegate:delegate]; + [cydia_ setDelegate:delegate]; } -- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController { - if ([self unselectedViewController]) - [self setUnselectedViewController:nil]; - - // presenting a UINavigationController on 2.x does not update its transitionView - // if this view was unloaded, the tranitionView may currently be presenting nothing - if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) { - UINavigationController *navigation((UINavigationController *) viewController); - [navigation pushViewController:[[[UIViewController alloc] init] autorelease] animated:NO]; - [navigation popViewControllerAnimated:NO]; - } +- (void) releaseSubviews { + promoted_.clear(); + [super releaseSubviews]; } -- (NSArray *) navigationURLCollection { - NSMutableArray *items([NSMutableArray array]); - - // XXX: Should this deal with transient view controllers? - for (id navigation in [self viewControllers]) { - NSArray *stack = [navigation performSelector:@selector(navigationURLCollection)]; - if (stack != nil) - [items addObject:stack]; - } +- (void)reloadData { + source_ = [database_ sourceWithKey:key_]; + key_ = [source_ key]; + [datasource_ setObject:source_ forFilter:@"source"]; - return items; + [super reloadData]; } -- (void) dismissModalViewControllerAnimated:(BOOL)animated { - if ([self modalViewController] == nil && [self unselectedViewController] != nil) - [self setUnselectedViewController:nil]; - else - [super dismissModalViewControllerAnimated:YES]; +@end +/* }}} */ +/* Sections Controller {{{ */ +@interface SectionsController : CyteViewController < + UITableViewDataSource, + UITableViewDelegate +> { + _transient Database *database_; + _H sections_; + _H filtered_; + _H list_; + _H key_; + _transient Source *source_; } -- (void) unloadData { - [super unloadData]; +- (id) initWithDatabase:(Database *)database source:(Source *)source; +- (void) editButtonClicked; - for (UINavigationController *controller in [self viewControllers]) - [controller unloadData]; +@end - if (UIViewController *selected = [self selectedViewController]) - [selected reloadData]; +@implementation SectionsController - if (UIViewController *unselected = [self unselectedViewController]) { - [unselected unloadData]; - [unselected reloadData]; +- (NSURL *) navigationURL { + if (source_ != nil) { + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sections/%@", [key_ stringByAddingPercentEscapesIncludingReserved]]]; + } else { + return [NSURL URLWithString:@"cydia://sections"]; } } -- (void) dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; +- (NSString *)defaultTitle { + if (source_ != nil) { + return [source_ label]; + } else { + return UCLocalize("SECTIONS"); + } +} - [super dealloc]; +- (void) updateNavigationItem { + [[self navigationItem] setTitle:[self isEditing] ? UCLocalize("SECTION_VISIBILITY") : [self defaultTitle]]; + if ([sections_ count] == 0) { + [[self navigationItem] setRightBarButtonItem:nil]; + } else { + [[self navigationItem] setRightBarButtonItem:[[UIBarButtonItem alloc] + initWithBarButtonSystemItem:([self isEditing] ? UIBarButtonSystemItemDone : UIBarButtonSystemItemEdit) + target:self + action:@selector(editButtonClicked) + ] animated:([[self navigationItem] rightBarButtonItem] != nil)]; + } } -- (id) initWithDatabase:(Database *)database { - if ((self = [super init]) != nil) { - database_ = database; - [self setDelegate:self]; +- (void) setEditing:(BOOL)editing animated:(BOOL)animated { + [super setEditing:editing animated:animated]; - [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; + if (editing) + [list_ reloadData]; + else + [delegate_ updateData]; - refreshbar_ = [[[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self] autorelease]; - } return self; + [self updateNavigationItem]; } -- (void) setUpdate:(NSDate *)date { - [self beginUpdate]; +- (void) viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; } -- (void) beginUpdate { - [(RefreshBar *) refreshbar_ start]; - [self dropBar:YES]; - - [updatedelegate_ retainNetworkActivityIndicator]; - updating_ = true; - - [NSThread - detachNewThreadSelector:@selector(performUpdate) - toTarget:self - withObject:nil - ]; +- (void) viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self setEditing:NO]; } -- (void) performUpdate { - NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]); +- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath { + Section *section = nil; + int index = [indexPath row]; + if (![self isEditing]) { + index -= 1; + if (index >= 0) + section = [filtered_ objectAtIndex:index]; + } else { + section = [sections_ objectAtIndex:index]; + } + return section; +} - Status status; - status.setDelegate(self); - [database_ updateWithStatus:status]; +- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if ([self isEditing]) + return [sections_ count]; + else + return [filtered_ count] + 1; +} - [self - performSelectorOnMainThread:@selector(completeUpdate) - withObject:nil - waitUntilDone:NO - ]; +/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 45.0f; +}*/ - [pool release]; -} +- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *reuseIdentifier = @"SectionCell"; -- (void) stopUpdateWithSelector:(SEL)selector { - updating_ = false; - [updatedelegate_ releaseNetworkActivityIndicator]; + SectionCell *cell = (SectionCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; + if (cell == nil) + cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease]; - [self raiseBar:YES]; - [refreshbar_ stop]; + [cell setSection:[self sectionAtIndexPath:indexPath] editing:[self isEditing]]; - [updatedelegate_ performSelector:selector withObject:nil afterDelay:0]; + return cell; } -- (void) completeUpdate { - if (!updating_) +- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if ([self isEditing]) return; - [self stopUpdateWithSelector:@selector(reloadData)]; -} -- (void) cancelUpdate { - [self stopUpdateWithSelector:@selector(updateDataAndLoad)]; -} + Section *section = [self sectionAtIndexPath:indexPath]; -- (void) cancelPressed { - [self cancelUpdate]; -} + SectionController *controller = [[[SectionController alloc] + initWithDatabase:database_ + section:[section name] + source:source_ + ] autorelease]; + [controller setDelegate:delegate_]; -- (BOOL) updating { - return updating_; + [[self navigationController] pushViewController:controller animated:YES]; } -- (void) addProgressEvent:(CydiaProgressEvent *)event { - [refreshbar_ setPrompt:[event compoundMessage]]; +- (void) loadView { + list_ = [[[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]; + [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [list_ setRowHeight:45.0f]; + [(UITableView *) list_ setDataSource:self]; + [list_ setDelegate:self]; + [self setView:list_]; } -- (bool) isProgressCancelled { - return !updating_; -} +- (void) viewDidLoad { + [super viewDidLoad]; -- (void) setProgressCancellable:(NSNumber *)cancellable { - [refreshbar_ setCancellable:(updating_ && [cancellable boolValue])]; + [[self navigationItem] setTitle:[self defaultTitle]]; } -- (void) setProgressPercent:(NSNumber *)percent { - [refreshbar_ setProgress:[percent floatValue]]; -} +- (void) releaseSubviews { + list_ = nil; -- (void) setProgressStatus:(NSDictionary *)status { - if (status != nil) - [self setProgressPercent:[status objectForKey:@"Percent"]]; -} + sections_ = nil; + filtered_ = nil; -- (void) setUpdateDelegate:(id)delegate { - updatedelegate_ = delegate; + [super releaseSubviews]; } -- (UIView *) transitionView { - if ([self respondsToSelector:@selector(_transitionView)]) - return [self _transitionView]; - else - return MSHookIvar(self, "_viewControllerTransitionView"); +- (id) initWithDatabase:(Database *)database source:(Source *)source { + if ((self = [super init]) != nil) { + database_ = database; + source_ = source; + key_ = [source_ key]; + } return self; } -- (void) dropBar:(BOOL)animated { - if (dropped_) - return; - dropped_ = true; - - UIView *transition([self transitionView]); - [[self view] addSubview:refreshbar_]; - - CGRect barframe([refreshbar_ frame]); - - if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_0) // XXX: _UIApplicationLinkedOnOrAfter(4) - barframe.origin.y = CYStatusBarHeight(); - else - barframe.origin.y = 0; - - [refreshbar_ setFrame:barframe]; - - if (animated) - [UIView beginAnimations:nil context:NULL]; - - CGRect viewframe = [transition frame]; - viewframe.origin.y += barframe.size.height; - viewframe.size.height -= barframe.size.height; - [transition setFrame:viewframe]; - - if (animated) - [UIView commitAnimations]; +- (void) reloadData { + source_ = [database_ sourceWithKey:key_]; + key_ = [source_ key]; - // Ensure bar has the proper width for our view, it might have changed - barframe.size.width = viewframe.size.width; - [refreshbar_ setFrame:barframe]; -} + [[self navigationItem] setTitle:[source_ label]]; -- (void) raiseBar:(BOOL)animated { - if (!dropped_) - return; - dropped_ = false; + [super reloadData]; - UIView *transition([self transitionView]); - [refreshbar_ removeFromSuperview]; + NSArray *packages = [database_ packages]; - CGRect barframe([refreshbar_ frame]); + sections_ = [NSMutableArray arrayWithCapacity:16]; + filtered_ = [NSMutableArray arrayWithCapacity:16]; - if (animated) - [UIView beginAnimations:nil context:NULL]; + NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]); - CGRect viewframe = [transition frame]; - viewframe.origin.y -= barframe.size.height; - viewframe.size.height += barframe.size.height; - [transition setFrame:viewframe]; + _trace(); + for (Package *package in packages) { + // Ignore packages from other sources (but allow all without a source). + if (source_ != nil && ![package isVisibleInSource:source_]) continue; - if (animated) - [UIView commitAnimations]; -} + NSString *name([package section]); + NSString *key(name == nil ? @"" : name); -- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { - bool dropped(dropped_); + Section *section; - if (dropped) - [self raiseBar:NO]; + _profile(SectionsView$reloadData$Section) + section = [sections objectForKey:key]; + if (section == nil) { + _profile(SectionsView$reloadData$Section$Allocate) + section = [[[Section alloc] initWithName:key localize:YES] autorelease]; + [sections setObject:section forKey:key]; + _end + } + _end - [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; + [section addToCount]; - if (dropped) - [self dropBar:NO]; -} + _profile(SectionsView$reloadData$Filter) + if (![package valid] || ![package visible]) + continue; + _end -- (void) statusBarFrameChanged:(NSNotification *)notification { - if (dropped_) { - [self raiseBar:NO]; - [self dropBar:NO]; + [section addToRow]; } -} + _trace(); -@end -/* }}} */ + [sections_ addObjectsFromArray:[sections allValues]]; -/* Cydia Navigation Controller Implementation {{{ */ -@implementation UINavigationController (Cydia) + [sections_ sortUsingSelector:@selector(compareByLocalized:)]; -- (NSArray *) navigationURLCollection { - NSMutableArray *stack([NSMutableArray array]); + for (Section *section in (id) sections_) { + size_t count([section row]); + if (count == 0) + continue; - for (CyteViewController *controller in [self viewControllers]) { - NSString *url = [[controller navigationURL] absoluteString]; - if (url != nil) - [stack addObject:url]; + section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease]; + [section setCount:count]; + [filtered_ addObject:section]; } - return stack; -} - -- (void) reloadData { - [super reloadData]; - - UIViewController *visible([self visibleViewController]); - if (visible != nil) - [visible reloadData]; - - // on the iPad, this view controller is ALSO visible. :( - if (IsWildcat_) - if (UIViewController *top = [self topViewController]) - if (top != visible) - [top reloadData]; + [self updateNavigationItem]; + [list_ reloadData]; + _trace(); } -- (void) unloadData { - for (CyteViewController *page in [self viewControllers]) - [page unloadData]; - - [super unloadData]; +- (void) editButtonClicked { + [self setEditing:![self isEditing] animated:YES]; } @end /* }}} */ -/* Cydia:// Protocol {{{ */ -@interface CydiaURLProtocol : NSURLProtocol { +/* Changes Controller {{{ */ +@interface ChangesPackageListDataSource : FilteredPackageListDataSource { + unsigned upgrades_; } @end -@implementation CydiaURLProtocol +@implementation ChangesPackageListDataSource -+ (BOOL) canInitWithRequest:(NSURLRequest *)request { - NSURL *url([request URL]); - if (url == nil) - return NO; +- (NSMutableArray *) _reloadPackages { +@synchronized (database_) { + era_ = [database_ era]; + NSArray *packages([database_ packages]); - NSString *scheme([[url scheme] lowercaseString]); - if (scheme != nil && [scheme isEqualToString:@"cydia"]) - return YES; - if ([[url absoluteString] hasPrefix:@"about:cydia-"]) - return YES; + NSMutableArray *filtered([NSMutableArray arrayWithCapacity:[packages count]]); - return NO; -} + _trace(); + _profile(ChangesPackageListController$_reloadPackages$Filter) + for (Package *package in packages) + if ([package upgradableAndEssential:YES] || [package visible]) + CFArrayAppendValue((CFMutableArrayRef) filtered, package); + _end + _trace(); + _profile(ChangesPackageListController$_reloadPackages$radixSort) + [filtered radixSortUsingFunction:reinterpret_cast(&PackageChangesRadix) withContext:NULL]; + _end + _trace(); -+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request { - return request; -} + return filtered; +} } -- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request { - id client([self client]); - if (icon == nil) - [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]]; - else { - NSData *data(UIImagePNGRepresentation(icon)); +- (void) reloadData { + NSArray *packages = [self _reloadPackages]; - NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]); - [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; - [client URLProtocol:self didLoadData:data]; - [client URLProtocolDidFinishLoading:self]; - } -} +@synchronized (database_) { + packages_ = packages; + sections_ = [NSMutableArray arrayWithCapacity:16]; -- (void) startLoading { - id client([self client]); - NSURLRequest *request([self request]); + Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease]; + Section *ignored = nil; + Section *section = nil; + time_t last = 0; - NSURL *url([request URL]); - NSString *href([url absoluteString]); - NSString *scheme([[url scheme] lowercaseString]); + upgrades_ = 0; + bool unseens = false; - NSString *path; + CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle)); - if ([scheme isEqualToString:@"cydia"]) - path = [href substringFromIndex:8]; - else if ([scheme isEqualToString:@"about"]) - path = [href substringFromIndex:12]; - else _assert(false); + for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) { + Package *package = [packages_ objectAtIndex:offset]; - NSRange slash([path rangeOfString:@"/"]); + BOOL uae = [package upgradableAndEssential:YES]; - NSString *command; - if (slash.location == NSNotFound) { - command = path; - path = nil; - } else { - command = [path substringToIndex:slash.location]; - path = [path substringFromIndex:(slash.location + 1)]; + if (!uae) { + unseens = true; + time_t seen([package seen]); + + if (section == nil || last != seen) { + last = seen; + + NSString *name; + name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]); + [name autorelease]; + + _profile(ChangesPackageListController$reloadData$Allocate) + name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name]; + section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease]; + [sections_ addObject:section]; + _end + } + + [section addToCount]; + } else if ([package ignored]) { + if (ignored == nil) { + ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease]; + } + [ignored addToCount]; + } else { + ++upgrades_; + [upgradable addToCount]; + } } + _trace(); - Database *database([Database sharedInstance]); + CFRelease(formatter); - if ([command isEqualToString:@"package-icon"]) { - if (path == nil) - goto fail; - path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - Package *package([database packageWithName:path]); - if (package == nil) - goto fail; - [package parse]; - UIImage *icon([package icon]); - [self _returnPNGWithImage:icon forRequest:request]; - } else if ([command isEqualToString:@"uikit-image"]) { - if (path == nil) - goto fail; - path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - UIImage *icon(_UIImageWithName(path)); - [self _returnPNGWithImage:icon forRequest:request]; - } else if ([command isEqualToString:@"section-icon"]) { - if (path == nil) - goto fail; - path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, [path stringByReplacingOccurrencesOfString:@" " withString:@"_"]]]); - if (icon == nil) - icon = [UIImage applicationImageNamed:@"unknown.png"]; - [self _returnPNGWithImage:icon forRequest:request]; - } else fail: { - [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]]; + if (unseens) { + Section *last = [sections_ lastObject]; + size_t count = [last count]; + [packages_ removeObjectsInRange:NSMakeRange([packages_ count] - count, count)]; + [sections_ removeLastObject]; } + + if ([ignored count] != 0) + [sections_ insertObject:ignored atIndex:0]; + if (upgrades_ != 0) + [sections_ insertObject:upgradable atIndex:0]; +} } + +- (int) upgrades { + return upgrades_; } -- (void) stopLoading { ++ (BOOL) supportsSearch { + return NO; } @end -/* }}} */ -/* Section Controller {{{ */ -@interface SectionController : FilteredPackageListController { +@interface ChangesPackageListController : FilteredPackageListController < + CyteWebViewDelegate +> { + _H dickbar_; _H indirect_; _H cydia_; - _H section_; - _H key_; - _transient Source *source_; - std::vector< _H > promoted_; } -- (id) initWithDatabase:(Database *)database section:(NSString *)section source:(Source *)source; +- (id) initWithDatabase:(Database *)database; @end -@implementation SectionController - -- (NSURL *) referrerURL { - NSMutableString *path = [NSMutableString string]; - - if (source_ != nil) { - [path appendString:[NSString stringWithFormat:@"sources/%@", [key_ stringByAddingPercentEscapesIncludingReserved]]]; - } else { - [path appendString:@"sections"]; - } - - [path appendString:@"/"]; +@implementation ChangesPackageListController - if (section_ != nil) { - [path appendString:[NSString stringWithFormat:@"%@", [section_ stringByAddingPercentEscapesIncludingReserved]]]; - } else { - [path appendString:@"all"]; - } +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://changes"]; +} - return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/%@", UI_, path]]; +- (void) viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; } -- (NSURL *) navigationURL { - NSMutableString *path = [NSMutableString string]; +- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path { + Package *package([datasource_ packageAtIndexPath:path]); + CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id] withReferrer:[NSString stringWithFormat:@"%@/#!/changes/", UI_]] autorelease]); + [view setDelegate:delegate_]; + [[self navigationController] pushViewController:view animated:YES]; +} - if (source_ != nil) { - [path appendString:[NSString stringWithFormat:@"sources/%@", [key_ stringByAddingPercentEscapesIncludingReserved]]]; - } else { - [path appendString:@"sections"]; - } ++ (Class) dataSourceClass { + return [ChangesPackageListDataSource class]; +} - [path appendString:@"/"]; +- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { + NSString *context([alert context]); - if (section_ != nil) { - [path appendString:[NSString stringWithFormat:@"%@", [section_ stringByAddingPercentEscapesIncludingReserved]]]; + if ([context isEqualToString:@"norefresh"]) + [alert dismissWithClickedButtonIndex:-1 animated:YES]; +} + +- (void) refreshButtonClicked { + if (IsReachable("cydia.saurik.com")) { + [delegate_ beginUpdate]; + [[self navigationItem] setLeftBarButtonItem:nil animated:YES]; } else { - [path appendString:@"all"]; + UIAlertView *alert = [[[UIAlertView alloc] + initWithTitle:[NSString stringWithFormat:Colon_, Error_, UCLocalize("REFRESH")] + message:@"Host Unreachable" // XXX: Localize + delegate:self + cancelButtonTitle:UCLocalize("OK") + otherButtonTitles:nil + ] autorelease]; + + [alert setContext:@"norefresh"]; + [alert show]; } +} - return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://%@", path]]; +- (void) upgradeButtonClicked { + [delegate_ distUpgrade]; + [[self navigationItem] setRightBarButtonItem:nil animated:YES]; } -- (id) initWithDatabase:(Database *)database section:(NSString *)name source:(Source *)source { - NSString *title; - if (name == nil) - title = UCLocalize("ALL_PACKAGES"); - else if (![name isEqual:@""]) - title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"]; - else - title = UCLocalize("NO_SECTION"); +- (void) loadView { + [super loadView]; - if ((self = [super initWithDatabase:database title:title]) != nil) { - indirect_ = [[[IndirectDelegate alloc] initWithDelegate:self] autorelease]; - cydia_ = [[[CydiaObject alloc] initWithDelegate:indirect_] autorelease]; + if (AprilFools_ && kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_0) { + CGRect dickframe([[self view] bounds]); + dickframe.size.height = 44; - [datasource_ addFilter:@"section" withSelector:@selector(isVisibleInSection:) priority:kPackageListFilterPriorityHigh object:name]; - [datasource_ addFilter:@"source" withSelector:@selector(isVisibleInSource:) priority:kPackageListFilterPriorityHigh object:source]; + dickbar_ = [[[CyteWebView alloc] initWithFrame:dickframe] autorelease]; + [dickbar_ setDelegate:self]; + [[self view] addSubview:dickbar_]; - section_ = name; - source_ = source; - key_ = [source key]; - } return self; -} + [dickbar_ setBackgroundColor:[UIColor clearColor]]; + [dickbar_ setScalesPageToFit:YES]; -/*- (NSInteger) numberOfSectionsInTableView:(UITableView *)list { - return [super numberOfSectionsInTableView:list] + 1; -} + UIWebDocumentView *document([dickbar_ _documentView]); + [document setBackgroundColor:[UIColor clearColor]]; + [document setDrawsBackground:NO]; -- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section { - return section == 0 ? nil : [super tableView:list titleForHeaderInSection:(section - 1)]; -} + WebView *webview([document webView]); + [webview setShouldUpdateWhileOffscreen:NO]; -- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section { - return section == 0 ? promoted_.size() : [super tableView:list numberOfRowsInSection:(section - 1)]; -} + UIScrollView *scroller([dickbar_ scrollView]); + [scroller setScrollingEnabled:NO]; + [scroller setFixedBackgroundPattern:YES]; + [scroller setBackgroundColor:[UIColor clearColor]]; -+ (NSIndexPath *) adjustedIndexPath:(NSIndexPath *)path { - return [NSIndexPath indexPathForRow:[path row] inSection:([path section] - 1)]; -} + WebPreferences *preferences([webview preferences]); + [preferences setCacheModel:WebCacheModelDocumentBrowser]; + [preferences setJavaScriptCanOpenWindowsAutomatically:YES]; + [preferences setOfflineWebApplicationCacheEnabled:YES]; -- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path { - if ([path section] != 0) - return [super tableView:table cellForRowAtIndexPath:[SectionController adjustedIndexPath:path]]; + [dickbar_ loadRequest:[NSURLRequest + requestWithURL:[Diversion divertURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/dickbar/", UI_]]] + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:120 + ]]; - return promoted_[[path row]]; -} + UIEdgeInsets inset = {44, 0, 0, 0}; + [list_ setContentInset:inset]; -- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path { - if ([path section] != 0) - return [super tableView:table didSelectRowAtIndexPath:[SectionController adjustedIndexPath:path]]; + [dickbar_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; + } } -- (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { - NSInteger section([super tableView:tableView sectionForSectionIndexTitle:title atIndex:index]); - return section == 0 ? 0 : section + 1; -}*/ - - (void) webView:(WebView *)view decidePolicyForNewWindowAction:(NSDictionary *)action request:(NSURLRequest *)request newFrameName:(NSString *)frame decisionListener:(id)listener { NSURL *url([request URL]); if (url == nil) @@ -7305,808 +7169,1029 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi [CydiaWebViewController didClearWindowObject:window forFrame:frame withCydia:cydia_]; } -- (void) loadView { - [super loadView]; - - // XXX: this code is horrible. I mean, wtf Jay? - if (ShowPromoted_ && [[Metadata_ objectForKey:@"ShowPromoted"] boolValue]) { - promoted_.resize(1); - - for (unsigned i(0); i != promoted_.size(); ++i) { - CyteWebViewTableViewCell *promoted([CyteWebViewTableViewCell cellWithRequest:[NSURLRequest - requestWithURL:[Diversion divertURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/sectionhead/%u/%@", - UI_, i, section_ == nil ? @"" : [section_ stringByAddingPercentEscapesIncludingReserved]] - ]] - - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:120 - ]]); - - [promoted setDelegate:self]; - promoted_[i] = promoted; - } - } -} - - (void) setDelegate:(id)delegate { [super setDelegate:delegate]; [cydia_ setDelegate:delegate]; } - (void) releaseSubviews { - promoted_.clear(); + dickbar_ = nil; + [super releaseSubviews]; } -- (void)reloadData { - source_ = [database_ sourceWithKey:key_]; - key_ = [source_ key]; - [datasource_ setObject:source_ forFilter:@"source"]; +- (id) initWithDatabase:(Database *)database { + if ((self = [super initWithDatabase:database title:(AprilFools_ ? @"Timeline" : UCLocalize("CHANGES"))]) != nil) { + indirect_ = [[[IndirectDelegate alloc] initWithDelegate:self] autorelease]; + cydia_ = [[[CydiaObject alloc] initWithDelegate:indirect_] autorelease]; + database_ = database; + } return self; +} +- (void) reloadData { [super reloadData]; + + [[self navigationItem] setRightBarButtonItem:([datasource_ upgrades] == 0 ? nil : [[[UIBarButtonItem alloc] + initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", [datasource_ upgrades]]] + style:UIBarButtonItemStylePlain + target:self + action:@selector(upgradeButtonClicked) + ] autorelease]) animated:YES]; + + [[self navigationItem] setLeftBarButtonItem:([delegate_ updating] ? nil : [[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("REFRESH") + style:UIBarButtonItemStylePlain + target:self + action:@selector(refreshButtonClicked) + ] autorelease]) animated:YES]; + + PrintTimes(); } @end /* }}} */ -/* Sections Controller {{{ */ -@interface SectionsController : CyteViewController < +/* Package Settings Controller {{{ */ +@interface PackageSettingsController : CyteViewController < UITableViewDataSource, UITableViewDelegate > { _transient Database *database_; - _H sections_; - _H filtered_; - _H list_; - _H key_; - _transient Source *source_; + _H name_; + _H package_; + _H table_; + _H subscribedSwitch_; + _H ignoredSwitch_; + _H subscribedCell_; + _H ignoredCell_; } -- (id) initWithDatabase:(Database *)database source:(Source *)source; -- (void) editButtonClicked; +- (id) initWithDatabase:(Database *)database package:(NSString *)package; @end -@implementation SectionsController +@implementation PackageSettingsController - (NSURL *) navigationURL { - if (source_ != nil) { - return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sections/%@", [key_ stringByAddingPercentEscapesIncludingReserved]]]; - } else { - return [NSURL URLWithString:@"cydia://sections"]; - } + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/settings", (id) name_]]; } -- (NSString *)defaultTitle { - if (source_ != nil) { - return [source_ label]; - } else { - return UCLocalize("SECTIONS"); - } +- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { + if (package_ == nil) + return 0; + + if ([package_ installed] == nil) + return 1; + else + return 2; } -- (void) updateNavigationItem { - [[self navigationItem] setTitle:[self isEditing] ? UCLocalize("SECTION_VISIBILITY") : [self defaultTitle]]; - if ([sections_ count] == 0) { - [[self navigationItem] setRightBarButtonItem:nil]; - } else { - [[self navigationItem] setRightBarButtonItem:[[UIBarButtonItem alloc] - initWithBarButtonSystemItem:([self isEditing] ? UIBarButtonSystemItemDone : UIBarButtonSystemItemEdit) - target:self - action:@selector(editButtonClicked) - ] animated:([[self navigationItem] rightBarButtonItem] != nil)]; - } +- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (package_ == nil) + return 0; + + // both sections contain just one item right now. + return 1; } -- (void) setEditing:(BOOL)editing animated:(BOOL)animated { - [super setEditing:editing animated:animated]; +- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return nil; +} - if (editing) - [list_ reloadData]; +- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { + if (section == 0) + return UCLocalize("SHOW_ALL_CHANGES_EX"); else + return UCLocalize("IGNORE_UPGRADES_EX"); +} + +- (void) onSubscribed:(id)control { + bool value([control isOn]); + if (package_ == nil) + return; + if ([package_ setSubscribed:value]) [delegate_ updateData]; - - [self updateNavigationItem]; } -- (void) viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; -} +- (void) _updateIgnored { + const char *package([name_ UTF8String]); + bool on([ignoredSwitch_ isOn]); -- (void) viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - [self setEditing:NO]; -} + pid_t pid(ExecFork()); + if (pid == 0) { + FILE *dpkg(popen("dpkg --set-selections", "w")); + fwrite(package, strlen(package), 1, dpkg); -- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath { - Section *section = nil; - int index = [indexPath row]; - if (![self isEditing]) { - index -= 1; - if (index >= 0) - section = [filtered_ objectAtIndex:index]; - } else { - section = [sections_ objectAtIndex:index]; + if (on) + fwrite(" hold\n", 6, 1, dpkg); + else + fwrite(" install\n", 9, 1, dpkg); + + pclose(dpkg); + + exit(0); + _assert(false); } - return section; -} -- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if ([self isEditing]) - return [sections_ count]; - else - return [filtered_ count] + 1; + ReapZombie(pid); } -/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - return 45.0f; -}*/ +- (void) onIgnored:(id)control { + NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(_updateIgnored)]]); + [invocation setTarget:self]; + [invocation setSelector:@selector(_updateIgnored)]; + + [delegate_ reloadDataWithInvocation:invocation]; +} - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *reuseIdentifier = @"SectionCell"; + if (package_ == nil) + return nil; - SectionCell *cell = (SectionCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; - if (cell == nil) - cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease]; + switch ([indexPath section]) { + case 0: return subscribedCell_; + case 1: return ignoredCell_; - [cell setSection:[self sectionAtIndexPath:indexPath] editing:[self isEditing]]; + _nodefault + } - return cell; + return nil; } -- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if ([self isEditing]) - return; +- (void) loadView { + UIView *view([[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]); + [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)]; + [self setView:view]; - Section *section = [self sectionAtIndexPath:indexPath]; + table_ = [[[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped] autorelease]; + [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [(UITableView *) table_ setDataSource:self]; + [table_ setDelegate:self]; + [view addSubview:table_]; - SectionController *controller = [[[SectionController alloc] - initWithDatabase:database_ - section:[section name] - source:source_ - ] autorelease]; - [controller setDelegate:delegate_]; + subscribedSwitch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)] autorelease]; + [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged]; - [[self navigationController] pushViewController:controller animated:YES]; -} + ignoredSwitch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)] autorelease]; + [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged]; -- (void) loadView { - list_ = [[[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]; - [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [list_ setRowHeight:45.0f]; - [(UITableView *) list_ setDataSource:self]; - [list_ setDelegate:self]; - [self setView:list_]; + subscribedCell_ = [[[UITableViewCell alloc] init] autorelease]; + [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")]; + [subscribedCell_ setAccessoryView:subscribedSwitch_]; + [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone]; + + ignoredCell_ = [[[UITableViewCell alloc] init] autorelease]; + [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")]; + [ignoredCell_ setAccessoryView:ignoredSwitch_]; + [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone]; } - (void) viewDidLoad { [super viewDidLoad]; - [[self navigationItem] setTitle:[self defaultTitle]]; + [[self navigationItem] setTitle:UCLocalize("SETTINGS")]; } - (void) releaseSubviews { - list_ = nil; - - sections_ = nil; - filtered_ = nil; + ignoredCell_ = nil; + subscribedCell_ = nil; + table_ = nil; + ignoredSwitch_ = nil; + subscribedSwitch_ = nil; [super releaseSubviews]; } -- (id) initWithDatabase:(Database *)database source:(Source *)source { +- (id) initWithDatabase:(Database *)database package:(NSString *)package { if ((self = [super init]) != nil) { database_ = database; - source_ = source; - key_ = [source_ key]; + name_ = package; } return self; } - (void) reloadData { - source_ = [database_ sourceWithKey:key_]; - key_ = [source_ key]; - - [[self navigationItem] setTitle:[source_ label]]; - [super reloadData]; - NSArray *packages = [database_ packages]; + package_ = [database_ packageWithName:name_]; - sections_ = [NSMutableArray arrayWithCapacity:16]; - filtered_ = [NSMutableArray arrayWithCapacity:16]; + if (package_ != nil) { + [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO]; + [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO]; + } // XXX: what now, G? - NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]); + [table_ reloadData]; +} - _trace(); - for (Package *package in packages) { - // Ignore packages from other sources (but allow all without a source). - if (source_ != nil && ![package isVisibleInSource:source_]) continue; +@end +/* }}} */ - NSString *name([package section]); - NSString *key(name == nil ? @"" : name); +/* Installed Controller {{{ */ +@interface InstalledController : FilteredPackageListController { + BOOL expert_; +} - Section *section; +- (id) initWithDatabase:(Database *)database; - _profile(SectionsView$reloadData$Section) - section = [sections objectForKey:key]; - if (section == nil) { - _profile(SectionsView$reloadData$Section$Allocate) - section = [[[Section alloc] initWithName:key localize:YES] autorelease]; - [sections setObject:section forKey:key]; - _end - } - _end +- (void) updateRoleButton; - [section addToCount]; +@end - _profile(SectionsView$reloadData$Filter) - if (![package valid] || ![package visible]) - continue; - _end +@implementation InstalledController - [section addToRow]; - } - _trace(); +- (NSURL *) referrerURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/installed/", UI_]]; +} - [sections_ addObjectsFromArray:[sections allValues]]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://installed"]; +} - [sections_ sortUsingSelector:@selector(compareByLocalized:)]; +- (id) initWithDatabase:(Database *)database { + if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") ]) != nil) { + [datasource_ addFilter:@"installed" withSelector:@selector(isInstalledAndUnfiltered:) priority:kPackageListFilterPriorityHigh object:[NSNumber numberWithBool:YES]]; + [self updateRoleButton]; + } return self; +} - for (Section *section in (id) sections_) { - size_t count([section row]); - if (count == 0) - continue; +- (void) updateRoleButton { + if (Role_ != nil && ![Role_ isEqualToString:@"Developer"]) + [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE")) + style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain) + target:self + action:@selector(roleButtonClicked) + ] autorelease]]; +} - section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease]; - [section setCount:count]; - [filtered_ addObject:section]; - } +- (void) roleButtonClicked { + [datasource_ setObject:[NSNumber numberWithBool:expert_] forFilter:@"installed"]; + [self reloadData]; + expert_ = !expert_; - [self updateNavigationItem]; - [list_ reloadData]; - _trace(); + [self updateRoleButton]; } -- (void) editButtonClicked { - [self setEditing:![self isEditing] animated:YES]; +@end +/* }}} */ + +/* Confirmation Controller {{{ */ +bool DepSubstrate(const pkgCache::VerIterator &iterator) { + if (!iterator.end()) + for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) { + if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends) + continue; + pkgCache::PkgIterator package(dep.TargetPkg()); + if (package.end()) + continue; + if (strcmp(package.Name(), "mobilesubstrate") == 0) + return true; + } + + return false; } +@protocol ConfirmationControllerDelegate +- (void) clearQueue; +- (void) confirmWithNavigationController:(UINavigationController *)navigation; @end -/* }}} */ -/* Changes Controller {{{ */ -@interface ChangesPackageListDataSource : FilteredPackageListDataSource { - unsigned upgrades_; +@interface ConfirmationControllerDataSource : FilteredPackageListDataSource { + _H issues_; + BOOL removeEssential_; + BOOL substrate_; } +@property (nonatomic, readonly) NSMutableArray *issues; +@property (nonatomic, readonly, assign, getter=willRemoveEssential) BOOL removeEssential; +@property (nonatomic, readonly, assign) BOOL substrate; + @end -@implementation ChangesPackageListDataSource +@implementation ConfirmationControllerDataSource + +- (int) numberOfSectionsInTableView:(UITableView *)table { +int n = [super numberOfSectionsInTableView:table]; + NSLog(@"nubmer of sectoins: %d"); + return n; +} + +- (NSMutableArray *)issues { + return issues_; +} + +- (BOOL)willRemoveEssential { + return removeEssential_; +} + +- (BOOL)substrate { + return substrate_; +} + ++ (BOOL)supportsSearch { + return NO; +} + +- (void)reloadData { + bool remove(false); -- (NSMutableArray *) _reloadPackages { -@synchronized (database_) { era_ = [database_ era]; + + pkgCacheFile &cache([database_ cache]); NSArray *packages([database_ packages]); + pkgDepCache::Policy *policy([database_ policy]); - NSMutableArray *filtered([NSMutableArray arrayWithCapacity:[packages count]]); + issues_ = [NSMutableArray arrayWithCapacity:4]; - _trace(); - _profile(ChangesPackageListController$_reloadPackages$Filter) - for (Package *package in packages) - if ([package upgradableAndEssential:YES] || [package visible]) - CFArrayAppendValue((CFMutableArrayRef) filtered, package); - _end - _trace(); - _profile(ChangesPackageListController$_reloadPackages$radixSort) - [filtered radixSortUsingFunction:reinterpret_cast(&PackageChangesRadix) withContext:NULL]; - _end - _trace(); + Section *installsSection = [[[Section alloc] initWithName:@"Install" localized:UCLocalize("INSTALLS")] autorelease]; + Section *reinstallsSection = [[[Section alloc] initWithName:@"Reinstall" localized:UCLocalize("REINSTALLS")] autorelease]; + Section *upgradesSection = [[[Section alloc] initWithName:@"Upgrade" localized:UCLocalize("UPGRADES")] autorelease]; + Section *downgradesSection = [[[Section alloc] initWithName:@"Downgrade" localized:UCLocalize("DOWNGRADES")] autorelease]; + Section *removesSection = [[[Section alloc] initWithName:@"Remove" localized:UCLocalize("REMOVES")] autorelease]; - return filtered; -} } + NSMutableArray *installs = [NSMutableArray arrayWithCapacity:16]; + NSMutableArray *reinstalls = [NSMutableArray arrayWithCapacity:16]; + NSMutableArray *upgrades = [NSMutableArray arrayWithCapacity:16]; + NSMutableArray *downgrades = [NSMutableArray arrayWithCapacity:16]; + NSMutableArray *removes = [NSMutableArray arrayWithCapacity:16]; -- (void) reloadData { - NSArray *packages = [self _reloadPackages]; + for (Package *package in packages) { + pkgCache::PkgIterator iterator([package iterator]); + NSString *name([package id]); -@synchronized (database_) { - packages_ = packages; - sections_ = [NSMutableArray arrayWithCapacity:16]; + if ([package broken]) { + NSMutableArray *reasons([NSMutableArray arrayWithCapacity:4]); - Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease]; - Section *ignored = nil; - Section *section = nil; - time_t last = 0; + [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys: + name, @"package", + reasons, @"reasons", + nil]]; - upgrades_ = 0; - bool unseens = false; + pkgCache::VerIterator ver(cache[iterator].InstVerIter(cache)); + if (ver.end()) + continue; - CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle)); + for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) { + pkgCache::DepIterator start; + pkgCache::DepIterator end; + dep.GlobOr(start, end); // ++dep - for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) { - Package *package = [packages_ objectAtIndex:offset]; + if (!cache->IsImportantDep(end)) + continue; + if ((cache[end] & pkgDepCache::DepGInstall) != 0) + continue; - BOOL uae = [package upgradableAndEssential:YES]; + NSMutableArray *clauses([NSMutableArray arrayWithCapacity:4]); - if (!uae) { - unseens = true; - time_t seen([package seen]); + [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithUTF8String:start.DepType()], @"relationship", + clauses, @"clauses", + nil]]; - if (section == nil || last != seen) { - last = seen; + _forever { + NSString *reason, *installed((NSString *) [WebUndefined undefined]); + + pkgCache::PkgIterator target(start.TargetPkg()); + if (target->ProvidesList != 0) + reason = @"missing"; + else { + pkgCache::VerIterator ver(cache[target].InstVerIter(cache)); + if (!ver.end()) { + reason = @"installed"; + installed = [NSString stringWithUTF8String:ver.VerStr()]; + } else if (!cache[target].CandidateVerIter(cache).end()) + reason = @"uninstalled"; + else if (target->ProvidesList == 0) + reason = @"uninstallable"; + else + reason = @"virtual"; + } - NSString *name; - name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]); - [name autorelease]; + NSDictionary *version(start.TargetVer() == 0 ? [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithUTF8String:start.CompType()], @"operator", + [NSString stringWithUTF8String:start.TargetVer()], @"value", + nil]); + [clauses addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithUTF8String:start.TargetPkg().Name()], @"package", + version, @"version", + reason, @"reason", + installed, @"installed", + nil]]; - _profile(ChangesPackageListController$reloadData$Allocate) - name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name]; - section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease]; - [sections_ addObject:section]; - _end + // yes, seriously. (wtf?) + if (start == end) + break; + ++start; + } } + } - [section addToCount]; - } else if ([package ignored]) { - if (ignored == nil) { - ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease]; - } - [ignored addToCount]; + pkgDepCache::StateCache &state(cache[iterator]); + + static Pcre special_r("^(firmware$|gsc\\.|cy\\+)"); + + if (state.NewInstall()) { + [installs addObject:package]; + [installsSection addToCount]; + // XXX: } else if (state.Install()) { + } else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall) { + [reinstalls addObject:package]; + [reinstallsSection addToCount]; + // XXX: move before previous if + } else if (state.Upgrade()) { + [upgrades addObject:package]; + [upgradesSection addToCount]; + } else if (state.Downgrade()) { + [downgrades addObject:package]; + [downgradesSection addToCount]; + } else if (!state.Delete()) { + // XXX: _assert(state.Keep()); + continue; + } else if (special_r(name)) { + [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNull null], @"package", + [NSArray arrayWithObjects: + [NSDictionary dictionaryWithObjectsAndKeys: + @"Conflicts", @"relationship", + [NSArray arrayWithObjects: + [NSDictionary dictionaryWithObjectsAndKeys: + name, @"package", + [NSNull null], @"version", + @"installed", @"reason", + nil], + nil], @"clauses", + nil], + nil], @"reasons", + nil]]; } else { - ++upgrades_; - [upgradable addToCount]; + if ([package essential]) + remove = true; + [removes addObject:package]; + [removesSection addToCount]; } - } - _trace(); - - CFRelease(formatter); - - if (unseens) { - Section *last = [sections_ lastObject]; - size_t count = [last count]; - [packages_ removeObjectsInRange:NSMakeRange([packages_ count] - count, count)]; - [sections_ removeLastObject]; - } - - if ([ignored count] != 0) - [sections_ insertObject:ignored atIndex:0]; - if (upgrades_ != 0) - [sections_ insertObject:upgradable atIndex:0]; -} } - -- (int) upgrades { - return upgrades_; -} -+ (BOOL) supportsSearch { - return NO; + substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator)); + substrate_ |= DepSubstrate(iterator.CurrentVer()); + } + + /*sizes_ = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:[database_ fetcher].FetchNeeded()], @"downloading", + [NSNumber numberWithInteger:[database_ fetcher].PartialPresent()], @"resuming", + nil];*/ + + NSMutableArray *packagesList = [NSMutableArray arrayWithCapacity:64]; + size_t count = 0; + count += [installs count]; + [packagesList addObjectsFromArray:installs]; + for (size_t i = 0; i < count; i++) [reinstallsSection addToRow]; + count += [reinstalls count]; + [packagesList addObjectsFromArray:reinstalls]; + for (size_t i = 0; i < count; i++) [upgradesSection addToRow]; + count += [upgrades count]; + [packagesList addObjectsFromArray:upgrades]; + for (size_t i = 0; i < count; i++) [downgradesSection addToRow]; + count += [downgrades count]; + [packagesList addObjectsFromArray:downgrades]; + for (size_t i = 0; i < count; i++) [removesSection addToRow]; + [packagesList addObjectsFromArray:removes]; + packages_ = packagesList; + + sections_ = [NSMutableArray arrayWithCapacity:4]; + if ([installsSection count] > 0) [sections_ addObject:installsSection]; + if ([reinstallsSection count] > 0) [sections_ addObject:reinstallsSection]; + if ([upgradesSection count] > 0) [sections_ addObject:upgradesSection]; + if ([downgradesSection count] > 0) [sections_ addObject:downgradesSection]; + if ([removesSection count] > 0) [sections_ addObject:removesSection]; } @end -@interface ChangesPackageListController : FilteredPackageListController < - CyteWebViewDelegate -> { - _H dickbar_; - _H indirect_; - _H cydia_; +@interface ConfirmationController : FilteredPackageListController { + _H essential_; } - (id) initWithDatabase:(Database *)database; @end -@implementation ChangesPackageListController +@implementation ConfirmationController -- (NSURL *) navigationURL { - return [NSURL URLWithString:@"cydia://changes"]; ++ (Class) dataSourceClass { + return [ConfirmationControllerDataSource class]; } -- (void) viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; +- (void) complete { + if ([datasource_ substrate]) + RestartSubstrate_ = true; + [delegate_ confirmWithNavigationController:[self navigationController]]; } -- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path { - Package *package([datasource_ packageAtIndexPath:path]); - CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id] withReferrer:[NSString stringWithFormat:@"%@/#!/changes/", UI_]] autorelease]); - [view setDelegate:delegate_]; - [[self navigationController] pushViewController:view animated:YES]; +- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { + NSString *context([alert context]); + + if ([context isEqualToString:@"remove"]) { + if (button == [alert cancelButtonIndex]) + [self dismissModalViewControllerAnimated:YES]; + else if (button == [alert firstOtherButtonIndex]) { + [self performSelector:@selector(complete) withObject:nil afterDelay:0]; + } + + [alert dismissWithClickedButtonIndex:-1 animated:YES]; + } else if ([context isEqualToString:@"unable"]) { + [self dismissModalViewControllerAnimated:YES]; + [alert dismissWithClickedButtonIndex:-1 animated:YES]; + } } -+ (Class) dataSourceClass { - return [ChangesPackageListDataSource class]; +- (id) initWithDatabase:(Database *)database { + if ((self = [super initWithDatabase:database title:UCLocalize("QUEUE")]) != nil) { + } return self; } -- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { - NSString *context([alert context]); +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; - if ([context isEqualToString:@"norefresh"]) - [alert dismissWithClickedButtonIndex:-1 animated:YES]; + [self reloadData]; } -- (void) refreshButtonClicked { - if (IsReachable("cydia.saurik.com")) { - [delegate_ beginUpdate]; - [[self navigationItem] setLeftBarButtonItem:nil animated:YES]; +- (void)reloadData { + [super reloadData]; + + if (![datasource_ willRemoveEssential]) { + essential_ = nil; + } else if (Advanced_) { + NSString *parenthetical(UCLocalize("PARENTHETICAL")); + + essential_ = [[[UIAlertView alloc] + initWithTitle:UCLocalize("REMOVING_ESSENTIALS") + message:UCLocalize("REMOVING_ESSENTIALS_EX") + delegate:self + cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")] + otherButtonTitles: + [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")], + nil + ] autorelease]; + + [essential_ setContext:@"remove"]; + [essential_ setNumberOfRows:2]; } else { - UIAlertView *alert = [[[UIAlertView alloc] - initWithTitle:[NSString stringWithFormat:Colon_, Error_, UCLocalize("REFRESH")] - message:@"Host Unreachable" // XXX: Localize + essential_ = [[[UIAlertView alloc] + initWithTitle:UCLocalize("UNABLE_TO_COMPLY") + message:UCLocalize("UNABLE_TO_COMPLY_EX") delegate:self - cancelButtonTitle:UCLocalize("OK") + cancelButtonTitle:UCLocalize("OKAY") otherButtonTitles:nil ] autorelease]; - [alert setContext:@"norefresh"]; - [alert show]; + [essential_ setContext:@"unable"]; + } + + if ([[datasource_ packages] count] > 0) { + [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("CLEAR") + style:UIBarButtonItemStylePlain + target:self + action:@selector(clearButtonClicked) + ] autorelease]]; + } else { + [[self navigationItem] setLeftBarButtonItem:nil]; + } + + if ([[datasource_ issues] count] == 0) { + [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("CONFIRM") + style:UIBarButtonItemStyleDone + target:self + action:@selector(confirmButtonClicked) + ] autorelease]]; + } else { + [[self navigationItem] setRightBarButtonItem:nil]; } } -- (void) upgradeButtonClicked { - [delegate_ distUpgrade]; - [[self navigationItem] setRightBarButtonItem:nil animated:YES]; +- (void) clearButtonClicked { + [delegate_ clearQueue]; + [self reloadData]; } -- (void) loadView { - [super loadView]; +- (void) confirmButtonClicked { + if (essential_ != nil) + [essential_ show]; + else + [self complete]; +} - if (AprilFools_ && kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_0) { - CGRect dickframe([[self view] bounds]); - dickframe.size.height = 44; +@end +/* }}} */ - dickbar_ = [[[CyteWebView alloc] initWithFrame:dickframe] autorelease]; - [dickbar_ setDelegate:self]; - [[self view] addSubview:dickbar_]; +/* Progress Data {{{ */ +@interface CydiaProgressData : NSObject { + _transient id delegate_; - [dickbar_ setBackgroundColor:[UIColor clearColor]]; - [dickbar_ setScalesPageToFit:YES]; + bool running_; + float percent_; - UIWebDocumentView *document([dickbar_ _documentView]); - [document setBackgroundColor:[UIColor clearColor]]; - [document setDrawsBackground:NO]; + float current_; + float total_; + float speed_; - WebView *webview([document webView]); - [webview setShouldUpdateWhileOffscreen:NO]; + _H events_; + _H title_; - UIScrollView *scroller([dickbar_ scrollView]); - [scroller setScrollingEnabled:NO]; - [scroller setFixedBackgroundPattern:YES]; - [scroller setBackgroundColor:[UIColor clearColor]]; + _H status_; + _H finish_; +} - WebPreferences *preferences([webview preferences]); - [preferences setCacheModel:WebCacheModelDocumentBrowser]; - [preferences setJavaScriptCanOpenWindowsAutomatically:YES]; - [preferences setOfflineWebApplicationCacheEnabled:YES]; +@end - [dickbar_ loadRequest:[NSURLRequest - requestWithURL:[Diversion divertURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/dickbar/", UI_]]] - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:120 - ]]; +@implementation CydiaProgressData + ++ (NSArray *) _attributeKeys { + return [NSArray arrayWithObjects: + @"current", + @"events", + @"finish", + @"percent", + @"running", + @"speed", + @"title", + @"total", + nil]; +} + +- (NSArray *) attributeKeys { + return [[self class] _attributeKeys]; +} + ++ (BOOL) isKeyExcludedFromWebScript:(const char *)name { + return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name]; +} + +- (id) init { + if ((self = [super init]) != nil) { + events_ = [NSMutableArray arrayWithCapacity:32]; + } return self; +} - UIEdgeInsets inset = {44, 0, 0, 0}; - [list_ setContentInset:inset]; +- (void) setDelegate:(id)delegate { + delegate_ = delegate; +} - [dickbar_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; - } +- (void) setPercent:(float)value { + percent_ = value; } -- (void) webView:(WebView *)view decidePolicyForNewWindowAction:(NSDictionary *)action request:(NSURLRequest *)request newFrameName:(NSString *)frame decisionListener:(id)listener { - NSURL *url([request URL]); - if (url == nil) - return; +- (NSNumber *) percent { + return [NSNumber numberWithFloat:percent_]; +} - if ([frame isEqualToString:@"_open"]) - [delegate_ openURL:url]; - else { - WebFrame *frame(nil); - if (NSDictionary *WebActionElement = [action objectForKey:@"WebActionElementKey"]) - frame = [WebActionElement objectForKey:@"WebElementFrame"]; - if (frame == nil) - frame = [view mainFrame]; +- (void) setCurrent:(float)value { + current_ = value; +} - WebDataSource *source([frame provisionalDataSource] ?: [frame dataSource]); +- (NSNumber *) current { + return [NSNumber numberWithFloat:current_]; +} - CyteViewController *controller([delegate_ pageForURL:url forExternal:NO withReferrer:([request valueForHTTPHeaderField:@"Referer"] ?: [[[source request] URL] absoluteString])] ?: [[[CydiaWebViewController alloc] initWithRequest:request] autorelease]); - [controller setDelegate:delegate_]; - [[self navigationController] pushViewController:controller animated:YES]; - } +- (void) setTotal:(float)value { + total_ = value; +} - [listener ignore]; +- (NSNumber *) total { + return [NSNumber numberWithFloat:total_]; } -- (NSURLRequest *) webView:(WebView *)view resource:(id)resource willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response fromDataSource:(WebDataSource *)source { - return [CydiaWebViewController requestWithHeaders:request]; +- (void) setSpeed:(float)value { + speed_ = value; } -- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { - [CydiaWebViewController didClearWindowObject:window forFrame:frame withCydia:cydia_]; +- (NSNumber *) speed { + return [NSNumber numberWithFloat:speed_]; } -- (void) setDelegate:(id)delegate { - [super setDelegate:delegate]; - [cydia_ setDelegate:delegate]; +- (NSArray *) events { + return events_; } -- (void) releaseSubviews { - dickbar_ = nil; +- (void) removeAllEvents { + [events_ removeAllObjects]; +} - [super releaseSubviews]; +- (void) addEvent:(CydiaProgressEvent *)event { + [events_ addObject:event]; } -- (id) initWithDatabase:(Database *)database { - if ((self = [super initWithDatabase:database title:(AprilFools_ ? @"Timeline" : UCLocalize("CHANGES"))]) != nil) { - indirect_ = [[[IndirectDelegate alloc] initWithDelegate:self] autorelease]; - cydia_ = [[[CydiaObject alloc] initWithDelegate:indirect_] autorelease]; - database_ = database; - } return self; +- (void) setTitle:(NSString *)text { + title_ = text; } -- (void) reloadData { - [super reloadData]; +- (NSString *) title { + return title_; +} - [[self navigationItem] setRightBarButtonItem:([datasource_ upgrades] == 0 ? nil : [[[UIBarButtonItem alloc] - initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", [datasource_ upgrades]]] - style:UIBarButtonItemStylePlain - target:self - action:@selector(upgradeButtonClicked) - ] autorelease]) animated:YES]; +- (void) setFinish:(NSString *)text { + finish_ = text; +} - [[self navigationItem] setLeftBarButtonItem:([delegate_ updating] ? nil : [[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("REFRESH") - style:UIBarButtonItemStylePlain - target:self - action:@selector(refreshButtonClicked) - ] autorelease]) animated:YES]; +- (NSString *) finish { + return (id) finish_ ?: [NSNull null]; +} - PrintTimes(); +- (void) setRunning:(bool)running { + running_ = running; +} + +- (NSNumber *) running { + return running_ ? (NSNumber *) kCFBooleanTrue : (NSNumber *) kCFBooleanFalse; } @end /* }}} */ -/* Package Settings Controller {{{ */ -@interface PackageSettingsController : CyteViewController < - UITableViewDataSource, - UITableViewDelegate +/* Progress Controller {{{ */ +@interface ProgressController : CydiaWebViewController < + ProgressDelegate > { _transient Database *database_; - _H name_; - _H package_; - _H table_; - _H subscribedSwitch_; - _H ignoredSwitch_; - _H subscribedCell_; - _H ignoredCell_; + _H progress_; + unsigned cancel_; } -- (id) initWithDatabase:(Database *)database package:(NSString *)package; +- (id) initWithDatabase:(Database *)database delegate:(id)delegate; + +- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title; + +- (void) setTitle:(NSString *)title; +- (void) setCancellable:(bool)cancellable; @end -@implementation PackageSettingsController +@implementation ProgressController -- (NSURL *) navigationURL { - return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/settings", (id) name_]]; +- (void) dealloc { + [database_ setProgressDelegate:nil]; + [super dealloc]; } -- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { - if (package_ == nil) - return 0; +- (UIBarButtonItem *) leftButton { + return cancel_ == 1 ? [[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("CANCEL") + style:UIBarButtonItemStylePlain + target:self + action:@selector(cancel) + ] autorelease] : nil; +} - if ([package_ installed] == nil) - return 1; - else - return 2; +- (void) updateCancel { + [super applyLeftButton]; } -- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (package_ == nil) - return 0; +- (id) initWithDatabase:(Database *)database delegate:(id)delegate { + if ((self = [super init]) != nil) { + database_ = database; + delegate_ = delegate; - // both sections contain just one item right now. - return 1; -} + [database_ setProgressDelegate:self]; -- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - return nil; + progress_ = [[[CydiaProgressData alloc] init] autorelease]; + [progress_ setDelegate:self]; + + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/progress/", UI_]]]; + + [scroller_ setBackgroundColor:[UIColor blackColor]]; + + [[self navigationItem] setHidesBackButton:YES]; + + [self updateCancel]; + } return self; } -- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { - if (section == 0) - return UCLocalize("SHOW_ALL_CHANGES_EX"); - else - return UCLocalize("IGNORE_UPGRADES_EX"); +- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { + [super webView:view didClearWindowObject:window forFrame:frame]; + [window setValue:progress_ forKey:@"cydiaProgress"]; } -- (void) onSubscribed:(id)control { - bool value([control isOn]); - if (package_ == nil) - return; - if ([package_ setSubscribed:value]) - [delegate_ updateData]; +- (void) updateProgress { + [self dispatchEvent:@"CydiaProgressUpdate"]; } -- (void) _updateIgnored { - const char *package([name_ UTF8String]); - bool on([ignoredSwitch_ isOn]); +- (void) viewWillAppear:(BOOL)animated { + [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack]; + [super viewWillAppear:animated]; +} +- (void) reloadSpringBoard { pid_t pid(ExecFork()); if (pid == 0) { - FILE *dpkg(popen("dpkg --set-selections", "w")); - fwrite(package, strlen(package), 1, dpkg); + pid_t pid(ExecFork()); + if (pid == 0) { + execl("/usr/bin/sbreload", "sbreload", NULL); + perror("sbreload"); + exit(0); + } - if (on) - fwrite(" hold\n", 6, 1, dpkg); - else - fwrite(" install\n", 9, 1, dpkg); + exit(0); + } - pclose(dpkg); + ReapZombie(pid); - exit(0); - _assert(false); + sleep(15); + system("/usr/bin/killall SpringBoard"); +} + +- (void) close { + UpdateExternalStatus(0); + + if (Finish_ > 1) + [delegate_ saveState]; + + switch (Finish_) { + case 0: + [delegate_ returnToCydia]; + break; + + case 1: + [delegate_ terminateWithSuccess]; + /*if ([delegate_ respondsToSelector:@selector(suspendWithAnimation:)]) + [delegate_ suspendWithAnimation:YES]; + else + [delegate_ suspend];*/ + break; + + case 2: + _trace(); + goto reload; + + case 3: + _trace(); + goto reload; + + reload: { + UIProgressHUD *hud([delegate_ addProgressHUD]); + [hud setText:UCLocalize("LOADING")]; + [self performSelector:@selector(reloadSpringBoard) withObject:nil afterDelay:0.5]; + return; + } + + case 4: + _trace(); + if (void (*SBReboot)(mach_port_t) = reinterpret_cast(dlsym(RTLD_DEFAULT, "SBReboot"))) + SBReboot(SBSSpringBoardServerPort()); + else + reboot2(RB_AUTOBOOT); + break; } - ReapZombie(pid); + [super close]; } -- (void) onIgnored:(id)control { - NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(_updateIgnored)]]); - [invocation setTarget:self]; - [invocation setSelector:@selector(_updateIgnored)]; +- (void) setTitle:(NSString *)title { + [progress_ setTitle:title]; + [self updateProgress]; +} - [delegate_ reloadDataWithInvocation:invocation]; +- (UIBarButtonItem *) rightButton { + return [[progress_ running] boolValue] ? [super rightButton] : [[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("CLOSE") + style:UIBarButtonItemStylePlain + target:self + action:@selector(close) + ] autorelease]; } -- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if (package_ == nil) - return nil; +- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title { + UpdateExternalStatus(1); - switch ([indexPath section]) { - case 0: return subscribedCell_; - case 1: return ignoredCell_; + [progress_ setRunning:true]; + [self setTitle:title]; + // implicit updateProgress - _nodefault + SHA1SumValue notifyconf; { + FileFd file; + if (!file.Open(NotifyConfig_, FileFd::ReadOnly)) + _error->Discard(); + else { + MMap mmap(file, MMap::ReadOnly); + SHA1Summation sha1; + sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); + notifyconf = sha1.Result(); + } } - return nil; -} - -- (void) loadView { - UIView *view([[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]); - [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)]; - [self setView:view]; + SHA1SumValue springlist; { + FileFd file; + if (!file.Open(SpringBoard_, FileFd::ReadOnly)) + _error->Discard(); + else { + MMap mmap(file, MMap::ReadOnly); + SHA1Summation sha1; + sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); + springlist = sha1.Result(); + } + } - table_ = [[[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped] autorelease]; - [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [(UITableView *) table_ setDataSource:self]; - [table_ setDelegate:self]; - [view addSubview:table_]; + if (invocation != nil) { + [invocation yieldToSelector:@selector(invoke)]; + [self setTitle:@"COMPLETE"]; + } - subscribedSwitch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)] autorelease]; - [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; - [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged]; + if (Finish_ < 4) { + FileFd file; + if (!file.Open(NotifyConfig_, FileFd::ReadOnly)) + _error->Discard(); + else { + MMap mmap(file, MMap::ReadOnly); + SHA1Summation sha1; + sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); + if (!(notifyconf == sha1.Result())) + Finish_ = 4; + } + } - ignoredSwitch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)] autorelease]; - [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; - [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged]; + if (Finish_ < 3) { + FileFd file; + if (!file.Open(SpringBoard_, FileFd::ReadOnly)) + _error->Discard(); + else { + MMap mmap(file, MMap::ReadOnly); + SHA1Summation sha1; + sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); + if (!(springlist == sha1.Result())) + Finish_ = 3; + } + } - subscribedCell_ = [[[UITableViewCell alloc] init] autorelease]; - [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")]; - [subscribedCell_ setAccessoryView:subscribedSwitch_]; - [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone]; + if (Finish_ < 2) { + if (RestartSubstrate_) + Finish_ = 2; + } - ignoredCell_ = [[[UITableViewCell alloc] init] autorelease]; - [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")]; - [ignoredCell_ setAccessoryView:ignoredSwitch_]; - [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone]; -} + RestartSubstrate_ = false; -- (void) viewDidLoad { - [super viewDidLoad]; + switch (Finish_) { + case 0: [progress_ setFinish:UCLocalize("RETURN_TO_CYDIA")]; break; /* XXX: Maybe UCLocalize("DONE")? */ + case 1: [progress_ setFinish:UCLocalize("CLOSE_CYDIA")]; break; + case 2: [progress_ setFinish:UCLocalize("RESTART_SPRINGBOARD")]; break; + case 3: [progress_ setFinish:UCLocalize("RELOAD_SPRINGBOARD")]; break; + case 4: [progress_ setFinish:UCLocalize("REBOOT_DEVICE")]; break; + } - [[self navigationItem] setTitle:UCLocalize("SETTINGS")]; -} + UpdateExternalStatus(Finish_ == 0 ? 0 : 2); -- (void) releaseSubviews { - ignoredCell_ = nil; - subscribedCell_ = nil; - table_ = nil; - ignoredSwitch_ = nil; - subscribedSwitch_ = nil; + [progress_ setRunning:false]; + [self updateProgress]; - [super releaseSubviews]; + [self applyRightButton]; } -- (id) initWithDatabase:(Database *)database package:(NSString *)package { - if ((self = [super init]) != nil) { - database_ = database; - name_ = package; - } return self; +- (void) addProgressEvent:(CydiaProgressEvent *)event { + [progress_ addEvent:event]; + [self updateProgress]; } -- (void) reloadData { - [super reloadData]; - - package_ = [database_ packageWithName:name_]; - - if (package_ != nil) { - [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO]; - [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO]; - } // XXX: what now, G? - - [table_ reloadData]; +- (bool) isProgressCancelled { + return cancel_ == 2; } -@end -/* }}} */ - -/* Installed Controller {{{ */ -@interface InstalledController : FilteredPackageListController { - BOOL expert_; +- (void) cancel { + cancel_ = 2; + [self updateCancel]; } -- (id) initWithDatabase:(Database *)database; - -- (void) updateRoleButton; -- (void) queueStatusDidChange; - -@end - -@implementation InstalledController +- (void) setCancellable:(bool)cancellable { + unsigned cancel(cancel_); -- (NSURL *) referrerURL { - return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/installed/", UI_]]; -} + if (!cancellable) + cancel_ = 0; + else if (cancel_ == 0) + cancel_ = 1; -- (NSURL *) navigationURL { - return [NSURL URLWithString:@"cydia://installed"]; + if (cancel != cancel_) + [self updateCancel]; } -- (id) initWithDatabase:(Database *)database { - if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") ]) != nil) { - [datasource_ addFilter:@"installed" withSelector:@selector(isInstalledAndUnfiltered:) priority:kPackageListFilterPriorityHigh object:[NSNumber numberWithBool:YES]]; - [self updateRoleButton]; - [self queueStatusDidChange]; - } return self; +- (void) setProgressCancellable:(NSNumber *)cancellable { + [self setCancellable:[cancellable boolValue]]; } -#if !AlwaysReload -- (void) queueButtonClicked { - [delegate_ queue]; +- (void) setProgressPercent:(NSNumber *)percent { + [progress_ setPercent:[percent floatValue]]; + [self updateProgress]; } -#endif -- (void) queueStatusDidChange { -#if !AlwaysReload - if (Queuing_) { - [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("QUEUE") - style:UIBarButtonItemStyleDone - target:self - action:@selector(queueButtonClicked) - ] autorelease]]; +- (void) setProgressStatus:(NSDictionary *)status { + if (status == nil) { + [progress_ setCurrent:0]; + [progress_ setTotal:0]; + [progress_ setSpeed:0]; } else { - [[self navigationItem] setLeftBarButtonItem:nil]; - } -#endif -} - -- (void) updateRoleButton { - if (Role_ != nil && ![Role_ isEqualToString:@"Developer"]) - [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE")) - style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain) - target:self - action:@selector(roleButtonClicked) - ] autorelease]]; -} + [progress_ setPercent:[[status objectForKey:@"Percent"] floatValue]]; -- (void) roleButtonClicked { - [datasource_ setObject:[NSNumber numberWithBool:expert_] forFilter:@"installed"]; - [self reloadData]; - expert_ = !expert_; + [progress_ setCurrent:[[status objectForKey:@"Current"] floatValue]]; + [progress_ setTotal:[[status objectForKey:@"Total"] floatValue]]; + [progress_ setSpeed:[[status objectForKey:@"Speed"] floatValue]]; + } - [self updateRoleButton]; + [self updateProgress]; } @end @@ -9084,12 +9169,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi CydiaWriteSources(); } -// Navigation controller for the queuing badge. -- (UINavigationController *) queueNavigationController { - NSArray *controllers = [tabbar_ viewControllers]; - return [controllers objectAtIndex:3]; -} - - (void) unloadData { [tabbar_ unloadData]; } @@ -9097,15 +9176,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi - (void) _updateData { [self _saveConfig]; [self unloadData]; - - UINavigationController *navigation = [self queueNavigationController]; - - id queuedelegate = nil; - if ([[navigation viewControllers] count] > 0) - queuedelegate = [[navigation viewControllers] objectAtIndex:0]; - - [queuedelegate queueStatusDidChange]; - [[navigation tabBarItem] setBadgeValue:(Queuing_ ? UCLocalize("Q_D") : nil)]; } - (void) _refreshIfPossible:(NSDate *)update { @@ -9290,39 +9360,10 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi _error->Discard(); } -- (bool) perform { - // XXX: this is a really crappy way of doing this. - // like, seriously: this state machine is still broken, and cancelling this here doesn't really /fix/ that. - // for one, the user can still /start/ a reloading data event while they have a queue, which is stupid - // for two, this just means there is a race condition between the refresh completing and the confirmation controller appearing. - if ([tabbar_ updating]) - [tabbar_ cancelUpdate]; - - if (![database_ prepare]) - return false; - - ConfirmationController *page([[[ConfirmationController alloc] initWithDatabase:database_] autorelease]); - [page setDelegate:self]; - UINavigationController *confirm_([[[UINavigationController alloc] initWithRootViewController:page] autorelease]); - - if (IsWildcat_) - [confirm_ setModalPresentationStyle:UIModalPresentationFormSheet]; - [tabbar_ presentModalViewController:confirm_ animated:YES]; - - return true; -} - -- (void) queue { - @synchronized (self) { - [self perform]; - } -} - - (void) clearPackage:(Package *)package { @synchronized (self) { [package clear]; [self resolve]; - [self perform]; } } @@ -9331,7 +9372,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi for (Package *package in packages) [package install]; [self resolve]; - [self perform]; } } @@ -9339,7 +9379,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi @synchronized (self) { [package install]; [self resolve]; - [self perform]; } } @@ -9347,7 +9386,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi @synchronized (self) { [package remove]; [self resolve]; - [self perform]; } } @@ -9355,7 +9393,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi @synchronized (self) { if (![database_ upgrade]) return; - [self perform]; } } @@ -9379,7 +9416,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi } - (void) confirmWithNavigationController:(UINavigationController *)navigation { - Queuing_ = false; ++locked_; [self detachNewProgressSelector:@selector(perform_) toTarget:self forController:navigation title:@"RUNNING"]; --locked_; @@ -9408,14 +9444,9 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi } -- (void) cancelAndClear:(bool)clear { +- (void) clearQueue { @synchronized (self) { - if (clear) { - [database_ clear]; - Queuing_ = false; - } else { - Queuing_ = true; - } + [database_ clear]; [self _updateData]; } @@ -9447,7 +9478,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi } [self resolve]; - [self perform]; + // XXX: [self perform]; } } else if (button == [alert firstOtherButtonIndex]) { [broken_ removeAllObjects]; @@ -9462,7 +9493,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi [essential install]; [self resolve]; - [self perform]; + // XXX: [self perform]; } } else if (button == [alert firstOtherButtonIndex] + 1) { [self distUpgrade]; @@ -9611,6 +9642,10 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi if ([base isEqualToString:@"installed"]) { controller = [[[InstalledController alloc] initWithDatabase:database_] autorelease]; } + + if ([base isEqualToString:@"queue"]) { + controller = [[[ConfirmationController alloc] initWithDatabase:database_] autorelease]; + } } else if ([components count] == 2) { NSString *argument = [components objectAtIndex:1]; @@ -9771,6 +9806,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi [[[UITabBarItem alloc] initWithTitle:UCLocalize("SOURCES") image:[UIImage applicationImageNamed:@"source.png"] tag:0] autorelease], [[[UITabBarItem alloc] initWithTitle:(AprilFools_ ? @"Timeline" : UCLocalize("CHANGES")) image:[UIImage applicationImageNamed:@"changes.png"] tag:0] autorelease], [[[UITabBarItem alloc] initWithTitle:UCLocalize("INSTALLED") image:[UIImage applicationImageNamed:@"manage.png"] tag:0] autorelease], + [[[UITabBarItem alloc] initWithTitle:UCLocalize("QUEUE") image:[UIImage applicationImageNamed:@"queue.png"] tag:0] autorelease], nil]); NSMutableArray *controllers([NSMutableArray array]); @@ -9899,6 +9935,7 @@ _trace(); [standard addObject:[NSArray arrayWithObject:@"cydia://sources"]]; [standard addObject:[NSArray arrayWithObject:@"cydia://changes"]]; [standard addObject:[NSArray arrayWithObject:@"cydia://installed"]]; + [standard addObject:[NSArray arrayWithObject:@"cydia://queue"]]; return standard; } diff --git a/iPhonePrivate.h b/iPhonePrivate.h index 98f86b4e..6208f4a6 100644 --- a/iPhonePrivate.h +++ b/iPhonePrivate.h @@ -275,6 +275,7 @@ typedef enum { @end @interface UITabBarItem (Apple) +@property(retain, nonatomic) UIView *view; - (void) setAnimatedBadge:(BOOL)animated; @end