]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Centralize addCydiaHost: to Cydia delegate_.
[cydia.git] / MobileCydia.mm
index 3d43570a2ce5f9feb864f782282eb8af73f3f97e..617562f6d68f8d63e4c99c474237ebe6ff7d6c64 100644 (file)
@@ -1,5 +1,5 @@
 /* Cydia - iPhone UIKit Front-End for Debian APT
- * Copyright (C) 2008-2010  Jay Freeman (saurik)
+ * Copyright (C) 2008-2011  Jay Freeman (saurik)
 */
 
 /* Modified BSD License {{{ */
@@ -125,6 +125,8 @@ extern "C" {
 #include "SDURLCache/SDURLCache.h"
 
 #include "substrate.h"
+
+#include "Version.h"
 /* }}} */
 
 /* Profiler {{{ */
@@ -217,21 +219,14 @@ union SplitHash {
 
 static const NSUInteger UIViewAutoresizingFlexibleBoth(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
 
-void NSLogPoint(const char *fix, const CGPoint &point) {
-    NSLog(@"%s(%g,%g)", fix, point.x, point.y);
-}
-
-void NSLogRect(const char *fix, const CGRect &rect) {
-    NSLog(@"%s(%g,%g)+(%g,%g)", fix, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
-}
-
 static _finline NSString *CydiaURL(NSString *path) {
-    char page[25];
-    page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = ':';
-    page[5] = '/'; page[6] = '/'; page[7] = 'c'; page[8] = 'y'; page[9] = 'd';
-    page[10] = 'i'; page[11] = 'a'; page[12] = '.'; page[13] = 's'; page[14] = 'a';
-    page[15] = 'u'; page[16] = 'r'; page[17] = 'i'; page[18] = 'k'; page[19] = '.';
-    page[20] = 'c'; page[21] = 'o'; page[22] = 'm'; page[23] = '/'; page[24] = '\0';
+    char page[26];
+    page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = 's';
+    page[5] = ':'; page[6] = '/'; page[7] = '/'; page[8] = 'c'; page[9] = 'y';
+    page[10] = 'd'; page[11] = 'i'; page[12] = 'a'; page[13] = '.'; page[14] = 's';
+    page[15] = 'a'; page[16] = 'u'; page[17] = 'r'; page[18] = 'i'; page[19] = 'k';
+    page[20] = '.'; page[21] = 'c'; page[22] = 'o'; page[23] = 'm'; page[24] = '/';
+    page[25] = '\0';
     return [[NSString stringWithUTF8String:page] stringByAppendingString:path];
 }
 
@@ -296,8 +291,9 @@ static _finline void UpdateExternalStatus(uint64_t newStatus) {
 
     NSRunLoop *loop([NSRunLoop currentRunLoop]);
     NSDate *future([NSDate distantFuture]);
+    NSString *mode([loop currentMode] ?: NSDefaultRunLoopMode);
 
-    while (!stopped && [loop runMode:NSDefaultRunLoopMode beforeDate:future]);
+    while (!stopped && [loop runMode:mode beforeDate:future]);
 
     return [context count] == 0 ? nil : [context objectAtIndex:0];
 }
@@ -309,18 +305,19 @@ static _finline void UpdateExternalStatus(uint64_t newStatus) {
 @end
 /* }}} */
 
-/* Cydia Action Sheet {{{ */
-@interface CYActionSheet : UIAlertView {
+/* Cydia Alert View {{{ */
+@interface CYAlertView : UIAlertView {
     unsigned button_;
 }
 
 - (int) yieldToPopupAlertAnimated:(BOOL)animated;
+
 @end
 
-@implementation CYActionSheet
+@implementation CYAlertView
 
 - (id) initWithTitle:(NSString *)title buttons:(NSArray *)buttons defaultButtonIndex:(int)index {
-    if ((self = [super init])) {
+    if ((self = [super init]) != nil) {
         [self setTitle:title];
         [self setDelegate:self];
         for (NSString *button in buttons) [self addButtonWithTitle:button];
@@ -363,32 +360,6 @@ static const NSStringCompareOptions MatchCompareOptions_ = NSLiteralSearch | NSC
 static const NSStringCompareOptions LaxCompareOptions_ = NSNumericSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch | NSCaseInsensitiveSearch;
 static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareNumerically | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
 
-/* Information Dictionaries {{{ */
-@interface NSMutableArray (Cydia)
-- (void) addInfoDictionary:(NSDictionary *)info;
-@end
-
-@implementation NSMutableArray (Cydia)
-
-- (void) addInfoDictionary:(NSDictionary *)info {
-    [self addObject:info];
-}
-
-@end
-
-@interface NSMutableDictionary (Cydia)
-- (void) addInfoDictionary:(NSDictionary *)info;
-@end
-
-@implementation NSMutableDictionary (Cydia)
-
-- (void) addInfoDictionary:(NSDictionary *)info {
-    [self setObject:info forKey:[info objectForKey:@"CFBundleIdentifier"]];
-}
-
-@end
-/* }}} */
-
 #define lprintf(args...) fprintf(stderr, args)
 
 #define ForRelease 1
@@ -400,7 +371,6 @@ static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive |
 #define TrackResize (0 && !ForRelease)
 #define ManualRefresh (1 && !ForRelease)
 #define ShowInternals (0 && !ForRelease)
-#define IgnoreInstall (0 && !ForRelease)
 #define AlwaysReload (0 && !ForRelease)
 #define TryIndexedCollation (0 && !ForRelease)
 
@@ -598,6 +568,21 @@ void CFArrayInsertionSortValues(CFMutableArrayRef array, CFRange range, CFCompar
 @end
 /* }}} */
 
+@interface NSInvocation (Cydia)
++ (NSInvocation *) invocationWithSelector:(SEL)selector forTarget:(id)target;
+@end
+
+@implementation NSInvocation (Cydia)
+
++ (NSInvocation *) invocationWithSelector:(SEL)selector forTarget:(id)target {
+    NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]]);
+    [invocation setTarget:target];
+    [invocation setSelector:selector];
+    return invocation;
+}
+
+@end
+
 @implementation WebScriptObject (NSFastEnumeration)
 
 - (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)objects count:(NSUInteger)count {
@@ -906,6 +891,7 @@ class Pcre {
 
 + (Address *) addressWithString:(NSString *)string;
 - (Address *) initWithString:(NSString *)string;
+
 @end
 
 @implementation Address
@@ -939,7 +925,10 @@ class Pcre {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"address", @"name", nil];
+    return [NSArray arrayWithObjects:
+        @"address",
+        @"name",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -1014,7 +1003,10 @@ class CYColor {
 /* Random Global Variables {{{ */
 static const int PulseInterval_ = 50000;
 
+static const NSString *UI_;
+
 static int Finish_;
+static bool RestartSubstrate_;
 static NSArray *Finishes_;
 
 #define SpringBoard_ "/System/Library/LaunchDaemons/com.apple.SpringBoard.plist"
@@ -1036,7 +1028,6 @@ static UIColor *InstallingColor_;
 static UIColor *RemovingColor_;
 
 static NSString *App_;
-static NSString *Home_;
 
 static BOOL Advanced_;
 static BOOL Ignored_;
@@ -1051,7 +1042,7 @@ static const char *Machine_ = NULL;
 static NSString *System_ = nil;
 static NSString *SerialNumber_ = nil;
 static NSString *ChipID_ = nil;
-static NSString *Token_ = nil;
+static _H<NSString> Token_;
 static NSString *UniqueID_ = nil;
 static NSString *PLMN_ = nil;
 static NSString *Build_ = nil;
@@ -1073,6 +1064,10 @@ static bool Changed_;
 static time_t now_;
 
 bool IsWildcat_;
+static CGFloat ScreenScale_;
+static NSString *Idiom_;
+
+static NSMutableSet *CydiaHosts_;
 /* }}} */
 
 /* Display Helpers {{{ */
@@ -1080,23 +1075,6 @@ inline float Interpolate(float begin, float end, float fraction) {
     return (end - begin) * fraction + begin;
 }
 
-/* XXX: localize this! */
-NSString *SizeString(double size) {
-    bool negative = size < 0;
-    if (negative)
-        size = -size;
-
-    unsigned power = 0;
-    while (size > 1024) {
-        size /= 1024;
-        ++power;
-    }
-
-    static const char *powers_[] = {"B", "kB", "MB", "GB"};
-
-    return [NSString stringWithFormat:@"%s%.1f %s", (negative ? "-" : ""), size, powers_[power]];
-}
-
 static _finline const char *StripVersion_(const char *version) {
     const char *colon(strchr(version, ':'));
     return colon == NULL ? version : colon + 1;
@@ -1152,7 +1130,7 @@ NSString *GetLastUpdate() {
 }
 
 bool isSectionVisible(NSString *section) {
-    NSDictionary *metadata([Sections_ objectForKey:section]);
+    NSDictionary *metadata([Sections_ objectForKey:(section ?: @"")]);
     NSNumber *hidden(metadata == nil ? nil : [metadata objectForKey:@"Hidden"]);
     return hidden == nil || ![hidden boolValue];
 }
@@ -1162,22 +1140,12 @@ bool isSectionVisible(NSString *section) {
 /* Delegate Prototypes {{{ */
 @class Package;
 @class Source;
+@class CydiaProgressEvent;
 
-@interface NSObject (ProgressDelegate)
-@end
-
-@protocol ProgressDelegate
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)id;
-- (void) setProgressTitle:(NSString *)title;
-- (void) setProgressPercent:(float)percent;
-- (void) startProgress;
-- (void) addProgressOutput:(NSString *)output;
-- (bool) isCancelling:(size_t)received;
-@end
-
-@protocol ConfigurationDelegate
+@protocol DatabaseDelegate
 - (void) repairWithSelector:(SEL)selector;
 - (void) setConfigurationData:(NSString *)data;
+- (void) addProgressEventOnMainThread:(CydiaProgressEvent *)event forTask:(NSString *)task;
 @end
 
 @class CYPackageController;
@@ -1185,7 +1153,6 @@ bool isSectionVisible(NSString *section) {
 @protocol CydiaDelegate
 - (void) retainNetworkActivityIndicator;
 - (void) releaseNetworkActivityIndicator;
-- (void) setPackageController:(CYPackageController *)view;
 - (void) clearPackage:(Package *)package;
 - (void) installPackage:(Package *)package;
 - (void) installPackages:(NSArray *)packages;
@@ -1196,30 +1163,78 @@ bool isSectionVisible(NSString *section) {
 - (void) loadData;
 - (void) updateData;
 - (void) syncData;
+- (void) addTrivialSource:(NSString *)href;
 - (void) showSettings;
 - (UIProgressHUD *) addProgressHUD;
 - (void) removeProgressHUD:(UIProgressHUD *)hud;
 - (CYViewController *) pageForPackage:(NSString *)name;
 - (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item;
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
+- (void) addCydiaHost:(NSString *)host;
 @end
-
-static id<CydiaDelegate> CydiaApp;
 /* }}} */
 
+/* ProgressEvent Interface/Delegate {{{ */
+@interface CydiaProgressEvent : NSObject {
+    _H<NSString> message_;
+    _H<NSString> type_;
+
+    _H<NSArray> item_;
+    _H<NSString> package_;
+    _H<NSString> url_;
+    _H<NSString> version_;
+}
+
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type;
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package;
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forItem:(pkgAcquire::ItemDesc &)item;
+
+- (id) initWithMessage:(NSString *)message ofType:(NSString *)type;
+
+- (NSString *) message;
+- (NSString *) type;
+
+- (NSArray *) item;
+- (NSString *) package;
+- (NSString *) url;
+- (NSString *) version;
+
+- (void) setItem:(NSArray *)item;
+- (void) setPackage:(NSString *)package;
+- (void) setURL:(NSString *)url;
+- (void) setVersion:(NSString *)version;
+
+- (NSString *) compound:(NSString *)value;
+- (NSString *) compoundMessage;
+- (NSString *) compoundTitle;
+
+@end
+
+@protocol ProgressDelegate
+- (void) addProgressEvent:(CydiaProgressEvent *)event;
+- (void) setProgressPercent:(NSNumber *)percent;
+- (void) setProgressStatus:(NSDictionary *)status;
+- (void) setProgressCancellable:(NSNumber *)cancellable;
+- (bool) isProgressCancelled;
+- (void) setTitle:(NSString *)title;
+@end
+/* }}} */
 /* Status Delegation {{{ */
 class Status :
     public pkgAcquireStatus
 {
   private:
     _transient NSObject<ProgressDelegate> *delegate_;
+    bool cancelled_;
 
   public:
     Status() :
-        delegate_(nil)
+        delegate_(nil),
+        cancelled_(false)
     {
     }
 
-    void setDelegate(id delegate) {
+    void setDelegate(NSObject<ProgressDelegate> *delegate) {
         delegate_ = delegate;
     }
 
@@ -1235,8 +1250,9 @@ class Status :
     }
 
     virtual void Fetch(pkgAcquire::ItemDesc &item) {
-        //NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
-        [delegate_ setProgressTitle:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), [NSString stringWithUTF8String:item.ShortDesc.c_str()]]];
+        NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
+        CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), name] ofType:@"STATUS" forItem:item]);
+        [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
     virtual void Done(pkgAcquire::ItemDesc &item) {
@@ -1253,80 +1269,50 @@ class Status :
         if (error.empty())
             return;
 
-        NSString *description([NSString stringWithUTF8String:item.Description.c_str()]);
-        NSArray *fields([description componentsSeparatedByString:@" "]);
-        NSString *source([fields count] == 0 ? nil : [fields objectAtIndex:0]);
-
-        [delegate_ performSelectorOnMainThread:@selector(_setProgressErrorPackage:)
-            withObject:[NSArray arrayWithObjects:
-                [NSString stringWithUTF8String:error.c_str()],
-                source,
-            nil]
-            waitUntilDone:YES
-        ];
+        CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:@"ERROR" forItem:item]);
+        [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
     virtual bool Pulse(pkgAcquire *Owner) {
         bool value = pkgAcquireStatus::Pulse(Owner);
 
-        float percent(
+        double percent(
             double(CurrentBytes + CurrentItems) /
             double(TotalBytes + TotalItems)
         );
 
-        [delegate_ setProgressPercent:percent];
-        return [delegate_ isCancelling:CurrentBytes] ? false : value;
-    }
-
-    virtual void Start() {
-        [delegate_ startProgress];
-    }
-
-    virtual void Stop() {
-    }
-};
-/* }}} */
-/* Progress Delegation {{{ */
-class Progress :
-    public OpProgress
-{
-  private:
-    _transient id<ProgressDelegate> delegate_;
-    float percent_;
-
-  protected:
-    virtual void Update() {
-        /*if (abs(Percent - percent_) > 2)
-            //NSLog(@"%s:%s:%f", Op.c_str(), SubOp.c_str(), Percent);
-            percent_ = Percent;
-        }*/
+        [delegate_ performSelectorOnMainThread:@selector(setProgressStatus:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:
+            [NSNumber numberWithDouble:percent], @"Percent",
 
-        /*[delegate_ setProgressTitle:[NSString stringWithUTF8String:Op.c_str()]];
-        [delegate_ setProgressPercent:(Percent / 100)];*/
-    }
+            [NSNumber numberWithDouble:CurrentBytes], @"Current",
+            [NSNumber numberWithDouble:TotalBytes], @"Total",
+            [NSNumber numberWithDouble:CurrentCPS], @"Speed",
+        nil] waitUntilDone:YES];
 
-  public:
-    Progress() :
-        delegate_(nil),
-        percent_(0)
-    {
+        if (value && ![delegate_ isProgressCancelled])
+            return true;
+        else {
+            cancelled_ = true;
+            return false;
+        }
     }
 
-    void setDelegate(id delegate) {
-        delegate_ = delegate;
+    _finline bool WasCancelled() const {
+        return cancelled_;
     }
 
-    id getDelegate() const {
-        return delegate_;
+    virtual void Start() {
+        pkgAcquireStatus::Start();
+        [delegate_ performSelectorOnMainThread:@selector(setProgressCancellable:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
     }
 
-    virtual void Done() {
-        //NSLog(@"DONE");
-        //[delegate_ setProgressPercent:1];
+    virtual void Stop() {
+        pkgAcquireStatus::Stop();
+        [delegate_ performSelectorOnMainThread:@selector(setProgressCancellable:) withObject:[NSNumber numberWithBool:NO] waitUntilDone:YES];
+        [delegate_ performSelectorOnMainThread:@selector(setProgressStatus:) withObject:nil waitUntilDone:YES];
     }
 };
 /* }}} */
-
 /* Database Interface {{{ */
 typedef std::map< unsigned long, _H<Source> > SourceMap;
 
@@ -1345,13 +1331,15 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
     SPtr<pkgPackageManager> manager_;
     pkgSourceList *list_;
 
-    SourceMap sources_;
-    CFMutableArrayRef deadSources_;
+    SourceMap sourceMap_;
+    NSMutableArray *sourceList_;
+
     CFMutableArrayRef packages_;
 
-    _transient NSObject<ConfigurationDelegate, ProgressDelegate> *delegate_;
+    _transient NSObject<DatabaseDelegate> *delegate_;
+    _transient NSObject<ProgressDelegate> *progress_;
+
     Status status_;
-    Progress progress_;
 
     int cydiafd_;
     int statusfd_;
@@ -1379,7 +1367,8 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 - (pkgSourceList &) list;
 - (NSArray *) packages;
 - (NSArray *) sources;
-- (void) reloadData;
+- (Source *) sourceWithKey:(NSString *)key;
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
 
 - (void) configure;
 - (bool) prepare;
@@ -1389,44 +1378,145 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 
 - (void) updateWithStatus:(Status &)status;
 
-- (void) setDelegate:(id)delegate;
+- (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate;
+
+- (void) setProgressDelegate:(NSObject<ProgressDelegate> *)delegate;
+- (NSObject<ProgressDelegate> *) progressDelegate;
+
 - (Source *) getSource:(pkgCache::PkgFileIterator)file;
 
 - (NSString *) mappedSectionForPointer:(const char *)pointer;
 
 @end
 /* }}} */
-/* Delegate Helpers {{{ */
-@implementation NSObject (ProgressDelegate)
+/* ProgressEvent Implementation {{{ */
+@implementation CydiaProgressEvent
 
-- (void) _setProgressErrorPackage:(NSArray *)args {
-    [self performSelector:@selector(setProgressError:forPackage:)
-        withObject:[args objectAtIndex:0]
-        withObject:([args count] == 1 ? nil : [args objectAtIndex:1])
-    ];
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type {
+    return [[[CydiaProgressEvent alloc] initWithMessage:message ofType:type] autorelease];
 }
 
-- (void) _setProgressErrorTitle:(NSArray *)args {
-    [self performSelector:@selector(setProgressError:withTitle:)
-        withObject:[args objectAtIndex:0]
-        withObject:([args count] == 1 ? nil : [args objectAtIndex:1])
-    ];
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package {
+    CydiaProgressEvent *event([self eventWithMessage:message ofType:type]);
+    [event setPackage:package];
+    return event;
 }
 
-- (void) _setProgressError:(NSString *)error withTitle:(NSString *)title {
-    [self performSelectorOnMainThread:@selector(_setProgressErrorTitle:)
-        withObject:[NSArray arrayWithObjects:error, title, nil]
-        waitUntilDone:YES
-    ];
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forItem:(pkgAcquire::ItemDesc &)item {
+    CydiaProgressEvent *event([self eventWithMessage:message ofType:type]);
+
+    NSString *description([NSString stringWithUTF8String:item.Description.c_str()]);
+    NSArray *fields([description componentsSeparatedByString:@" "]);
+    [event setItem:fields];
+
+    if ([fields count] > 3) {
+        [event setPackage:[fields objectAtIndex:2]];
+        [event setVersion:[fields objectAtIndex:3]];
+    }
+
+    [event setURL:[NSString stringWithUTF8String:item.URI.c_str()]];
+
+    return event;
 }
 
-- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
-    Package *package = id == nil ? nil : [[Database sharedInstance] packageWithName:id];
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"item",
+        @"message",
+        @"package",
+        @"type",
+        @"url",
+        @"version",
+    nil];
+}
 
-    [self performSelector:@selector(setProgressError:withTitle:)
-        withObject:error
-        withObject:(package == nil ? id : [package name])
-    ];
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (id) initWithMessage:(NSString *)message ofType:(NSString *)type {
+    if ((self = [super init]) != nil) {
+        message_ = message;
+        type_ = type;
+    } return self;
+}
+
+- (NSString *) message {
+    return message_;
+}
+
+- (NSString *) type {
+    return type_;
+}
+
+- (NSArray *) item {
+    return (id) item_ ?: [NSNull null];
+}
+
+- (void) setItem:(NSArray *)item {
+    item_ = item;
+}
+
+- (NSString *) package {
+    return (id) package_ ?: [NSNull null];
+}
+
+- (void) setPackage:(NSString *)package {
+    package_ = package;
+}
+
+- (NSString *) url {
+    return (id) url_ ?: [NSNull null];
+}
+
+- (void) setURL:(NSString *)url {
+    url_ = url;
+}
+
+- (void) setVersion:(NSString *)version {
+    version_ = version;
+}
+
+- (NSString *) version {
+    return (id) version_ ?: [NSNull null];
+}
+
+- (NSString *) compound:(NSString *)value {
+    if (value != nil) {
+        NSString *mode(nil); {
+            NSString *type([self type]);
+            if ([type isEqualToString:@"ERROR"])
+                mode = UCLocalize("ERROR");
+            else if ([type isEqualToString:@"WARNING"])
+                mode = UCLocalize("WARNING");
+        }
+
+        if (mode != nil)
+            value = [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), mode, value];
+    }
+
+    return value;
+}
+
+- (NSString *) compoundMessage {
+    return [self compound:[self message]];
+}
+
+- (NSString *) compoundTitle {
+    NSString *title;
+
+    if (package_ == nil)
+        title = nil;
+    else if (Package *package = [[Database sharedInstance] packageWithName:package_])
+        title = [package name];
+    else
+        title = package_;
+
+    return [self compound:title];
 }
 
 @end
@@ -1462,7 +1552,7 @@ struct MetaValue :
 static Cytore::File<MetaValue> MetaFile_;
 // }}}
 // Cytore Helper Functions {{{
-static PackageValue *PackageFind(const char *name, size_t length) {
+static PackageValue *PackageFind(const char *name, size_t length, bool *fail = NULL) {
     SplitHash nhash = { hashlittle(name, length) };
 
     PackageValue *metadata;
@@ -1472,6 +1562,14 @@ static PackageValue *PackageFind(const char *name, size_t length) {
         *offset = MetaFile_.New<PackageValue>(length + 1);
         metadata = &MetaFile_.Get(*offset);
 
+        if (metadata == NULL) {
+            if (fail != NULL)
+                *fail = true;
+
+            metadata = new PackageValue();
+            memset(metadata, 0, sizeof(*metadata));
+        }
+
         memcpy(metadata->name_, name, length + 1);
         metadata->nhash_ = nhash.u16[1];
     } else {
@@ -1487,13 +1585,15 @@ static PackageValue *PackageFind(const char *name, size_t length) {
 }
 
 static void PackageImport(const void *key, const void *value, void *context) {
+    bool &fail(*reinterpret_cast<bool *>(context));
+
     char buffer[1024];
     if (!CFStringGetCString((CFStringRef) key, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
         NSLog(@"failed to import package %@", key);
         return;
     }
 
-    PackageValue *metadata(PackageFind(buffer, strlen(buffer)));
+    PackageValue *metadata(PackageFind(buffer, strlen(buffer), &fail));
     NSDictionary *package((NSDictionary *) value);
 
     if (NSNumber *subscribed = [package objectForKey:@"IsSubscribed"])
@@ -1541,12 +1641,12 @@ static void PackageImport(const void *key, const void *value, void *context) {
     CYString type_;
     CYString version_;
 
-    NSString *host_;
-    NSString *authority_;
+    _H<NSString> host_;
+    _H<NSString> authority_;
 
     CYString defaultIcon_;
 
-    NSDictionary *record_;
+    _H<NSDictionary> record_;
     BOOL trusted_;
 }
 
@@ -1591,20 +1691,9 @@ static void PackageImport(const void *key, const void *value, void *context) {
     version_.clear();
     defaultIcon_.clear();
 
-    if (record_ != nil) {
-        [record_ release];
-        record_ = nil;
-    }
-
-    if (host_ != nil) {
-        [host_ release];
-        host_ = nil;
-    }
-
-    if (authority_ != nil) {
-        [authority_ release];
-        authority_ = nil;
-    }
+    record_ = nil;
+    host_ = nil;
+    authority_ = nil;
 }
 
 - (void) dealloc {
@@ -1614,7 +1703,19 @@ static void PackageImport(const void *key, const void *value, void *context) {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"description", @"distribution", @"host", @"key", @"label", @"name", @"origin", @"trusted", @"type", @"uri", @"version", nil];
+    return [NSArray arrayWithObjects:
+        @"description",
+        @"distribution",
+        @"host",
+        @"key",
+        @"label",
+        @"name",
+        @"origin",
+        @"trusted",
+        @"type",
+        @"uri",
+        @"version",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -1670,22 +1771,18 @@ static void PackageImport(const void *key, const void *value, void *context) {
     }
 
     record_ = [Sources_ objectForKey:[self key]];
-    if (record_ != nil)
-        record_ = [record_ retain];
 
     NSURL *url([NSURL URLWithString:uri_]);
 
     host_ = [url host];
     if (host_ != nil)
-        host_ = [[host_ lowercaseString] retain];
+        host_ = [host_ lowercaseString];
 
     if (host_ != nil)
-        authority_ = host_;
+        // XXX: this is due to a bug in _H<>
+        authority_ = (id) host_;
     else
         authority_ = [url path];
-
-    if (authority_ != nil)
-        authority_ = [authority_ retain];
 }
 
 - (Source *) initWithMetaIndex:(metaIndex *)index inPool:(apr_pool_t *)pool {
@@ -1754,7 +1851,7 @@ static void PackageImport(const void *key, const void *value, void *context) {
 }
 
 - (NSString *) name {
-    return origin_.empty() ? authority_ : origin_;
+    return origin_.empty() ? (id) authority_ : origin_;
 }
 
 - (NSString *) description {
@@ -1762,7 +1859,7 @@ static void PackageImport(const void *key, const void *value, void *context) {
 }
 
 - (NSString *) label {
-    return label_.empty() ? authority_ : label_;
+    return label_.empty() ? (id) authority_ : label_;
 }
 
 - (NSString *) origin {
@@ -1779,91 +1876,230 @@ static void PackageImport(const void *key, const void *value, void *context) {
 
 @end
 /* }}} */
-/* Relationship Class {{{ */
-@interface Relationship : NSObject {
-    NSString *type_;
-    NSString *id_;
+/* CydiaOperation Class {{{ */
+@interface CydiaOperation : NSObject {
+    NSString *operator_;
+    NSString *value_;
 }
 
-- (NSString *) type;
-- (NSString *) id;
-- (NSString *) name;
+- (NSString *) operator;
+- (NSString *) value;
 
 @end
 
-@implementation Relationship
+@implementation CydiaOperation
 
 - (void) dealloc {
-    [type_ release];
-    [id_ release];
+    [operator_ release];
+    [value_ release];
     [super dealloc];
 }
 
-- (NSString *) type {
-    return type_;
+- (id) initWithOperator:(const char *)_operator value:(const char *)value {
+    if ((self = [super init]) != nil) {
+        operator_ = [[NSString alloc] initWithUTF8String:_operator];
+        value_ = [[NSString alloc] initWithUTF8String:value];
+    } return self;
 }
 
-- (NSString *) id {
-    return id_;
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"operator",
+        @"value",
+    nil];
 }
 
-- (NSString *) name {
-    _assert(false);
-    return nil;
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
 }
 
-@end
-/* }}} */
-/* Package Class {{{ */
-struct ParsedPackage {
-    CYString tagline_;
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
 
-    CYString icon_;
+- (NSString *) operator {
+    return operator_;
+}
 
-    CYString depiction_;
-    CYString homepage_;
+- (NSString *) value {
+    return value_;
+}
 
-    CYString sponsor_;
-    CYString author_;
+@end
+/* }}} */
+/* CydiaClause Class {{{ */
+@interface CydiaClause : NSObject {
+    NSString *package_;
+    CydiaOperation *version_;
+}
 
-    CYString bugs_;
-    CYString support_;
-};
+- (NSString *) package;
+- (CydiaOperation *) version;
 
-@interface Package : NSObject {
-    uint32_t era_ : 26;
-    uint32_t role_ : 3;
-    uint32_t essential_ : 1;
-    uint32_t obsolete_ : 1;
-    uint32_t ignored_ : 1;
+@end
 
-    apr_pool_t *pool_;
+@implementation CydiaClause
 
-    _transient Database *database_;
+- (void) dealloc {
+    [package_ release];
+    [version_ release];
+    [super dealloc];
+}
 
-    pkgCache::VerIterator version_;
-    pkgCache::PkgIterator iterator_;
-    pkgCache::VerFileIterator file_;
+- (id) initWithIterator:(pkgCache::DepIterator &)dep {
+    if ((self = [super init]) != nil) {
+        package_ = [[NSString alloc] initWithUTF8String:dep.TargetPkg().Name()];
 
-    CYString id_;
-    CYString name_;
+        if (const char *version = dep.TargetVer())
+            version_ = [[CydiaOperation alloc] initWithOperator:dep.CompType() value:version];
+        else
+            version_ = [[NSNull null] retain];
+    } return self;
+}
 
-    CYString latest_;
-    CYString installed_;
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"package",
+        @"version",
+    nil];
+}
 
-    const char *section_;
-    _transient NSString *section$_;
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
 
-    Source *source_;
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
 
-    PackageValue *metadata_;
-    ParsedPackage *parsed_;
+- (NSString *) package {
+    return package_;
+}
 
-    NSMutableArray *tags_;
+- (CydiaOperation *) version {
+    return version_;
 }
 
-- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
-+ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
+@end
+/* }}} */
+/* CydiaRelation Class {{{ */
+@interface CydiaRelation : NSObject {
+    NSString *relationship_;
+    NSMutableArray *clauses_;
+}
+
+- (NSString *) relationship;
+- (NSArray *) clauses;
+
+@end
+
+@implementation CydiaRelation
+
+- (void) dealloc {
+    [relationship_ release];
+    [clauses_ release];
+    [super dealloc];
+}
+
+- (id) initWithIterator:(pkgCache::DepIterator &)dep {
+    if ((self = [super init]) != nil) {
+        relationship_ = [[NSString alloc] initWithUTF8String:dep.DepType()];
+        clauses_ = [[NSMutableArray alloc] initWithCapacity:8];
+
+        pkgCache::DepIterator start;
+        pkgCache::DepIterator end;
+        dep.GlobOr(start, end); // ++dep
+
+        _forever {
+            [clauses_ addObject:[[[CydiaClause alloc] initWithIterator:start] autorelease]];
+
+            // yes, seriously. (wtf?)
+            if (start == end)
+                break;
+            ++start;
+        }
+    } return self;
+}
+
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"clauses",
+        @"relationship",
+    nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (NSString *) relationship {
+    return relationship_;
+}
+
+- (NSArray *) clauses {
+    return clauses_;
+}
+
+- (void) addClause:(CydiaClause *)clause {
+    [clauses_ addObject:clause];
+}
+
+@end
+/* }}} */
+/* Package Class {{{ */
+struct ParsedPackage {
+    CYString tagline_;
+
+    CYString icon_;
+
+    CYString depiction_;
+    CYString homepage_;
+
+    CYString sponsor_;
+    CYString author_;
+
+    CYString bugs_;
+    CYString support_;
+};
+
+@interface Package : NSObject {
+    uint32_t era_ : 26;
+    uint32_t role_ : 3;
+    uint32_t essential_ : 1;
+    uint32_t obsolete_ : 1;
+    uint32_t ignored_ : 1;
+
+    apr_pool_t *pool_;
+
+    _transient Database *database_;
+
+    pkgCache::VerIterator version_;
+    pkgCache::PkgIterator iterator_;
+    pkgCache::VerFileIterator file_;
+
+    CYString id_;
+    CYString name_;
+
+    CYString latest_;
+    CYString installed_;
+
+    const char *section_;
+    _transient NSString *section$_;
+
+    Source *source_;
+
+    PackageValue *metadata_;
+    ParsedPackage *parsed_;
+
+    NSMutableArray *tags_;
+}
+
+- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
++ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
 
 - (pkgCache::PkgIterator) iterator;
 - (void) parse;
@@ -2089,8 +2325,17 @@ struct PackageNameOrdering :
 }
 
 + (NSString *) webScriptNameForSelector:(SEL)selector {
-    if (selector == @selector(hasTag:))
+    if (false);
+    else if (selector == @selector(clear))
+        return @"clear";
+    else if (selector == @selector(getField:))
+        return @"getField";
+    else if (selector == @selector(hasTag:))
         return @"hasTag";
+    else if (selector == @selector(install))
+        return @"install";
+    else if (selector == @selector(remove))
+        return @"remove";
     else
         return nil;
 }
@@ -2100,7 +2345,36 @@ struct PackageNameOrdering :
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"applications", @"author", @"depiction", @"longDescription", @"essential", @"homepage", @"icon", @"id", @"installed", @"latest", @"longSection", @"maintainer", @"mode", @"name", @"purposes", @"section", @"shortDescription", @"shortSection", @"simpleSection", @"size", @"source", @"sponsor", @"support", @"warnings", nil];
+    return [NSArray arrayWithObjects:
+        @"applications",
+        @"author",
+        @"depiction",
+        @"essential",
+        @"homepage",
+        @"icon",
+        @"id",
+        @"installed",
+        @"latest",
+        @"longDescription",
+        @"longSection",
+        @"maintainer",
+        @"mode",
+        @"name",
+        @"purposes",
+        @"relations",
+        @"section",
+        @"selection",
+        @"shortDescription",
+        @"shortSection",
+        @"simpleSection",
+        @"size",
+        @"source",
+        @"sponsor",
+        @"state",
+        @"support",
+        @"tags",
+        @"warnings",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -2111,6 +2385,28 @@ struct PackageNameOrdering :
     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
 }
 
+- (NSArray *) relations {
+@synchronized (database_) {
+    NSMutableArray *relations([NSMutableArray arrayWithCapacity:16]);
+    for (pkgCache::DepIterator dep(version_.DependsList()); !dep.end(); ++dep)
+        [relations addObject:[[[CydiaRelation alloc] initWithIterator:dep] autorelease]];
+    return relations;
+} }
+
+- (NSString *) getField:(NSString *)name {
+@synchronized (database_) {
+    if ([database_ era] != era_ || file_.end())
+        return nil;
+
+    pkgRecords::Parser &parser([database_ records]->Lookup(file_));
+
+    const char *start, *end;
+    if (!parser.Find([name UTF8String], start, end))
+        return (NSString *) [NSNull null];
+
+    return [(NSString *) CYStringCreate(start, end - start) autorelease];
+} }
+
 - (void) parse {
     if (parsed_ != NULL)
         return;
@@ -2502,7 +2798,7 @@ struct PackageNameOrdering :
     _end
 
     _profile(Package$visible$isSectionVisible)
-        if (section != nil && !isSectionVisible(section))
+        if (!isSectionVisible(section))
             return false;
     _end
 
@@ -2625,6 +2921,54 @@ struct PackageNameOrdering :
     return files;
 }
 
+- (NSString *) state {
+@synchronized (database_) {
+    if ([database_ era] != era_ || file_.end())
+        return nil;
+
+    switch (iterator_->CurrentState) {
+        case pkgCache::State::NotInstalled:
+            return @"NotInstalled";
+        case pkgCache::State::UnPacked:
+            return @"UnPacked";
+        case pkgCache::State::HalfConfigured:
+            return @"HalfConfigured";
+        case pkgCache::State::HalfInstalled:
+            return @"HalfInstalled";
+        case pkgCache::State::ConfigFiles:
+            return @"ConfigFiles";
+        case pkgCache::State::Installed:
+            return @"Installed";
+        case pkgCache::State::TriggersAwaited:
+            return @"TriggersAwaited";
+        case pkgCache::State::TriggersPending:
+            return @"TriggersPending";
+    }
+
+    return (NSString *) [NSNull null];
+} }
+
+- (NSString *) selection {
+@synchronized (database_) {
+    if ([database_ era] != era_ || file_.end())
+        return nil;
+
+    switch (iterator_->SelectedState) {
+        case pkgCache::State::Unknown:
+            return @"Unknown";
+        case pkgCache::State::Install:
+            return @"Install";
+        case pkgCache::State::Hold:
+            return @"Hold";
+        case pkgCache::State::DeInstall:
+            return @"DeInstall";
+        case pkgCache::State::Purge:
+            return @"Purge";
+    }
+
+    return (NSString *) [NSNull null];
+} }
+
 - (NSArray *) warnings {
     NSMutableArray *warnings([NSMutableArray arrayWithCapacity:4]);
     const char *name(iterator_.Name());
@@ -2736,6 +3080,8 @@ struct PackageNameOrdering :
     if (range.location != NSNotFound)
         return YES;
 
+    [self parse];
+
     range = [[self shortDescription] rangeOfString:text options:MatchCompareOptions_];
     if (range.location != NSNotFound)
         return YES;
@@ -2761,6 +3107,10 @@ struct PackageNameOrdering :
     _assert(false);
 }
 
+- (NSArray *) tags {
+    return tags_;
+}
+
 - (BOOL) hasTag:(NSString *)tag {
     return tags_ == nil ? NO : [tags_ containsObject:tag];
 }
@@ -3041,8 +3391,7 @@ static NSString *Warning_;
 - (void) dealloc {
     // XXX: actually implement this thing
     _assert(false);
-    if (deadSources_)
-           CFRelease(deadSources_);
+    [sourceList_ release];
     [self releasePackages];
     apr_pool_destroy(pool_);
     NSRecycleZone(zone_);
@@ -3086,28 +3435,36 @@ static NSString *Warning_;
         lprintf("S:%s\n", data);
 
         if (conffile_r(data, size)) {
-            [delegate_ setConfigurationData:conffile_r[1]];
+            // status: /fail : conffile-prompt : '/fail' '/fail.dpkg-new' 1 1
+            [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:conffile_r[1] waitUntilDone:YES];
         } else if (strncmp(data, "status: ", 8) == 0) {
-            NSString *string = [NSString stringWithUTF8String:(data + 8)];
-            [delegate_ setProgressTitle:string];
+            // status: <package>: {unpacked,half-configured,installed}
+            CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 8)] ofType:@"STATUS"]);
+            [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
+        } else if (strncmp(data, "processing: ", 12) == 0) {
+            // processing: configure: config-test
+            CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 12)] ofType:@"STATUS"]);
+            [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
         } else if (pmstatus_r(data, size)) {
             std::string type([pmstatus_r[1] UTF8String]);
-            NSString *id = pmstatus_r[2];
+
+            NSString *package = pmstatus_r[2];
+            if ([package isEqualToString:@"dpkg-exec"])
+                package = nil;
 
             float percent([pmstatus_r[3] floatValue]);
-            [delegate_ setProgressPercent:(percent / 100)];
+            [progress_ performSelectorOnMainThread:@selector(setProgressPercent:) withObject:[NSNumber numberWithFloat:(percent / 100)] waitUntilDone:YES];
 
             NSString *string = pmstatus_r[4];
 
-            if (type == "pmerror")
-                [delegate_ performSelectorOnMainThread:@selector(_setProgressErrorPackage:)
-                    withObject:[NSArray arrayWithObjects:string, id, nil]
-                    waitUntilDone:YES
-                ];
-            else if (type == "pmstatus") {
-                [delegate_ setProgressTitle:string];
+            if (type == "pmerror") {
+                CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:@"ERROR" forPackage:package]);
+                [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
+            } else if (type == "pmstatus") {
+                CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:@"STATUS" forPackage:package]);
+                [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
             } else if (type == "pmconffile")
-                [delegate_ setConfigurationData:string];
+                [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:string waitUntilDone:YES];
             else
                 lprintf("E:unknown pmstatus\n");
         } else
@@ -3124,7 +3481,9 @@ static NSString *Warning_;
 
     while (std::getline(is, line)) {
         lprintf("O:%s\n", line.c_str());
-        [delegate_ addProgressOutput:[NSString stringWithUTF8String:line.c_str()]];
+
+        CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:line.c_str()] ofType:@"INFORMATION"]);
+        [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
     _assume(false);
@@ -3160,7 +3519,7 @@ static NSString *Warning_;
             capacity += 1024;
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL);
-        deadSources_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+        sourceList_ = [[NSMutableArray alloc] initWithCapacity:16];
 
         int fds[2];
 
@@ -3232,104 +3591,37 @@ static NSString *Warning_;
 }
 
 - (NSArray *) sources {
-    NSMutableArray *sources([NSMutableArray arrayWithCapacity:sources_.size()]);
-    for (SourceMap::const_iterator i(sources_.begin()); i != sources_.end(); ++i)
-        [sources addObject:i->second];
-    [sources addObjectsFromArray:(NSArray *)deadSources_];
-    return sources;
+    return sourceList_;
 }
 
-- (NSArray *) issues {
-    if (cache_->BrokenCount() == 0)
-        return nil;
-
-    NSMutableArray *issues([NSMutableArray arrayWithCapacity:4]);
-
-    for (Package *package in [self packages]) {
-        if (![package broken])
-            continue;
-        pkgCache::PkgIterator pkg([package iterator]);
-
-        NSMutableArray *entry([NSMutableArray arrayWithCapacity:4]);
-        [entry addObject:[package name]];
-        [issues addObject:entry];
-
-        pkgCache::VerIterator ver(cache_[pkg].InstVerIter(cache_));
-        if (ver.end())
-            continue;
-
-        for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
-            pkgCache::DepIterator start;
-            pkgCache::DepIterator end;
-            dep.GlobOr(start, end); // ++dep
-
-            if (!cache_->IsImportantDep(end))
-                continue;
-            if ((cache_[end] & pkgDepCache::DepGInstall) != 0)
-                continue;
-
-            NSMutableArray *failure([NSMutableArray arrayWithCapacity:4]);
-            [entry addObject:failure];
-            [failure addObject:[NSString stringWithUTF8String:start.DepType()]];
-
-            NSString *name([NSString stringWithUTF8String:start.TargetPkg().Name()]);
-            if (Package *package = [self packageWithName:name])
-                name = [package name];
-            [failure addObject:name];
-
-            pkgCache::PkgIterator target(start.TargetPkg());
-            if (target->ProvidesList != 0)
-                [failure addObject:@"?"];
-            else {
-                pkgCache::VerIterator ver(cache_[target].InstVerIter(cache_));
-                if (!ver.end())
-                    [failure addObject:[NSString stringWithUTF8String:ver.VerStr()]];
-                else if (!cache_[target].CandidateVerIter(cache_).end())
-                    [failure addObject:@"-"];
-                else if (target->ProvidesList == 0)
-                    [failure addObject:@"!"];
-                else
-                    [failure addObject:@"%"];
-            }
-
-            _forever {
-                if (start.TargetVer() != 0)
-                    [failure addObject:[NSString stringWithFormat:@"%s %s", start.CompType(), start.TargetVer()]];
-                if (start == end)
-                    break;
-                ++start;
-            }
-        }
-    }
-
-    return issues;
+- (Source *) sourceWithKey:(NSString *)key {
+    for (Source *source in [self sources]) {
+        if ([[source key] isEqualToString:key])
+            return source;
+    } return nil;
 }
 
 - (bool) popErrorWithTitle:(NSString *)title {
     bool fatal(false);
-    std::string message;
 
     while (!_error->empty()) {
         std::string error;
         bool warning(!_error->PopMessage(error));
         if (!warning)
             fatal = true;
+
         for (;;) {
             size_t size(error.size());
             if (size == 0 || error[size - 1] != '\n')
                 break;
             error.resize(size - 1);
         }
+
         lprintf("%c:[%s]\n", warning ? 'W' : 'E', error.c_str());
 
-        if (!message.empty())
-            message += "\n\n";
-        message += error;
+        [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? @"WARNING" : @"ERROR")] forTask:title];
     }
 
-    if (fatal && !message.empty())
-        [delegate_ _setProgressError:[NSString stringWithUTF8String:message.c_str()] withTitle:[NSString stringWithFormat:Colon_, fatal ? Error_ : Warning_, title]];
-
     return fatal;
 }
 
@@ -3337,14 +3629,14 @@ static NSString *Warning_;
     return [self popErrorWithTitle:title] || !success;
 }
 
-- (void) reloadData { CYPoolStart() {
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation { CYPoolStart() {
 @synchronized (self) {
     ++era_;
 
     [self releasePackages];
 
-    sources_.clear();
-    CFArrayRemoveAllValues(deadSources_);
+    sourceMap_.clear();
+    [sourceList_ removeAllObjects];
 
     _error->Discard();
 
@@ -3373,10 +3665,14 @@ static NSString *Warning_;
     if (chk != -1)
         close(chk);
 
+    if (invocation != nil)
+        [invocation invoke];
+
     NSString *title(UCLocalize("DATABASE"));
 
     _trace();
-    if (!cache_.Open(progress_, true)) { pop:
+    OpProgress progress;
+    while (!cache_.Open(progress, true)) { pop:
         std::string error;
         bool warning(!_error->PopMessage(error));
         lprintf("cache_.Open():[%s]\n", error.c_str());
@@ -3385,16 +3681,18 @@ static NSString *Warning_;
             [delegate_ repairWithSelector:@selector(configure)];
         else if (error == "The package lists or status file could not be parsed or opened.")
             [delegate_ repairWithSelector:@selector(update)];
-        // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
         // else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
+        // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
+        // else if (error == "Malformed Status line")
         // else if (error == "The list of sources could not be read.")
-        else
-            [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, warning ? Warning_ : Error_, title]];
+        else {
+            [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? @"WARNING" : @"ERROR")] forTask:title];
+            return;
+        }
 
         if (warning)
             goto pop;
         _error->Discard();
-        return;
     }
     _trace();
 
@@ -3413,7 +3711,7 @@ static NSString *Warning_;
         return;
 
     if (cache_->DelCount() != 0 || cache_->InstCount() != 0) {
-        [delegate_ _setProgressError:@"COUNTS_NONZERO_EX" withTitle:title];
+        [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("COUNTS_NONZERO_EX") ofType:@"ERROR"] forTask:title];
         return;
     }
 
@@ -3425,7 +3723,7 @@ static NSString *Warning_;
             return;
 
         if (cache_->BrokenCount() != 0) {
-            [delegate_ _setProgressError:@"STILL_BROKEN_EX" withTitle:title];
+            [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("STILL_BROKEN_EX") ofType:@"ERROR"] forTask:title];
             return;
         }
 
@@ -3434,19 +3732,17 @@ static NSString *Warning_;
     }
 
     for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
-        bool found = false;
+        Source *object([[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease]);
+        [sourceList_ addObject:object];
+
         std::vector<pkgIndexFile *> *indices = (*source)->GetIndexFiles();
         for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
             // XXX: this could be more intelligent
             if (dynamic_cast<debPackagesIndex *>(*index) != NULL) {
                 pkgCache::PkgFileIterator cached((*index)->FindInCache(cache_));
-                if (!cached.end()) {
-                    sources_[cached->ID] = [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease];
-                    found = true;
-                }
+                if (!cached.end())
+                    sourceMap_[cached->ID] = object;
             }
-        if (!found)
-            CFArrayAppendValue(deadSources_, [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease]);
     }
 
     {
@@ -3507,12 +3803,11 @@ static NSString *Warning_;
     delete resolver_;
     resolver_ = new pkgProblemResolver(cache_);
 
-    for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator) {
-        if (!cache_[iterator].Keep()) {
+    for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator)
+        if (!cache_[iterator].Keep())
             cache_->MarkKeep(iterator, false);
+        else if ((cache_[iterator].iFlags & pkgDepCache::ReInstall) != 0)
             cache_->SetReInstall(iterator, false);
-        }
-    }
 } }
 
 - (void) configure {
@@ -3578,6 +3873,9 @@ static NSString *Warning_;
 }
 
 - (void) perform {
+    bool substrate(RestartSubstrate_);
+    RestartSubstrate_ = false;
+
     NSString *title(UCLocalize("PERFORM_SELECTIONS"));
 
     NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
@@ -3588,13 +3886,14 @@ static NSString *Warning_;
             [before addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
     }
 
+    [delegate_ performSelectorOnMainThread:@selector(retainNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
+
     if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue) {
         _trace();
+        [self popErrorWithTitle:title];
         return;
     }
 
-    [CydiaApp retainNetworkActivityIndicator];
-
     bool failed = false;
     for (pkgAcquire::ItemIterator item = fetcher_->ItemsBegin(); item != fetcher_->ItemsEnd(); item++) {
         if ((*item)->Status == pkgAcquire::Item::StatDone && (*item)->Complete)
@@ -3602,27 +3901,19 @@ static NSString *Warning_;
         if ((*item)->Status == pkgAcquire::Item::StatIdle)
             continue;
 
-        std::string uri = (*item)->DescURI();
-        std::string error = (*item)->ErrorText;
-
-        lprintf("pAf:%s:%s\n", uri.c_str(), error.c_str());
         failed = true;
-
-        [delegate_ performSelectorOnMainThread:@selector(_setProgressErrorPackage:)
-            withObject:[NSArray arrayWithObjects:
-                [NSString stringWithUTF8String:error.c_str()],
-            nil]
-            waitUntilDone:YES
-        ];
     }
 
-    [CydiaApp releaseNetworkActivityIndicator];
+    [delegate_ performSelectorOnMainThread:@selector(releaseNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
 
     if (failed) {
         _trace();
         return;
     }
 
+    if (substrate)
+        RestartSubstrate_ = true;
+
     _system->UnLock();
     pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
 
@@ -3665,35 +3956,47 @@ static NSString *Warning_;
 }
 
 - (void) updateWithStatus:(Status &)status {
-    _transient NSObject<ProgressDelegate> *delegate(status.getDelegate());
     NSString *title(UCLocalize("REFRESHING_DATA"));
 
     pkgSourceList list;
-    if (!list.ReadMainList())
-        [delegate _setProgressError:@"Unable to read source list." withTitle:title];
+    if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
+        return;
 
     FileFd lock;
     lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
     if ([self popErrorWithTitle:title])
         return;
 
-    if ([self popErrorWithTitle:title forOperation:ListUpdate(status, list, PulseInterval_)])
-        /* XXX: ignore this because users suck and don't understand why refreshing is important: return */
-        /* XXX: why the hell is an empty if statement a clang error? */ (void) 0;
+    [delegate_ performSelectorOnMainThread:@selector(retainNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
+
+    bool success(ListUpdate(status, list, PulseInterval_));
+    if (status.WasCancelled())
+        _error->Discard();
+    else
+        [self popErrorWithTitle:title forOperation:success];
+
+    [delegate_ performSelectorOnMainThread:@selector(releaseNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
 
     [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"];
     Changed_ = true;
 }
 
-- (void) setDelegate:(id)delegate {
+- (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate {
     delegate_ = delegate;
+}
+
+- (void) setProgressDelegate:(NSObject<ProgressDelegate> *)delegate {
+    progress_ = delegate;
     status_.setDelegate(delegate);
-    progress_.setDelegate(delegate);
+}
+
+- (NSObject<ProgressDelegate> *) progressDelegate {
+    return progress_;
 }
 
 - (Source *) getSource:(pkgCache::PkgFileIterator)file {
-    SourceMap::const_iterator i(sources_.find(file->ID));
-    return i == sources_.end() ? nil : i->second;
+    SourceMap::const_iterator i(sourceMap_.find(file->ID));
+    return i == sourceMap_.end() ? nil : i->second;
 }
 
 - (NSString *) mappedSectionForPointer:(const char *)section {
@@ -3737,6 +4040,7 @@ static NSString *Warning_;
 }
 
 - (id) initWithDelegate:(IndirectDelegate *)indirect;
+
 @end
 
 @implementation CydiaObject
@@ -3757,7 +4061,19 @@ static NSString *Warning_;
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"device", @"firewire", @"imei", @"mac", @"serial", nil];
+    return [NSArray arrayWithObjects:
+        @"device",
+        @"ecid",
+        @"firmware",
+        @"hostname",
+        @"idiom",
+        @"model",
+        @"plmn",
+        @"role",
+        @"serial",
+        @"token",
+        @"version",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -3768,63 +4084,108 @@ static NSString *Warning_;
     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
 }
 
+- (NSString *) version {
+    return @ Cydia_;
+}
+
 - (NSString *) device {
     return [[UIDevice currentDevice] uniqueIdentifier];
 }
 
-#if 0 // XXX: implement!
-- (NSString *) mac {
-    if (![indirect_ promptForSensitive:@"Mac Address"])
-        return nil;
+- (NSString *) firmware {
+    return [[UIDevice currentDevice] systemVersion];
+}
+
+- (NSString *) hostname {
+    return [[UIDevice currentDevice] name];
+}
+
+- (NSString *) idiom {
+    return (id) Idiom_ ?: [NSNull null];
+}
+
+- (NSString *) plmn {
+    return (id) PLMN_ ?: [NSNull null];
+}
+
+- (NSString *) ecid {
+    return (id) ChipID_ ?: [NSNull null];
 }
 
 - (NSString *) serial {
-    if (![indirect_ promptForSensitive:@"Serial #"])
-        return nil;
+    return SerialNumber_;
 }
 
-- (NSString *) firewire {
-    if (![indirect_ promptForSensitive:@"Firewire GUID"])
-        return nil;
+- (NSString *) role {
+    return (id) Role_ ?: [NSNull null];
 }
 
-- (NSString *) imei {
-    if (![indirect_ promptForSensitive:@"IMEI"])
-        return nil;
+- (NSString *) model {
+    return [NSString stringWithUTF8String:Machine_];
+}
+
+- (NSString *) token {
+    return (id) Token_ ?: [NSNull null];
 }
-#endif
 
 + (NSString *) webScriptNameForSelector:(SEL)selector {
-    if (selector == @selector(close))
+    if (false);
+    else if (selector == @selector(addCydiaHost:))
+        return @"addCydiaHost";
+    else if (selector == @selector(addTrivialSource:))
+        return @"addTrivialSource";
+    else if (selector == @selector(close))
         return @"close";
+    else if (selector == @selector(du:))
+        return @"du";
+    else if (selector == @selector(stringWithFormat:arguments:))
+        return @"format";
+    else if (selector == @selector(getAllSources))
+        return @"getAllSourcs";
+    else if (selector == @selector(getKernelNumber:))
+        return @"getKernelNumber";
+    else if (selector == @selector(getKernelString:))
+        return @"getKernelString";
     else if (selector == @selector(getInstalledPackages))
         return @"getInstalledPackages";
     else if (selector == @selector(getPackageById:))
         return @"getPackageById";
     else if (selector == @selector(installPackages:))
         return @"installPackages";
+    else if (selector == @selector(localizedStringForKey:value:table:))
+        return @"localize";
+    else if (selector == @selector(refreshSources))
+        return @"refreshSources";
+    else if (selector == @selector(removeButton))
+        return @"removeButton";
+    else if (selector == @selector(substitutePackageNames:))
+        return @"substitutePackageNames";
+    else if (selector == @selector(scrollToBottom:))
+        return @"scrollToBottom";
+    else if (selector == @selector(setAllowsNavigationAction:))
+        return @"setAllowsNavigationAction";
     else if (selector == @selector(setButtonImage:withStyle:toFunction:))
         return @"setButtonImage";
     else if (selector == @selector(setButtonTitle:withStyle:toFunction:))
         return @"setButtonTitle";
+    else if (selector == @selector(setHidesBackButton:))
+        return @"setHidesBackButton";
+    else if (selector == @selector(setHidesNavigationBar:))
+        return @"setHidesNavigationBar";
+    else if (selector == @selector(setNavigationBarStyle:))
+        return @"setNavigationBarStyle";
+    else if (selector == @selector(setNavigationBarTintRed:green:blue:alpha:))
+        return @"setNavigationBarTintColor";
     else if (selector == @selector(setPopupHook:))
         return @"setPopupHook";
-    else if (selector == @selector(setSpecial:))
-        return @"setSpecial";
     else if (selector == @selector(setToken:))
         return @"setToken";
     else if (selector == @selector(setViewportWidth:))
         return @"setViewportWidth";
-    else if (selector == @selector(supports:))
-        return @"supports";
-    else if (selector == @selector(stringWithFormat:arguments:))
-        return @"format";
-    else if (selector == @selector(localizedStringForKey:value:table:))
-        return @"localize";
-    else if (selector == @selector(du:))
-        return @"du";
     else if (selector == @selector(statfs:))
         return @"statfs";
+    else if (selector == @selector(supports:))
+        return @"supports";
     else
         return nil;
 }
@@ -3837,19 +4198,73 @@ static NSString *Warning_;
     return [feature isEqualToString:@"window.open"];
 }
 
+- (NSNumber *) getKernelNumber:(NSString *)name {
+    const char *string([name UTF8String]);
+
+    size_t size;
+    if (sysctlbyname(string, NULL, &size, NULL, 0) == -1)
+        return (id) [NSNull null];
+
+    if (size != sizeof(int))
+        return (id) [NSNull null];
+
+    int value;
+    if (sysctlbyname(string, &value, &size, NULL, 0) == -1)
+        return (id) [NSNull null];
+
+    return [NSNumber numberWithInt:value];
+}
+
+- (NSString *) getKernelString:(NSString *)name {
+    const char *string([name UTF8String]);
+
+    size_t size;
+    if (sysctlbyname(string, NULL, &size, NULL, 0) == -1)
+        return (id) [NSNull null];
+
+    char value[size + 1];
+    if (sysctlbyname(string, value, &size, NULL, 0) == -1)
+        return (id) [NSNull null];
+
+    // XXX: just in case you request something ludicrous
+    value[size] = '\0';
+
+    return [NSString stringWithCString:value];
+}
+
+- (void) addCydiaHost:(NSString *)host {
+    [delegate_ performSelectorOnMainThread:@selector(addCydiaHost:) withObject:host waitUntilDone:NO];
+}
+
+- (void) addTrivialSource:(NSString *)href {
+    [delegate_ performSelectorOnMainThread:@selector(addTrivialSource:) withObject:href waitUntilDone:NO];
+}
+
+- (void) refreshSources {
+    [delegate_ performSelectorOnMainThread:@selector(syncData) withObject:nil waitUntilDone:NO];
+}
+
+- (NSArray *) getAllSources {
+    return [[Database sharedInstance] sources];
+}
+
 - (NSArray *) getInstalledPackages {
-    NSArray *packages([[Database sharedInstance] packages]);
+    Database *database([Database sharedInstance]);
+@synchronized (database) {
+    NSArray *packages([database packages]);
     NSMutableArray *installed([NSMutableArray arrayWithCapacity:1024]);
     for (Package *package in packages)
-        if ([package installed] != nil)
+        if (![package uninstalled])
             [installed addObject:package];
     return installed;
-}
+} }
 
 - (Package *) getPackageById:(NSString *)id {
-    Package *package([[Database sharedInstance] packageWithName:id]);
-    [package parse];
-    return package;
+    if (Package *package = [[Database sharedInstance] packageWithName:id]) {
+        [package parse];
+        return package;
+    } else
+        return (Package *) [NSNull null];
 }
 
 - (NSArray *) statfs:(NSString *)path {
@@ -3910,13 +4325,28 @@ static NSString *Warning_;
 }
 
 - (void) close {
-    [indirect_ close];
+    [indirect_ performSelectorOnMainThread:@selector(close) withObject:nil waitUntilDone:NO];
 }
 
 - (void) installPackages:(NSArray *)packages {
     [delegate_ performSelectorOnMainThread:@selector(installPackages:) withObject:packages waitUntilDone:NO];
 }
 
+- (NSString *) substitutePackageNames:(NSString *)message {
+    NSMutableArray *words([[message componentsSeparatedByString:@" "] mutableCopy]);
+    for (size_t i(0), e([words count]); i != e; ++i) {
+        NSString *word([words objectAtIndex:i]);
+        if (Package *package = [[Database sharedInstance] packageWithName:word])
+            [words replaceObjectAtIndex:i withObject:[package name]];
+    }
+
+    return [words componentsJoinedByString:@" "];
+}
+
+- (void) removeButton {
+    [indirect_ removeButton];
+}
+
 - (void) setButtonImage:(NSString *)button withStyle:(NSString *)style toFunction:(id)function {
     [indirect_ setButtonImage:button withStyle:style toFunction:function];
 }
@@ -3925,25 +4355,53 @@ static NSString *Warning_;
     [indirect_ setButtonTitle:button withStyle:style toFunction:function];
 }
 
-- (void) setSpecial:(id)function {
-    [indirect_ setSpecial:function];
+- (void) setAllowsNavigationAction:(NSString *)value {
+    [indirect_ performSelectorOnMainThread:@selector(setAllowsNavigationActionByNumber:) withObject:value waitUntilDone:NO];
 }
 
-- (void) setToken:(NSString *)token {
-    if (Token_ != nil)
-        [Token_ release];
-    Token_ = [token retain];
+- (void) setHidesBackButton:(NSString *)value {
+    [indirect_ performSelectorOnMainThread:@selector(setHidesBackButtonByNumber:) withObject:value waitUntilDone:NO];
+}
 
-    [Metadata_ setObject:Token_ forKey:@"Token"];
-    Changed_ = true;
+- (void) setHidesNavigationBar:(NSString *)value {
+    [indirect_ performSelectorOnMainThread:@selector(setHidesNavigationBarByNumber:) withObject:value waitUntilDone:NO];
+}
+
+- (void) setNavigationBarStyle:(NSString *)value {
+    [indirect_ performSelectorOnMainThread:@selector(setNavigationBarStyle:) withObject:value waitUntilDone:NO];
+}
+
+- (void) setNavigationBarTintRed:(NSNumber *)red green:(NSNumber *)green blue:(NSNumber *)blue alpha:(NSNumber *)alpha {
+    float opacity(alpha == (id) [WebUndefined undefined] ? 1 : [alpha floatValue]);
+    UIColor *color([UIColor colorWithRed:[red floatValue] green:[green floatValue] blue:[blue floatValue] alpha:opacity]);
+    [indirect_ performSelectorOnMainThread:@selector(setNavigationBarTintColor:) withObject:color waitUntilDone:NO];
+}
+
+- (void) _setToken:(NSString *)token {
+    Token_ = token;
+
+    if (token == nil)
+        [Metadata_ removeObjectForKey:@"Token"];
+    else
+        [Metadata_ setObject:Token_ forKey:@"Token"];
+
+    Changed_ = true;
+}
+
+- (void) setToken:(NSString *)token {
+    [self performSelectorOnMainThread:@selector(_setToken:) withObject:token waitUntilDone:NO];
 }
 
 - (void) setPopupHook:(id)function {
     [indirect_ setPopupHook:function];
 }
 
+- (void) scrollToBottom:(NSNumber *)animated {
+    [indirect_ performSelectorOnMainThread:@selector(scrollToBottomAnimated:) withObject:animated waitUntilDone:NO];
+}
+
 - (void) setViewportWidth:(float)width {
-    [indirect_ setViewportWidth:width];
+    [indirect_ setViewportWidthOnMainThread:width];
 }
 
 - (NSString *) stringWithFormat:(NSString *)format arguments:(WebScriptObject *)arguments {
@@ -3952,7 +4410,7 @@ static NSString *Warning_;
     id values[count];
     for (unsigned i(0); i != count; ++i)
         values[i] = [arguments objectAtIndex:i];
-    return [[[NSString alloc] initWithFormat:format arguments:*(reinterpret_cast<va_list *>(&values))] autorelease];
+    return [[[NSString alloc] initWithFormat:format arguments:reinterpret_cast<va_list>(values)] autorelease];
 }
 
 - (NSString *) localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)table {
@@ -3968,9 +4426,9 @@ static NSString *Warning_;
 
 /* @ Loading... Indicator {{{ */
 @interface CYLoadingIndicator : UIView {
-    UIActivityIndicatorView *spinner_;
-    UILabel *label_;
-    UIView *container_;
+    _H<UIActivityIndicatorView> spinner_;
+    _H<UILabel> label_;
+    _H<UIView> container_;
 }
 
 @property (readonly, nonatomic) UILabel *label;
@@ -3980,8 +4438,8 @@ static NSString *Warning_;
 
 @implementation CYLoadingIndicator
 
-- (id)initWithFrame:(CGRect)frame {
-    if ((self = [super initWithFrame:frame])) {
+- (id) initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
         container_ = [[[UIView alloc] init] autorelease];
         [container_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin];
 
@@ -4020,49 +4478,69 @@ static NSString *Warning_;
         [spinner_ setFrame:spinrect];
         [label_ setFrame:textrect];
         [self addSubview:container_];
-    }
+    } return self;
+}
 
-    return self;
+- (UILabel *) label {
+    return label_;
 }
 
-- (UILabel *)label { return label_; }
-- (UIActivityIndicatorView *)activityIndicatorView { return spinner_; }
+- (UIActivityIndicatorView *) activityIndicatorView {
+    return spinner_;
+}
 
 @end
 /* }}} */
 /* Emulated Loading Controller {{{ */
 @interface CYEmulatedLoadingController : CYViewController {
-    CYLoadingIndicator *indicator_;
-    UITabBar *tabbar_;
-    UINavigationBar *navbar_;
+    _transient Database *database_;
+    _H<CYLoadingIndicator> indicator_;
+    _H<UITabBar> tabbar_;
+    _H<UINavigationBar> navbar_;
 }
+
 @end
 
 @implementation CYEmulatedLoadingController
 
-- (CYEmulatedLoadingController *) init {
-    if ((self = [super init])) {
-        [[self view] setBackgroundColor:[UIColor pinStripeColor]];
-
-        indicator_ = [[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]];
-        [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:indicator_];
-        [indicator_ release];
-
-        tabbar_ = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)];
-        [tabbar_ setFrame:CGRectMake(0.0f, [[self view] bounds].size.height - [tabbar_ bounds].size.height, [[self view] bounds].size.width, [tabbar_ bounds].size.height)];
-        [tabbar_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth];
-        [[self view] addSubview:tabbar_];
-        [tabbar_ release];
-
-        navbar_ = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)];
-        [navbar_ setFrame:CGRectMake(0.0f, 0.0f, [[self view] bounds].size.width, [navbar_ bounds].size.height)];
-        [navbar_ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth];
-        [[self view] addSubview:navbar_];
-        [navbar_ release];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
     } return self;
 }
 
+// XXX: factor this out somewhere
+- (UIColor *) groupTableViewBackgroundColor {
+    UIDevice *device([UIDevice currentDevice]);
+    bool iPad([device respondsToSelector:@selector(userInterfaceIdiom)] && [device userInterfaceIdiom] == UIUserInterfaceIdiomPad);
+    return iPad ? [UIColor colorWithRed:0.821 green:0.834 blue:0.860 alpha:1] : [UIColor groupTableViewBackgroundColor];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+    [[self view] setBackgroundColor:[self groupTableViewBackgroundColor]];
+
+    indicator_ = [[[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]] autorelease];
+    [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [[self view] addSubview:indicator_];
+
+    tabbar_ = [[[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)] autorelease];
+    [tabbar_ setFrame:CGRectMake(0.0f, [[self view] bounds].size.height - [tabbar_ bounds].size.height, [[self view] bounds].size.width, [tabbar_ bounds].size.height)];
+    [tabbar_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth];
+    [[self view] addSubview:tabbar_];
+
+    navbar_ = [[[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)] autorelease];
+    [navbar_ setFrame:CGRectMake(0.0f, 0.0f, [[self view] bounds].size.width, [navbar_ bounds].size.height)];
+    [navbar_ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth];
+    [[self view] addSubview:navbar_];
+}
+
+- (void) releaseSubviews {
+    indicator_ = nil;
+    tabbar_ = nil;
+    navbar_ = nil;
+}
+
 @end
 /* }}} */
 
@@ -4080,6 +4558,10 @@ static NSString *Warning_;
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://url/%@", [[[webview_ request] URL] absoluteString]]];
+}
+
 - (void) setHeaders:(NSDictionary *)headers forHost:(NSString *)host {
 }
 
@@ -4088,24 +4570,18 @@ static NSString *Warning_;
 
     WebDataSource *source([frame dataSource]);
     NSURLResponse *response([source response]);
-    NSURL *url([response URL]);
-    NSString *scheme([url scheme]);
 
-    NSHTTPURLResponse *http;
-    if (scheme != nil && ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]))
-        http = (NSHTTPURLResponse *) response;
-    else
-        http = nil;
-
-    NSDictionary *headers([http allHeaderFields]);
+    NSURL *url([response URL]);
+    //NSString *scheme([url scheme]);
     NSString *host([url host]);
-    [self setHeaders:headers forHost:host];
 
-    if (
-        [host isEqualToString:@"cydia.saurik.com"] ||
-        [host hasSuffix:@".cydia.saurik.com"] ||
-        [scheme isEqualToString:@"file"]
-    )
+    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
+        NSHTTPURLResponse *http((NSHTTPURLResponse *) response);
+        NSDictionary *headers([http allHeaderFields]);
+        [self setHeaders:headers forHost:host];
+    }
+
+    if ([CydiaHosts_ containsObject:host])
         [window setValue:cydia_ forKey:@"cydia"];
 }
 
@@ -4137,12 +4613,7 @@ static NSString *Warning_;
 
         WebView *webview([[webview_ _documentView] webView]);
 
-        Package *package([[Database sharedInstance] packageWithName:@"cydia"]);
-
-        NSString *application = package == nil ? @"Cydia" : [NSString
-            stringWithFormat:@"Cydia/%@",
-            [package installed]
-        ];
+        NSString *application([NSString stringWithFormat:@"Cydia/%@", @ Cydia_]);
 
         if (Safari_ != nil)
             application = [NSString stringWithFormat:@"Safari/%@ %@", Safari_, application];
@@ -4158,6 +4629,42 @@ static NSString *Warning_;
 @end
 /* }}} */
 
+// CydiaScript {{{
+@interface NSObject (CydiaScript)
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context;
+@end
+
+@implementation NSObject (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    return self;
+}
+
+@end
+
+@implementation NSArray (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    WebScriptObject *object([context evaluateWebScript:@"[]"]);
+    for (size_t i(0), e([self count]); i != e; ++i)
+        [object setWebScriptValueAtIndex:i value:[[self objectAtIndex:i] Cydia$webScriptObjectInContext:context]];
+    return object;
+}
+
+@end
+
+@implementation NSDictionary (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    WebScriptObject *object([context evaluateWebScript:@"({})"]);
+    for (id i in self)
+        [object setValue:[[self objectForKey:i] Cydia$webScriptObjectInContext:context] forKey:i];
+    return object;
+}
+
+@end
+// }}}
+
 /* Confirmation Controller {{{ */
 bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (!iterator.end())
@@ -4182,10 +4689,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @interface ConfirmationController : CYBrowserController {
     _transient Database *database_;
+
     UIAlertView *essential_;
-    NSArray *changes_;
-    NSArray *issues_;
-    NSArray *sizes_;
+
+    NSDictionary *changes_;
+    NSMutableArray *issues_;
+    NSDictionary *sizes_;
+
     BOOL substrate_;
 }
 
@@ -4197,24 +4707,29 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) dealloc {
     [changes_ release];
-    if (issues_ != nil)
-        [issues_ release];
+    [issues_ release];
     [sizes_ release];
+
     if (essential_ != nil)
         [essential_ release];
+
     [super dealloc];
 }
 
+- (void) complete {
+    if (substrate_)
+        RestartSubstrate_ = true;
+    [delegate_ confirmWithNavigationController:[self navigationController]];
+}
+
 - (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
     NSString *context([alert context]);
 
     if ([context isEqualToString:@"remove"]) {
-        if (button == [alert cancelButtonIndex]) {
+        if (button == [alert cancelButtonIndex])
             [self dismissModalViewControllerAnimated:YES];
-        } else if (button == [alert firstOtherButtonIndex]) {
-            if (substrate_)
-                Finish_ = 2;
-            [delegate_ confirmWithNavigationController:[self navigationController]];
+        else if (button == [alert firstOtherButtonIndex]) {
+            [self complete];
         }
 
         [alert dismissWithClickedButtonIndex:-1 animated:YES];
@@ -4238,49 +4753,140 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
     [super webView:view didClearWindowObject:window forFrame:frame];
-    [window setValue:changes_ forKey:@"changes"];
-    [window setValue:issues_ forKey:@"issues"];
-    [window setValue:sizes_ forKey:@"sizes"];
-    [window setValue:self forKey:@"queue"];
+
+    [window setValue:[[NSDictionary dictionaryWithObjectsAndKeys:
+        changes_, @"changes",
+        issues_, @"issues",
+        sizes_, @"sizes",
+        self, @"queue",
+    nil] Cydia$webScriptObjectInContext:window] forKey:@"cydiaConfirm"];
 }
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("CONFIRM")];
-
-        NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *downgrading = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *removing = [NSMutableArray arrayWithCapacity:16];
+        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]);
 
         bool remove(false);
 
+        pkgCacheFile &cache([database_ cache]);
+        NSArray *packages([database_ packages]);
         pkgDepCache::Policy *policy([database_ policy]);
 
-        pkgCacheFile &cache([database_ cache]);
-        NSArray *packages = [database_ packages];
+        issues_ = [[NSMutableArray arrayWithCapacity:4] retain];
+
         for (Package *package in packages) {
-            pkgCache::PkgIterator iterator = [package iterator];
+            pkgCache::PkgIterator iterator([package iterator]);
+            NSString *name([package id]);
+
+            if ([package broken]) {
+                NSMutableArray *reasons([NSMutableArray arrayWithCapacity:4]);
+
+                [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                    name, @"package",
+                    reasons, @"reasons",
+                nil]];
+
+                pkgCache::VerIterator ver(cache[iterator].InstVerIter(cache));
+                if (ver.end())
+                    continue;
+
+                for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
+                    pkgCache::DepIterator start;
+                    pkgCache::DepIterator end;
+                    dep.GlobOr(start, end); // ++dep
+
+                    if (!cache->IsImportantDep(end))
+                        continue;
+                    if ((cache[end] & pkgDepCache::DepGInstall) != 0)
+                        continue;
+
+                    NSMutableArray *clauses([NSMutableArray arrayWithCapacity:4]);
+
+                    [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                        [NSString stringWithUTF8String:start.DepType()], @"relationship",
+                        clauses, @"clauses",
+                    nil]];
+
+                    _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;
+                    }
+                }
+            }
+
             pkgDepCache::StateCache &state(cache[iterator]);
 
-            NSString *name([package name]);
+            static Pcre special_r("^(firmware$|gsc\\.|cy\\+)");
 
             if (state.NewInstall())
-                [installing addObject:name];
+                [installs addObject:name];
             else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
-                [reinstalling addObject:name];
+                [reinstalls addObject:name];
             else if (state.Upgrade())
-                [upgrading addObject:name];
+                [upgrades addObject:name];
             else if (state.Downgrade())
-                [downgrading addObject:name];
-            else if (state.Delete()) {
+                [downgrades addObject:name];
+            else if (!state.Delete())
+                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;
-                [removing addObject:name];
-            } else continue;
+                [removes addObject:name];
+            }
 
             substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
             substrate_ |= DepSubstrate(iterator.CurrentVer());
@@ -4296,7 +4902,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 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
+                otherButtonTitles:
+                    [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")],
+                nil
             ];
 
             [essential_ setContext:@"remove"];
@@ -4312,28 +4920,23 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             [essential_ setContext:@"unable"];
         }
 
-        changes_ = [[NSArray alloc] initWithObjects:
-            installing,
-            reinstalling,
-            upgrading,
-            downgrading,
-            removing,
+        changes_ = [[NSDictionary alloc] initWithObjectsAndKeys:
+            installs, @"installs",
+            reinstalls, @"reinstalls",
+            upgrades, @"upgrades",
+            downgrades, @"downgrades",
+            removes, @"removes",
         nil];
 
-        issues_ = [database_ issues];
-        if (issues_ != nil)
-            issues_ = [issues_ retain];
-
-        sizes_ = [[NSArray alloc] initWithObjects:
-            SizeString([database_ fetcher].FetchNeeded()),
-            SizeString([database_ fetcher].PartialPresent()),
+        sizes_ = [[NSDictionary alloc] initWithObjectsAndKeys:
+            [NSNumber numberWithInteger:[database_ fetcher].FetchNeeded()], @"downloading",
+            [NSNumber numberWithInteger:[database_ fetcher].PartialPresent()], @"resuming",
         nil];
 
-        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"confirm" ofType:@"html"]]];
+        [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/confirm/", UI_]]];
 
         [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CANCEL")
-            // OLD: [NSString stringWithFormat:UCLocalize("SLASH_DELIMITED"), UCLocalize("CANCEL"), UCLocalize("QUEUE")]
             style:UIBarButtonItemStylePlain
             target:self
             action:@selector(cancelButtonClicked)
@@ -4341,21 +4944,19 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
+#if !AlwaysReload
 - (void) applyRightButton {
-#if !AlwaysReload && !IgnoreInstall
-    if (issues_ == nil && ![self isLoading])
+    if ([issues_ count] == 0 && ![self isLoading])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CONFIRM")
-            style:UIBarButtonItemStylePlain
+            style:UIBarButtonItemStyleDone
             target:self
             action:@selector(confirmButtonClicked)
         ] autorelease]];
     else
-        [super applyRightButton];
-#else
-    [[self navigationItem] setRightBarButtonItem:nil];
-#endif
+        [[self navigationItem] setRightBarButtonItem:nil];
 }
+#endif
 
 - (void) cancelButtonClicked {
     [self dismissModalViewControllerAnimated:YES];
@@ -4364,16 +4965,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 #if !AlwaysReload
 - (void) confirmButtonClicked {
-#if IgnoreInstall
-    return;
-#endif
     if (essential_ != nil)
         [essential_ show];
-    else {
-        if (substrate_)
-            Finish_ = 2;
-        [delegate_ confirmWithNavigationController:[self navigationController]];
-    }
+    else
+        [self complete];
 }
 #endif
 
@@ -4381,195 +4976,204 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* }}} */
 
 /* Progress Data {{{ */
-@interface ProgressData : NSObject {
-    SEL selector_;
-    // XXX: should these really both be _transient?
-    _transient id target_;
-    _transient id object_;
-}
+@interface CydiaProgressData : NSObject {
+    _transient id delegate_;
+
+    bool running_;
+    float percent_;
+
+    float current_;
+    float total_;
+    float speed_;
+
+    _H<NSMutableArray> events_;
+    _H<NSString> title_;
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
+    _H<NSString> status_;
+    _H<NSString> finish_;
+}
 
-- (SEL) selector;
-- (id) target;
-- (id) object;
 @end
 
-@implementation ProgressData
+@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];
+}
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
+- (id) init {
     if ((self = [super init]) != nil) {
-        selector_ = selector;
-        target_ = target;
-        object_ = object;
+        events_ = [NSMutableArray arrayWithCapacity:32];
     } return self;
 }
 
