]> git.saurik.com Git - cydia.git/commitdiff
Begin work to restructure confirmation controller into a separate tab. new-search-simpler-sections-new-progress
authorGrant Paul <chpwn@chpwn.com>
Sun, 15 Jan 2012 06:59:39 +0000 (22:59 -0800)
committerGrant Paul <chpwn@chpwn.com>
Sun, 15 Jan 2012 06:59:39 +0000 (22:59 -0800)
MobileCydia.mm
iPhonePrivate.h

index dee717bd1063e2fc1a8eae24cdd7ea22fdd64ffb..1eb1880892a81d43845ba9b1475258cb7b6ff40c 100644 (file)
@@ -67,6 +67,8 @@
 #include <IOKit/IOKitLib.h>
 
 #include <QuartzCore/CALayer.h>
+#include <QuartzCore/CAAnimation.h>
+#include <QuartzCore/CAMediaTimingFunction.h>
 
 #include <WebCore/WebCoreThread.h>
 
@@ -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<NSMutableSet> 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<UIImage> icon_;
+    _H<NSString> name_;
+    _H<NSString> description_;
+    bool commercial_;
+    _H<NSString> source_;
+    _H<UIImage> badge_;
+    _H<UIImage> placard_;
 }
 
-@protocol ConfirmationControllerDelegate
-- (void) cancelAndClear:(bool)clear;
-- (void) confirmWithNavigationController:(UINavigationController *)navigation;
-- (void) queue;
-@end
-
-@interface ConfirmationController : CydiaWebViewController {
-    _transient Database *database_;
-
-    _H<UIAlertView> essential_;
-
-    _H<NSDictionary> changes_;
-    _H<NSMutableArray> issues_;
-    _H<NSDictionary> 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<NSString> basic_;
+    _H<NSString> section_;
+    _H<NSString> name_;
+    _H<NSString> count_;
+    _H<UIImage> icon_;
+    _H<UISwitch> 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<NSMutableArray> events_;
-    _H<NSString> title_;
-
-    _H<NSString> status_;
-    _H<NSString> 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<CydiaProgressData, 1> progress_;
-    unsigned cancel_;
+    _H<Package> package_;
+    _H<NSString> name_;
+    _H<NSMutableArray> files_;
+    _H<UITableView, 2> 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> package_;
+    _H<NSString> name_;
+    bool commercial_;
+    _H<NSMutableArray> buttons_;
+    _H<UIBarButtonItem> 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<void (*)(mach_port_t)>(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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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<UIImage> icon_;
-    _H<NSString> name_;
-    _H<NSString> description_;
-    bool commercial_;
-    _H<NSString> source_;
-    _H<UIImage> badge_;
-    _H<UIImage> 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 <UITableViewDataSource> {
+    _transient Database *database_;
+    unsigned era_;
+    _H<NSArray> packages_;
+    _H<NSMutableArray> sections_;
+    _H<NSMutableArray> index_;
+    _H<NSMutableDictionary> indices_;
+    _H<NSMutableDictionary> 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<NSString> basic_;
-    _H<NSString> section_;
-    _H<NSString> name_;
-    _H<NSString> count_;
-    _H<UIImage> icon_;
-    _H<UISwitch> 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<PackageListFilterPriority>(static_cast<int>(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<NSObject> 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<bool (*)(id, SEL, id)>(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 <UITableViewDelegate, UISearchDisplayDelegate> {
     _transient Database *database_;
-    _H<Package> package_;
-    _H<NSString> name_;
-    _H<NSMutableArray> files_;
+    _H<UISearchDisplayController> searchController_;
+    _H<UISearchBar> searchBar_;
+    _H<FilteredPackageListDataSource> datasource_;
     _H<UITableView, 2> list_;
+    _H<NSString> 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> package_;
-    _H<NSString> name_;
-    bool commercial_;
-    _H<NSMutableArray> buttons_;
-    _H<UIBarButtonItem> 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<UIProgressIndicator> indicator_;
+    _H<UITextLabel> prompt_;
+    _H<UIProgressBar> progress_;
+    _H<UINavigationButton> 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 <UITableViewDataSource> {
-    _transient Database *database_;
-    unsigned era_;
-    _H<NSArray> packages_;
-    _H<NSMutableArray> sections_;
-    _H<NSMutableArray> index_;
-    _H<NSMutableDictionary> indices_;
-    _H<NSMutableDictionary> 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, 1> refreshbar_;
 
-- (void)setPackageListFilter:(PackageListFilter)filter forFilter:(NSString *)filterName {
-    [filters_ setObject:[NSValue valueWithPackageListFilter:filter] forKey:filterName];
-}
+    bool dropped_;
+    bool updating_;
+    // XXX: ok, "updatedelegate_"?...
+    _transient NSObject<CydiaDelegate> *updatedelegate_;
 
-- (id)objectForFilter:(NSString *)filter {
-    return [self packageListFilterForFilter:filter].object;
+    _H<UIViewController> 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<PackageListFilterPriority>(static_cast<int>(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<NSObject> 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<bool (*)(id, SEL, id)>(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 <UITableViewDelegate, UISearchDisplayDelegate> {
-    _transient Database *database_;
-    _H<UISearchDisplayController> searchController_;
-    _H<UISearchBar> searchBar_;
-    _H<FilteredPackageListDataSource> datasource_;
-    _H<UITableView, 2> list_;
-    _H<NSString> title_;
+- (UIView *) transitionView {
+    if ([self respondsToSelector:@selector(_transitionView)])
+        return [self _transitionView];
+    else
+        return MSHookIvar<id>(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<NSURLProtocolClient> 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<NSURLProtocolClient> 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<UIProgressIndicator> indicator_;
-    _H<UITextLabel> prompt_;
-    _H<UIProgressBar> progress_;
-    _H<UINavigationButton> cancel_;
+/* Section Controller {{{ */
+@interface SectionController : FilteredPackageListController {
+    _H<IndirectDelegate, 1> indirect_;
+    _H<CydiaObject> cydia_;
+    _H<NSString> section_;
+    _H<NSString> key_;
+    _transient Source *source_;
+    std::vector< _H<CyteWebViewTableViewCell, 1> > 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<WebPolicyDecisionListener>)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, 1> refreshbar_;
+        WebDataSource *source([frame provisionalDataSource] ?: [frame dataSource]);
 
-    bool dropped_;
-    bool updating_;
-    // XXX: ok, "updatedelegate_"?...
-    _transient NSObject<CydiaDelegate> *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<UIViewController> 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<NSMutableArray> sections_;
+    _H<NSMutableArray> filtered_;
+    _H<UITableView, 2> list_;
+    _H<NSString> 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<id>(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<MenesRadixSortFunction>(&PackageChangesRadix) withContext:NULL];
+    _end
+    _trace();
 
-+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
-    return request;
-}
+    return filtered;
+} }
 
-- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request {
-    id<NSURLProtocolClient> 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<NSURLProtocolClient> 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<CyteWebView, 1> dickbar_;
     _H<IndirectDelegate, 1> indirect_;
     _H<CydiaObject> cydia_;
-    _H<NSString> section_;
-    _H<NSString> key_;
-    _transient Source *source_;
-    std::vector< _H<CyteWebViewTableViewCell, 1> > 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<WebPolicyDecisionListener>)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<NSMutableArray> sections_;
-    _H<NSMutableArray> filtered_;
-    _H<UITableView, 2> list_;
-    _H<NSString> key_;
-    _transient Source *source_;
+    _H<NSString> name_;
+    _H<Package> package_;
+    _H<UITableView, 2> table_;
+    _H<UISwitch> subscribedSwitch_;
+    _H<UISwitch> ignoredSwitch_;
+    _H<UITableViewCell> subscribedCell_;
+    _H<UITableViewCell> 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<NSMutableArray> 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<MenesRadixSortFunction>(&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<CyteWebView, 1> dickbar_;
-    _H<IndirectDelegate, 1> indirect_;
-    _H<CydiaObject> cydia_;
+@interface ConfirmationController : FilteredPackageListController {
+    _H<UIAlertView> 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<NSMutableArray> events_;
+    _H<NSString> title_;
 
-        UIScrollView *scroller([dickbar_ scrollView]);
-        [scroller setScrollingEnabled:NO];
-        [scroller setFixedBackgroundPattern:YES];
-        [scroller setBackgroundColor:[UIColor clearColor]];
+    _H<NSString> status_;
+    _H<NSString> 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<WebPolicyDecisionListener>)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<NSString> name_;
-    _H<Package> package_;
-    _H<UITableView, 2> table_;
-    _H<UISwitch> subscribedSwitch_;
-    _H<UISwitch> ignoredSwitch_;
-    _H<UITableViewCell> subscribedCell_;
-    _H<UITableViewCell> ignoredCell_;
+    _H<CydiaProgressData, 1> 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<void (*)(mach_port_t)>(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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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;
 }
 
index 98f86b4e0160b78a68455b083aead077122274bf..6208f4a63e8c9c9d5791dfa0d55c87f50fc03c6c 100644 (file)
@@ -275,6 +275,7 @@ typedef enum {
 @end
 
 @interface UITabBarItem (Apple)
+@property(retain, nonatomic) UIView *view;
 - (void) setAnimatedBadge:(BOOL)animated;
 @end