-- (SEL) selector {
-    return selector_;
+- (void) setDelegate:(id)delegate {
+    delegate_ = delegate;
+}
+
+- (void) setPercent:(float)value {
+    percent_ = value;
+}
+
+- (NSNumber *) percent {
+    return [NSNumber numberWithFloat:percent_];
+}
+
+- (void) setCurrent:(float)value {
+    current_ = value;
+}
+
+- (NSNumber *) current {
+    return [NSNumber numberWithFloat:current_];
+}
+
+- (void) setTotal:(float)value {
+    total_ = value;
 }
 
-- (id) target {
-    return target_;
+- (NSNumber *) total {
+    return [NSNumber numberWithFloat:total_];
 }
 
-- (id) object {
-    return object_;
+- (void) setSpeed:(float)value {
+    speed_ = value;
+}
+
+- (NSNumber *) speed {
+    return [NSNumber numberWithFloat:speed_];
+}
+
+- (NSArray *) events {
+    return events_;
+}
+
+- (void) removeAllEvents {
+    [events_ removeAllObjects];
+}
+
+- (void) addEvent:(CydiaProgressEvent *)event {
+    [events_ addObject:event];
+}
+
+- (void) setTitle:(NSString *)text {
+    title_ = text;
+}
+
+- (NSString *) title {
+    return title_;
+}
+
+- (void) setFinish:(NSString *)text {
+    finish_ = text;
+}
+
+- (NSString *) finish {
+    return (id) finish_ ?: [NSNull null];
+}
+
+- (void) setRunning:(bool)running {
+    running_ = running;
+}
+
+- (NSNumber *) running {
+    return running_ ? (NSNumber *) kCFBooleanTrue : (NSNumber *) kCFBooleanFalse;
 }
 
 @end
 /* }}} */
 /* Progress Controller {{{ */
-@interface ProgressController : CYViewController <
-    ConfigurationDelegate,
+@interface ProgressController : CYBrowserController <
     ProgressDelegate
 > {
     _transient Database *database_;
-    UIProgressBar *progress_;
-    UITextView *output_;
-    UITextLabel *status_;
-    UIPushButton *close_;
-    BOOL running_;
-    SHA1SumValue springlist_;
-    SHA1SumValue notifyconf_;
-    NSString *title_;
+    _H<CydiaProgressData> progress_;
+    unsigned cancel_;
 }
 
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate;
 
-- (void) _retachThread;
-- (void) _detachNewThreadData:(ProgressData *)data;
-- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title;
+- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title;
 
-- (BOOL) isRunning;
-
-@end
+- (void) setTitle:(NSString *)title;
+- (void) setCancellable:(bool)cancellable;
 
-@protocol ProgressControllerDelegate
-- (void) progressControllerIsComplete:(ProgressController *)sender;
 @end
 
 @implementation ProgressController
 
 - (void) dealloc {
-    [database_ setDelegate:nil];
-    [progress_ release];
-    [output_ release];
-    [status_ release];
-    [close_ release];
-    if (title_ != nil)
-        [title_ release];
+    [database_ setProgressDelegate:nil];
+    [progress_ setDelegate:nil];
     [super dealloc];
 }
 
+- (void) updateCancel {
+    [[self navigationItem] setLeftBarButtonItem:(cancel_ == 1 ? [[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("CANCEL")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(cancel)
+    ] autorelease] : nil)];
+}
+
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate {
     if ((self = [super init]) != nil) {
         database_ = database;
-        [database_ setDelegate:self];
         delegate_ = delegate;
 
-        [[self view] setBackgroundColor:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]];
+        [database_ setProgressDelegate:self];
 
-        progress_ = [[UIProgressBar alloc] init];
-        [progress_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [progress_ setStyle:0];
+        progress_ = [[[CydiaProgressData alloc] init] autorelease];
+        [progress_ setDelegate:self];
 
-        status_ = [[UITextLabel alloc] init];
-        [status_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [status_ setColor:[UIColor whiteColor]];
-        [status_ setBackgroundColor:[UIColor clearColor]];
-        [status_ setCentersHorizontally:YES];
-        //[status_ setFont:font];
-
-        output_ = [[UITextView alloc] init];
-        [output_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        //[output_ setTextFont:@"Courier New"];
-        [output_ setFont:[[output_ font] fontWithSize:12]];
-        [output_ setTextColor:[UIColor whiteColor]];
-        [output_ setBackgroundColor:[UIColor clearColor]];
-        [output_ setMarginTop:0];
-        [output_ setAllowsRubberBanding:YES];
-        [output_ setEditable:NO];
-        [[self view] addSubview:output_];
-
-        close_ = [[UIPushButton alloc] init];
-        [close_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [close_ setAutosizesToFit:NO];
-        [close_ setDrawsShadow:YES];
-        [close_ setStretchBackground:YES];
-        [close_ setEnabled:YES];
-        [close_ setTitleFont:[UIFont boldSystemFontOfSize:22]];
-        [close_ addTarget:self action:@selector(closeButtonPushed) forEvents:UIControlEventTouchUpInside];
-        [close_ setBackground:[UIImage applicationImageNamed:@"green-up.png"] forState:0];
-        [close_ setBackground:[UIImage applicationImageNamed:@"green-dn.png"] forState:1];
-    } return self;
-}
+        [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/progress/", UI_]]];
 
-- (void) positionViews {
-    CGRect bounds = [[self view] bounds];
-    CGSize prgsize = [UIProgressBar defaultSize];
-
-    CGRect prgrect = {{
-        (bounds.size.width - prgsize.width) / 2,
-        bounds.size.height - prgsize.height - 20
-    }, prgsize};
+        [scroller_ setBackgroundColor:[UIColor blackColor]];
 
-    float closewidth = std::min(bounds.size.width - 20, 300.0f);
+        [[self navigationItem] setHidesBackButton:YES];
 
-    [progress_ setFrame:prgrect];
-    [status_ setFrame:CGRectMake(
-        10,
-        bounds.size.height - prgsize.height - 50,
-        bounds.size.width - 20,
-        24
-    )];
-    [output_ setFrame:CGRectMake(
-        10,
-        20,
-        bounds.size.width - 20,
-        bounds.size.height - 96
-    )];
-    [close_ setFrame:CGRectMake(
-        (bounds.size.width - closewidth) / 2,
-        bounds.size.height - prgsize.height - 50,
-        closewidth,
-        32 + prgsize.height
-    )];
+        [self updateCancel];
+    } return self;
 }
 
-- (void) viewWillAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [[self navigationItem] setHidesBackButton:YES];
-    [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack];
-
-    [self positionViews];
+- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+    [super webView:view didClearWindowObject:window forFrame:frame];
+    [window setValue:progress_ forKey:@"cydiaProgress"];
 }
 
-- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
-    [self positionViews];
+- (void) updateProgress {
+    [self dispatchEvent:@"CydiaProgressUpdate"];
 }
 
-- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
-    NSString *context([alert context]);
+- (void) viewWillAppear:(BOOL)animated {
+    if (![self hasLoaded])
+        [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack];
 
-    if ([context isEqualToString:@"conffile"]) {
-        FILE *input = [database_ input];
-        if (button == [alert cancelButtonIndex])
-            fprintf(input, "N\n");
-        else if (button == [alert firstOtherButtonIndex])
-            fprintf(input, "Y\n");
-        fflush(input);
-    }
+    [super viewWillAppear:animated];
 }
 
-- (void) closeButtonPushed {
-    running_ = NO;
-
+- (void) close {
     UpdateExternalStatus(0);
 
     switch (Finish_) {
         case 0:
-            [self dismissModalViewControllerAnimated:YES];
         break;
 
         case 1:
@@ -4601,19 +5205,32 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 reboot2(RB_AUTOBOOT);
         break;
     }
+
+    [super close];
+}
+
+- (void) setTitle:(NSString *)title {
+    [progress_ setTitle:title];
+    [self updateProgress];
 }
 
-- (void) _retachThread {
-    [[self navigationItem] setTitle:UCLocalize("COMPLETE")];
+- (UIBarButtonItem *) rightButton {
+    return [[progress_ running] boolValue] ? nil : [[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("CLOSE")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(close)
+    ] autorelease];
+}
 
-    [[self view] addSubview:close_];
-    [progress_ removeFromSuperview];
-    [status_ removeFromSuperview];
+- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title {
+    UpdateExternalStatus(1);
 
-    [database_ popErrorWithTitle:title_];
-    [delegate_ progressControllerIsComplete:self];
+    [progress_ setRunning:true];
+    [self setTitle:title];
+    // implicit updateProgress
 
-    if (Finish_ < 4) {
+    SHA1SumValue notifyconf; {
         FileFd file;
         if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
             _error->Discard();
@@ -4621,12 +5238,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             MMap mmap(file, MMap::ReadOnly);
             SHA1Summation sha1;
             sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
-            if (!(notifyconf_ == sha1.Result()))
-                Finish_ = 4;
+            notifyconf = sha1.Result();
         }
     }
 
-    if (Finish_ < 3) {
+    SHA1SumValue springlist; {
         FileFd file;
         if (!file.Open(SpringBoard_, FileFd::ReadOnly))
             _error->Discard();
@@ -4634,57 +5250,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             MMap mmap(file, MMap::ReadOnly);
             SHA1Summation sha1;
             sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
-            if (!(springlist_ == sha1.Result()))
-                Finish_ = 3;
+            springlist = sha1.Result();
         }
     }
 
-    switch (Finish_) {
-        case 0: [close_ setTitle:UCLocalize("RETURN_TO_CYDIA")]; break; /* XXX: Maybe UCLocalize("DONE")? */
-        case 1: [close_ setTitle:UCLocalize("CLOSE_CYDIA")]; break;
-        case 2: [close_ setTitle:UCLocalize("RESTART_SPRINGBOARD")]; break;
-        case 3: [close_ setTitle:UCLocalize("RELOAD_SPRINGBOARD")]; break;
-        case 4: [close_ setTitle:UCLocalize("REBOOT_DEVICE")]; break;
+    if (invocation != nil) {
+        [invocation yieldToSelector:@selector(invoke)];
+        [self setTitle:@"COMPLETE"];
     }
 
-    _trace();
-    system("su -c /usr/bin/uicache mobile");
-    _trace();
-
-    UpdateExternalStatus(Finish_ == 0 ? 2 : 0);
-
-    [delegate_ setStatusBarShowsProgress:NO];
-}
-
-- (void) _detachNewThreadData:(ProgressData *)data { _pooled
-    [[data target] performSelector:[data selector] withObject:[data object]];
-    [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES];
-}
-
-- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title {
-    UpdateExternalStatus(1);
-
-    if (title_ != nil)
-        [title_ release];
-    if (title == nil)
-        title_ = nil;
-    else
-        title_ = [title retain];
-
-    [[self navigationItem] setTitle:title_];
-
-    [status_ setText:nil];
-    [output_ setText:@""];
-    [progress_ setProgress:0];
-
-    [close_ removeFromSuperview];
-    [[self view] addSubview:progress_];
-    [[self view] addSubview:status_];
-
-    [delegate_ setStatusBarShowsProgress:YES];
-    running_ = YES;
-
-    {
+    if (Finish_ < 4) {
         FileFd file;
         if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
             _error->Discard();
@@ -4692,11 +5267,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             MMap mmap(file, MMap::ReadOnly);
             SHA1Summation sha1;
             sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
-            notifyconf_ = sha1.Result();
+            if (!(notifyconf == sha1.Result()))
+                Finish_ = 4;
         }
     }
 
-    {
+    if (Finish_ < 3) {
         FileFd file;
         if (!file.Open(SpringBoard_, FileFd::ReadOnly))
             _error->Discard();
@@ -4704,133 +5280,87 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             MMap mmap(file, MMap::ReadOnly);
             SHA1Summation sha1;
             sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
-            springlist_ = sha1.Result();
+            if (!(springlist == sha1.Result()))
+                Finish_ = 3;
         }
     }
 
-    [NSThread
-        detachNewThreadSelector:@selector(_detachNewThreadData:)
-        toTarget:self
-        withObject:[[[ProgressData alloc]
-            initWithSelector:selector
-            target:target
-            object:object
-        ] autorelease]
-    ];
-}
-
-- (void) repairWithSelector:(SEL)selector {
-    [self
-        detachNewThreadSelector:selector
-        toTarget:database_
-        withObject:nil
-        title:UCLocalize("REPAIRING")
-    ];
-}
-
-- (void) setConfigurationData:(NSString *)data {
-    [self
-        performSelectorOnMainThread:@selector(_setConfigurationData:)
-        withObject:data
-        waitUntilDone:YES
-    ];
-}
-
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    CYActionSheet *sheet([[[CYActionSheet alloc]
-        initWithTitle:title
-        buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
-        defaultButtonIndex:0
-    ] autorelease]);
+    if (Finish_ < 2) {
+        if (RestartSubstrate_)
+            Finish_ = 2;
+    }
 
-    [sheet setMessage:error];
-    [sheet yieldToPopupAlertAnimated:YES];
-    [sheet dismiss];
-}
+    RestartSubstrate_ = false;
 
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
-}
+    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;
+    }
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
-}
+    _trace();
+    system("su -c /usr/bin/uicache mobile");
+    _trace();
 
-- (void) startProgress {
-}
+    UpdateExternalStatus(Finish_ == 0 ? 0 : 2);
 
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
-}
+    [progress_ setRunning:false];
+    [self updateProgress];
 
-- (bool) isCancelling:(size_t)received {
-    return false;
+    [self applyRightButton];
 }
 
-- (void) _setConfigurationData:(NSString *)data {
-    static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
-
-    if (!conffile_r(data)) {
-        lprintf("E:invalid conffile\n");
-        return;
-    }
-
-    NSString *ofile = conffile_r[1];
-    //NSString *nfile = conffile_r[2];
+- (void) addProgressEvent:(CydiaProgressEvent *)event {
+    [progress_ addEvent:event];
+    [self updateProgress];
+}
 
-    UIAlertView *alert = [[[UIAlertView alloc]
-        initWithTitle:UCLocalize("CONFIGURATION_UPGRADE")
-        message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
-        delegate:self
-        cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
-        otherButtonTitles:UCLocalize("ACCEPT_NEW_COPY"),
-        // XXX: UCLocalize("SEE_WHAT_CHANGED"),
-        nil
-    ] autorelease];
+- (bool) isProgressCancelled {
+    return cancel_ == 2;
+}
 
-    [alert setContext:@"conffile"];
-    [alert show];
+- (void) cancel {
+    cancel_ = 2;
+    [self updateCancel];
 }
 
-- (void) _setProgressTitle:(NSString *)title {
-    NSMutableArray *words([[title componentsSeparatedByString:@" "] mutableCopy]);
-    for (size_t i(0), e([words count]); i != e; ++i) {
-        NSString *word([words objectAtIndex:i]);
-        if (Package *package = [database_ packageWithName:word])
-            [words replaceObjectAtIndex:i withObject:[package name]];
-    }
+- (void) setCancellable:(bool)cancellable {
+    unsigned cancel(cancel_);
 
-    [status_ setText:[words componentsJoinedByString:@" "]];
+    if (!cancellable)
+        cancel_ = 0;
+    else if (cancel_ == 0)
+        cancel_ = 1;
+
+    if (cancel != cancel_)
+        [self updateCancel];
 }
 
-- (void) _setProgressPercent:(NSNumber *)percent {
-    [progress_ setProgress:[percent floatValue]];
+- (void) setProgressCancellable:(NSNumber *)cancellable {
+    [self setCancellable:[cancellable boolValue]];
 }
 
-- (void) _addProgressOutput:(NSString *)output {
-    [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]];
-    CGSize size = [output_ contentSize];
-    CGPoint offset = [output_ contentOffset];
-    if (size.height - offset.y < [output_ frame].size.height + 20.f) {
-        CGRect rect = {{0, size.height-1}, {size.width, 1}};
-        [output_ scrollRectToVisible:rect animated:YES];
-    }
+- (void) setProgressPercent:(NSNumber *)percent {
+    [progress_ setPercent:[percent floatValue]];
+    [self updateProgress];
 }
 
-- (BOOL) isRunning {
-    return running_;
+- (void) setProgressStatus:(NSDictionary *)status {
+    if (status == nil) {
+        [progress_ setCurrent:0];
+        [progress_ setTotal:0];
+        [progress_ setSpeed:0];
+    } else {
+        [progress_ setPercent:[[status objectForKey:@"Percent"] floatValue]];
+
+        [progress_ setCurrent:[[status objectForKey:@"Current"] floatValue]];
+        [progress_ setTotal:[[status objectForKey:@"Total"] floatValue]];
+        [progress_ setSpeed:[[status objectForKey:@"Speed"] floatValue]];
+    }
+
+    [self updateProgress];
 }
 
 @end
@@ -4981,18 +5511,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void) _setBackgroundColor {
-    UIColor *color;
-    if (NSString *mode = [package_ mode]) {
-        bool remove([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]);
-        color = remove ? RemovingColor_ : InstallingColor_;
-    } else
-        color = [UIColor whiteColor];
-
-    [content_ setBackgroundColor:color];
-    [self setNeedsDisplay];
-}
-
 - (NSString *) accessibilityLabel {
     return [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), name_, description_];
 }
@@ -5043,11 +5561,36 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         if ((badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]) != nil)
             badge_ = [badge_ retain];
 
-    if ([package installed] != nil)
-        if ((placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/installed.png", App_]]) != nil)
+    UIColor *color;
+    NSString *placard;
+
+    if (NSString *mode = [package_ mode]) {
+        if ([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]) {
+            color = RemovingColor_;
+            //placard = @"removing";
+        } else {
+            color = InstallingColor_;
+            //placard = @"installing";
+        }
+
+        // XXX: the removing/installing placards are not @2x
+        placard = nil;
+    } else {
+        color = [UIColor whiteColor];
+
+        if ([package installed] != nil)
+            placard = @"installed";
+        else
+            placard = nil;
+    }
+
+    [content_ setBackgroundColor:color];
+
+    if (placard != nil)
+        if ((placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/%@.png", App_, placard]]) != nil)
             placard_ = [placard_ retain];
 
-    [self _setBackgroundColor];
+    [self setNeedsDisplay];
     [content_ setNeedsDisplay];
 }
 
@@ -5275,12 +5818,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation FileTable
 
 - (void) dealloc {
-    if (package_ != nil)
-        [package_ release];
-    if (name_ != nil)
-        [name_ release];
+    [self releaseSubviews];
+
+    [package_ release];
+    [name_ release];
     [files_ release];
-    [list_ release];
+
     [super dealloc];
 }
 
@@ -5306,21 +5849,35 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return cell;
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/files", [package_ id]]];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:24.0f];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
+
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")];
-
         files_ = [[NSMutableArray arrayWithCapacity:32] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:24.0f];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
     } return self;
 }
 
@@ -5370,6 +5927,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     [self setPackage:[database_ packageWithName:name_]];
 }
 
@@ -5380,38 +5939,21 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UIActionSheetDelegate
 > {
     _transient Database *database_;
-    Package *package_;
-    NSString *name_;
+    _H<Package> package_;
+    _H<NSString> name_;
     bool commercial_;
-    NSMutableArray *buttons_;
-    UIBarButtonItem *button_;
+    _H<NSMutableArray> buttons_;
+    _H<UIBarButtonItem> button_;
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) setPackage:(Package *)package;
+- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name;
 
 @end
 
 @implementation CYPackageController
 
-- (void) dealloc {
-    if (package_ != nil)
-        [package_ release];
-    if (name_ != nil)
-        [name_ release];
-
-    [buttons_ release];
-
-    if (button_ != nil)
-        [button_ release];
-
-    [super dealloc];
-}
-
-- (void) release {
-    if ([self retainCount] == 1)
-        [delegate_ setPackageController:self];
-    [super release];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@", (id) name_]];
 }
 
 /* XXX: this is not safe at all... localization of /fail/ */
@@ -5442,11 +5984,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    [super webView:view didClearWindowObject:window forFrame:frame];
-    [window setValue:package_ forKey:@"package"];
-}
-
 - (bool) _allowJavaScriptPanel {
     return commercial_;
 }
@@ -5507,33 +6044,24 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 #endif
 
-- (id) initWithDatabase:(Database *)database {
+- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name {
     if ((self = [super init]) != nil) {
         database_ = database;
-        buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
-        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"package" ofType:@"html"]]];
+        buttons_ = [NSMutableArray arrayWithCapacity:4];
+        name_ = [NSString stringWithString:name];
+        [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/package/%@", UI_, (id) name_]]];
     } return self;
 }
 
-- (void) setPackage:(Package *)package {
-    if (package_ != nil) {
-        [package_ autorelease];
-        package_ = nil;
-    }
-
-    if (name_ != nil) {
-        [name_ release];
-        name_ = nil;
-    }
+- (void) reloadData {
+    package_ = [database_ packageWithName:name_];
 
     [buttons_ removeAllObjects];
 
-    if (package != nil) {
-        [package parse];
+    if (package_ != nil) {
+        [(Package *) package_ parse];
 
-        package_ = [package retain];
-        name_ = [[package id] retain];
-        commercial_ = [package isCommercial];
+        commercial_ = [package_ isCommercial];
 
         if ([package_ mode] != nil)
             [buttons_ addObject:UCLocalize("CLEAR")];
@@ -5548,9 +6076,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             [buttons_ addObject:UCLocalize("REMOVE")];
     }
 
-    if (button_ != nil)
-        [button_ release];
-
     NSString *title;
     switch ([buttons_ count]) {
         case 0: title = nil; break;
@@ -5558,29 +6083,25 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         default: title = UCLocalize("MODIFY"); break;
     }
 
-    button_ = [[UIBarButtonItem alloc]
+    button_ = [[[UIBarButtonItem alloc]
         initWithTitle:title
         style:UIBarButtonItemStylePlain
         target:self
         action:@selector(customButtonClicked)
-    ];
+    ] autorelease];
 
-    [self reloadURL];
+    [super reloadData];
 }
 
 - (bool) isLoading {
     return commercial_ ? [super isLoading] : false;
 }
 
-- (void) reloadData {
-    [self setPackage:[database_ packageWithName:name_]];
-}
-
 @end
 /* }}} */
 
-/* Package Table {{{ */
-@interface PackageTable : UIView <
+/* Package List Controller {{{ */
+@interface PackageListController : CYViewController <
     UITableViewDataSource,
     UITableViewDelegate
 > {
@@ -5591,29 +6112,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UITableView *list_;
     NSMutableArray *index_;
     NSMutableDictionary *indices_;
-    // XXX: this target_ seems to be delegate_. :(
-    _transient id target_;
-    SEL action_;
-    // XXX: why do we even have this delegate_?
-    _transient id delegate_;
+    NSString *title_;
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action;
-
+- (id) initWithDatabase:(Database *)database title:(NSString *)title;
 - (void) setDelegate:(id)delegate;
-
-- (void) reloadData;
 - (void) resetCursor;
 
-- (UITableView *) list;
-
-- (void) setShouldHideHeaderInShortLists:(BOOL)hide;
-
-- (void) deselectWithAnimation:(BOOL)animated;
-
 @end
 
-@implementation PackageTable
+@implementation PackageListController
 
 - (void) dealloc {
     [packages_ release];
@@ -5621,10 +6129,92 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [list_ release];
     [index_ release];
     [indices_ release];
+    [title_ release];
 
     [super dealloc];
 }
 
+- (void) deselectWithAnimation:(BOOL)animated {
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve {
+    CGRect base = [[self view] bounds];
+    base.size.height -= bounds.size.height;
+    base.origin = [list_ frame].origin;
+
+    [UIView beginAnimations:nil context:NULL];
+    [UIView setAnimationBeginsFromCurrentState:YES];
+    [UIView setAnimationCurve:curve];
+    [UIView setAnimationDuration:duration];
+    [list_ setFrame:base];
+    [UIView commitAnimations];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration {
+    [self resizeForKeyboardBounds:bounds duration:duration curve:UIViewAnimationCurveLinear];
+}
+
+- (void) resizeForKeyboardBounds:(CGRect)bounds {
+    [self resizeForKeyboardBounds:bounds duration:0];
+}
+
+- (void) keyboardWillShow:(NSNotification *)notification {
+    CGRect bounds;
+    CGPoint center;
+    NSTimeInterval duration;
+    UIViewAnimationCurve curve;
+    [[[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey] getValue:&bounds];
+    [[[notification userInfo] objectForKey:UIKeyboardCenterEndUserInfoKey] getValue:&center];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];
+
+    CGRect kbframe = CGRectMake(round(center.x - bounds.size.width / 2.0), round(center.y - bounds.size.height / 2.0), bounds.size.width, bounds.size.height);
+    UIViewController *base = self;
+    while ([base parentViewController] != nil)
+        base = [base parentViewController];
+    CGRect viewframe = [[base view] convertRect:[list_ frame] fromView:[list_ superview]];
+    CGRect intersection = CGRectIntersection(viewframe, kbframe);
+
+    [self resizeForKeyboardBounds:intersection duration:duration curve:curve];
+}
+
+- (void) keyboardWillHide:(NSNotification *)notification {
+    NSTimeInterval duration;
+    UIViewAnimationCurve curve;
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];
+    [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];
+
+    [self resizeForKeyboardBounds:CGRectZero duration:duration curve:curve];
+}
+
+- (void) viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+
+    [self resizeForKeyboardBounds:CGRectZero];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
+}
+
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+
+    [self resizeForKeyboardBounds:CGRectZero];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
+    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
+}
+
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [self deselectWithAnimation:animated];
+}
+
+- (void) didSelectPackage:(Package *)package {
+    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id]] autorelease]);
+    [view setDelegate:delegate_];
+    [[self navigationController] pushViewController:view animated:YES];
+}
+
 #if TryIndexedCollation
 + (BOOL) hasIndexedCollation {
     return NO; // XXX: objc_getClass("UILocalizedIndexedCollation") != nil;
@@ -5667,15 +6257,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return cell;
 }
 
-- (void) deselectWithAnimation:(BOOL)animated {
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-}
-
-- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
+- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path {
     Package *package([self packageAtIndexPath:path]);
     package = [database_ packageWithName:[package id]];
-    [target_ performSelector:action_ withObject:package];
-    return path;
+    [self didSelectPackage:package];
 }
 
 - (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView {
@@ -5693,12 +6278,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return index;
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action {
-    if ((self = [super initWithFrame:frame]) != nil) {
+- (id) initWithDatabase:(Database *)database title:(NSString *)title {
+    if ((self = [super init]) != nil) {
         database_ = database;
-
-        target_ = target;
-        action_ = action;
+        title_ = [title copy];
+        [[self navigationItem] setTitle:title_];
 
 #if TryIndexedCollation
         if ([[self class] hasIndexedCollation])
@@ -5712,10 +6296,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-        list_ = [[UITableView alloc] initWithFrame:[self bounds] style:UITableViewStylePlain];
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
         [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
         [list_ setRowHeight:73];
-        [self addSubview:list_];
+        [[self view] addSubview:list_];
 
         [list_ setDataSource:self];
         [list_ setDelegate:self];
@@ -5731,6 +6315,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     era_ = [database_ era];
     NSArray *packages = [database_ packages];
 
@@ -5820,18 +6406,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [list_ scrollRectToVisible:CGRectMake(0, 0, 0, 0) animated:NO];
 }
 
-- (UITableView *) list {
-    return list_;
-}
-
-- (void) setShouldHideHeaderInShortLists:(BOOL)hide {
-    //XXX:[list_ setShouldHideHeaderInShortLists:hide];
-}
-
 @end
 /* }}} */
-/* Filtered Package Table {{{ */
-@interface FilteredPackageTable : PackageTable {
+/* Filtered Package List Controller {{{ */
+@interface FilteredPackageListController : PackageListController {
     SEL filter_;
     IMP imp_;
     id object_;
@@ -5840,11 +6418,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) setObject:(id)object;
 - (void) setObject:(id)object forFilter:(SEL)filter;
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action filter:(SEL)filter with:(id)object;
+- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
 
 @end
 
-@implementation FilteredPackageTable
+@implementation FilteredPackageListController
 
 - (void) dealloc {
     if (object_ != nil)
@@ -5882,92 +6460,32 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _end
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action filter:(SEL)filter with:(id)object {
-    if ((self = [super initWithFrame:frame database:database target:target action:action]) != nil) {
+- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
+    if ((self = [super initWithDatabase:database title:title]) != nil) {
         [self setFilter:filter];
-        object_ = [object retain];
-        [self reloadData];
+        [self setObject:object];
     } return self;
 }
 
 @end
 /* }}} */
-/* Filtered Package Controller {{{ */
-@interface FilteredPackageController : CYViewController {
-    _transient Database *database_;
-    FilteredPackageTable *packages_;
-    NSString *title_;
-}
-
-- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
-
-@end
-
-@implementation FilteredPackageController
-
-- (void) dealloc {
-    [packages_ release];
-    [title_ release];
-
-    [super dealloc];
-}
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [packages_ deselectWithAnimation:animated];
+/* Home Controller {{{ */
+@interface HomeController : CYBrowserController {
 }
 
-- (void) didSelectPackage:(Package *)package {
-    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]);
-    [view setPackage:package];
-    [view setDelegate:delegate_];
-    [[self navigationController] pushViewController:view animated:YES];
-}
+@end
 
-- (NSString *) title { return title_; }
+@implementation HomeController
 
-- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
+- (id) init {
     if ((self = [super init]) != nil) {
-        database_ = database;
-        title_ = [title copy];
-        [[self navigationItem] setTitle:title_];
-
-        packages_ = [[FilteredPackageTable alloc]
-            initWithFrame:[[self view] bounds]
-            database:database
-            target:self
-            action:@selector(didSelectPackage:)
-            filter:filter
-            with:object
-        ];
-
-        [packages_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:packages_];
+        [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/home/", UI_]]];
     } return self;
 }
 
-- (void) reloadData {
-    [packages_ reloadData];
-}
-
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
-}
-
-@end
-
-/* }}} */
-
-/* Home Controller {{{ */
-@interface HomeController : CYBrowserController {
-}
-@end
-
-@implementation HomeController
-
-+ (BOOL)shouldHideNavigationBar {
-    return NO;
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://home"];
 }
 
 - (void) _setMoreHeaders:(NSMutableURLRequest *)request {
@@ -5989,7 +6507,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [alert setCancelButtonIndex:0];
 
     [alert setMessage:
-        @"Copyright (C) 2008-2010\n"
+        @"Copyright (C) 2008-2011\n"
         "Jay Freeman (saurik)\n"
         "saurik@saurik.com\n"
         "http://www.saurik.com/"
@@ -5998,31 +6516,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [alert show];
 }
 
-- (void) viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
-
-    if ([[self class] shouldHideNavigationBar])
-        [[self navigationController] setNavigationBarHidden:YES animated:animated];
-}
-
-- (void) viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
-
-    if ([[self class] shouldHideNavigationBar])
-        [[self navigationController] setNavigationBarHidden:NO animated:animated];
-}
-
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [self loadURL:[NSURL URLWithString:CydiaURL(@"")]];
-
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("ABOUT")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(aboutButtonClicked)
-        ] autorelease]];
-    } return self;
+- (void) viewDidLoad {
+    [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("ABOUT")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(aboutButtonClicked)
+    ] autorelease]];
 }
 
 @end
@@ -6032,25 +6532,30 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) queueStatusDidChange;
+
 @end
 
 @implementation ManageController
 
 - (id) init {
     if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("MANAGE")];
+        [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/manage/", UI_]]];
+    } return self;
+}
 
-        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"manage" ofType:@"html"]]];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://manage"];
+}
 
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
+- (void) viewDidLoad {
+    [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("SETTINGS")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(settingsButtonClicked)
+    ] autorelease]];
 
-        [self queueStatusDidChange];
-    } return self;
+    [self queueStatusDidChange];
 }
 
 - (void) settingsButtonClicked {
@@ -6063,11 +6568,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) applyLoadingTitle {
-    // No "Loading" title.
+    // Disable "Loading" title.
 }
 
 - (void) applyRightButton {
-    // No right button.
+    // Disable right button.
 }
 #endif
 
@@ -6087,6 +6592,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (bool) isLoading {
+    // Never show as loading.
     return false;
 }
 
@@ -6140,14 +6646,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [prompt_ setFrame:prmrect];
 }
 
-- (void)setFrame:(CGRect)frame {
+- (void) setFrame:(CGRect)frame {
     [super setFrame:frame];
-
     [self positionViews];
 }
 
 - (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
-    if ((self = [super initWithFrame:frame])) {
+    if ((self = [super initWithFrame:frame]) != nil) {
         [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
 
         [self setBarStyle:UIBarStyleBlack];
@@ -6184,18 +6689,20 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void) cancel {
-    [cancel_ removeFromSuperview];
+- (void) setCancellable:(bool)cancellable {
+    if (cancellable)
+        [self addSubview:cancel_];
+    else
+        [cancel_ removeFromSuperview];
 }
 
 - (void) start {
     [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
     [progress_ setProgress:0];
-    [self addSubview:cancel_];
 }
 
 - (void) stop {
-    [cancel_ removeFromSuperview];
+    [self setCancellable:NO];
 }
 
 - (void) setPrompt:(NSString *)prompt {
@@ -6213,6 +6720,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 /* Cydia Tab Bar Controller {{{ */
 @interface CYTabBarController : UITabBarController <
+    UITabBarControllerDelegate,
     ProgressDelegate
 > {
     _transient Database *database_;
@@ -6224,8 +6732,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _transient NSObject<CydiaDelegate> *updatedelegate_;
 
     id root_;
+    UIViewController *remembered_;
+    _transient UIViewController *transient_;
 }
 
+- (NSArray *) navigationURLCollection;
 - (void) dropBar:(BOOL)animated;
 - (void) beginUpdate;
 - (void) raiseBar:(BOOL)animated;
@@ -6235,19 +6746,68 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation CYTabBarController
 
-- (void) reloadData {
-    size_t count([[self viewControllers] count]);
-    for (size_t i(0); i != count; ++i) {
-        CYNavigationController *page([[self viewControllers] objectAtIndex:(count - i - 1)]);
-        [page reloadData];
+- (void) setUnselectedViewController:(UIViewController *)transient {
+    NSMutableArray *controllers = [[self viewControllers] mutableCopy];
+    if (transient != nil) {
+        if (transient_ == nil)
+            remembered_ = [[controllers objectAtIndex:0] retain];
+        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_ release];
+        remembered_ = nil;
+        [self setViewControllers:controllers];
+        [self revealTabBarSelection];
+    }
+}
+
+- (UIViewController *) unselectedViewController {
+    return transient_;
+}
+
+- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
+    if ([self unselectedViewController])
+        [self setUnselectedViewController:nil];
+}
+
+- (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];
     }
 
-    [(CYNavigationController *)[self transientViewController] reloadData];
+    return items;
+}
+
+- (void) reloadData {
+    for (CYViewController *controller in [self viewControllers])
+        [controller reloadData];
+
+    [(CYNavigationController *)[self unselectedViewController] reloadData];
+}
+
+- (void) dealloc {
+    [refreshbar_ release];
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+    [super dealloc];
 }
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
+        [self setDelegate:self];
 
         [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
@@ -6314,50 +6874,25 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return updating_;
 }
 
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]];
-}
-
-- (void) startProgress {
-}
-
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
+- (void) addProgressEvent:(CydiaProgressEvent *)event {
+    [refreshbar_ setPrompt:[event compoundMessage]];
 }
 
-- (bool) isCancelling:(size_t)received {
+- (bool) isProgressCancelled {
     return !updating_;
 }
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
-}
-
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
-}
-
-- (void) _setProgressTitle:(NSString *)title {
-    [refreshbar_ setPrompt:title];
+- (void) setProgressCancellable:(NSNumber *)cancellable {
+    [refreshbar_ setCancellable:(updating_ && [cancellable boolValue])];
 }
 
-- (void) _setProgressPercent:(NSNumber *)percent {
+- (void) setProgressPercent:(NSNumber *)percent {
     [refreshbar_ setProgress:[percent floatValue]];
 }
 
-- (void) _addProgressOutput:(NSString *)output {
+- (void) setProgressStatus:(NSDictionary *)status {
+    if (status != nil)
+        [self setProgressPercent:[status objectForKey:@"Percent"]];
 }
 
 - (void) setUpdateDelegate:(id)delegate {
@@ -6469,12 +7004,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) dealloc {
-    [refreshbar_ release];
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
-    [super dealloc];
-}
-
 @end
 /* }}} */
 /* Cydia Navigation Controller {{{ */
@@ -6483,6 +7012,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _transient id<UINavigationControllerDelegate> delegate_;
 }
 
+- (NSArray *) navigationURLCollection;
 - (id) initWithDatabase:(Database *)database;
 - (void) reloadData;
 
@@ -6491,15 +7021,25 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation CYNavigationController
 
-- (void) dealloc {
-    [super dealloc];
+- (NSArray *) navigationURLCollection {
+    NSMutableArray *stack([NSMutableArray array]);
+
+    for (CYViewController *controller in [self viewControllers]) {
+        NSString *url = [[controller navigationURL] absoluteString];
+        if (url != nil)
+            [stack addObject:url];
+    }
+
+    return stack;
 }
 
 - (void) reloadData {
-    size_t count([[self viewControllers] count]);
-    for (size_t i(0); i != count; ++i) {
-        CYViewController *page([[self viewControllers] objectAtIndex:(count - i - 1)]);
-        [page reloadData];
+    for (CYViewController *page in [self viewControllers]) {
+        // Only reload controllers that have already loaded.
+        // This prevents a page from accidentally loading too
+        // early if it hasn't been shown on the screen yet.
+        if ([page hasLoaded])
+            [page reloadData];
     }
 }
 
@@ -6528,10 +7068,14 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     NSURL *url([request URL]);
     if (url == nil)
         return NO;
+
     NSString *scheme([[url scheme] lowercaseString]);
-    if (scheme == nil || ![scheme isEqualToString:@"cydia"])
-        return NO;
-    return YES;
+    if (scheme != nil && [scheme isEqualToString:@"cydia"])
+        return YES;
+    if ([[url absoluteString] hasPrefix:@"about:cydia-"])
+        return YES;
+
+    return NO;
 }
 
 + (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
@@ -6558,8 +7102,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     NSURL *url([request URL]);
     NSString *href([url absoluteString]);
+    NSString *scheme([[url scheme] lowercaseString]);
+
+    NSString *path;
+
+    if ([scheme isEqualToString:@"cydia"])
+        path = [href substringFromIndex:8];
+    else if ([scheme isEqualToString:@"about"])
+        path = [href substringFromIndex:12];
+    else _assert(false);
 
-    NSString *path([href substringFromIndex:8]);
     NSRange slash([path rangeOfString:@"/"]);
 
     NSString *command;
@@ -6618,7 +7170,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* }}} */
 
 /* Section Controller {{{ */
-@interface SectionController : FilteredPackageController {
+@interface SectionController : FilteredPackageListController {
+    _H<NSString> section_;
 }
 
 - (id) initWithDatabase:(Database *)database section:(NSString *)section;
@@ -6627,18 +7180,25 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation SectionController
 
+- (NSURL *) navigationURL {
+    NSString *name = section_;
+    if (name == nil)
+        name = @"all";
+
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sections/%@", name]];
+}
+
 - (id) initWithDatabase:(Database *)database section:(NSString *)name {
     NSString *title;
-
-    if (name == nil) {
+    if (name == nil)
         title = UCLocalize("ALL_PACKAGES");
-    } else if (![name isEqual:@""]) {
+    else if (![name isEqual:@""])
         title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"];
-    } else {
+    else
         title = UCLocalize("NO_SECTION");
-    }
 
     if ((self = [super initWithDatabase:database title:title filter:@selector(isVisibleInSection:) with:name]) != nil) {
+        section_ = name;
     } return self;
 }
 
@@ -6657,9 +7217,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
-- (void) resetView;
-
 - (void) editButtonClicked;
 
 @end
@@ -6667,15 +7224,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation SectionsController
 
 - (void) dealloc {
-    [list_ setDataSource:nil];
-    [list_ setDelegate:nil];
-
+    [self releaseSubviews];
     [sections_ release];
     [filtered_ release];
-    [list_ release];
+
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://sections"];
+}
+
 - (void) updateNavigationItem {
     [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")];
     if ([sections_ count] == 0) {
@@ -6689,6 +7248,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
+- (BOOL) isEditing {
+    return editing_;
+}
+
 - (void) setEditing:(BOOL)editing {
     if ((editing_ = editing))
         [list_ reloadData];
@@ -6724,7 +7287,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     static NSString *reuseIdentifier = @"SectionCell";
 
-    SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
+    SectionCell *cell = (SectionCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
     if (cell == nil)
         cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
 
@@ -6748,28 +7311,38 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[self navigationController] pushViewController:controller animated:YES];
 }
 
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:45.0f];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
+
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
-
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
         filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:45.0f];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
-
-        [self reloadData];
     } return self;
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     NSArray *packages = [database_ packages];
 
     [sections_ removeAllObjects];
@@ -6788,7 +7361,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             section = [sections objectForKey:key];
             if (section == nil) {
                 _profile(SectionsView$reloadData$Section$Allocate)
-                    section = [[[Section alloc] initWithName:name localize:YES] autorelease];
+                    section = [[[Section alloc] initWithName:key localize:YES] autorelease];
                     [sections setObject:section forKey:key];
                 _end
             }
@@ -6824,13 +7397,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _trace();
 }
 
-- (void) resetView {
-    if (editing_)
-        [self editButtonClicked];
-}
-
-- (void)editButtonClicked {
-    [self setEditing:!editing_];
+- (void) editButtonClicked {
+    [self setEditing:(!editing_)];
 }
 
 @end
@@ -6857,18 +7425,26 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation ChangesController
 
 - (void) dealloc {
-    [list_ setDelegate:nil];
-    [list_ setDataSource:nil];
-
+    [self releaseSubviews];
     CFRelease(packages_);
-
     [sections_ release];
-    [list_ release];
+
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://changes"];
+}
+
+- (void) viewWillAppear:(BOOL)animated {
+    // Loads after it appears, so don't load beforehand.
+    loaded_ = YES;
+    [super viewWillAppear:animated];
+}
+
 - (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
+
     if (!hasSentFirstLoad_) {
         hasSentFirstLoad_ = YES;
         [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
@@ -6921,9 +7497,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
     Package *package([self packageAtIndexPath:path]);
-    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]);
+    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id]] autorelease]);
     [view setDelegate:delegate_];
-    [view setPackage:package];
     [[self navigationController] pushViewController:view animated:YES];
     return path;
 }
@@ -6937,26 +7512,37 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [delegate_ distUpgrade];
 }
 
-- (NSString *) title { return UCLocalize("CHANGES"); }
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:73];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("CHANGES")];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
-        [[self navigationItem] setTitle:UCLocalize("CHANGES")];
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
         sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:73];
-        [[self view] addSubview:list_];
-
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
     } return self;
 }
 
+// this mostly works because reloadData (below) is @synchronized (database_)
+// XXX: that said, I've been running into problems with NSRangeExceptions :(
 - (void) _reloadPackages:(NSArray *)packages {
     CFRelease(packages_);
     packages_ = CFArrayCreateMutable(kCFAllocatorDefault, [packages count], NULL);
@@ -7075,10 +7661,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 /* Search Controller {{{ */
-@interface SearchController : FilteredPackageController <
+@interface SearchController : FilteredPackageListController <
     UISearchBarDelegate
 > {
-    UISearchBar *search_;
+    _H<UISearchBar> search_;
+    BOOL searchloaded_;
 }
 
 - (id) initWithDatabase:(Database *)database;
@@ -7090,33 +7677,46 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation SearchController
 
 - (void) dealloc {
-    [search_ release];
+    [search_ setDelegate:nil];
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    if ([search_ text] == nil || [[search_ text] isEqualToString:@""])
+        return [NSURL URLWithString:@"cydia://search"];
+    else
+        return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://search/%@", [search_ text]]];
+}
+
 - (void) setSearchTerm:(NSString *)searchTerm {
     [search_ setText:searchTerm];
+    [self reloadData];
 }
 
 - (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
-    [packages_ setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
+    [self setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
     [search_ resignFirstResponder];
     [self reloadData];
 }
 
 - (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
-    [packages_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
+    [self setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
     [self reloadData];
 }
 
 - (id) initWithDatabase:(Database *)database {
-    return [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil];
+    if ((self = [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil])) {
+        search_ = [[[UISearchBar alloc] init] autorelease];
+        [search_ setDelegate:self];
+    } return self;
 }
 
-- (void)viewDidAppear:(BOOL)animated {
+- (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
-    if (!search_) {
-        search_ = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
+
+    if (!searchloaded_) {
+        searchloaded_ = YES;
+        [search_ setFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
         [search_ layoutSubviews];
         [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
 
@@ -7127,21 +7727,15 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             textField = MSHookIvar<UITextField *>(search_, "_searchField");
 
         [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [search_ setDelegate:self];
         [textField setEnablesReturnKeyAutomatically:NO];
         [[self navigationItem] setTitleView:textField];
     }
 }
 
-- (void) _reloadData {
-}
-
 - (void) reloadData {
-    _profile(SearchController$reloadData)
-        [packages_ reloadData];
-    _end
-    PrintTimes();
-    [packages_ resetCursor];
+    [self setObject:[search_ text]];
+    [super reloadData];
+    [self resetCursor];
 }
 
 - (void) didSelectPackage:(Package *)package {
@@ -7173,38 +7767,44 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @implementation PackageSettingsController
 
 - (void) dealloc {
+    [self releaseSubviews];
     [name_ release];
-    if (package_ != nil)
-        [package_ release];
-    [table_ release];
-    [subscribedSwitch_ release];
-    [ignoredSwitch_ release];
-    [subscribedCell_ release];
-    [ignoredCell_ release];
+    [package_ release];
 
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/settings", [package_ id]]];
+}
+
 - (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
     if (package_ == nil)
         return 0;
 
-    return 1;
+    if ([package_ installed] == nil)
+        return 1;
+    else
+        return 2;
 }
 
 - (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
     if (package_ == nil)
         return 0;
 
-    return 2;
+    // both sections contain just one item right now.
+    return 1;
 }
 
 - (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
-    return UCLocalize("CHANGE_PACKAGE_SETTINGS");
+    return nil;
 }
 
 - (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
-    return UCLocalize("SHOW_ALL_CHANGES_EX");
+    if (section == 0)
+        return UCLocalize("SHOW_ALL_CHANGES_EX");
+    else
+        return UCLocalize("IGNORE_UPGRADES_EX");
 }
 
 - (void) onSubscribed:(id)control {
@@ -7215,15 +7815,50 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [delegate_ updateData];
 }
 
+- (void) _updateIgnored {
+    const char *package([name_ UTF8String]);
+    bool on([ignoredSwitch_ isOn]);
+
+    pid_t pid(ExecFork());
+    if (pid == 0) {
+        FILE *dpkg(popen("dpkg --set-selections", "w"));
+        fwrite(package, strlen(package), 1, dpkg);
+
+        if (on)
+            fwrite(" hold\n", 6, 1, dpkg);
+        else
+            fwrite(" install\n", 9, 1, dpkg);
+
+        pclose(dpkg);
+
+        exit(0);
+        _assert(false);
+    }
+
+    _forever {
+        int status;
+        int result(waitpid(pid, &status, 0));
+
+        if (result != -1) {
+            _assert(result == pid);
+            break;
+        }
+    }
+}
+
 - (void) onIgnored:(id)control {
-    // TODO: set Held state - possibly call out to dpkg, etc.
+    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 {
     if (package_ == nil)
         return nil;
 
-    switch ([indexPath row]) {
+    switch ([indexPath section]) {
         case 0: return subscribedCell_;
         case 1: return ignoredCell_;
 
@@ -7233,101 +7868,83 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return nil;
 }
 
-- (NSString *) title { return UCLocalize("SETTINGS"); }
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init])) {
-        database_ = database;
-        name_ = [package retain];
+    table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+    [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [table_ setDataSource:self];
+    [table_ setDelegate:self];
+    [[self view] addSubview:table_];
 
-        [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
-
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:table_];
-
-        subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
-
-        ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
-        // Disable this switch, since it only reflects (not modifies) the ignored state.
-        [ignoredSwitch_ setUserInteractionEnabled:NO];
-
-        subscribedCell_ = [[UITableViewCell alloc] init];
-        [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
-        [subscribedCell_ setAccessoryView:subscribedSwitch_];
-        [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
-
-        ignoredCell_ = [[UITableViewCell alloc] init];
-        [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
-        [ignoredCell_ setAccessoryView:ignoredSwitch_];
-        [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
-        // FIXME: Ignored state is not saved.
-        [ignoredCell_ setUserInteractionEnabled:NO];
-
-        [table_ setDataSource:self];
-        [table_ setDelegate:self];
-        [self reloadData];
-    } return self;
-}
+    subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+    [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+    [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
 
-- (void) reloadData {
-    if (package_ != nil)
-        [package_ autorelease];
-    package_ = [database_ packageWithName:name_];
-    if (package_ != nil) {
-        [package_ retain];
-        [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO];
-        [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO];
-    }
+    ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+    [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+    [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
 
-    [table_ reloadData];
+    subscribedCell_ = [[UITableViewCell alloc] init];
+    [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
+    [subscribedCell_ setAccessoryView:subscribedSwitch_];
+    [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+
+    ignoredCell_ = [[UITableViewCell alloc] init];
+    [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
+    [ignoredCell_ setAccessoryView:ignoredSwitch_];
+    [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
 }
 
-@end
-/* }}} */
-/* Signature Controller {{{ */
-@interface SignatureController : CYBrowserController {
-    _transient Database *database_;
-    NSString *package_;
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
 }
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
+- (void) releaseSubviews {
+    [ignoredCell_ release];
+    ignoredCell_ = nil;
 
-@end
+    [subscribedCell_ release];
+    subscribedCell_ = nil;
 
-@implementation SignatureController
+    [table_ release];
+    table_ = nil;
 
-- (void) dealloc {
-    [package_ release];
-    [super dealloc];
-}
+    [ignoredSwitch_ release];
+    ignoredSwitch_ = nil;
 
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    // XXX: dude!
-    [super webView:view didClearWindowObject:window forFrame:frame];
+    [subscribedSwitch_ release];
+    subscribedSwitch_ = nil;
 }
 
 - (id) initWithDatabase:(Database *)database package:(NSString *)package {
     if ((self = [super init]) != nil) {
         database_ = database;
-        package_ = [package retain];
-        [self reloadData];
+        name_ = [package retain];
     } return self;
 }
 
 - (void) reloadData {
-    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
+    [super reloadData];
+
+    if (package_ != nil)
+        [package_ autorelease];
+    package_ = [database_ packageWithName:name_];
+
+    if (package_ != nil) {
+        package_ = [package_ retain];
+        [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO];
+        [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO];
+    } // XXX: what now, G?
+
+    [table_ reloadData];
 }
 
 @end
 /* }}} */
 
 /* Installed Controller {{{ */
-@interface InstalledController : FilteredPackageController {
+@interface InstalledController : FilteredPackageListController {
     BOOL expert_;
 }
 
@@ -7344,7 +7961,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
-- (NSString *) title { return UCLocalize("INSTALLED"); }
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://installed"];
+}
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) {
@@ -7376,10 +7995,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 #endif
 }
 
-- (void) reloadData {
-    [packages_ reloadData];
-}
-
 - (void) updateRoleButton {
     if (Role_ != nil && ![Role_ isEqualToString:@"Developer"])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
@@ -7391,18 +8006,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) roleButtonClicked {
-    [packages_ setObject:[NSNumber numberWithBool:expert_]];
-    [packages_ reloadData];
+    [self setObject:[NSNumber numberWithBool:expert_]];
+    [self reloadData];
     expert_ = !expert_;
 
     [self updateRoleButton];
 }
 
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
-}
-
 @end
 /* }}} */
 
@@ -7492,7 +8102,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 /* Source Controller {{{ */
-@interface SourceController : FilteredPackageController {
+@interface SourceController : FilteredPackageListController {
+    _transient Source *source_;
+    NSString *key_;
 }
 
 - (id) initWithDatabase:(Database *)database source:(Source *)source;
@@ -7501,11 +8113,27 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @implementation SourceController
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sources/%@", [source_ name]]];
+}
+
 - (id) initWithDatabase:(Database *)database source:(Source *)source {
     if ((self = [super initWithDatabase:database title:[source label] filter:@selector(isVisibleInSource:) with:source]) != nil) {
+        source_ = source;
+        key_ = [[source key] retain];
     } return self;
 }
 
+- (void) reloadData {
+    source_ = [database_ sourceWithKey:key_];
+    [key_ release];
+    key_ = [[source_ key] retain];
+    [self setObject:source_];
+    [[self navigationItem] setTitle:[source_ label]];
+
+    [super reloadData];
+}
+
 @end
 /* }}} */
 /* Sources Controller {{{ */
@@ -7532,7 +8160,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithDatabase:(Database *)database;
-
 - (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
 
 @end
@@ -7548,12 +8175,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) dealloc {
-    if (href_ != nil)
-        [href_ release];
-    if (hud_ != nil)
-        [hud_ release];
-    if (error_ != nil)
-        [error_ release];
+    [self releaseSubviews];
+
+    [href_ release];
+    [hud_ release];
+    [error_ release];
 
     //[self _releaseConnection:installer_];
     [self _releaseConnection:trivial_];
@@ -7562,10 +8188,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     //[self _releaseConnection:automatic_];
 
     [sources_ release];
-    [list_ release];
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://sources"];
+}
+
 - (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
     [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
@@ -7629,24 +8258,19 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[self navigationController] pushViewController:controller animated:YES];
 }
 
-- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+- (BOOL) tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
     Source *source = [self sourceAtIndexPath:indexPath];
     return [source record] != nil;
 }
 
-- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
+- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
     Source *source = [self sourceAtIndexPath:indexPath];
     [Sources_ removeObjectForKey:[source key]];
     [delegate_ syncData];
 }
 
 - (void) complete {
-    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
-        @"deb", @"Type",
-        href_, @"URI",
-        @"./", @"Distribution",
-    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
-
+    [delegate_ addTrivialSource:href_];
     [delegate_ syncData];
 }
 
@@ -7699,7 +8323,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                     message:warning
                     delegate:self
                     cancelButtonTitle:UCLocalize("CANCEL")
-                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
+                    otherButtonTitles:
+                        UCLocalize("ADD_ANYWAY"),
+                    nil
                 ] autorelease];
 
                 [alert setContext:@"warning"];
@@ -7731,9 +8357,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             [alert show];
         }
 
-        [delegate_ setStatusBarShowsProgress:NO];
-        [delegate_ removeProgressHUD:hud_];
+        [delegate_ releaseNetworkActivityIndicator];
 
+        [delegate_ removeProgressHUD:hud_];
         [hud_ autorelease];
         hud_ = nil;
 
@@ -7767,8 +8393,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self _endConnection:connection];
 }
 
-- (NSString *) title { return UCLocalize("SOURCES"); }
-
 - (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
     NSMutableURLRequest *request = [NSMutableURLRequest
         requestWithURL:[NSURL URLWithString:href]
@@ -7788,7 +8412,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
 }
 
-- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
+- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
     NSString *context([alert context]);
 
     if ([context isEqualToString:@"source"]) {
@@ -7814,6 +8438,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 // XXX: this is stupid
                 hud_ = [[delegate_ addProgressHUD] retain];
                 [hud_ setText:UCLocalize("VERIFYING_URL")];
+                [delegate_ retainNetworkActivityIndicator];
             } break;
 
             case 0:
@@ -7846,29 +8471,39 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("SOURCES")];
-        [self updateButtonsForEditingStatus:NO animated:NO];
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-        database_ = database;
-        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+    [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [list_ setRowHeight:56];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:56];
-        [[self view] addSubview:list_];
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SOURCES")];
+    [self updateButtonsForEditingStatus:NO animated:NO];
+}
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
 
-        [self reloadData];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
     } return self;
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     pkgSourceList list;
-    if (!list.ReadMainList())
+    if ([database_ popErrorWithTitle:UCLocalize("SOURCES") forOperation:list.ReadMainList()])
         return;
 
     [sources_ removeAllObjects];
@@ -7896,7 +8531,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         message:nil
         delegate:self
         cancelButtonTitle:UCLocalize("CANCEL")
-        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
+        otherButtonTitles:
+            UCLocalize("ADD_SOURCE"),
+        nil
     ] autorelease];
 
     [alert setContext:@"source"];
@@ -7975,48 +8612,63 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 
 @implementation SettingsController
+
 - (void) dealloc {
-    [table_ release];
-    [segment_ release];
-    [container_ release];
+    [self releaseSubviews];
 
     [super dealloc];
 }
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
-    if ((self = [super init])) {
-        database_ = database;
-        roledelegate_ = delegate;
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-        [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")];
+    table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+    [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [table_ setDelegate:self];
+    [table_ setDataSource:self];
+    [[self view] addSubview:table_];
 
-        NSArray *items = [NSArray arrayWithObjects:
-            UCLocalize("USER"),
-            UCLocalize("HACKER"),
-            UCLocalize("DEVELOPER"),
-        nil];
-        segment_ = [[UISegmentedControl alloc] initWithItems:items];
-        container_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, 44.0f)];
-        [container_ addSubview:segment_];
-
-        int index = -1;
-        if ([Role_ isEqualToString:@"User"]) index = 0;
-        if ([Role_ isEqualToString:@"Hacker"]) index = 1;
-        if ([Role_ isEqualToString:@"Developer"]) index = 2;
-        if (index != -1) {
-            [segment_ setSelectedSegmentIndex:index];
-            [self showDoneButton];
-        }
+    NSArray *items = [NSArray arrayWithObjects:
+        UCLocalize("USER"),
+        UCLocalize("HACKER"),
+        UCLocalize("DEVELOPER"),
+    nil];
+    segment_ = [[UISegmentedControl alloc] initWithItems:items];
+    container_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, 44.0f)];
+    [container_ addSubview:segment_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")];
+
+    int index = -1;
+    if ([Role_ isEqualToString:@"User"]) index = 0;
+    if ([Role_ isEqualToString:@"Hacker"]) index = 1;
+    if ([Role_ isEqualToString:@"Developer"]) index = 2;
+    if (index != -1) {
+        [segment_ setSelectedSegmentIndex:index];
+        [self showDoneButton];
+    }
+
+    [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
+    [self resizeSegmentedControl];
+}
+
+- (void) releaseSubviews {
+    [table_ release];
+    table_ = nil;
+
+    [segment_ release];
+    segment_ = nil;
 
-        [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
-        [self resizeSegmentedControl];
+    [container_ release];
+    container_ = nil;
+}
 
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [table_ setDelegate:self];
-        [table_ setDataSource:self];
-        [[self view] addSubview:table_];
-        [table_ reloadData];
+- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        roledelegate_ = delegate;
     } return self;
 }
 
@@ -8106,7 +8758,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return 0; // :(
 }
 
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     return nil; // This method is required by the protocol.
 }
 
@@ -8131,61 +8783,83 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return section == 3 ? container_ : nil;
 }
 
+- (void) reloadData {
+    [super reloadData];
+    [table_ reloadData];
+}
+
 @end
 /* }}} */
 /* Stash Controller {{{ */
 @interface StashController : CYViewController {
-    // XXX: just delete these things
-    _transient UIActivityIndicatorView *spinner_;
-    _transient UILabel *status_;
-    _transient UILabel *caption_;
+    UIActivityIndicatorView *spinner_;
+    UILabel *status_;
+    UILabel *caption_;
 }
+
 @end
 
 @implementation StashController
-- (id) init {
-    if ((self = [super init])) {
-        [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]];
 
-        spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
-        CGRect spinrect = [spinner_ frame];
-        spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2);
-        spinrect.origin.y = [[self view] frame].size.height - 80.0f;
-        [spinner_ setFrame:spinrect];
-        [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
-        [[self view] addSubview:spinner_];
-        [spinner_ startAnimating];
+- (void) dealloc {
+    [self releaseSubviews];
 
-        CGRect captrect;
-        captrect.size.width = [[self view] frame].size.width;
-        captrect.size.height = 40.0f;
-        captrect.origin.x = 0;
-        captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2);
-        caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease];
-        [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")];
-        [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]];
-        [caption_ setTextColor:[UIColor whiteColor]];
-        [caption_ setBackgroundColor:[UIColor clearColor]];
-        [caption_ setShadowColor:[UIColor blackColor]];
-        [caption_ setTextAlignment:UITextAlignmentCenter];
-        [[self view] addSubview:caption_];
-
-        CGRect statusrect;
-        statusrect.size.width = [[self view] frame].size.width;
-        statusrect.size.height = 30.0f;
-        statusrect.origin.x = 0;
-        statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height;
-        status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease];
-        [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")];
-        [status_ setFont:[UIFont systemFontOfSize:16.0f]];
-        [status_ setTextColor:[UIColor whiteColor]];
-        [status_ setBackgroundColor:[UIColor clearColor]];
-        [status_ setShadowColor:[UIColor blackColor]];
-        [status_ setTextAlignment:UITextAlignmentCenter];
-        [[self view] addSubview:status_];
-    } return self;
+    [super dealloc];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+    [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]];
+
+    spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
+    CGRect spinrect = [spinner_ frame];
+    spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2);
+    spinrect.origin.y = [[self view] frame].size.height - 80.0f;
+    [spinner_ setFrame:spinrect];
+    [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
+    [[self view] addSubview:spinner_];
+    [spinner_ startAnimating];
+
+    CGRect captrect;
+    captrect.size.width = [[self view] frame].size.width;
+    captrect.size.height = 40.0f;
+    captrect.origin.x = 0;
+    captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2);
+    caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease];
+    [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")];
+    [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+    [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]];
+    [caption_ setTextColor:[UIColor whiteColor]];
+    [caption_ setBackgroundColor:[UIColor clearColor]];
+    [caption_ setShadowColor:[UIColor blackColor]];
+    [caption_ setTextAlignment:UITextAlignmentCenter];
+    [[self view] addSubview:caption_];
+
+    CGRect statusrect;
+    statusrect.size.width = [[self view] frame].size.width;
+    statusrect.size.height = 30.0f;
+    statusrect.origin.x = 0;
+    statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height;
+    status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease];
+    [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+    [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")];
+    [status_ setFont:[UIFont systemFontOfSize:16.0f]];
+    [status_ setTextColor:[UIColor whiteColor]];
+    [status_ setBackgroundColor:[UIColor clearColor]];
+    [status_ setShadowColor:[UIColor blackColor]];
+    [status_ setTextAlignment:UITextAlignmentCenter];
+    [[self view] addSubview:status_];
+}
+
+- (void) releaseSubviews {
+    [spinner_ release];
+    spinner_ = nil;
+
+    [status_ release];
+    status_ = nil;
+
+    [caption_ release];
+    caption_ = nil;
 }
 
 @end
@@ -8193,7 +8867,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @interface Cydia : UIApplication <
     ConfirmationControllerDelegate,
-    ProgressControllerDelegate,
+    DatabaseDelegate,
     CydiaDelegate,
     UINavigationControllerDelegate,
     UITabBarControllerDelegate
@@ -8202,6 +8876,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     UIWindow *window_;
     CYTabBarController *tabbar_;
+    CYEmulatedLoadingController *emulated_;
 
     NSMutableArray *essential_;
     NSMutableArray *broken_;
@@ -8241,10 +8916,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             message:UCLocalize("HALFINSTALLED_PACKAGE_EX")
             delegate:self
             cancelButtonTitle:UCLocalize("FORCIBLY_CLEAR")
-            otherButtonTitles:UCLocalize("TEMPORARY_IGNORE"), nil
+            otherButtonTitles:
+                UCLocalize("TEMPORARY_IGNORE"),
+            nil
         ] autorelease];
 
         [alert setContext:@"fixhalf"];
+        [alert setNumberOfRows:2];
         [alert show];
     } else if (!Ignored_ && [essential_ count] != 0) {
         int count = [essential_ count];
@@ -8254,7 +8932,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             message:UCLocalize("ESSENTIAL_UPGRADE_EX")
             delegate:self
             cancelButtonTitle:UCLocalize("TEMPORARY_IGNORE")
-            otherButtonTitles:UCLocalize("UPGRADE_ESSENTIAL"), UCLocalize("COMPLETE_UPGRADE"), nil
+            otherButtonTitles:
+                UCLocalize("UPGRADE_ESSENTIAL"),
+                UCLocalize("COMPLETE_UPGRADE"),
+            nil
         ] autorelease];
 
         [alert setContext:@"upgrade"];
@@ -8305,11 +8986,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[navigation tabBarItem] setBadgeValue:(Queuing_ ? UCLocalize("Q_D") : nil)];
 }
 
-- (void) _refreshIfPossible {
+- (void) _refreshIfPossible:(NSDate *)update {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
     bool recently = false;
-    NSDate *update([Metadata_ objectForKey:@"LastUpdate"]);
     if (update != nil) {
         NSTimeInterval interval([update timeIntervalSinceNow]);
         if (interval <= 0 && interval > -(15*60))
@@ -8323,8 +9003,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (recently || loaded_ || ManualRefresh) {
         [self performSelectorOnMainThread:@selector(_loaded) withObject:nil waitUntilDone:NO];
 
-        // If we are cancelling due to ManualRefresh or a recent refresh
-        // we need to make sure it knows it's already loaded.
+        // If we are cancelling, we need to make sure it knows it's already loaded.
         loaded_ = true;
         return;
     } else {
@@ -8358,14 +9037,14 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) refreshIfPossible {
-    [NSThread detachNewThreadSelector:@selector(_refreshIfPossible) toTarget:self withObject:nil];
+    [NSThread detachNewThreadSelector:@selector(_refreshIfPossible:) toTarget:self withObject:[Metadata_ objectForKey:@"LastUpdate"]];
 }
 
-- (void) _reloadData {
+- (void) _reloadDataWithInvocation:(NSInvocation *)invocation {
     UIProgressHUD *hud(loaded_ ? [self addProgressHUD] : nil);
     [hud setText:UCLocalize("RELOADING_DATA")];
 
-    [database_ yieldToSelector:@selector(reloadData) withObject:nil];
+    [database_ yieldToSelector:@selector(reloadDataWithInvocation:) withObject:invocation];
 
     if (hud != nil)
         [self removeProgressHUD:hud];
@@ -8415,7 +9094,70 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [database_ update];
 }
 
+- (void) complete {
+    @synchronized (self) {
+        [self _reloadDataWithInvocation:nil];
+    }
+}
+
+- (void) disemulate {
+    if (emulated_ == nil)
+        return;
+
+    [window_ addSubview:[tabbar_ view]];
+    [[emulated_ view] removeFromSuperview];
+    [emulated_ release];
+    emulated_ = nil;
+    [window_ setUserInteractionEnabled:YES];
+}
+
+- (void) presentModalViewController:(UIViewController *)controller force:(BOOL)force {
+    UINavigationController *navigation([[[CYNavigationController alloc] initWithRootViewController:controller] autorelease]);
+    if (IsWildcat_)
+        [navigation setModalPresentationStyle:UIModalPresentationFormSheet];
+
+    UIViewController *parent;
+    if (emulated_ == nil)
+        parent = tabbar_;
+    else if (!force)
+        parent = emulated_;
+    else {
+        [self disemulate];
+        parent = tabbar_;
+    }
+
+    [parent presentModalViewController:navigation animated:YES];
+}
+
+- (ProgressController *) invokeNewProgress:(NSInvocation *)invocation forController:(UINavigationController *)navigation withTitle:(NSString *)title {
+    ProgressController *progress([[[ProgressController alloc] initWithDatabase:database_ delegate:self] autorelease]);
+
+    if (navigation != nil)
+        [navigation pushViewController:progress animated:YES];
+    else
+        [self presentModalViewController:progress force:YES];
+
+    [progress invoke:invocation withTitle:title];
+    return progress;
+}
+
+- (void) detachNewProgressSelector:(SEL)selector toTarget:(id)target forController:(UINavigationController *)navigation title:(NSString *)title {
+    [self invokeNewProgress:[NSInvocation invocationWithSelector:selector forTarget:target] forController:navigation withTitle:title];
+}
+
+- (void) repairWithInvocation:(NSInvocation *)invocation {
+    _trace();
+    [self invokeNewProgress:invocation forController:nil withTitle:@"REPAIRING"];
+    _trace();
+}
+
+- (void) repairWithSelector:(SEL)selector {
+    [self performSelectorOnMainThread:@selector(repairWithInvocation:) withObject:[NSInvocation invocationWithSelector:selector forTarget:database_] waitUntilDone:YES];
+}
+
 - (void) syncData {
+    [self _saveConfig];
+
     FILE *file(fopen("/etc/apt/sources.list.d/cydia.list", "w"));
     _assert(file != NULL);
 
@@ -8431,28 +9173,31 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     fclose(file);
 
-    [self _saveConfig];
+    [self detachNewProgressSelector:@selector(update_) toTarget:self forController:nil title:@"UPDATING_SOURCES"];
 
-    ProgressController *progress = [[[ProgressController alloc] initWithDatabase:database_ delegate:self] autorelease];
-    CYNavigationController *navigation = [[[CYNavigationController alloc] initWithRootViewController:progress] autorelease];
-    if (IsWildcat_)
-        [navigation setModalPresentationStyle:UIModalPresentationFormSheet];
-    [tabbar_ presentModalViewController:navigation animated:YES];
+    [self complete];
+}
 
-    [progress
-        detachNewThreadSelector:@selector(update_)
-        toTarget:self
-        withObject:nil
-        title:UCLocalize("UPDATING_SOURCES")
-    ];
+- (void) addTrivialSource:(NSString *)href {
+    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
+        @"deb", @"Type",
+        href, @"URI",
+        @"./", @"Distribution",
+    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href]];
+
+    Changed_ = true;
 }
 
-- (void) reloadData {
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation {
     @synchronized (self) {
-        [self _reloadData];
+        [self _reloadDataWithInvocation:invocation];
     }
 }
 
+- (void) reloadData {
+    [self reloadDataWithInvocation:nil];
+}
+
 - (void) resolve {
     pkgProblemResolver *resolver = [database_ resolver];
 
@@ -8462,6 +9207,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (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;
 
@@ -8524,76 +9276,14 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) complete {
-    @synchronized (self) {
-        [self _reloadData];
-    }
-}
-
 - (void) confirmWithNavigationController:(UINavigationController *)navigation {
     Queuing_ = false;
-
-    ProgressController *progress = [[[ProgressController alloc] initWithDatabase:database_ delegate:self] autorelease];
-
-    if (navigation != nil) {
-        [navigation pushViewController:progress animated:YES];
-    } else {
-        navigation = [[[CYNavigationController alloc] initWithRootViewController:progress] autorelease];
-        if (IsWildcat_)
-            [navigation setModalPresentationStyle:UIModalPresentationFormSheet];
-        [tabbar_ presentModalViewController:navigation animated:YES];
-    }
-
-    [progress
-        detachNewThreadSelector:@selector(perform)
-        toTarget:database_
-        withObject:nil
-        title:UCLocalize("RUNNING")
-    ];
-
     ++locked_;
-}
-
-- (void) progressControllerIsComplete:(ProgressController *)progress {
+    [self detachNewProgressSelector:@selector(perform) toTarget:database_ forController:navigation title:@"RUNNING"];
     --locked_;
     [self complete];
 }
 
-- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
-    CYNavigationController *controller = (CYNavigationController *) viewController;
-
-    if ([[controller viewControllers] count] == 0) {
-        int index = [[tabbar_ viewControllers] indexOfObjectIdenticalTo:controller];
-        CYViewController *root = nil;
-
-        if (index == 0)
-            root = [[[HomeController alloc] init] autorelease];
-        else if (index == 1)
-            root = [[[SectionsController alloc] initWithDatabase:database_] autorelease];
-        else if (index == 2)
-            root = [[[ChangesController alloc] initWithDatabase:database_] autorelease];
-
-        if (IsWildcat_) {
-            if (index == 3)
-                root = [[[InstalledController alloc] initWithDatabase:database_] autorelease];
-            else if (index == 4)
-                root = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
-            else if (index == 5)
-                root = [[[SearchController alloc] initWithDatabase:database_] autorelease];
-        } else {
-            if (index == 3)
-                root = [[[ManageController alloc] init] autorelease];
-            else if (index == 4)
-                root = [[[SearchController alloc] initWithDatabase:database_] autorelease];
-        }
-
-        [root setDelegate:self];
-
-        if (root != nil)
-            [controller setViewControllers:[NSArray arrayWithObject:root]];
-    }
-}
-
 - (void) showSettings {
     SettingsController *role = [[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease];
     CYNavigationController *nav = [[[CYNavigationController alloc] initWithRootViewController:role] autorelease];
@@ -8605,17 +9295,20 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) retainNetworkActivityIndicator {
     if (activity_++ == 0)
         [self setNetworkActivityIndicatorVisible:YES];
+
+#if TraceLogging
+    NSLog(@"retainNetworkActivityIndicator->%d", activity_);
+#endif
 }
 
 - (void) releaseNetworkActivityIndicator {
     if (--activity_ == 0)
         [self setNetworkActivityIndicatorVisible:NO];
-}
 
-- (void) setPackageController:(CYPackageController *)view {
-    WebThreadLock();
-    [view setPackage:nil];
-    WebThreadUnlock();
+#if TraceLogging
+    NSLog(@"releaseNetworkActivityIndicator->%d", activity_);
+#endif
+
 }
 
 - (void) cancelAndClear:(bool)clear {
@@ -8634,8 +9327,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
     NSString *context([alert context]);
 
-    if ([context isEqualToString:@"fixhalf"]) {
-        if (button == [alert firstOtherButtonIndex]) {
+    if ([context isEqualToString:@"conffile"]) {
+        FILE *input = [database_ input];
+        if (button == [alert cancelButtonIndex])
+            fprintf(input, "N\n");
+        else if (button == [alert firstOtherButtonIndex])
+            fprintf(input, "Y\n");
+        fflush(input);
+
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    } else if ([context isEqualToString:@"fixhalf"]) {
+        if (button == [alert cancelButtonIndex]) {
             @synchronized (self) {
                 for (Package *broken in broken_) {
                     [broken remove];
@@ -8650,7 +9352,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 [self resolve];
                 [self perform];
             }
-        } else if (button == [alert cancelButtonIndex]) {
+        } else if (button == [alert firstOtherButtonIndex]) {
             [broken_ removeAllObjects];
             [self _loaded];
         }
@@ -8738,20 +9440,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (CYViewController *) pageForPackage:(NSString *)name {
-    if (Package *package = [database_ packageWithName:name]) {
-        CYPackageController *view = [[[CYPackageController alloc] initWithDatabase:database_] autorelease];
-        [view setPackage:package];
-        return view;
-    } else {
-        NSURL *url([NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"unknown" ofType:@"html"]]);
-        url = [NSURL URLWithString:[[url absoluteString] stringByAppendingString:[NSString stringWithFormat:@"?%@", name]]];
-        CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease];
-        [browser loadURL:url];
-        return browser;
-    }
+    return [[[CYPackageController alloc] initWithDatabase:database_ forPackage:name] autorelease];
 }
 
-- (CYViewController *) pageForURL:(NSURL *)url {
+- (CYViewController *) pageForURL:(NSURL *)url forExternal:(BOOL)external {
     NSString *scheme([[url scheme] lowercaseString]);
     if ([[url absoluteString] length] <= [scheme length] + 3)
         return nil;
@@ -8771,12 +9463,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if ([base isEqualToString:@"url"]) {
         // This kind of URL can contain slashes in the argument, so we can't parse them below.
         NSString *destination = [[url absoluteString] substringFromIndex:([scheme length] + [@"://" length] + [base length] + [@"/" length])];
-        controller = [[[CYBrowserController alloc] init] autorelease];
-        [(CYBrowserController *)controller loadURL:[NSURL URLWithString:destination]];
-    } else if ([components count] == 1) {
-        if ([base isEqualToString:@"storage"]) {
-            controller = [[[CYBrowserController alloc] init] autorelease];
-            [(CYBrowserController *)controller loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"storage" ofType:@"html"]]];
+        controller = [[[CYBrowserController alloc] initWithURL:[NSURL URLWithString:destination]] autorelease];
+    } else if (!external && [components count] == 1) {
+        if ([base isEqualToString:@"manage"]) {
+            controller = [[[ManageController alloc] init] autorelease];
         }
 
         if ([base isEqualToString:@"sources"]) {
@@ -8809,45 +9499,38 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             controller = [self pageForPackage:argument];
         }
 
-        if ([base isEqualToString:@"search"]) {
+        if (!external && [base isEqualToString:@"search"]) {
             controller = [[[SearchController alloc] initWithDatabase:database_] autorelease];
             [(SearchController *)controller setSearchTerm:argument];
         }
 
-        if ([base isEqualToString:@"sections"]) {
+        if (!external && [base isEqualToString:@"sections"]) {
             if ([argument isEqualToString:@"all"])
                 argument = nil;
             controller = [[[SectionController alloc] initWithDatabase:database_ section:argument] autorelease];
         }
 
-        if ([base isEqualToString:@"sources"]) {
+        if (!external && [base isEqualToString:@"sources"]) {
             if ([argument isEqualToString:@"add"]) {
                 controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
                 [(SourcesController *)controller showAddSourcePrompt];
             } else {
-                NSArray *sources = [database_ sources];
-                for (Source *source in sources) {
-                    if ([[source name] caseInsensitiveCompare:argument] == NSOrderedSame) {
-                        controller = [[[SourceController alloc] initWithDatabase:database_ source:source] autorelease];
-                        break;
-                    }
-                }
+                Source *source = [database_ sourceWithKey:argument];
+                controller = [[[SourceController alloc] initWithDatabase:database_ source:source] autorelease];
             }
         }
 
-        if ([base isEqualToString:@"launch"]) {
+        if (!external && [base isEqualToString:@"launch"]) {
             [self launchApplicationWithIdentifier:argument suspended:NO];
             return nil;
         }
-    } else if ([components count] == 3) {
+    } else if (!external && [components count] == 3) {
         NSString *arg1 = [components objectAtIndex:1];
         NSString *arg2 = [components objectAtIndex:2];
 
         if ([base isEqualToString:@"package"]) {
             if ([arg2 isEqualToString:@"settings"]) {
                 controller = [[[PackageSettingsController alloc] initWithDatabase:database_ package:arg1] autorelease];
-            } else if ([arg2 isEqualToString:@"signature"]) {
-                controller = [[[SignatureController alloc] initWithDatabase:database_ package:arg1] autorelease];
             } else if ([arg2 isEqualToString:@"files"]) {
                 if (Package *package = [database_ packageWithName:arg1]) {
                     controller = [[[FileTable alloc] initWithDatabase:database_] autorelease];
@@ -8861,13 +9544,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return controller;
 }
 
-- (BOOL) openCydiaURL:(NSURL *)url {
-    CYViewController *page([self pageForURL:url]);
+- (BOOL) openCydiaURL:(NSURL *)url forExternal:(BOOL)external {
+    CYViewController *page([self pageForURL:url forExternal:external]);
 
     if (page != nil) {
         CYNavigationController *nav = [[[CYNavigationController alloc] init] autorelease];
         [nav setViewControllers:[NSArray arrayWithObject:page]];
-        [tabbar_ setTransientViewController:nav];
+        [tabbar_ setUnselectedViewController:nav];
     }
 
     return page != nil;
@@ -8877,7 +9560,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super applicationOpenURL:url];
 
     if (!loaded_) starturl_ = [url retain];
-    else [self openCydiaURL:url];
+    else [self openCydiaURL:url forExternal:YES];
 }
 
 - (void) applicationWillResignActive:(UIApplication *)application {
@@ -8889,6 +9572,42 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [super applicationWillResignActive:application];
 }
 
+- (void) applicationWillTerminate:(UIApplication *)application {
+    Changed_ = true;
+    [Metadata_ setObject:[tabbar_ navigationURLCollection] forKey:@"InterfaceState"];
+    [Metadata_ setObject:[NSDate date] forKey:@"LastClosed"];
+    [Metadata_ setObject:[NSNumber numberWithInt:[tabbar_ selectedIndex]] forKey:@"InterfaceIndex"];
+
+    [self _saveConfig];
+}
+
+- (void) setConfigurationData:(NSString *)data {
+    static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
+
+    if (!conffile_r(data)) {
+        lprintf("E:invalid conffile\n");
+        return;
+    }
+
+    NSString *ofile = conffile_r[1];
+    //NSString *nfile = conffile_r[2];
+
+    UIAlertView *alert = [[[UIAlertView alloc]
+        initWithTitle:UCLocalize("CONFIGURATION_UPGRADE")
+        message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
+        delegate:self
+        cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
+        otherButtonTitles:
+            UCLocalize("ACCEPT_NEW_COPY"),
+            // XXX: UCLocalize("SEE_WHAT_CHANGED"),
+        nil
+    ] autorelease];
+
+    [alert setContext:@"conffile"];
+    [alert setNumberOfRows:2];
+    [alert show];
+}
+
 - (void) addStashController {
     ++locked_;
     stash_ = [[StashController alloc] init];
@@ -8905,13 +9624,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self setIdleTimerDisabled:YES];
 
     [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque];
-    [self setStatusBarShowsProgress:YES];
     UpdateExternalStatus(1);
-
     [self yieldToSelector:@selector(system:) withObject:@"/usr/libexec/cydia/free.sh"];
-
     UpdateExternalStatus(0);
-    [self setStatusBarShowsProgress:NO];
 
     [self removeStashController];
 
@@ -8923,7 +9638,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) setupViewControllers {
     tabbar_ = [[CYTabBarController alloc] initWithDatabase:database_];
-    [tabbar_ setDelegate:self];
 
     NSMutableArray *items([NSMutableArray arrayWithObjects:
         [[[UITabBarItem alloc] initWithTitle:@"Cydia" image:[UIImage applicationImageNamed:@"home.png"] tag:0] autorelease],
@@ -8950,24 +9664,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [tabbar_ setUpdateDelegate:self];
 }
 
-- (CYEmulatedLoadingController *)showEmulatedLoadingControllerInView:(UIView *)view {
-    static CYEmulatedLoadingController *fake = [[CYEmulatedLoadingController alloc] init];
-    if (view != nil) {
-        [view addSubview:[fake view]];
-    } else {
-        [[fake view] removeFromSuperview];
-    }
-
-    return fake;
+- (void) addCydiaHost:(NSString *)host {
+    [CydiaHosts_ addObject:host];
 }
 
 - (void) applicationDidFinishLaunching:(id)unused {
 _trace();
-    CydiaApp = self;
-
     if ([self respondsToSelector:@selector(setApplicationSupportsShakeToEdit:)])
         [self setApplicationSupportsShakeToEdit:NO];
 
+    [self addCydiaHost:[[NSURL URLWithString:CydiaURL(@"")] host]];
+
     [NSURLCache setSharedURLCache:[[[SDURLCache alloc]
         initWithMemoryCapacity:524288
         diskCapacity:10485760
@@ -9013,10 +9720,13 @@ _trace();
     }
 
     database_ = [Database sharedInstance];
+    [database_ setDelegate:self];
 
     [window_ setUserInteractionEnabled:NO];
     [self setupViewControllers];
-    [self showEmulatedLoadingControllerInView:window_];
+
+    emulated_ = [[CYEmulatedLoadingController alloc] initWithDatabase:database_];
+    [window_ addSubview:[emulated_ view]];
 
     [self performSelector:@selector(loadData) withObject:nil afterDelay:0];
 _trace();
@@ -9026,34 +9736,74 @@ _trace();
 _trace();
     if (Role_ == nil) {
         [window_ setUserInteractionEnabled:YES];
-
-        SettingsController *role = [[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease];
-        CYNavigationController *nav = [[[CYNavigationController alloc] initWithRootViewController:role] autorelease];
-        if (IsWildcat_)
-            [nav setModalPresentationStyle:UIModalPresentationFormSheet];
-        [[self showEmulatedLoadingControllerInView:window_] presentModalViewController:nav animated:YES];
-
+        [self presentModalViewController:[[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease] force:NO];
         return;
     } else {
-        if ([[self showEmulatedLoadingControllerInView:window_] modalViewController] != nil)
-            [[self showEmulatedLoadingControllerInView:window_] dismissModalViewControllerAnimated:YES];
+        if ([emulated_ modalViewController] != nil)
+            [emulated_ dismissModalViewControllerAnimated:YES];
         [window_ setUserInteractionEnabled:NO];
     }
 
     [self reloadData];
     PrintTimes();
 
-    [window_ addSubview:[tabbar_ view]];
-    [self showEmulatedLoadingControllerInView:nil];
-    [window_ setUserInteractionEnabled:YES];
+    [self disemulate];
+
+    int selectedIndex = 0;
+    NSMutableArray *items = nil;
+
+    bool recently = false;
+    NSDate *closed([Metadata_ objectForKey:@"LastClosed"]);
+    if (closed != nil) {
+        NSTimeInterval interval([closed timeIntervalSinceNow]);
+        // XXX: Is 15 minutes the optimal time here?
+        if (interval <= 0 && interval > -(15*60))
+            recently = true;
+    }
+
+    items = [[Metadata_ objectForKey:@"InterfaceState"] mutableCopy];
+    selectedIndex = [[Metadata_ objectForKey:@"InterfaceIndex"] intValue];
 
-    // Show the home page.
-    CYNavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:0];
-    [navigation setViewControllers:[NSArray arrayWithObject:[self pageForURL:[NSURL URLWithString:@"cydia://home"]]]];
+    BOOL enough = YES;
+    for (NSArray *entry in items)
+        if ([entry count] <= 0)
+            enough = NO;
+
+    if (!recently || !items || !enough) {
+        selectedIndex = 0;
+        items = [NSMutableArray array];
+        [items addObject:[NSArray arrayWithObject:@"cydia://home"]];
+        [items addObject:[NSArray arrayWithObject:@"cydia://sections"]];
+        [items addObject:[NSArray arrayWithObject:@"cydia://changes"]];
+        if (!IsWildcat_) {
+            [items addObject:[NSArray arrayWithObject:@"cydia://manage"]];
+        } else {
+            [items addObject:[NSArray arrayWithObject:@"cydia://installed"]];
+            [items addObject:[NSArray arrayWithObject:@"cydia://sources"]];
+        }
+        [items addObject:[NSArray arrayWithObject:@"cydia://search"]];
+    }
+
+    [tabbar_ setSelectedIndex:selectedIndex];
+    for (unsigned int tab = 0; tab < [[tabbar_ viewControllers] count]; tab++) {
+        NSArray *stack = [items objectAtIndex:tab];
+        CYNavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:tab];
+        NSMutableArray *current = [NSMutableArray array];
+
+        for (unsigned int nav = 0; nav < [stack count]; nav++) {
+            NSString *addr = [stack objectAtIndex:nav];
+            NSURL *url = [NSURL URLWithString:addr];
+            CYViewController *page = [self pageForURL:url forExternal:NO];
+            if (page != nil)
+                [current addObject:page];
+        }
+
+        [navigation setViewControllers:current];
+    }
 
     // (Try to) show the startup URL.
     if (starturl_ != nil) {
-        [self openCydiaURL:starturl_];
+        [self openCydiaURL:starturl_ forExternal:NO];
         [starturl_ release];
         starturl_ = nil;
     }
@@ -9067,6 +9817,22 @@ _trace();
     }
 }
 
+- (void) addProgressEvent:(CydiaProgressEvent *)event forTask:(NSString *)task {
+    id<ProgressDelegate> progress([database_ progressDelegate] ?: [self invokeNewProgress:nil forController:nil withTitle:task]);
+    [progress setTitle:task];
+    [progress addProgressEvent:event];
+}
+
+- (void) addProgressEventForTask:(NSArray *)data {
+    CydiaProgressEvent *event([data objectAtIndex:0]);
+    NSString *task([data count] < 2 ? nil : [data objectAtIndex:1]);
+    [self addProgressEvent:event forTask:task];
+}
+
+- (void) addProgressEventOnMainThread:(CydiaProgressEvent *)event forTask:(NSString *)task {
+    [self performSelectorOnMainThread:@selector(addProgressEventForTask:) withObject:[NSArray arrayWithObjects:event, task, nil] waitUntilDone:YES];
+}
+
 @end
 
 /*IMP alloc_;
@@ -9142,12 +9908,37 @@ MSHook(void, UIApplication$_updateApplicationAccessibility, UIApplication *self,
 int main(int argc, char *argv[]) { _pooled
     _trace();
 
+    UpdateExternalStatus(0);
+
     if (Class $UIDevice = objc_getClass("UIDevice")) {
         UIDevice *device([$UIDevice currentDevice]);
         IsWildcat_ = [device respondsToSelector:@selector(isWildcat)] && [device isWildcat];
     } else
         IsWildcat_ = false;
 
+    UIScreen *screen([UIScreen mainScreen]);
+    if ([screen respondsToSelector:@selector(scale)])
+        ScreenScale_ = [screen scale];
+    else
+        ScreenScale_ = 1;
+
+    UIDevice *device([UIDevice currentDevice]);
+    if (![device respondsToSelector:@selector(userInterfaceIdiom)])
+        Idiom_ = @"iphone";
+    else {
+        UIUserInterfaceIdiom idiom([device userInterfaceIdiom]);
+        if (idiom == UIUserInterfaceIdiomPhone)
+            Idiom_ = @"iphone";
+        else if (idiom == UIUserInterfaceIdiomPad)
+            Idiom_ = @"ipad";
+        else
+            NSLog(@"unknown UIUserInterfaceIdiom!");
+    }
+
+    CydiaHosts_ = [NSMutableSet setWithCapacity:2];
+
+    UI_ = CydiaURL([NSString stringWithFormat:@"ui/ios~%@", Idiom_]);
+
     PackageName = reinterpret_cast<CYString &(*)(Package *, SEL)>(method_getImplementation(class_getInstanceMethod([Package class], @selector(cyname))));
 
     /* Library Hacks {{{ */
@@ -9187,9 +9978,9 @@ int main(int argc, char *argv[]) { _pooled
     else {
         lang = [[Languages_ objectAtIndex:0] UTF8String];
         setenv("LANG", lang, true);
+        std::setlocale(LC_ALL, lang);
     }
 
-    //std::setlocale(LC_ALL, lang);
     NSLog(@"Setting Language: %s", lang);
     /* }}} */
 
@@ -9220,7 +10011,6 @@ int main(int argc, char *argv[]) { _pooled
     /* }}} */
 
     App_ = [[NSBundle mainBundle] bundlePath];
-    Home_ = NSHomeDirectory();
     Advanced_ = YES;
 
     setuid(0);
@@ -9346,19 +10136,31 @@ int main(int argc, char *argv[]) { _pooled
     _trace();
 
     if (Packages_ != nil) {
-        CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, NULL);
+        bool fail(false);
+        CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, &fail);
         _trace();
-        [Metadata_ removeObjectForKey:@"Packages"];
-        Packages_ = nil;
-        Changed_ = true;
+
+        if (!fail) {
+            [Metadata_ removeObjectForKey:@"Packages"];
+            Packages_ = nil;
+            Changed_ = true;
+        }
     }
 
     Finishes_ = [NSArray arrayWithObjects:@"return", @"reopen", @"restart", @"reload", @"reboot", nil];
 
-    if (substrate && access("/Library/MobileSubstrate/DynamicLibraries/SimulatedKeyEvents.dylib", F_OK) == 0)
-        dlopen("/Library/MobileSubstrate/DynamicLibraries/SimulatedKeyEvents.dylib", RTLD_LAZY | RTLD_GLOBAL);
-    if (substrate && access("/Applications/WinterBoard.app/WinterBoard.dylib", F_OK) == 0)
-        dlopen("/Applications/WinterBoard.app/WinterBoard.dylib", RTLD_LAZY | RTLD_GLOBAL);
+#define MobileSubstrate_(name) \
+    if (substrate && access("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", F_OK) == 0) { \
+        void *handle(dlopen("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", RTLD_LAZY | RTLD_GLOBAL)); \
+        if (handle == NULL) \
+            NSLog(@"%s", dlerror()); \
+    }
+
+    MobileSubstrate_(Activator)
+    MobileSubstrate_(libstatusbar)
+    MobileSubstrate_(SimulatedKeyEvents)
+    MobileSubstrate_(WinterBoard)
+
     /*if (substrate && access("/Library/MobileSubstrate/MobileSubstrate.dylib", F_OK) == 0)
         dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib", RTLD_LAZY | RTLD_GLOBAL);*/
 
@@ -9367,7 +10169,7 @@ int main(int argc, char *argv[]) { _pooled
     if (access("/tmp/.cydia.fw", F_OK) == 0) {
         unlink("/tmp/.cydia.fw");
         goto firmware;
-    } else if (access("/User", F_OK) != 0 || version < 2) {
+    } else if (access("/User", F_OK) != 0 || version < 4) {
       firmware:
         _trace();
         system("/usr/libexec/cydia/firmware.sh");