]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Support NULL Cytore return in PackageFind().
[cydia.git] / MobileCydia.mm
index 52f2ec70ab5aa393e07d08bf05ec303340183fb4..fe48f96980f2b6569bcf69bf9a7ac83c31eba10b 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 {{{ */
@@ -217,14 +217,6 @@ 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] = ':';
@@ -309,15 +301,15 @@ 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])) {
@@ -363,32 +355,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
@@ -402,6 +368,7 @@ static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive |
 #define ShowInternals (0 && !ForRelease)
 #define IgnoreInstall (0 && !ForRelease)
 #define AlwaysReload (0 && !ForRelease)
+#define TryIndexedCollation (0 && !ForRelease)
 
 #if !TraceLogging
 #undef _trace
@@ -1071,7 +1038,7 @@ static _transient NSMutableDictionary *Sources_;
 static bool Changed_;
 static time_t now_;
 
-static bool IsWildcat_;
+bool IsWildcat_;
 /* }}} */
 
 /* Display Helpers {{{ */
@@ -1179,12 +1146,11 @@ bool isSectionVisible(NSString *section) {
 - (void) setConfigurationData:(NSString *)data;
 @end
 
-@class PackageController;
+@class CYPackageController;
 
 @protocol CydiaDelegate
 - (void) retainNetworkActivityIndicator;
 - (void) releaseNetworkActivityIndicator;
-- (void) setPackageController:(PackageController *)view;
 - (void) clearPackage:(Package *)package;
 - (void) installPackage:(Package *)package;
 - (void) installPackages:(NSArray *)packages;
@@ -1199,7 +1165,6 @@ bool isSectionVisible(NSString *section) {
 - (UIProgressHUD *) addProgressHUD;
 - (void) removeProgressHUD:(UIProgressHUD *)hud;
 - (CYViewController *) pageForPackage:(NSString *)name;
-- (PackageController *) packageController;
 - (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item;
 @end
 
@@ -1346,6 +1311,7 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
     pkgSourceList *list_;
 
     SourceMap sources_;
+    CFMutableArrayRef deadSources_;
     CFMutableArrayRef packages_;
 
     _transient NSObject<ConfigurationDelegate, ProgressDelegate> *delegate_;
@@ -1355,6 +1321,8 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
     int cydiafd_;
     int statusfd_;
     FILE *input_;
+
+    std::map<const char *, _H<NSString> > sections_;
 }
 
 + (Database *) sharedInstance;
@@ -1388,6 +1356,9 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 
 - (void) setDelegate:(id)delegate;
 - (Source *) getSource:(pkgCache::PkgFileIterator)file;
+
+- (NSString *) mappedSectionForPointer:(const char *)pointer;
+
 @end
 /* }}} */
 /* Delegate Helpers {{{ */
@@ -1456,7 +1427,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;
@@ -1466,6 +1437,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 {
@@ -1481,13 +1460,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"])
@@ -1825,7 +1806,8 @@ struct ParsedPackage {
 };
 
 @interface Package : NSObject {
-    uint32_t era_ : 29;
+    uint32_t era_ : 26;
+    uint32_t role_ : 3;
     uint32_t essential_ : 1;
     uint32_t obsolete_ : 1;
     uint32_t ignored_ : 1;
@@ -1844,7 +1826,7 @@ struct ParsedPackage {
     CYString latest_;
     CYString installed_;
 
-    CYString section_;
+    const char *section_;
     _transient NSString *section$_;
 
     Source *source_;
@@ -1853,7 +1835,6 @@ struct ParsedPackage {
     ParsedPackage *parsed_;
 
     NSMutableArray *tags_;
-    NSString *role_;
 }
 
 - (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
@@ -1915,7 +1896,6 @@ struct ParsedPackage {
 - (NSArray *) applications;
 
 - (Source *) source;
-- (NSString *) role;
 
 - (BOOL) matches:(NSString *)text;
 
@@ -2076,15 +2056,10 @@ struct PackageNameOrdering :
 - (void) dealloc {
     if (parsed_ != NULL)
         delete parsed_;
-
     if (source_ != nil)
         [source_ release];
-
     if (tags_ != nil)
         [tags_ release];
-    if (role_ != nil)
-        [role_ release];
-
     [super dealloc];
 }
 
@@ -2218,8 +2193,18 @@ struct PackageNameOrdering :
                     const char *name(tag.Name());
                     [tags_ addObject:[(NSString *)CYStringCreate(name) autorelease]];
 
-                    if (role_ == nil && strncmp(name, "role::", 6) == 0 /*&& strcmp(name, "role::leaper") != 0*/)
-                        role_ = (NSString *) CYStringCreate(name + 6);
+                    if (role_ == 0 && strncmp(name, "role::", 6) == 0 /*&& strcmp(name, "role::leaper") != 0*/) {
+                        if (strcmp(name + 6, "enduser") == 0)
+                            role_ = 1;
+                        else if (strcmp(name + 6, "hacker") == 0)
+                            role_ = 2;
+                        else if (strcmp(name + 6, "developer") == 0)
+                            role_ = 3;
+                        else if (strcmp(name + 6, "cydia") == 0)
+                            role_ = 7;
+                        else
+                            role_ = 4;
+                    }
 
                     if (strncmp(name, "cydia::", 7) == 0) {
                         if (strcmp(name + 7, "essential") == 0)
@@ -2267,7 +2252,7 @@ struct PackageNameOrdering :
         _end
 
         _profile(Package$initWithVersion$Section)
-            section_.set(NULL, iterator.Section());
+            section_ = iterator.Section();
         _end
 
         _profile(Package$initWithVersion$Flags)
@@ -2315,13 +2300,11 @@ struct PackageNameOrdering :
 
 - (NSString *) section {
     if (section$_ == nil) {
-        if (section_.empty())
+        if (section_ == NULL)
             return nil;
 
-        _profile(Package$section)
-            std::replace(section_.data(), section_.data() + section_.size(), '_', ' ');
-            NSString *name(section_);
-            section$_ = [SectionMap_ objectForKey:name] ?: name;
+        _profile(Package$section$mappedSectionForPointer)
+            section$_ = [database_ mappedSectionForPointer:section_];
         _end
     } return section$_;
 }
@@ -2471,12 +2454,12 @@ struct PackageNameOrdering :
 
 - (BOOL) unfiltered {
     _profile(Package$unfiltered$obsolete)
-        if (obsolete_)
+        if (_unlikely(obsolete_))
             return false;
     _end
 
     _profile(Package$unfiltered$hasSupportingRole)
-        if (![self hasSupportingRole])
+        if (_unlikely(![self hasSupportingRole]))
             return false;
     _end
 
@@ -2487,7 +2470,11 @@ struct PackageNameOrdering :
     if (![self unfiltered])
         return false;
 
-    NSString *section([self section]);
+    NSString *section;
+
+    _profile(Package$visible$section)
+        section = [self section];
+    _end
 
     _profile(Package$visible$isSectionVisible)
         if (section != nil && !isSectionVisible(section))
@@ -2710,10 +2697,6 @@ struct PackageNameOrdering :
     return source_ == (Source *) [NSNull null] ? nil : source_;
 }
 
-- (NSString *) role {
-    return role_;
-}
-
 - (BOOL) matches:(NSString *)text {
     if (text == nil)
         return NO;
@@ -2728,6 +2711,8 @@ struct PackageNameOrdering :
     if (range.location != NSNotFound)
         return YES;
 
+    [self parse];
+
     range = [[self shortDescription] rangeOfString:text options:MatchCompareOptions_];
     if (range.location != NSNotFound)
         return YES;
@@ -2736,17 +2721,17 @@ struct PackageNameOrdering :
 }
 
 - (bool) hasSupportingRole {
-    if (role_ == nil)
+    if (role_ == 0)
         return true;
-    if ([role_ isEqualToString:@"enduser"])
+    if (role_ == 1)
         return true;
     if ([Role_ isEqualToString:@"User"])
         return false;
-    if ([role_ isEqualToString:@"hacker"])
+    if (role_ == 2)
         return true;
     if ([Role_ isEqualToString:@"Hacker"])
         return false;
-    if ([role_ isEqualToString:@"developer"])
+    if (role_ == 3)
         return true;
     if ([Role_ isEqualToString:@"Developer"])
         return false;
@@ -2868,7 +2853,7 @@ struct PackageNameOrdering :
 }
 
 - (bool) isInstalledAndUnfiltered:(NSNumber *)number {
-    return ![self uninstalled] && (![number boolValue] && ![role_ isEqualToString:@"cydia"] || [self unfiltered]);
+    return ![self uninstalled] && (![number boolValue] && role_ != 7 || [self unfiltered]);
 }
 
 - (bool) isVisibleInSection:(NSString *)name {
@@ -3033,6 +3018,8 @@ static NSString *Warning_;
 - (void) dealloc {
     // XXX: actually implement this thing
     _assert(false);
+    if (deadSources_)
+           CFRelease(deadSources_);
     [self releasePackages];
     apr_pool_destroy(pool_);
     NSRecycleZone(zone_);
@@ -3150,6 +3137,7 @@ static NSString *Warning_;
             capacity += 1024;
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL);
+        deadSources_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
 
         int fds[2];
 
@@ -3224,6 +3212,7 @@ static NSString *Warning_;
     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;
 }
 
@@ -3330,7 +3319,9 @@ static NSString *Warning_;
     ++era_;
 
     [self releasePackages];
+
     sources_.clear();
+    CFArrayRemoveAllValues(deadSources_);
 
     _error->Discard();
 
@@ -3420,14 +3411,19 @@ static NSString *Warning_;
     }
 
     for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
+        bool found = false;
         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())
+                if (!cached.end()) {
                     sources_[cached->ID] = [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease];
+                    found = true;
+                }
             }
+        if (!found)
+            CFArrayAppendValue(deadSources_, [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease]);
     }
 
     {
@@ -3677,24 +3673,38 @@ static NSString *Warning_;
     return i == sources_.end() ? nil : i->second;
 }
 
-@end
-/* }}} */
+- (NSString *) mappedSectionForPointer:(const char *)section {
+    _H<NSString> *mapped;
 
-/* Confirmation Controller {{{ */
-bool DepSubstrate(const pkgCache::VerIterator &iterator) {
-    if (!iterator.end())
-        for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) {
-            if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends)
-                continue;
-            pkgCache::PkgIterator package(dep.TargetPkg());
-            if (package.end())
-                continue;
-            if (strcmp(package.Name(), "mobilesubstrate") == 0)
-                return true;
-        }
+    _profile(Database$mappedSectionForPointer$Cache)
+        mapped = &sections_[section];
+    _end
 
-    return false;
+    if (*mapped == NULL) {
+        size_t length(strlen(section));
+        char spaced[length + 1];
+
+        _profile(Database$mappedSectionForPointer$Replace)
+            for (size_t index(0); index != length; ++index)
+                spaced[index] = section[index] == '_' ? ' ' : section[index];
+            spaced[length] = '\0';
+        _end
+
+        NSString *string;
+
+        _profile(Database$mappedSectionForPointer$stringWithUTF8String)
+            string = [NSString stringWithUTF8String:spaced];
+        _end
+
+        _profile(Database$mappedSectionForPointer$Map)
+            string = [SectionMap_ objectForKey:string] ?: string;
+        _end
+
+        *mapped = string;
+    } return *mapped;
 }
+
+@end
 /* }}} */
 
 /* Web Scripting {{{ */
@@ -3995,6 +4005,54 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (UILabel *)label { return label_; }
 - (UIActivityIndicatorView *)activityIndicatorView { return spinner_; }
 
+@end
+/* }}} */
+/* Emulated Loading Controller {{{ */
+@interface CYEmulatedLoadingController : CYViewController {
+    CYLoadingIndicator *indicator_;
+    UITabBar *tabbar_;
+    UINavigationBar *navbar_;
+}
+@end
+
+@implementation CYEmulatedLoadingController
+
+- (void) dealloc {
+    [self releaseSubviews];
+
+    [super dealloc];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+    [[self view] setBackgroundColor:[UIColor pinStripeColor]];
+
+    indicator_ = [[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]];
+    [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [[self view] addSubview:indicator_];
+
+    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_];
+
+    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_];
+}
+
+- (void) releaseSubviews {
+    [indicator_ release];
+    indicator_ = nil;
+
+    [tabbar_ release];
+    tabbar_ = nil;
+
+    [navbar_ release];
+    navbar_ = nil;
+}
+
 @end
 /* }}} */
 
@@ -4012,6 +4070,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super dealloc];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://url/%@", [[[webview_ request] URL] absoluteString]]];
+}
+
 - (void) setHeaders:(NSDictionary *)headers forHost:(NSString *)host {
 }
 
@@ -4020,18 +4082,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     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]);
     NSString *host([url host]);
-    [self setHeaders:headers forHost:host];
+
+    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
+        NSHTTPURLResponse *http((NSHTTPURLResponse *) response);
+        NSDictionary *headers([http allHeaderFields]);
+        [self setHeaders:headers forHost:host];
+    }
 
     if (
         [host isEqualToString:@"cydia.saurik.com"] ||
@@ -4090,7 +4150,22 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 
-/* Confirmation {{{ */
+/* Confirmation Controller {{{ */
+bool DepSubstrate(const pkgCache::VerIterator &iterator) {
+    if (!iterator.end())
+        for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) {
+            if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends)
+                continue;
+            pkgCache::PkgIterator package(dep.TargetPkg());
+            if (package.end())
+                continue;
+            if (strcmp(package.Name(), "mobilesubstrate") == 0)
+                return true;
+        }
+
+    return false;
+}
+
 @protocol ConfirmationControllerDelegate
 - (void) cancelAndClear:(bool)clear;
 - (void) confirmWithNavigationController:(UINavigationController *)navigation;
@@ -4250,7 +4325,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
         [[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)
@@ -4263,7 +4337,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (issues_ == nil && ![self isLoading])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CONFIRM")
-            style:UIBarButtonItemStylePlain
+            style:UIBarButtonItemStyleDone
             target:self
             action:@selector(confirmButtonClicked)
         ] autorelease]];
@@ -4399,7 +4473,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         //[status_ setFont:font];
 
         output_ = [[UITextView alloc] init];
-
         [output_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
         //[output_ setTextFont:@"Courier New"];
         [output_ setFont:[[output_ font] fontWithSize:12]];
@@ -4445,7 +4518,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         10,
         20,
         bounds.size.width - 20,
-        bounds.size.height - 62
+        bounds.size.height - 96
     )];
     [close_ setFrame:CGRectMake(
         (bounds.size.width - closewidth) / 2,
@@ -4655,7 +4728,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    CYActionSheet *sheet([[[CYActionSheet alloc]
+    CYAlertView *sheet([[[CYAlertView alloc]
         initWithTitle:title
         buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
         defaultButtonIndex:0
@@ -4820,6 +4893,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @end
 /* }}} */
+
 /* Package Cell {{{ */
 @interface PackageCell : CYTableViewCell <
     ContentDelegate
@@ -4837,7 +4911,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (PackageCell *) init;
 - (void) setPackage:(Package *)package;
 
-+ (int) heightForPackage:(Package *)package;
 - (void) drawContentRect:(CGRect)rect;
 
 @end
@@ -4911,6 +4984,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self setNeedsDisplay];
 }
 
+- (NSString *) accessibilityLabel {
+    return [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), name_, description_];
+}
+
 - (void) setPackage:(Package *)package {
     [self clearPackage];
     [package parse];
@@ -5017,10 +5094,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [placard_ drawAtPoint:CGPointMake(width - 52, 9)];
 }
 
-+ (int) heightForPackage:(Package *)package {
-    return 73;
-}
-
 @end
 /* }}} */
 /* Section Cell {{{ */
@@ -5143,8 +5216,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [switch_ setFrame:CGRectMake(frame.size.width - 102, 9, rect.size.width, rect.size.height)];
 }
 
+- (NSString *) accessibilityLabel {
+    return name_;
+}
+
 - (void) drawContentRect:(CGRect)rect {
-    bool highlighted(highlighted_);
+    bool highlighted(highlighted_ && !editing_);
 
     [icon_ drawInRect:CGRectMake(8, 7, 32, 32)];
 
@@ -5189,12 +5266,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];
 }
 
@@ -5220,21 +5297,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;
 }
 
@@ -5284,13 +5375,15 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     [self setPackage:[database_ packageWithName:name_]];
 }
 
 @end
 /* }}} */
 /* Package Controller {{{ */
-@interface PackageController : CYBrowserController <
+@interface CYPackageController : CYBrowserController <
     UIActionSheetDelegate
 > {
     _transient Database *database_;
@@ -5306,7 +5399,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @end
 
-@implementation PackageController
+@implementation CYPackageController
 
 - (void) dealloc {
     if (package_ != nil)
@@ -5323,11 +5416,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) release {
-    if ([self retainCount] == 1)
-        [delegate_ setPackageController:self];
     [super release];
 }
 
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@", [package_ id]]];
+}
+
 /* XXX: this is not safe at all... localization of /fail/ */
 - (void) _clickButtonWithName:(NSString *)name {
     if ([name isEqualToString:UCLocalize("CLEAR")])
@@ -5421,11 +5516,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 #endif
 
+- (void) viewWillAppear:(BOOL)animated {
+    if (![self hasLoaded])
+        [self loadURL:[NSURL URLWithString:CydiaURL(@"ui/package/")]];
+    [super viewWillAppear:animated];
+}
+
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
         buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
-        [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"package" ofType:@"html"]]];
     } return self;
 }
 
@@ -5487,13 +5587,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     [self setPackage:[database_ packageWithName:name_]];
 }
 
 @end
 /* }}} */
-/* Package Table {{{ */
-@interface PackageTable : UIView <
+
+/* Package List Controller {{{ */
+@interface PackageListController : CYViewController <
     UITableViewDataSource,
     UITableViewDelegate
 > {
@@ -5504,29 +5607,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];
@@ -5534,13 +5624,98 @@ 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_] autorelease]);
+    [view setPackage:package];
+    [view setDelegate:delegate_];
+    [[self navigationController] pushViewController:view animated:YES];
+}
+
+#if TryIndexedCollation
 + (BOOL) hasIndexedCollation {
     return NO; // XXX: objc_getClass("UILocalizedIndexedCollation") != nil;
 }
+#endif
 
 - (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
     NSInteger count([sections_ count]);
@@ -5578,19 +5753,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return cell;
 }
 
-- (void) deselectWithAnimation:(BOOL)animated {
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-}
-
-/*- (CGFloat) tableView:(UITableView *)table heightForRowAtIndexPath:(NSIndexPath *)path {
-    return [PackageCell heightForPackage:[self packageAtIndexPath:path]];
-}*/
-
-- (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 {
@@ -5599,32 +5765,37 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
+#if TryIndexedCollation
     if ([[self class] hasIndexedCollation]) {
         return [[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionForSectionIndexTitleAtIndex:index];
     }
+#endif
 
     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;
+        title_ = [title copy];
+        [[self navigationItem] setTitle:title_];
 
-        target_ = target;
-        action_ = action;
+#if TryIndexedCollation
+        if ([[self class] hasIndexedCollation])
+            index_ = [[[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionIndexTitles] retain]
+        else
+#endif
+            index_ = [[NSMutableArray alloc] initWithCapacity:32];
 
-        index_ = [[self class] hasIndexedCollation]
-            ? [[[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionIndexTitles] retain]
-            : [[NSMutableArray alloc] initWithCapacity:32];
         indices_ = [[NSMutableDictionary alloc] initWithCapacity:32];
 
         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.0f];
-        [self addSubview:list_];
+        [list_ setRowHeight:73];
+        [[self view] addSubview:list_];
 
         [list_ setDataSource:self];
         [list_ setDelegate:self];
@@ -5640,6 +5811,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) reloadData {
+    [super reloadData];
+
     era_ = [database_ era];
     NSArray *packages = [database_ packages];
 
@@ -5656,6 +5829,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     Section *section = nil;
 
+#if TryIndexedCollation
     if ([[self class] hasIndexedCollation]) {
         id collation = [objc_getClass("UILocalizedIndexedCollation") currentCollation];
         NSArray *titles = [collation sectionIndexTitles];
@@ -5686,7 +5860,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 [section addToCount];
             }
         _end
-    } else {
+    } else
+#endif
+    {
         [index_ removeAllObjects];
 
         _profile(PackageTable$reloadData$Section)
@@ -5726,18 +5902,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_;
@@ -5746,11 +5914,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)
@@ -5788,10 +5956,10 @@ 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 setObject:object];
         [self reloadData];
     } return self;
 }
@@ -5799,2055 +5967,2077 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 
-/* Filtered Package Controller {{{ */
-@interface FilteredPackageController : CYViewController {
-    _transient Database *database_;
-    FilteredPackageTable *packages_;
-    NSString *title_;
+/* Home Controller {{{ */
+@interface HomeController : CYBrowserController {
 }
-
-- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
-
 @end
 
-@implementation FilteredPackageController
-
-- (void) dealloc {
-    [packages_ release];
-    [title_ release];
+@implementation HomeController
 
-    [super dealloc];
++ (BOOL) shouldHideNavigationBar {
+    return NO;
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [packages_ deselectWithAnimation:animated];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://home"];
 }
 
-- (void) didSelectPackage:(Package *)package {
-    PackageController *view([delegate_ packageController]);
-    [view setPackage:package];
-    [view setDelegate:delegate_];
-    [[self navigationController] pushViewController:view animated:YES];
-}
+- (void) _setMoreHeaders:(NSMutableURLRequest *)request {
+    [super _setMoreHeaders:request];
 
-- (NSString *) title { return title_; }
+    if (ChipID_ != nil)
+        [request setValue:ChipID_ forHTTPHeaderField:@"X-Chip-ID"];
+    if (UniqueID_ != nil)
+        [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
+    if (PLMN_ != nil)
+        [request setValue:PLMN_ forHTTPHeaderField:@"X-Carrier-ID"];
+}
 
-- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        title_ = [title copy];
-        [[self navigationItem] setTitle:title_];
+- (void) aboutButtonClicked {
+    UIAlertView *alert([[[UIAlertView alloc] init] autorelease]);
 
-        packages_ = [[FilteredPackageTable alloc]
-            initWithFrame:[[self view] bounds]
-            database:database
-            target:self
-            action:@selector(didSelectPackage:)
-            filter:filter
-            with:object
-        ];
+    [alert setTitle:UCLocalize("ABOUT_CYDIA")];
+    [alert addButtonWithTitle:UCLocalize("CLOSE")];
+    [alert setCancelButtonIndex:0];
 
-        [packages_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:packages_];
-    } return self;
-}
+    [alert setMessage:
+        @"Copyright (C) 2008-2011\n"
+        "Jay Freeman (saurik)\n"
+        "saurik@saurik.com\n"
+        "http://www.saurik.com/"
+    ];
 
-- (void) reloadData {
-    [packages_ reloadData];
+    [alert show];
 }
 
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+
+    if ([[self class] shouldHideNavigationBar])
+        [[self navigationController] setNavigationBarHidden:NO animated:animated];
 }
 
-@end
+- (void) viewWillAppear:(BOOL)animated {
+    if (![self hasLoaded])
+        [self loadURL:[NSURL URLWithString:CydiaURL(@"ui/home/")]];
 
-/* }}} */
+    [super viewWillAppear:animated];
 
-/* Source Cell {{{ */
-@interface SourceCell : CYTableViewCell <
-    ContentDelegate
-> {
-    UIImage *icon_;
-    NSString *origin_;
-    NSString *description_;
-    NSString *label_;
+    if ([[self class] shouldHideNavigationBar])
+        [[self navigationController] setNavigationBarHidden:YES animated:animated];
 }
 
-- (void) setSource:(Source *)source;
+- (void) viewDidLoad {
+    [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("ABOUT")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(aboutButtonClicked)
+    ] autorelease]];
+}
 
 @end
-
-@implementation SourceCell
-
-- (void) clearSource {
-    [icon_ release];
-    [origin_ release];
-    [description_ release];
-    [label_ release];
-
-    icon_ = nil;
-    origin_ = nil;
-    description_ = nil;
-    label_ = nil;
+/* }}} */
+/* Manage Controller {{{ */
+@interface ManageController : CYBrowserController {
 }
 
-- (void) setSource:(Source *)source {
-    [self clearSource];
-
-    if (icon_ == nil)
-        icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]];
-    if (icon_ == nil)
-        icon_ = [UIImage applicationImageNamed:@"unknown.png"];
-    icon_ = [icon_ retain];
+- (void) queueStatusDidChange;
+@end
 
-    origin_ = [[source name] retain];
-    label_ = [[source uri] retain];
-    description_ = [[source description] retain];
+@implementation ManageController
 
-    [content_ setNeedsDisplay];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://manage"];
 }
 
-- (void) dealloc {
-    [self clearSource];
-    [super dealloc];
+- (void) viewWillAppear:(BOOL)animated {
+    if (![self hasLoaded])
+        [self loadURL:[NSURL URLWithString:CydiaURL(@"ui/manage/")]];
+
+    [super viewWillAppear:animated];
 }
 
-- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
-    if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) {
-        UIView *content([self contentView]);
-        CGRect bounds([content bounds]);
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("MANAGE")];
 
-        content_ = [[ContentView alloc] initWithFrame:bounds];
-        [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [content_ setBackgroundColor:[UIColor whiteColor]];
-        [content addSubview:content_];
+    [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("SETTINGS")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(settingsButtonClicked)
+    ] autorelease]];
 
-        [content_ setDelegate:self];
-        [content_ setOpaque:YES];
-    } return self;
+    [self queueStatusDidChange];
 }
 
-- (void) drawContentRect:(CGRect)rect {
-    bool highlighted(highlighted_);
-    float width(rect.size.width);
+- (void) settingsButtonClicked {
+    [delegate_ showSettings];
+}
 
-    if (icon_ != nil)
-        [icon_ drawInRect:CGRectMake(10, 10, 30, 30)];
+#if !AlwaysReload
+- (void) queueButtonClicked {
+    [delegate_ queue];
+}
 
-    if (highlighted)
-        UISetColor(White_);
+- (void) applyLoadingTitle {
+    // Disable "Loading" title.
+}
 
-    if (!highlighted)
-        UISetColor(Black_);
-    [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - 80) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation];
+- (void) applyRightButton {
+    // Disable right button.
+}
+#endif
 
-    if (!highlighted)
-        UISetColor(Blue_);
-    [label_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation];
+- (void) queueStatusDidChange {
+#if !AlwaysReload
+    if (!IsWildcat_ && Queuing_) {
+        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("QUEUE")
+            style:UIBarButtonItemStyleDone
+            target:self
+            action:@selector(queueButtonClicked)
+        ] autorelease]];
+    } else {
+        [[self navigationItem] setRightBarButtonItem:nil];
+    }
+#endif
+}
 
-    if (!highlighted)
-        UISetColor(Gray_);
-    [description_ drawAtPoint:CGPointMake(12, 46) forWidth:(width - 40) withFont:Font14_ lineBreakMode:UILineBreakModeTailTruncation];
+- (bool) isLoading {
+    // Never show as loading.
+    return false;
 }
 
 @end
 /* }}} */
-/* Source Table {{{ */
-@interface SourceController : CYViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
-> {
-    _transient Database *database_;
-    UITableView *list_;
-    NSMutableArray *sources_;
-    int offset_;
-
-    NSString *href_;
-    UIProgressHUD *hud_;
-    NSError *error_;
-
-    //NSURLConnection *installer_;
-    NSURLConnection *trivial_;
-    NSURLConnection *trivial_bz2_;
-    NSURLConnection *trivial_gz_;
-    //NSURLConnection *automatic_;
 
-    BOOL cydia_;
+/* Refresh Bar {{{ */
+@interface RefreshBar : UINavigationBar {
+    UIProgressIndicator *indicator_;
+    UITextLabel *prompt_;
+    UIProgressBar *progress_;
+    UINavigationButton *cancel_;
 }
 
-- (id) initWithDatabase:(Database *)database;
-
-- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
-
 @end
 
-@implementation SourceController
-
-- (void) _releaseConnection:(NSURLConnection *)connection {
-    if (connection != nil) {
-        [connection cancel];
-        //[connection setDelegate:nil];
-        [connection release];
-    }
-}
+@implementation RefreshBar
 
 - (void) dealloc {
-    if (href_ != nil)
-        [href_ release];
-    if (hud_ != nil)
-        [hud_ release];
-    if (error_ != nil)
-        [error_ release];
-
-    //[self _releaseConnection:installer_];
-    [self _releaseConnection:trivial_];
-    [self _releaseConnection:trivial_gz_];
-    [self _releaseConnection:trivial_bz2_];
-    //[self _releaseConnection:automatic_];
-
-    [sources_ release];
-    [list_ release];
+    [indicator_ release];
+    [prompt_ release];
+    [progress_ release];
+    [cancel_ release];
     [super dealloc];
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-}
+- (void) positionViews {
+    CGRect frame = [cancel_ frame];
+    frame.size = [cancel_ sizeThatFits:frame.size];
+    frame.origin.x = [self frame].size.width - frame.size.width - 5;
+    frame.origin.y = ([self frame].size.height - frame.size.height) / 2;
+    [cancel_ setFrame:frame];
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
-    return offset_ == 0 ? 1 : 2;
-}
+    CGSize prgsize = {75, 100};
+    CGRect prgrect = {{
+        [self frame].size.width - prgsize.width - 10,
+        ([self frame].size.height - prgsize.height) / 2
+    } , prgsize};
+    [progress_ setFrame:prgrect];
 
-- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
-    switch (section + (offset_ == 0 ? 1 : 0)) {
-        case 0: return UCLocalize("ENTERED_BY_USER");
-        case 1: return UCLocalize("INSTALLED_BY_PACKAGE");
+    CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]);
+    unsigned indoffset = ([self frame].size.height - indsize.height) / 2;
+    CGRect indrect = {{indoffset, indoffset}, indsize};
+    [indicator_ setFrame:indrect];
 
-        _nodefault
-    }
+    CGSize prmsize = {215, indsize.height + 4};
+    CGRect prmrect = {{
+        indoffset * 2 + indsize.width,
+        unsigned([self frame].size.height - prmsize.height) / 2 - 1
+    }, prmsize};
+    [prompt_ setFrame:prmrect];
 }
 
-- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    int count = [sources_ count];
-    switch (section) {
-        case 0: return (offset_ == 0 ? count : offset_);
-        case 1: return count - offset_;
+- (void)setFrame:(CGRect)frame {
+    [super setFrame:frame];
 
-        _nodefault
-    }
+    [self positionViews];
 }
 
-- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath {
-    unsigned idx = 0;
-    switch (indexPath.section) {
-        case 0: idx = indexPath.row; break;
-        case 1: idx = indexPath.row + offset_; break;
+- (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
+    if ((self = [super initWithFrame:frame])) {
+        [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
 
-        _nodefault
-    }
-    return [sources_ objectAtIndex:idx];
-}
+        [self setBarStyle:UIBarStyleBlack];
 
-- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    return [source description] == nil ? 56 : 73;
-}
-
-- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    static NSString *cellIdentifier = @"SourceCell";
+        UIBarStyle barstyle([self _barStyle:NO]);
+        bool ugly(barstyle == UIBarStyleDefault);
 
-    SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
-    if(cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease];
-    [cell setSource:[self sourceAtIndexPath:indexPath]];
+        UIProgressIndicatorStyle style = ugly ?
+            UIProgressIndicatorStyleMediumBrown :
+            UIProgressIndicatorStyleMediumWhite;
 
-    return cell;
-}
+        indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectZero];
+        [indicator_ setStyle:style];
+        [indicator_ startAnimation];
+        [self addSubview:indicator_];
 
-- (UITableViewCellAccessoryType) tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
-    return UITableViewCellAccessoryDisclosureIndicator;
-}
+        prompt_ = [[UITextLabel alloc] initWithFrame:CGRectZero];
+        [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
+        [prompt_ setBackgroundColor:[UIColor clearColor]];
+        [prompt_ setFont:[UIFont systemFontOfSize:15]];
+        [self addSubview:prompt_];
 
-- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
+        progress_ = [[UIProgressBar alloc] initWithFrame:CGRectZero];
+        [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin];
+        [progress_ setStyle:0];
+        [self addSubview:progress_];
 
-    FilteredPackageController *packages = [[[FilteredPackageController alloc]
-        initWithDatabase:database_
-        title:[source label]
-        filter:@selector(isVisibleInSource:)
-        with:source
-    ] autorelease];
+        cancel_ = [[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted];
+        [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+        [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside];
+        [cancel_ setBarStyle:barstyle];
 
-    [packages setDelegate:delegate_];
+        [self positionViews];
+    } return self;
+}
 
-    [[self navigationController] pushViewController:packages animated:YES];
+- (void) cancel {
+    [cancel_ removeFromSuperview];
 }
 
-- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    return [source record] != nil;
+- (void) start {
+    [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
+    [progress_ setProgress:0];
+    [self addSubview:cancel_];
 }
 
-- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    [Sources_ removeObjectForKey:[source key]];
-    [delegate_ syncData];
+- (void) stop {
+    [cancel_ removeFromSuperview];
 }
 
-- (void) complete {
-    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
-        @"deb", @"Type",
-        href_, @"URI",
-        @"./", @"Distribution",
-    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
+- (void) setPrompt:(NSString *)prompt {
+    [prompt_ setText:prompt];
+}
 
-    [delegate_ syncData];
+- (void) setProgress:(float)progress {
+    [progress_ setProgress:progress];
 }
 
-- (NSString *) getWarning {
-    NSString *href(href_);
-    NSRange colon([href rangeOfString:@"://"]);
-    if (colon.location != NSNotFound)
-        href = [href substringFromIndex:(colon.location + 3)];
-    href = [href stringByAddingPercentEscapes];
-    href = [CydiaURL(@"api/repotag/") stringByAppendingString:href];
-    href = [href stringByCachingURLWithCurrentCDN];
+@end
+/* }}} */
 
-    NSURL *url([NSURL URLWithString:href]);
+@class CYNavigationController;
 
-    NSStringEncoding encoding;
-    NSError *error(nil);
+/* Cydia Tab Bar Controller {{{ */
+@interface CYTabBarController : UITabBarController <
+    ProgressDelegate
+> {
+    _transient Database *database_;
+    RefreshBar *refreshbar_;
 
-    if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error])
-        return [warning length] == 0 ? nil : warning;
-    return nil;
+    bool dropped_;
+    bool updating_;
+    // XXX: ok, "updatedelegate_"?...
+    _transient NSObject<CydiaDelegate> *updatedelegate_;
+
+    id root_;
 }
 
-- (void) _endConnection:(NSURLConnection *)connection {
-    // XXX: the memory management in this method is horribly awkward
+- (NSArray *) navigationURLCollection;
+- (void) dropBar:(BOOL)animated;
+- (void) beginUpdate;
+- (void) raiseBar:(BOOL)animated;
+- (BOOL) updating;
 
-    NSURLConnection **field = NULL;
-    if (connection == trivial_)
-        field = &trivial_;
-    else if (connection == trivial_bz2_)
-        field = &trivial_bz2_;
-    else if (connection == trivial_gz_)
-        field = &trivial_gz_;
-    _assert(field != NULL);
-    [connection release];
-    *field = nil;
+@end
 
-    if (
-        trivial_ == nil &&
-        trivial_bz2_ == nil &&
-        trivial_gz_ == nil
-    ) {
-        bool defer(false);
+@implementation CYTabBarController
 
-        if (cydia_) {
-            if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) {
-                defer = true;
+- (NSArray *) navigationURLCollection {
+    NSMutableArray *items([NSMutableArray array]);
 
-                UIAlertView *alert = [[[UIAlertView alloc]
-                    initWithTitle:UCLocalize("SOURCE_WARNING")
-                    message:warning
-                    delegate:self
-                    cancelButtonTitle:UCLocalize("CANCEL")
-                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
-                ] autorelease];
+    // 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];
+    }
 
-                [alert setContext:@"warning"];
-                [alert setNumberOfRows:1];
-                [alert show];
-            } else
-                [self complete];
-        } else if (error_ != nil) {
-            UIAlertView *alert = [[[UIAlertView alloc]
-                initWithTitle:UCLocalize("VERIFICATION_ERROR")
-                message:[error_ localizedDescription]
-                delegate:self
-                cancelButtonTitle:UCLocalize("OK")
-                otherButtonTitles:nil
-            ] autorelease];
+    return items;
+}
 
-            [alert setContext:@"urlerror"];
-            [alert show];
-        } else {
-            UIAlertView *alert = [[[UIAlertView alloc]
-                initWithTitle:UCLocalize("NOT_REPOSITORY")
-                message:UCLocalize("NOT_REPOSITORY_EX")
-                delegate:self
-                cancelButtonTitle:UCLocalize("OK")
-                otherButtonTitles:nil
-            ] autorelease];
+- (void) reloadData {
+    for (CYViewController *controller in [self viewControllers])
+        [controller reloadData];
 
-            [alert setContext:@"trivial"];
-            [alert show];
-        }
+    [(CYNavigationController *)[self transientViewController] reloadData];
+}
 
-        [delegate_ setStatusBarShowsProgress:NO];
-        [delegate_ removeProgressHUD:hud_];
+- (void) dealloc {
+    [refreshbar_ release];
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
 
-        [hud_ autorelease];
-        hud_ = nil;
+    [super dealloc];
+}
 
-        if (!defer) {
-            [href_ release];
-            href_ = nil;
-        }
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
 
-        if (error_ != nil) {
-            [error_ release];
-            error_ = nil;
-        }
-    }
-}
+        [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
 
-- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
-    switch ([response statusCode]) {
-        case 200:
-            cydia_ = YES;
-    }
+        refreshbar_ = [[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self];
+    } return self;
 }
 
-- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
-    lprintf("connection:\"%s\" didFailWithError:\"%s\"", [href_ UTF8String], [[error localizedDescription] UTF8String]);
-    if (error_ != nil)
-        error_ = [error retain];
-    [self _endConnection:connection];
+- (void) setUpdate:(NSDate *)date {
+    [self beginUpdate];
 }
 
-- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
-    [self _endConnection:connection];
+- (void) beginUpdate {
+    [refreshbar_ start];
+    [self dropBar:YES];
+
+    [updatedelegate_ retainNetworkActivityIndicator];
+    updating_ = true;
+
+    [NSThread
+        detachNewThreadSelector:@selector(performUpdate)
+        toTarget:self
+        withObject:nil
+    ];
 }
 
-- (NSString *) title { return UCLocalize("SOURCES"); }
+- (void) performUpdate { _pooled
+    Status status;
+    status.setDelegate(self);
+    [database_ updateWithStatus:status];
 
-- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
-    NSMutableURLRequest *request = [NSMutableURLRequest
-        requestWithURL:[NSURL URLWithString:href]
-        cachePolicy:NSURLRequestUseProtocolCachePolicy
-        timeoutInterval:120.0
+    [self
+        performSelectorOnMainThread:@selector(completeUpdate)
+        withObject:nil
+        waitUntilDone:NO
     ];
+}
 
-    [request setHTTPMethod:method];
+- (void) stopUpdateWithSelector:(SEL)selector {
+    updating_ = false;
+    [updatedelegate_ releaseNetworkActivityIndicator];
 
-    if (Machine_ != NULL)
-        [request setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"];
-    if (UniqueID_ != nil)
-        [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
-    if (Role_ != nil)
-        [request setValue:Role_ forHTTPHeaderField:@"X-Role"];
+    [self raiseBar:YES];
+    [refreshbar_ stop];
 
-    return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
+    [updatedelegate_ performSelector:selector withObject:nil afterDelay:0];
 }
 
-- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
-    NSString *context([alert context]);
+- (void) completeUpdate {
+    if (!updating_)
+        return;
+    [self stopUpdateWithSelector:@selector(reloadData)];
+}
 
-    if ([context isEqualToString:@"source"]) {
-        switch (button) {
-            case 1: {
-                NSString *href = [[alert textField] text];
+- (void) cancelUpdate {
+    [self stopUpdateWithSelector:@selector(updateData)];
+}
 
-                //installer_ = [[self _requestHRef:href method:@"GET"] retain];
+- (void) cancelPressed {
+    [self cancelUpdate];
+}
 
-                if (![href hasSuffix:@"/"])
-                    href_ = [href stringByAppendingString:@"/"];
-                else
-                    href_ = href;
-                href_ = [href_ retain];
+- (BOOL) updating {
+    return updating_;
+}
 
-                trivial_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages"] method:@"HEAD"] retain];
-                trivial_bz2_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.bz2"] method:@"HEAD"] retain];
-                trivial_gz_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.gz"] method:@"HEAD"] retain];
-                //trivial_bz2_ = [[self _requestHRef:[href stringByAppendingString:@"dists/Release"] method:@"HEAD"] retain];
+- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
+    [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]];
+}
 
-                cydia_ = false;
+- (void) startProgress {
+}
 
-                // XXX: this is stupid
-                hud_ = [[delegate_ addProgressHUD] retain];
-                [hud_ setText:UCLocalize("VERIFYING_URL")];
-            } break;
+- (void) setProgressTitle:(NSString *)title {
+    [self
+        performSelectorOnMainThread:@selector(_setProgressTitle:)
+        withObject:title
+        waitUntilDone:YES
+    ];
+}
 
-            case 0:
-            break;
+- (bool) isCancelling:(size_t)received {
+    return !updating_;
+}
 
-            _nodefault
-        }
+- (void) setProgressPercent:(float)percent {
+    [self
+        performSelectorOnMainThread:@selector(_setProgressPercent:)
+        withObject:[NSNumber numberWithFloat:percent]
+        waitUntilDone:YES
+    ];
+}
 
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
-    } else if ([context isEqualToString:@"trivial"])
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
-    else if ([context isEqualToString:@"urlerror"])
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
-    else if ([context isEqualToString:@"warning"]) {
-        switch (button) {
-            case 1:
-                [self complete];
-            break;
+- (void) addProgressOutput:(NSString *)output {
+    [self
+        performSelectorOnMainThread:@selector(_addProgressOutput:)
+        withObject:output
+        waitUntilDone:YES
+    ];
+}
 
-            case 0:
-            break;
+- (void) _setProgressTitle:(NSString *)title {
+    [refreshbar_ setPrompt:title];
+}
 
-            _nodefault
-        }
+- (void) _setProgressPercent:(NSNumber *)percent {
+    [refreshbar_ setProgress:[percent floatValue]];
+}
 
-        [href_ release];
-        href_ = nil;
+- (void) _addProgressOutput:(NSString *)output {
+}
 
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+- (void) setUpdateDelegate:(id)delegate {
+    updatedelegate_ = delegate;
+}
+
+- (CGFloat) statusBarHeight {
+    if (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) {
+        return [[UIApplication sharedApplication] statusBarFrame].size.height;
+    } else {
+        return [[UIApplication sharedApplication] statusBarFrame].size.width;
     }
 }
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("SOURCES")];
-        [self updateButtonsForEditingStatus:NO animated:NO];
+- (UIView *) transitionView {
+    if ([self respondsToSelector:@selector(_transitionView)])
+        return [self _transitionView];
+    else
+        return MSHookIvar<id>(self, "_viewControllerTransitionView");
+}
 
-        database_ = database;
-        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
+- (void) dropBar:(BOOL)animated {
+    if (dropped_)
+        return;
+    dropped_ = true;
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:list_];
+    UIView *transition([self transitionView]);
+    [[self view] addSubview:refreshbar_];
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+    CGRect barframe([refreshbar_ frame]);
 
-        [self reloadData];
-    } return self;
-}
+    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_0) // XXX: _UIApplicationLinkedOnOrAfter(4)
+        barframe.origin.y = [self statusBarHeight];
+    else
+        barframe.origin.y = 0;
 
-- (void) reloadData {
-    pkgSourceList list;
-    if (!list.ReadMainList())
-        return;
+    [refreshbar_ setFrame:barframe];
 
-    [sources_ removeAllObjects];
-    [sources_ addObjectsFromArray:[database_ sources]];
-    _trace();
-    [sources_ sortUsingSelector:@selector(compareByNameAndType:)];
-    _trace();
+    if (animated)
+        [UIView beginAnimations:nil context:NULL];
 
-    int count([sources_ count]);
-    offset_ = 0;
-    for (int i = 0; i != count; i++) {
-        if ([[sources_ objectAtIndex:i] record] == nil)
-            break;
-        offset_++;
-    }
+    CGRect viewframe = [transition frame];
+    viewframe.origin.y += barframe.size.height;
+    viewframe.size.height -= barframe.size.height;
+    [transition setFrame:viewframe];
 
-    [list_ setEditing:NO];
-    [self updateButtonsForEditingStatus:NO animated:NO];
-    [list_ reloadData];
-}
+    if (animated)
+        [UIView commitAnimations];
 
-- (void) addButtonClicked {
-    /*[book_ pushPage:[[[AddSourceController alloc]
-        initWithBook:book_
-        database:database_
-    ] autorelease]];*/
+    // Ensure bar has the proper width for our view, it might have changed
+    barframe.size.width = viewframe.size.width;
+    [refreshbar_ setFrame:barframe];
 
-    UIAlertView *alert = [[[UIAlertView alloc]
-        initWithTitle:UCLocalize("ENTER_APT_URL")
-        message:nil
-        delegate:self
-        cancelButtonTitle:UCLocalize("CANCEL")
-        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
-    ] autorelease];
+    // XXX: fix Apple's layout bug
+    [[root_ selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
+}
 
-    [alert setContext:@"source"];
-    [alert setTransform:CGAffineTransformTranslate([alert transform], 0.0, 100.0)];
+- (void) raiseBar:(BOOL)animated {
+    if (!dropped_)
+        return;
+    dropped_ = false;
 
-    [alert setNumberOfRows:1];
-    [alert addTextFieldWithValue:@"http://" label:@""];
+    UIView *transition([self transitionView]);
+    [refreshbar_ removeFromSuperview];
 
-    UITextInputTraits *traits = [[alert textField] textInputTraits];
-    [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
-    [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
-    [traits setKeyboardType:UIKeyboardTypeURL];
-    // XXX: UIReturnKeyDone
-    [traits setReturnKeyType:UIReturnKeyNext];
+    CGRect barframe([refreshbar_ frame]);
 
-    [alert show];
-}
+    if (animated)
+        [UIView beginAnimations:nil context:NULL];
 
-- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated {
-    [[self navigationItem] setLeftBarButtonItem:(editing ? [[[UIBarButtonItem alloc]
-        initWithTitle:UCLocalize("ADD")
-        style:UIBarButtonItemStylePlain
-        target:self
-        action:@selector(addButtonClicked)
-    ] autorelease] : [[self navigationItem] backBarButtonItem]) animated:animated];
+    CGRect viewframe = [transition frame];
+    viewframe.origin.y -= barframe.size.height;
+    viewframe.size.height += barframe.size.height;
+    [transition setFrame:viewframe];
 
-    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-        initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
-        style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
-        target:self
-        action:@selector(editButtonClicked)
-    ] autorelease] animated:animated];
+    if (animated)
+        [UIView commitAnimations];
 
-    if (IsWildcat_ && !editing)
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
+    // XXX: fix Apple's layout bug
+    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
 }
 
-- (void) settingsButtonClicked {
-    [delegate_ showSettings];
+#if 0
+- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
+    // XXX: fix Apple's layout bug
+    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
 }
+#endif
 
-- (void) editButtonClicked {
-    [list_ setEditing:![list_ isEditing] animated:YES];
+- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
+    bool dropped(dropped_);
 
-    [self updateButtonsForEditingStatus:[list_ isEditing] animated:YES];
+    if (dropped)
+        [self raiseBar:NO];
+
+    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
+
+    if (dropped)
+        [self dropBar:NO];
+
+    // XXX: fix Apple's layout bug
+    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
+}
+
+- (void) statusBarFrameChanged:(NSNotification *)notification {
+    if (dropped_) {
+        [self raiseBar:NO];
+        [self dropBar:NO];
+    }
 }
 
 @end
 /* }}} */
-
-/* Installed Controller {{{ */
-@interface InstalledController : FilteredPackageController {
-    BOOL expert_;
+/* Cydia Navigation Controller {{{ */
+@interface CYNavigationController : UINavigationController {
+    _transient Database *database_;
+    _transient id<UINavigationControllerDelegate> delegate_;
 }
 
+- (NSArray *) navigationURLCollection;
 - (id) initWithDatabase:(Database *)database;
-
-- (void) updateRoleButton;
-- (void) queueStatusDidChange;
+- (void) reloadData;
 
 @end
 
-@implementation InstalledController
+
+@implementation CYNavigationController
 
 - (void) dealloc {
     [super dealloc];
 }
 
-- (NSString *) title { return UCLocalize("INSTALLED"); }
+- (NSArray *) navigationURLCollection {
+    NSMutableArray *stack([NSMutableArray array]);
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) {
-        [self updateRoleButton];
-        [self queueStatusDidChange];
-    } return self;
-}
-
-#if !AlwaysReload
-- (void) queueButtonClicked {
-    [delegate_ queue];
-}
-#endif
-
-- (void) queueStatusDidChange {
-#if !AlwaysReload
-    if (IsWildcat_) {
-        if (Queuing_) {
-            [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-                initWithTitle:UCLocalize("QUEUE")
-                style:UIBarButtonItemStyleDone
-                target:self
-                action:@selector(queueButtonClicked)
-            ] autorelease]];
-        } else {
-            [[self navigationItem] setLeftBarButtonItem:nil];
-        }
-    }
-#endif
+    for (CYViewController *controller in [self viewControllers]) {
+        NSString *url = [[controller navigationURL] absoluteString];
+        if (url != nil)
+            [stack addObject:url];
+    }
+
+    return stack;
 }
 
 - (void) reloadData {
-    [packages_ reloadData];
-}
-
-- (void) updateRoleButton {
-    if (Role_ != nil && ![Role_ isEqualToString:@"Developer"])
-        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE"))
-            style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
-            target:self
-            action:@selector(roleButtonClicked)
-        ] autorelease]];
+    for (CYViewController *page in [self viewControllers]) {
+        if ([page hasLoaded])
+            [page reloadData];
+    }
 }
 
-- (void) roleButtonClicked {
-    [packages_ setObject:[NSNumber numberWithBool:expert_]];
-    [packages_ reloadData];
-    expert_ = !expert_;
-
-    [self updateRoleButton];
+- (void) setDelegate:(id<UINavigationControllerDelegate>)delegate {
+    delegate_ = delegate;
 }
 
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+    } return self;
 }
 
 @end
 /* }}} */
 
-/* Home Controller {{{ */
-@interface HomeController : CYBrowserController {
+/* Cydia:// Protocol {{{ */
+@interface CydiaURLProtocol : NSURLProtocol {
 }
 
 @end
 
-@implementation HomeController
+@implementation CydiaURLProtocol
 
-- (void) _setMoreHeaders:(NSMutableURLRequest *)request {
-    [super _setMoreHeaders:request];
++ (BOOL) canInitWithRequest:(NSURLRequest *)request {
+    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 (ChipID_ != nil)
-        [request setValue:ChipID_ forHTTPHeaderField:@"X-Chip-ID"];
-    if (UniqueID_ != nil)
-        [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
-    if (PLMN_ != nil)
-        [request setValue:PLMN_ forHTTPHeaderField:@"X-Carrier-ID"];
++ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
+    return request;
 }
 
-- (void) aboutButtonClicked {
-    UIAlertView *alert([[[UIAlertView alloc] init] autorelease]);
+- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request {
+    id<NSURLProtocolClient> client([self client]);
+    if (icon == nil)
+        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
+    else {
+        NSData *data(UIImagePNGRepresentation(icon));
 
-    [alert setTitle:UCLocalize("ABOUT_CYDIA")];
-    [alert addButtonWithTitle:UCLocalize("CLOSE")];
-    [alert setCancelButtonIndex:0];
+        NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
+        [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+        [client URLProtocol:self didLoadData:data];
+        [client URLProtocolDidFinishLoading:self];
+    }
+}
 
-    [alert setMessage:
-        @"Copyright (C) 2008-2010\n"
-        "Jay Freeman (saurik)\n"
-        "saurik@saurik.com\n"
-        "http://www.saurik.com/"
-    ];
+- (void) startLoading {
+    id<NSURLProtocolClient> client([self client]);
+    NSURLRequest *request([self request]);
 
-    [alert show];
-}
+    NSURL *url([request URL]);
+    NSString *href([url absoluteString]);
 
-- (void) viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
-    //[[self navigationController] setNavigationBarHidden:YES animated:animated];
-}
+    NSString *path([href substringFromIndex:8]);
+    NSRange slash([path rangeOfString:@"/"]);
 
-- (void) viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
-    //[[self navigationController] setNavigationBarHidden:NO animated:animated];
+    NSString *command;
+    if (slash.location == NSNotFound) {
+        command = path;
+        path = nil;
+    } else {
+        command = [path substringToIndex:slash.location];
+        path = [path substringFromIndex:(slash.location + 1)];
+    }
+
+    Database *database([Database sharedInstance]);
+
+    if ([command isEqualToString:@"package-icon"]) {
+        if (path == nil)
+            goto fail;
+        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        Package *package([database packageWithName:path]);
+        if (package == nil)
+            goto fail;
+        UIImage *icon([package icon]);
+        [self _returnPNGWithImage:icon forRequest:request];
+    } else if ([command isEqualToString:@"source-icon"]) {
+        if (path == nil)
+            goto fail;
+        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        NSString *source(Simplify(path));
+        UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sources/%@.png", App_, source]]);
+        if (icon == nil)
+            icon = [UIImage applicationImageNamed:@"unknown.png"];
+        [self _returnPNGWithImage:icon forRequest:request];
+    } else if ([command isEqualToString:@"uikit-image"]) {
+        if (path == nil)
+            goto fail;
+        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        UIImage *icon(_UIImageWithName(path));
+        [self _returnPNGWithImage:icon forRequest:request];
+    } else if ([command isEqualToString:@"section-icon"]) {
+        if (path == nil)
+            goto fail;
+        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        NSString *section(Simplify(path));
+        UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]]);
+        if (icon == nil)
+            icon = [UIImage applicationImageNamed:@"unknown.png"];
+        [self _returnPNGWithImage:icon forRequest:request];
+    } else fail: {
+        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
+    }
 }
 
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("ABOUT")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(aboutButtonClicked)
-        ] autorelease]];
-    } return self;
+- (void) stopLoading {
 }
 
 @end
 /* }}} */
-/* Manage Controller {{{ */
-@interface ManageController : CYBrowserController {
+
+/* Section Controller {{{ */
+@interface SectionController : FilteredPackageListController {
+    NSString *section_;
 }
 
-- (void) queueStatusDidChange;
+- (id) initWithDatabase:(Database *)database section:(NSString *)section;
+
 @end
 
-@implementation ManageController
+@implementation SectionController
 
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("MANAGE")];
+- (NSURL *) navigationURL {
+    NSString *name = section_;
+    if (name == nil)
+        name = @"all";
 
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sections/%@", name]];
+}
 
-        [self queueStatusDidChange];
+- (id) initWithDatabase:(Database *)database section:(NSString *)name {
+    NSString *title;
+
+    if (name == nil) {
+        title = UCLocalize("ALL_PACKAGES");
+    } else if (![name isEqual:@""]) {
+        title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"];
+    } else {
+        title = UCLocalize("NO_SECTION");
+    }
+
+    section_ = name;
+
+    if ((self = [super initWithDatabase:database title:title filter:@selector(isVisibleInSection:) with:name]) != nil) {
     } return self;
 }
 
-- (void) settingsButtonClicked {
-    [delegate_ showSettings];
+@end
+/* }}} */
+/* Sections Controller {{{ */
+@interface SectionsController : CYViewController <
+    UITableViewDataSource,
+    UITableViewDelegate
+> {
+    _transient Database *database_;
+    NSMutableArray *sections_;
+    NSMutableArray *filtered_;
+    UITableView *list_;
+    BOOL editing_;
 }
 
-#if !AlwaysReload
-- (void) queueButtonClicked {
-    [delegate_ queue];
-}
+- (id) initWithDatabase:(Database *)database;
+- (void) editButtonClicked;
 
-- (void) applyLoadingTitle {
-    // No "Loading" title.
+@end
+
+@implementation SectionsController
+
+- (void) dealloc {
+    [self releaseSubviews];
+    [sections_ release];
+    [filtered_ release];
+
+    [super dealloc];
 }
 
-- (void) applyRightButton {
-    // No right button.
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://sections"];
 }
-#endif
 
-- (void) queueStatusDidChange {
-#if !AlwaysReload
-    if (!IsWildcat_ && Queuing_) {
-        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("QUEUE")
-            style:UIBarButtonItemStyleDone
-            target:self
-            action:@selector(queueButtonClicked)
-        ] autorelease]];
-    } else {
+- (void) updateNavigationItem {
+    [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")];
+    if ([sections_ count] == 0) {
         [[self navigationItem] setRightBarButtonItem:nil];
+    } else {
+        [[self navigationItem] setRightBarButtonItem:[[UIBarButtonItem alloc]
+            initWithBarButtonSystemItem:(editing_ ? UIBarButtonSystemItemDone : UIBarButtonSystemItemEdit)
+            target:self
+            action:@selector(editButtonClicked)
+        ] animated:([[self navigationItem] rightBarButtonItem] != nil)];
     }
-#endif
 }
 
-- (bool) isLoading {
-    return false;
+- (BOOL) isEditing {
+    return editing_;
 }
 
-@end
-/* }}} */
+- (void) setEditing:(BOOL)editing {
+    if ((editing_ = editing))
+        [list_ reloadData];
+    else
+        [delegate_ updateData];
 
-/* Refresh Bar {{{ */
-@interface RefreshBar : UINavigationBar {
-    UIProgressIndicator *indicator_;
-    UITextLabel *prompt_;
-    UIProgressBar *progress_;
-    UINavigationButton *cancel_;
+    [self updateNavigationItem];
 }
 
-@end
-
-@implementation RefreshBar
-
-- (void) dealloc {
-    [indicator_ release];
-    [prompt_ release];
-    [progress_ release];
-    [cancel_ release];
-    [super dealloc];
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
 }
 
-- (void) positionViews {
-    CGRect frame = [cancel_ frame];
-    frame.size = [cancel_ sizeThatFits:frame.size];
-    frame.origin.x = [self frame].size.width - frame.size.width - 5;
-    frame.origin.y = ([self frame].size.height - frame.size.height) / 2;
-    [cancel_ setFrame:frame];
-
-    CGSize prgsize = {75, 100};
-    CGRect prgrect = {{
-        [self frame].size.width - prgsize.width - 10,
-        ([self frame].size.height - prgsize.height) / 2
-    } , prgsize};
-    [progress_ setFrame:prgrect];
-
-    CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]);
-    unsigned indoffset = ([self frame].size.height - indsize.height) / 2;
-    CGRect indrect = {{indoffset, indoffset}, indsize};
-    [indicator_ setFrame:indrect];
-
-    CGSize prmsize = {215, indsize.height + 4};
-    CGRect prmrect = {{
-        indoffset * 2 + indsize.width,
-        unsigned([self frame].size.height - prmsize.height) / 2 - 1
-    }, prmsize};
-    [prompt_ setFrame:prmrect];
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    if (editing_) [self setEditing:NO];
 }
 
-- (void)setFrame:(CGRect)frame {
-    [super setFrame:frame];
+- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath {
+    Section *section = (editing_ ? [sections_ objectAtIndex:[indexPath row]] : ([indexPath row] == 0 ? nil : [filtered_ objectAtIndex:([indexPath row] - 1)]));
+    return section;
+}
 
-    [self positionViews];
+- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    return editing_ ? [sections_ count] : [filtered_ count] + 1;
 }
 
-- (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
-    if ((self = [super initWithFrame:frame])) {
-        [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
+/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+    return 45.0f;
+}*/
 
-        [self setBarStyle:UIBarStyleBlack];
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    static NSString *reuseIdentifier = @"SectionCell";
 
-        UIBarStyle barstyle([self _barStyle:NO]);
-        bool ugly(barstyle == UIBarStyleDefault);
+    SectionCell *cell = (SectionCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
+    if (cell == nil)
+        cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
 
-        UIProgressIndicatorStyle style = ugly ?
-            UIProgressIndicatorStyleMediumBrown :
-            UIProgressIndicatorStyleMediumWhite;
+    [cell setSection:[self sectionAtIndexPath:indexPath] editing:editing_];
 
-        indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectZero];
-        [indicator_ setStyle:style];
-        [indicator_ startAnimation];
-        [self addSubview:indicator_];
+    return cell;
+}
 
-        prompt_ = [[UITextLabel alloc] initWithFrame:CGRectZero];
-        [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
-        [prompt_ setBackgroundColor:[UIColor clearColor]];
-        [prompt_ setFont:[UIFont systemFontOfSize:15]];
-        [self addSubview:prompt_];
+- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (editing_)
+        return;
 
-        progress_ = [[UIProgressBar alloc] initWithFrame:CGRectZero];
-        [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin];
-        [progress_ setStyle:0];
-        [self addSubview:progress_];
+    Section *section = [self sectionAtIndexPath:indexPath];
 
-        cancel_ = [[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted];
-        [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside];
-        [cancel_ setBarStyle:barstyle];
+    SectionController *controller = [[[SectionController alloc]
+        initWithDatabase:database_
+        section:[section name]
+    ] autorelease];
+    [controller setDelegate:delegate_];
 
-        [self positionViews];
-    } return self;
+    [[self navigationController] pushViewController:controller animated:YES];
 }
 
-- (void) cancel {
-    [cancel_ removeFromSuperview];
-}
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-- (void) start {
-    [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
-    [progress_ setProgress:0];
-    [self addSubview:cancel_];
+    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) stop {
-    [cancel_ removeFromSuperview];
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
 }
 
-- (void) setPrompt:(NSString *)prompt {
-    [prompt_ setText:prompt];
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
 }
 
-- (void) setProgress:(float)progress {
-    [progress_ setProgress:progress];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+        filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    } return self;
 }
 
-@end
-/* }}} */
+- (void) reloadData {
+    [super reloadData];
 
-@class CYNavigationController;
+    NSArray *packages = [database_ packages];
 
-/* Cydia Tab Bar Controller {{{ */
-@interface CYTabBarController : UITabBarController <
-    ProgressDelegate
-> {
-    _transient Database *database_;
-    RefreshBar *refreshbar_;
+    [sections_ removeAllObjects];
+    [filtered_ removeAllObjects];
 
-    bool dropped_;
-    bool updating_;
-    // XXX: ok, "updatedelegate_"?...
-    _transient NSObject<CydiaDelegate> *updatedelegate_;
+    NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]);
 
-    id root_;
-}
+    _trace();
+    for (Package *package in packages) {
+        NSString *name([package section]);
+        NSString *key(name == nil ? @"" : name);
 
-- (void) dropBar:(BOOL)animated;
-- (void) beginUpdate;
-- (void) raiseBar:(BOOL)animated;
-- (BOOL) updating;
+        Section *section;
 
-@end
+        _profile(SectionsView$reloadData$Section)
+            section = [sections objectForKey:key];
+            if (section == nil) {
+                _profile(SectionsView$reloadData$Section$Allocate)
+                    section = [[[Section alloc] initWithName:name localize:YES] autorelease];
+                    [sections setObject:section forKey:key];
+                _end
+            }
+        _end
 
-@implementation CYTabBarController
+        [section addToCount];
 
-/* XXX: some logic should probably go here related to
-freeing the view controllers on tab change */
+        _profile(SectionsView$reloadData$Filter)
+            if (![package valid] || ![package visible])
+                continue;
+        _end
 
-- (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];
+        [section addToRow];
     }
-}
+    _trace();
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        database_ = database;
+    [sections_ addObjectsFromArray:[sections allValues]];
 
-        [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
+    [sections_ sortUsingSelector:@selector(compareByLocalized:)];
 
-        refreshbar_ = [[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self];
-    } return self;
-}
+    for (Section *section in sections_) {
+        size_t count([section row]);
+        if (count == 0)
+            continue;
 
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    return IsWildcat_ || orientation == UIInterfaceOrientationPortrait;
-}
+        section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease];
+        [section setCount:count];
+        [filtered_ addObject:section];
+    }
 
-- (void) setUpdate:(NSDate *)date {
-    [self beginUpdate];
+    [self updateNavigationItem];
+    [list_ reloadData];
+    _trace();
 }
 
-- (void) beginUpdate {
-    [refreshbar_ start];
-    [self dropBar:YES];
+- (void)editButtonClicked {
+    [self setEditing:!editing_];
+}
 
-    [updatedelegate_ retainNetworkActivityIndicator];
-    updating_ = true;
+@end
+/* }}} */
 
-    [NSThread
-        detachNewThreadSelector:@selector(performUpdate)
-        toTarget:self
-        withObject:nil
-    ];
+/* Changes Controller {{{ */
+@interface ChangesController : CYViewController <
+    UITableViewDataSource,
+    UITableViewDelegate
+> {
+    _transient Database *database_;
+    unsigned era_;
+    CFMutableArrayRef packages_;
+    NSMutableArray *sections_;
+    UITableView *list_;
+    unsigned upgrades_;
+    BOOL hasSentFirstLoad_;
 }
 
-- (void) performUpdate { _pooled
-    Status status;
-    status.setDelegate(self);
-    [database_ updateWithStatus:status];
+- (id) initWithDatabase:(Database *)database;
 
-    [self
-        performSelectorOnMainThread:@selector(completeUpdate)
-        withObject:nil
-        waitUntilDone:NO
-    ];
-}
+@end
 
-- (void) stopUpdateWithSelector:(SEL)selector {
-    updating_ = false;
-    [updatedelegate_ releaseNetworkActivityIndicator];
+@implementation ChangesController
 
-    [self raiseBar:YES];
-    [refreshbar_ stop];
+- (void) dealloc {
+    [self releaseSubviews];
+    CFRelease(packages_);
+    [sections_ release];
 
-    [updatedelegate_ performSelector:selector withObject:nil afterDelay:0];
+    [super dealloc];
 }
 
-- (void) completeUpdate {
-    if (!updating_)
-        return;
-    [self stopUpdateWithSelector:@selector(reloadData)];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://changes"];
 }
 
-- (void) cancelUpdate {
-    [self stopUpdateWithSelector:@selector(updateData)];
+- (void) viewWillAppear:(BOOL)animated {
+    // Loads after it appears, so don't load beforehand.
+    loaded_ = YES;
+    [super viewWillAppear:animated];
 }
 
-- (void) cancelPressed {
-    [self cancelUpdate];
-}
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
 
-- (BOOL) updating {
-    return updating_;
+    if (!hasSentFirstLoad_) {
+        hasSentFirstLoad_ = YES;
+        [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
+    } else {
+        [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+    }
 }
 
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]];
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
+    NSInteger count([sections_ count]);
+    return count == 0 ? 1 : count;
 }
 
-- (void) startProgress {
+- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
+    if ([sections_ count] == 0)
+        return nil;
+    return [[sections_ objectAtIndex:section] name];
 }
 
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
+- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section {
+    if ([sections_ count] == 0)
+        return 0;
+    return [[sections_ objectAtIndex:section] count];
 }
 
-- (bool) isCancelling:(size_t)received {
-    return !updating_;
+- (Package *) packageAtIndex:(NSUInteger)index {
+    return (Package *) CFArrayGetValueAtIndex(packages_, index);
 }
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
-}
+- (Package *) packageAtIndexPath:(NSIndexPath *)path {
+@synchronized (database_) {
+    if ([database_ era] != era_)
+        return nil;
 
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
-}
+    NSUInteger sectionIndex([path section]);
+    if (sectionIndex >= [sections_ count])
+        return nil;
+    Section *section([sections_ objectAtIndex:sectionIndex]);
+    NSInteger row([path row]);
+    return [[[self packageAtIndex:([section row] + row)] retain] autorelease];
+} }
 
-- (void) _setProgressTitle:(NSString *)title {
-    [refreshbar_ setPrompt:title];
+- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path {
+    PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]);
+    if (cell == nil)
+        cell = [[[PackageCell alloc] init] autorelease];
+    [cell setPackage:[self packageAtIndexPath:path]];
+    return cell;
 }
 
-- (void) _setProgressPercent:(NSNumber *)percent {
-    [refreshbar_ setProgress:[percent floatValue]];
+- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
+    Package *package([self packageAtIndexPath:path]);
+    CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]);
+    [view setDelegate:delegate_];
+    [view setPackage:package];
+    [[self navigationController] pushViewController:view animated:YES];
+    return path;
 }
 
-- (void) _addProgressOutput:(NSString *)output {
+- (void) refreshButtonClicked {
+    [delegate_ beginUpdate];
+    [[self navigationItem] setLeftBarButtonItem:nil animated:YES];
 }
 
-- (void) setUpdateDelegate:(id)delegate {
-    updatedelegate_ = delegate;
+- (void) upgradeButtonClicked {
+    [delegate_ distUpgrade];
 }
 
-- (CGFloat) statusBarHeight {
-    if (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) {
-        return [[UIApplication sharedApplication] statusBarFrame].size.height;
-    } else {
-        return [[UIApplication sharedApplication] statusBarFrame].size.width;
-    }
+- (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_];
 }
 
-- (UIView *) transitionView {
-    if ([self respondsToSelector:@selector(_transitionView)])
-        return [self _transitionView];
-    else
-        return MSHookIvar<id>(self, "_viewControllerTransitionView");
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("CHANGES")];
 }
 
-- (void) dropBar:(BOOL)animated {
-    if (dropped_)
-        return;
-    dropped_ = true;
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
 
-    UIView *transition([self transitionView]);
-    [[self view] addSubview:refreshbar_];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
 
-    CGRect barframe([refreshbar_ frame]);
+        packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    } return self;
+}
 
-    if (false) // XXX: _UIApplicationLinkedOnOrAfter(4)
-        barframe.origin.y = [self statusBarHeight];
-    else
-        barframe.origin.y = 0;
+- (void) _reloadPackages:(NSArray *)packages {
+    CFRelease(packages_);
+    packages_ = CFArrayCreateMutable(kCFAllocatorDefault, [packages count], NULL);
 
-    [refreshbar_ setFrame:barframe];
+    _trace();
+    _profile(ChangesController$_reloadPackages$Filter)
+        for (Package *package in packages)
+            if ([package upgradableAndEssential:YES] || [package visible])
+                CFArrayAppendValue(packages_, package);
+    _end
+    _trace();
+    _profile(ChangesController$_reloadPackages$radixSort)
+        [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackageChangesRadix) withContext:NULL];
+    _end
+    _trace();
+}
 
-    if (animated)
-        [UIView beginAnimations:nil context:NULL];
+- (void) reloadData {
+@synchronized (database_) {
+    era_ = [database_ era];
+    NSArray *packages = [database_ packages];
 
-    CGRect viewframe = [transition frame];
-    viewframe.origin.y += barframe.size.height;
-    viewframe.size.height -= barframe.size.height;
-    [transition setFrame:viewframe];
+    [sections_ removeAllObjects];
 
-    if (animated)
-        [UIView commitAnimations];
+#if 1
+    UIProgressHUD *hud([delegate_ addProgressHUD]);
+    [hud setText:UCLocalize("LOADING")];
+    //NSLog(@"HUD:%@::%@", delegate_, hud);
+    [self yieldToSelector:@selector(_reloadPackages:) withObject:packages];
+    [delegate_ removeProgressHUD:hud];
+#else
+    [self _reloadPackages:packages];
+#endif
 
-    // Ensure bar has the proper width for our view, it might have changed
-    barframe.size.width = viewframe.size.width;
-    [refreshbar_ setFrame:barframe];
+    Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
+    Section *ignored = nil;
+    Section *section = nil;
+    time_t last = 0;
 
-    // XXX: fix Apple's layout bug
-    [[root_ selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
+    upgrades_ = 0;
+    bool unseens = false;
 
-- (void) raiseBar:(BOOL)animated {
-    if (!dropped_)
-        return;
-    dropped_ = false;
+    CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
 
-    UIView *transition([self transitionView]);
-    [refreshbar_ removeFromSuperview];
+    for (size_t offset = 0, count = CFArrayGetCount(packages_); offset != count; ++offset) {
+        Package *package = [self packageAtIndex:offset];
 
-    CGRect barframe([refreshbar_ frame]);
+        BOOL uae = [package upgradableAndEssential:YES];
 
-    if (animated)
-        [UIView beginAnimations:nil context:NULL];
+        if (!uae) {
+            unseens = true;
+            time_t seen([package seen]);
 
-    CGRect viewframe = [transition frame];
-    viewframe.origin.y -= barframe.size.height;
-    viewframe.size.height += barframe.size.height;
-    [transition setFrame:viewframe];
+            if (section == nil || last != seen) {
+                last = seen;
 
-    if (animated)
-        [UIView commitAnimations];
+                NSString *name;
+                name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]);
+                [name autorelease];
 
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
+                _profile(ChangesController$reloadData$Allocate)
+                    name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name];
+                    section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease];
+                    [sections_ addObject:section];
+                _end
+            }
 
-#if 0
-- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
-#endif
+            [section addToCount];
+        } else if ([package ignored]) {
+            if (ignored == nil) {
+                ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease];
+            }
+            [ignored addToCount];
+        } else {
+            ++upgrades_;
+            [upgradable addToCount];
+        }
+    }
+    _trace();
 
-- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
-    bool dropped(dropped_);
+    CFRelease(formatter);
 
-    if (dropped)
-        [self raiseBar:NO];
+    if (unseens) {
+        Section *last = [sections_ lastObject];
+        size_t count = [last count];
+        CFArrayReplaceValues(packages_, CFRangeMake(CFArrayGetCount(packages_) - count, count), NULL, 0);
+        [sections_ removeLastObject];
+    }
 
-    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
+    if ([ignored count] != 0)
+        [sections_ insertObject:ignored atIndex:0];
+    if (upgrades_ != 0)
+        [sections_ insertObject:upgradable atIndex:0];
 
-    if (dropped)
-        [self dropBar:NO];
+    [list_ reloadData];
 
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
+    if (upgrades_ > 0)
+        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", upgrades_]]
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(upgradeButtonClicked)
+        ] autorelease]];
 
-- (void) statusBarFrameChanged:(NSNotification *)notification {
-    if (dropped_) {
-        [self raiseBar:NO];
-        [self dropBar:NO];
-    }
-}
+    if (![delegate_ updating])
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("REFRESH")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(refreshButtonClicked)
+        ] autorelease]];
 
-- (void) dealloc {
-    [refreshbar_ release];
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
-    [super dealloc];
-}
+    PrintTimes();
+} }
 
 @end
 /* }}} */
-
-/* Cydia Navigation Controller {{{ */
-@interface CYNavigationController : UINavigationController {
-    _transient Database *database_;
-    _transient id<UINavigationControllerDelegate> delegate_;
+/* Search Controller {{{ */
+@interface SearchController : FilteredPackageListController <
+    UISearchBarDelegate
+> {
+    UISearchBar *search_;
+    BOOL searchloaded_;
 }
 
 - (id) initWithDatabase:(Database *)database;
+- (void) setSearchTerm:(NSString *)term;
 - (void) reloadData;
 
 @end
 
-
-@implementation CYNavigationController
-
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    // Inherit autorotation settings for modal parents.
-    if ([self parentViewController] && [[self parentViewController] modalViewController] == self) {
-        return [[self parentViewController] shouldAutorotateToInterfaceOrientation:orientation];
-    } else if ([self parentViewController]) {
-        return [[self parentViewController] shouldAutorotateToInterfaceOrientation:orientation];
-    } else {
-        return [super shouldAutorotateToInterfaceOrientation:orientation];
-    }
-}
+@implementation SearchController
 
 - (void) dealloc {
+    [search_ release];
     [super dealloc];
 }
 
-- (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];
-    }
-}
-
-- (void) setDelegate:(id<UINavigationControllerDelegate>)delegate {
-    delegate_ = delegate;
-}
-
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-    } return self;
+- (NSURL *) navigationURL {
+    if ([search_ text] == nil || [[search_ text] isEqualToString:@""])
+        return [NSURL URLWithString:@"cydia://search"];
+    else
+        return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://search/%@", [search_ text]]];
 }
 
-@end
-/* }}} */
-/* Cydia:// Protocol {{{ */
-@interface CydiaURLProtocol : NSURLProtocol {
+- (void) setSearchTerm:(NSString *)searchTerm {
+    [search_ setText:searchTerm];
+    [self reloadData];
 }
 
-@end
-
-@implementation CydiaURLProtocol
-
-+ (BOOL) canInitWithRequest:(NSURLRequest *)request {
-    NSURL *url([request URL]);
-    if (url == nil)
-        return NO;
-    NSString *scheme([[url scheme] lowercaseString]);
-    if (scheme == nil || ![scheme isEqualToString:@"cydia"])
-        return NO;
-    return YES;
+- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
+    [self setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
+    [search_ resignFirstResponder];
+    [self reloadData];
 }
 
-+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
-    return request;
+- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
+    [self setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
+    [self reloadData];
 }
 
-- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request {
-    id<NSURLProtocolClient> client([self client]);
-    if (icon == nil)
-        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
-    else {
-        NSData *data(UIImagePNGRepresentation(icon));
-
-        NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
-        [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
-        [client URLProtocol:self didLoadData:data];
-        [client URLProtocolDidFinishLoading:self];
-    }
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil])) {
+        search_ = [[UISearchBar alloc] init];
+    } return self;
 }
 
-- (void) startLoading {
-    id<NSURLProtocolClient> client([self client]);
-    NSURLRequest *request([self request]);
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
 
-    NSURL *url([request URL]);
-    NSString *href([url absoluteString]);
+    if (!searchloaded_) {
+        searchloaded_ = YES;
+        [search_ setFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
+        [search_ layoutSubviews];
+        [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
 
-    NSString *path([href substringFromIndex:8]);
-    NSRange slash([path rangeOfString:@"/"]);
+        UITextField *textField;
+        if ([search_ respondsToSelector:@selector(searchField)])
+            textField = [search_ searchField];
+        else
+            textField = MSHookIvar<UITextField *>(search_, "_searchField");
 
-    NSString *command;
-    if (slash.location == NSNotFound) {
-        command = path;
-        path = nil;
-    } else {
-        command = [path substringToIndex:slash.location];
-        path = [path substringFromIndex:(slash.location + 1)];
+        [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+        [search_ setDelegate:self];
+        [textField setEnablesReturnKeyAutomatically:NO];
+        [[self navigationItem] setTitleView:textField];
     }
+}
 
-    Database *database([Database sharedInstance]);
-
-    if ([command isEqualToString:@"package-icon"]) {
-        if (path == nil)
-            goto fail;
-        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        Package *package([database packageWithName:path]);
-        if (package == nil)
-            goto fail;
-        UIImage *icon([package icon]);
-        [self _returnPNGWithImage:icon forRequest:request];
-    } else if ([command isEqualToString:@"source-icon"]) {
-        if (path == nil)
-            goto fail;
-        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        NSString *source(Simplify(path));
-        UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sources/%@.png", App_, source]]);
-        if (icon == nil)
-            icon = [UIImage applicationImageNamed:@"unknown.png"];
-        [self _returnPNGWithImage:icon forRequest:request];
-    } else if ([command isEqualToString:@"uikit-image"]) {
-        if (path == nil)
-            goto fail;
-        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        UIImage *icon(_UIImageWithName(path));
-        [self _returnPNGWithImage:icon forRequest:request];
-    } else if ([command isEqualToString:@"section-icon"]) {
-        if (path == nil)
-            goto fail;
-        path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        NSString *section(Simplify(path));
-        UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]]);
-        if (icon == nil)
-            icon = [UIImage applicationImageNamed:@"unknown.png"];
-        [self _returnPNGWithImage:icon forRequest:request];
-    } else fail: {
-        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
-    }
+- (void) reloadData {
+    [self setObject:[search_ text]];
+    [super reloadData];
+    [self resetCursor];
 }
 
-- (void) stopLoading {
+- (void) didSelectPackage:(Package *)package {
+    [search_ resignFirstResponder];
+    [super didSelectPackage:package];
 }
 
 @end
 /* }}} */
-
-/* Sections Controller {{{ */
-@interface CYSectionsController : CYViewController <
+/* Package Settings Controller {{{ */
+@interface PackageSettingsController : CYViewController <
     UITableViewDataSource,
     UITableViewDelegate
 > {
     _transient Database *database_;
-    NSMutableArray *sections_;
-    NSMutableArray *filtered_;
-    UITableView *list_;
-    UIView *accessory_;
-    BOOL editing_;
+    NSString *name_;
+    Package *package_;
+    UITableView *table_;
+    UISwitch *subscribedSwitch_;
+    UISwitch *ignoredSwitch_;
+    UITableViewCell *subscribedCell_;
+    UITableViewCell *ignoredCell_;
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
-- (void) resetView;
-
-- (void) editButtonClicked;
+- (id) initWithDatabase:(Database *)database package:(NSString *)package;
 
 @end
 
-@implementation CYSectionsController
+@implementation PackageSettingsController
 
 - (void) dealloc {
-    [list_ setDataSource:nil];
-    [list_ setDelegate:nil];
+    [self releaseSubviews];
+    [name_ release];
+    [package_ release];
 
-    [sections_ release];
-    [filtered_ release];
-    [list_ release];
-    [accessory_ release];
     [super dealloc];
 }
 
-- (void) setEditing:(BOOL)editing {
-    if ((editing_ = editing))
-        [list_ reloadData];
-    else
-        [delegate_ updateData];
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/settings", [package_ id]]];
+}
 
-    [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")];
-    [[[self navigationItem] rightBarButtonItem] setTitle:[sections_ count] == 0 ? nil : editing_ ? UCLocalize("DONE") : UCLocalize("EDIT")];
-    [[[self navigationItem] rightBarButtonItem] setStyle:editing_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain];
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
+    if (package_ == nil)
+        return 0;
+
+    return 1;
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    if (package_ == nil)
+        return 0;
+
+    return 2;
 }
 
-- (void) viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
-    if (editing_) [self setEditing:NO];
+- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    return UCLocalize("CHANGE_PACKAGE_SETTINGS");
 }
 
-- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath {
-    Section *section = (editing_ ? [sections_ objectAtIndex:[indexPath row]] : ([indexPath row] == 0 ? nil : [filtered_ objectAtIndex:([indexPath row] - 1)]));
-    return section;
+- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
+    return UCLocalize("SHOW_ALL_CHANGES_EX");
 }
 
-- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    return editing_ ? [sections_ count] : [filtered_ count] + 1;
+- (void) onSubscribed:(id)control {
+    bool value([control isOn]);
+    if (package_ == nil)
+        return;
+    if ([package_ setSubscribed:value])
+        [delegate_ updateData];
 }
 
-/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-    return 45.0f;
-}*/
+- (void) onIgnored:(id)control {
+    // TODO: set Held state - possibly call out to dpkg, etc.
+}
 
 - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    static NSString *reuseIdentifier = @"SectionCell";
+    if (package_ == nil)
+        return nil;
 
-    SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
-    if (cell == nil)
-        cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
+    switch ([indexPath row]) {
+        case 0: return subscribedCell_;
+        case 1: return ignoredCell_;
 
-    [cell setSection:[self sectionAtIndexPath:indexPath] editing:editing_];
+        _nodefault
+    }
 
-    return cell;
+    return nil;
 }
 
-- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    if (editing_)
-        return;
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
 
-    Section *section = [self sectionAtIndexPath:indexPath];
-    NSString *name = [section name];
-    NSString *title;
+    table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+    [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+    [table_ setDataSource:self];
+    [table_ setDelegate:self];
+    [[self view] addSubview:table_];
 
-    if ([indexPath row] == 0) {
-        section = nil;
-        name = nil;
-        title = UCLocalize("ALL_PACKAGES");
-    } else {
-        if (name != nil) {
-            name = [NSString stringWithString:name];
-            title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"];
-        } else {
-            name = @"";
-            title = UCLocalize("NO_SECTION");
-        }
-    }
+    subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+    [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+    [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
 
-    FilteredPackageController *table = [[[FilteredPackageController alloc]
-        initWithDatabase:database_
-        title:title
-        filter:@selector(isVisibleInSection:)
-        with:name
-    ] autorelease];
+    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];
 
-    [table setDelegate:delegate_];
+    subscribedCell_ = [[UITableViewCell alloc] init];
+    [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
+    [subscribedCell_ setAccessoryView:subscribedSwitch_];
+    [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
 
-    [[self navigationController] pushViewController:table animated:YES];
+    ignoredCell_ = [[UITableViewCell alloc] init];
+    [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
+    [ignoredCell_ setAccessoryView:ignoredSwitch_];
+    [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+    // FIXME: Ignored state is not saved.
+    [ignoredCell_ setUserInteractionEnabled:NO];
 }
 
-- (NSString *) title { return UCLocalize("SECTIONS"); }
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
+}
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        database_ = database;
+- (void) releaseSubviews {
+    [ignoredCell_ release];
+    ignoredCell_ = nil;
 
-        [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
+    [subscribedCell_ release];
+    subscribedCell_ = nil;
 
-        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
-        filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    [table_ release];
+    table_ = nil;
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:45.0f];
-        [[self view] addSubview:list_];
+    [ignoredSwitch_ release];
+    ignoredSwitch_ = nil;
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+    [subscribedSwitch_ release];
+    subscribedSwitch_ = nil;
+}
 
-        [self reloadData];
+- (id) initWithDatabase:(Database *)database package:(NSString *)package {
+    if ((self = [super init])) {
+        database_ = database;
+        name_ = [package retain];
     } return self;
 }
 
 - (void) reloadData {
-    NSArray *packages = [database_ packages];
-
-    [sections_ removeAllObjects];
-    [filtered_ removeAllObjects];
+    [super reloadData];
 
-    NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]);
+    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];
+    }
 
-    _trace();
-    for (Package *package in packages) {
-        NSString *name([package section]);
-        NSString *key(name == nil ? @"" : name);
+    [table_ reloadData];
+}
 
-        Section *section;
+@end
+/* }}} */
 
-        _profile(SectionsView$reloadData$Section)
-            section = [sections objectForKey:key];
-            if (section == nil) {
-                _profile(SectionsView$reloadData$Section$Allocate)
-                    section = [[[Section alloc] initWithName:name localize:YES] autorelease];
-                    [sections setObject:section forKey:key];
-                _end
-            }
-        _end
-
-        [section addToCount];
+/* Installed Controller {{{ */
+@interface InstalledController : FilteredPackageListController {
+    BOOL expert_;
+}
 
-        _profile(SectionsView$reloadData$Filter)
-            if (![package valid] || ![package visible])
-                continue;
-        _end
+- (id) initWithDatabase:(Database *)database;
 
-        [section addToRow];
-    }
-    _trace();
+- (void) updateRoleButton;
+- (void) queueStatusDidChange;
 
-    [sections_ addObjectsFromArray:[sections allValues]];
+@end
 
-    [sections_ sortUsingSelector:@selector(compareByLocalized:)];
+@implementation InstalledController
 
-    for (Section *section in sections_) {
-        size_t count([section row]);
-        if (count == 0)
-            continue;
+- (void) dealloc {
+    [super dealloc];
+}
 
-        section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease];
-        [section setCount:count];
-        [filtered_ addObject:section];
-    }
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://installed"];
+}
 
-    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-        initWithTitle:([sections_ count] == 0 ? nil : UCLocalize("EDIT"))
-        style:UIBarButtonItemStylePlain
-        target:self
-        action:@selector(editButtonClicked)
-    ] autorelease] animated:([[self navigationItem] rightBarButtonItem] != nil)];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) {
+        [self updateRoleButton];
+        [self queueStatusDidChange];
+    } return self;
+}
 
-    [list_ reloadData];
-    _trace();
+#if !AlwaysReload
+- (void) queueButtonClicked {
+    [delegate_ queue];
 }
+#endif
 
-- (void) resetView {
-    if (editing_)
-        [self editButtonClicked];
+- (void) queueStatusDidChange {
+#if !AlwaysReload
+    if (IsWildcat_) {
+        if (Queuing_) {
+            [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+                initWithTitle:UCLocalize("QUEUE")
+                style:UIBarButtonItemStyleDone
+                target:self
+                action:@selector(queueButtonClicked)
+            ] autorelease]];
+        } else {
+            [[self navigationItem] setLeftBarButtonItem:nil];
+        }
+    }
+#endif
 }
 
-- (void)editButtonClicked {
-    [self setEditing:!editing_];
+- (void) updateRoleButton {
+    if (Role_ != nil && ![Role_ isEqualToString:@"Developer"])
+        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE"))
+            style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
+            target:self
+            action:@selector(roleButtonClicked)
+        ] autorelease]];
 }
 
-- (UIView *) accessoryView {
-    return accessory_;
+- (void) roleButtonClicked {
+    [self setObject:[NSNumber numberWithBool:expert_]];
+    [self reloadData];
+    expert_ = !expert_;
+
+    [self updateRoleButton];
 }
 
 @end
 /* }}} */
-/* Changes Controller {{{ */
-@interface ChangesController : CYViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
+
+/* Source Cell {{{ */
+@interface SourceCell : CYTableViewCell <
+    ContentDelegate
 > {
-    _transient Database *database_;
-    unsigned era_;
-    CFMutableArrayRef packages_;
-    NSMutableArray *sections_;
-    UITableView *list_;
-    unsigned upgrades_;
-    BOOL hasSentFirstLoad_;
+    UIImage *icon_;
+    NSString *origin_;
+    NSString *label_;
 }
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate;
-- (void) reloadData;
+- (void) setSource:(Source *)source;
 
 @end
 
-@implementation ChangesController
-
-- (void) dealloc {
-    [list_ setDelegate:nil];
-    [list_ setDataSource:nil];
+@implementation SourceCell
 
-    CFRelease(packages_);
+- (void) clearSource {
+    [icon_ release];
+    [origin_ release];
+    [label_ release];
 
-    [sections_ release];
-    [list_ release];
-    [super dealloc];
+    icon_ = nil;
+    origin_ = nil;
+    label_ = nil;
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    if (!hasSentFirstLoad_) {
-        hasSentFirstLoad_ = YES;
-        [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0];
-    } else {
-        [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-    }
-}
+- (void) setSource:(Source *)source {
+    [self clearSource];
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
-    NSInteger count([sections_ count]);
-    return count == 0 ? 1 : count;
-}
+    if (icon_ == nil)
+        icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]];
+    if (icon_ == nil)
+        icon_ = [UIImage applicationImageNamed:@"unknown.png"];
+    icon_ = [icon_ retain];
 
-- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
-        return nil;
-    return [[sections_ objectAtIndex:section] name];
-}
+    origin_ = [[source name] retain];
+    label_ = [[source uri] retain];
 
-- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
-        return 0;
-    return [[sections_ objectAtIndex:section] count];
+    [content_ setNeedsDisplay];
 }
 
-- (Package *) packageAtIndex:(NSUInteger)index {
-    return (Package *) CFArrayGetValueAtIndex(packages_, index);
+- (void) dealloc {
+    [self clearSource];
+    [super dealloc];
 }
 
-- (Package *) packageAtIndexPath:(NSIndexPath *)path {
-@synchronized (database_) {
-    if ([database_ era] != era_)
-        return nil;
+- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
+    if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) {
+        UIView *content([self contentView]);
+        CGRect bounds([content bounds]);
 
-    Section *section([sections_ objectAtIndex:[path section]]);
-    NSInteger row([path row]);
-    return [[[self packageAtIndex:([section row] + row)] retain] autorelease];
-} }
+        content_ = [[ContentView alloc] initWithFrame:bounds];
+        [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [content_ setBackgroundColor:[UIColor whiteColor]];
+        [content addSubview:content_];
 
-- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path {
-    PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]);
-    if (cell == nil)
-        cell = [[[PackageCell alloc] init] autorelease];
-    [cell setPackage:[self packageAtIndexPath:path]];
-    return cell;
+        [content_ setDelegate:self];
+        [content_ setOpaque:YES];
+    } return self;
 }
 
-/*- (CGFloat) tableView:(UITableView *)table heightForRowAtIndexPath:(NSIndexPath *)path {
-    return [PackageCell heightForPackage:[self packageAtIndexPath:path]];
-}*/
-
-- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
-    Package *package([self packageAtIndexPath:path]);
-    PackageController *view([delegate_ packageController]);
-    [view setDelegate:delegate_];
-    [view setPackage:package];
-    [[self navigationController] pushViewController:view animated:YES];
-    return path;
+- (NSString *) accessibilityLabel {
+    return label_;
 }
 
-- (void) refreshButtonClicked {
-    [delegate_ beginUpdate];
-    [[self navigationItem] setLeftBarButtonItem:nil animated:YES];
-}
+- (void) drawContentRect:(CGRect)rect {
+    bool highlighted(highlighted_);
+    float width(rect.size.width);
 
-- (void) upgradeButtonClicked {
-    [delegate_ distUpgrade];
-}
+    if (icon_ != nil)
+        [icon_ drawInRect:CGRectMake(10, 10, 30, 30)];
 
-- (NSString *) title { return UCLocalize("CHANGES"); }
+    if (highlighted)
+        UISetColor(White_);
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        [[self navigationItem] setTitle:UCLocalize("CHANGES")];
+    if (!highlighted)
+        UISetColor(Black_);
+    [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - 80) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation];
 
-        packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
+    if (!highlighted)
+        UISetColor(Blue_);
+    [label_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation];
+}
 
-        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+@end
+/* }}} */
+/* Source Controller {{{ */
+@interface SourceController : FilteredPackageListController {
+    Source *source_;
+}
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:73.0f];
-        [[self view] addSubview:list_];
+- (id) initWithDatabase:(Database *)database source:(Source *)source;
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+@end
 
-        delegate_ = delegate;
-    } return self;
+@implementation SourceController
+
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sources/%@", [source_ name]]];
 }
 
-- (void) _reloadPackages:(NSArray *)packages {
-    _trace();
-    for (Package *package in packages)
-        if ([package upgradableAndEssential:YES] || [package visible])
-            CFArrayAppendValue(packages_, package);
+- (id) initWithDatabase:(Database *)database source:(Source *)source {
+    source_ = source;
 
-    _trace();
-    [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackageChangesRadix) withContext:NULL];
-    _trace();
+    if ((self = [super initWithDatabase:database title:[source label] filter:@selector(isVisibleInSource:) with:source]) != nil) {
+    } return self;
 }
 
-- (void) reloadData {
-    era_ = [database_ era];
-    NSArray *packages = [database_ packages];
+@end
+/* }}} */
+/* Sources Controller {{{ */
+@interface SourcesController : CYViewController <
+    UITableViewDataSource,
+    UITableViewDelegate
+> {
+    _transient Database *database_;
+    UITableView *list_;
+    NSMutableArray *sources_;
+    int offset_;
 
-    CFArrayRemoveAllValues(packages_);
+    NSString *href_;
+    UIProgressHUD *hud_;
+    NSError *error_;
 
-    [sections_ removeAllObjects];
+    //NSURLConnection *installer_;
+    NSURLConnection *trivial_;
+    NSURLConnection *trivial_bz2_;
+    NSURLConnection *trivial_gz_;
+    //NSURLConnection *automatic_;
 
-#if 1
-    UIProgressHUD *hud([delegate_ addProgressHUD]);
-    [hud setText:UCLocalize("LOADING")];
-    //NSLog(@"HUD:%@::%@", delegate_, hud);
-    [self yieldToSelector:@selector(_reloadPackages:) withObject:packages];
-    [delegate_ removeProgressHUD:hud];
-#else
-    [self _reloadPackages:packages];
-#endif
+    BOOL cydia_;
+}
 
-    Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
-    Section *ignored = nil;
-    Section *section = nil;
-    time_t last = 0;
+- (id) initWithDatabase:(Database *)database;
+- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
 
-    upgrades_ = 0;
-    bool unseens = false;
+@end
 
-    CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
+@implementation SourcesController
 
-    for (size_t offset = 0, count = CFArrayGetCount(packages_); offset != count; ++offset) {
-        Package *package = [self packageAtIndex:offset];
+- (void) _releaseConnection:(NSURLConnection *)connection {
+    if (connection != nil) {
+        [connection cancel];
+        //[connection setDelegate:nil];
+        [connection release];
+    }
+}
 
-        BOOL uae = [package upgradableAndEssential:YES];
+- (void) dealloc {
+    [self releaseSubviews];
 
-        if (!uae) {
-            unseens = true;
-            time_t seen([package seen]);
+    [href_ release];
+    [hud_ release];
+    [error_ release];
 
-            if (section == nil || last != seen) {
-                last = seen;
+    //[self _releaseConnection:installer_];
+    [self _releaseConnection:trivial_];
+    [self _releaseConnection:trivial_gz_];
+    [self _releaseConnection:trivial_bz2_];
+    //[self _releaseConnection:automatic_];
 
-                NSString *name;
-                name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]);
-                [name autorelease];
+    [sources_ release];
+    [super dealloc];
+}
 
-                _profile(ChangesController$reloadData$Allocate)
-                    name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name];
-                    section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease];
-                    [sections_ addObject:section];
-                _end
-            }
+- (NSURL *) navigationURL {
+    return [NSURL URLWithString:@"cydia://sources"];
+}
 
-            [section addToCount];
-        } else if ([package ignored]) {
-            if (ignored == nil) {
-                ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease];
-            }
-            [ignored addToCount];
-        } else {
-            ++upgrades_;
-            [upgradable addToCount];
-        }
-    }
-    _trace();
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+}
 
-    CFRelease(formatter);
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
+    return offset_ == 0 ? 1 : 2;
+}
 
-    if (unseens) {
-        Section *last = [sections_ lastObject];
-        size_t count = [last count];
-        CFArrayReplaceValues(packages_, CFRangeMake(CFArrayGetCount(packages_) - count, count), NULL, 0);
-        [sections_ removeLastObject];
+- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    switch (section + (offset_ == 0 ? 1 : 0)) {
+        case 0: return UCLocalize("ENTERED_BY_USER");
+        case 1: return UCLocalize("INSTALLED_BY_PACKAGE");
+
+        _nodefault
     }
+}
 
-    if ([ignored count] != 0)
-        [sections_ insertObject:ignored atIndex:0];
-    if (upgrades_ != 0)
-        [sections_ insertObject:upgradable atIndex:0];
+- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    int count = [sources_ count];
+    switch (section) {
+        case 0: return (offset_ == 0 ? count : offset_);
+        case 1: return count - offset_;
 
-    [list_ reloadData];
+        _nodefault
+    }
+}
 
-    if (upgrades_ > 0)
-        [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", upgrades_]]
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(upgradeButtonClicked)
-        ] autorelease]];
+- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath {
+    unsigned idx = 0;
+    switch (indexPath.section) {
+        case 0: idx = indexPath.row; break;
+        case 1: idx = indexPath.row + offset_; break;
 
-    if (![delegate_ updating])
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("REFRESH")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(refreshButtonClicked)
-        ] autorelease]];
+        _nodefault
+    }
+    return [sources_ objectAtIndex:idx];
 }
 
-@end
-/* }}} */
-/* Search Controller {{{ */
-@interface SearchController : FilteredPackageController <
-    UISearchBarDelegate
-> {
-    UISearchBar *search_;
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    static NSString *cellIdentifier = @"SourceCell";
+
+    SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
+    if(cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease];
+    [cell setSource:[self sourceAtIndexPath:indexPath]];
+    [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
+
+    return cell;
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
+- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    Source *source = [self sourceAtIndexPath:indexPath];
 
-@end
+    SourceController *controller = [[[SourceController alloc]
+        initWithDatabase:database_
+        source:source
+    ] autorelease];
 
-@implementation SearchController
+    [controller setDelegate:delegate_];
 
-- (void) dealloc {
-    [search_ release];
-    [super dealloc];
+    [[self navigationController] pushViewController:controller animated:YES];
 }
 
-- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
-    [packages_ setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
-    [search_ resignFirstResponder];
-    [self reloadData];
+- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+    Source *source = [self sourceAtIndexPath:indexPath];
+    return [source record] != nil;
 }
 
-- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
-    [packages_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
-    [self reloadData];
+- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
+    Source *source = [self sourceAtIndexPath:indexPath];
+    [Sources_ removeObjectForKey:[source key]];
+    [delegate_ syncData];
 }
 
-- (NSString *) title { return nil; }
+- (void) complete {
+    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
+        @"deb", @"Type",
+        href_, @"URI",
+        @"./", @"Distribution",
+    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
 
-- (id) initWithDatabase:(Database *)database {
-    return [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil];
+    [delegate_ syncData];
 }
 
-- (void)viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    if (!search_) {
-        search_ = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
-        [search_ layoutSubviews];
-        [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
+- (NSString *) getWarning {
+    NSString *href(href_);
+    NSRange colon([href rangeOfString:@"://"]);
+    if (colon.location != NSNotFound)
+        href = [href substringFromIndex:(colon.location + 3)];
+    href = [href stringByAddingPercentEscapes];
+    href = [CydiaURL(@"api/repotag/") stringByAppendingString:href];
+    href = [href stringByCachingURLWithCurrentCDN];
 
-        UITextField *textField;
-        if ([search_ respondsToSelector:@selector(searchField)])
-            textField = [search_ searchField];
-        else
-            textField = MSHookIvar<UITextField *>(search_, "_searchField");
+    NSURL *url([NSURL URLWithString:href]);
 
-        [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
-        [search_ setDelegate:self];
-        [textField setEnablesReturnKeyAutomatically:NO];
-        [[self navigationItem] setTitleView:textField];
-    }
-}
+    NSStringEncoding encoding;
+    NSError *error(nil);
 
-- (void) _reloadData {
+    if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error])
+        return [warning length] == 0 ? nil : warning;
+    return nil;
 }
 
-- (void) reloadData {
-    _profile(SearchController$reloadData)
-        [packages_ reloadData];
-    _end
-    PrintTimes();
-    [packages_ resetCursor];
-}
+- (void) _endConnection:(NSURLConnection *)connection {
+    // XXX: the memory management in this method is horribly awkward
 
-- (void) didSelectPackage:(Package *)package {
-    [search_ resignFirstResponder];
-    [super didSelectPackage:package];
-}
+    NSURLConnection **field = NULL;
+    if (connection == trivial_)
+        field = &trivial_;
+    else if (connection == trivial_bz2_)
+        field = &trivial_bz2_;
+    else if (connection == trivial_gz_)
+        field = &trivial_gz_;
+    _assert(field != NULL);
+    [connection release];
+    *field = nil;
 
-@end
-/* }}} */
-/* Settings Controller {{{ */
-@interface CYPackageSettingsController : CYViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
-> {
-    _transient Database *database_;
-    NSString *name_;
-    Package *package_;
-    UITableView *table_;
-    UISwitch *subscribedSwitch_;
-    UISwitch *ignoredSwitch_;
-    UITableViewCell *subscribedCell_;
-    UITableViewCell *ignoredCell_;
-}
+    if (
+        trivial_ == nil &&
+        trivial_bz2_ == nil &&
+        trivial_gz_ == nil
+    ) {
+        bool defer(false);
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
+        if (cydia_) {
+            if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) {
+                defer = true;
 
-@end
+                UIAlertView *alert = [[[UIAlertView alloc]
+                    initWithTitle:UCLocalize("SOURCE_WARNING")
+                    message:warning
+                    delegate:self
+                    cancelButtonTitle:UCLocalize("CANCEL")
+                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
+                ] autorelease];
 
-@implementation CYPackageSettingsController
+                [alert setContext:@"warning"];
+                [alert setNumberOfRows:1];
+                [alert show];
+            } else
+                [self complete];
+        } else if (error_ != nil) {
+            UIAlertView *alert = [[[UIAlertView alloc]
+                initWithTitle:UCLocalize("VERIFICATION_ERROR")
+                message:[error_ localizedDescription]
+                delegate:self
+                cancelButtonTitle:UCLocalize("OK")
+                otherButtonTitles:nil
+            ] autorelease];
 
-- (void) dealloc {
-    [name_ release];
-    if (package_ != nil)
-        [package_ release];
-    [table_ release];
-    [subscribedSwitch_ release];
-    [ignoredSwitch_ release];
-    [subscribedCell_ release];
-    [ignoredCell_ release];
+            [alert setContext:@"urlerror"];
+            [alert show];
+        } else {
+            UIAlertView *alert = [[[UIAlertView alloc]
+                initWithTitle:UCLocalize("NOT_REPOSITORY")
+                message:UCLocalize("NOT_REPOSITORY_EX")
+                delegate:self
+                cancelButtonTitle:UCLocalize("OK")
+                otherButtonTitles:nil
+            ] autorelease];
 
-    [super dealloc];
-}
+            [alert setContext:@"trivial"];
+            [alert show];
+        }
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
-    if (package_ == nil)
-        return 0;
+        [delegate_ setStatusBarShowsProgress:NO];
+        [delegate_ removeProgressHUD:hud_];
 
-    return 1;
-}
+        [hud_ autorelease];
+        hud_ = nil;
 
-- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if (package_ == nil)
-        return 0;
+        if (!defer) {
+            [href_ release];
+            href_ = nil;
+        }
 
-    return 2;
+        if (error_ != nil) {
+            [error_ release];
+            error_ = nil;
+        }
+    }
 }
 
-- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
-    return UCLocalize("SHOW_ALL_CHANGES_EX");
+- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
+    switch ([response statusCode]) {
+        case 200:
+            cydia_ = YES;
+    }
 }
 
-- (void) onSubscribed:(id)control {
-    bool value([control isOn]);
-    if (package_ == nil)
-        return;
-    if ([package_ setSubscribed:value])
-        [delegate_ updateData];
+- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
+    lprintf("connection:\"%s\" didFailWithError:\"%s\"", [href_ UTF8String], [[error localizedDescription] UTF8String]);
+    if (error_ != nil)
+        error_ = [error retain];
+    [self _endConnection:connection];
 }
 
-- (void) onIgnored:(id)control {
-    // TODO: set Held state - possibly call out to dpkg, etc.
+- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
+    [self _endConnection:connection];
 }
 
-- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    if (package_ == nil)
-        return nil;
+- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
+    NSMutableURLRequest *request = [NSMutableURLRequest
+        requestWithURL:[NSURL URLWithString:href]
+        cachePolicy:NSURLRequestUseProtocolCachePolicy
+        timeoutInterval:120.0
+    ];
 
-    switch ([indexPath row]) {
-        case 0: return subscribedCell_;
-        case 1: return ignoredCell_;
+    [request setHTTPMethod:method];
 
-        _nodefault
-    }
+    if (Machine_ != NULL)
+        [request setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"];
+    if (UniqueID_ != nil)
+        [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
+    if (Role_ != nil)
+        [request setValue:Role_ forHTTPHeaderField:@"X-Role"];
 
-    return nil;
+    return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
 }
 
-- (NSString *) title { return UCLocalize("SETTINGS"); }
+- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
+    NSString *context([alert context]);
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init])) {
-        database_ = database;
-        name_ = [package retain];
+    if ([context isEqualToString:@"source"]) {
+        switch (button) {
+            case 1: {
+                NSString *href = [[alert textField] text];
+
+                //installer_ = [[self _requestHRef:href method:@"GET"] retain];
+
+                if (![href hasSuffix:@"/"])
+                    href_ = [href stringByAppendingString:@"/"];
+                else
+                    href_ = href;
+                href_ = [href_ retain];
+
+                trivial_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages"] method:@"HEAD"] retain];
+                trivial_bz2_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.bz2"] method:@"HEAD"] retain];
+                trivial_gz_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.gz"] method:@"HEAD"] retain];
+                //trivial_bz2_ = [[self _requestHRef:[href stringByAppendingString:@"dists/Release"] method:@"HEAD"] retain];
 
-        [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
+                cydia_ = false;
 
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:table_];
+                // XXX: this is stupid
+                hud_ = [[delegate_ addProgressHUD] retain];
+                [hud_ setText:UCLocalize("VERIFYING_URL")];
+            } break;
 
-        subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
+            case 0:
+            break;
 
-        ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
+            _nodefault
+        }
 
-        subscribedCell_ = [[UITableViewCell alloc] init];
-        [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
-        [subscribedCell_ setAccessoryView:subscribedSwitch_];
-        [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    } else if ([context isEqualToString:@"trivial"])
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    else if ([context isEqualToString:@"urlerror"])
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    else if ([context isEqualToString:@"warning"]) {
+        switch (button) {
+            case 1:
+                [self complete];
+            break;
 
-        ignoredCell_ = [[UITableViewCell alloc] init];
-        [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
-        [ignoredCell_ setAccessoryView:ignoredSwitch_];
-        [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+            case 0:
+            break;
 
-        [table_ setDataSource:self];
-        [table_ setDelegate:self];
-        [self reloadData];
+            _nodefault
+        }
+
+        [href_ release];
+        href_ = nil;
+
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    }
+}
+
+- (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:56];
+    [list_ setDataSource:self];
+    [list_ setDelegate:self];
+    [[self view] addSubview:list_];
+}
+
+- (void) viewDidLoad {
+    [[self navigationItem] setTitle:UCLocalize("SOURCES")];
+    [self updateButtonsForEditingStatus:NO animated:NO];
+}
+
+- (void) releaseSubviews {
+    [list_ release];
+    list_ = nil;
+}
+
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
     } return self;
 }
 
-- (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];
-    }
+- (void) reloadData {
+    [super reloadData];
+
+    pkgSourceList list;
+    if (!list.ReadMainList())
+        return;
+
+    [sources_ removeAllObjects];
+    [sources_ addObjectsFromArray:[database_ sources]];
+    _trace();
+    [sources_ sortUsingSelector:@selector(compareByNameAndType:)];
+    _trace();
+
+    int count([sources_ count]);
+    offset_ = 0;
+    for (int i = 0; i != count; i++) {
+        if ([[sources_ objectAtIndex:i] record] == nil)
+            break;
+        offset_++;
+    }
+
+    [list_ setEditing:NO];
+    [self updateButtonsForEditingStatus:NO animated:NO];
+    [list_ reloadData];
+}
+
+- (void) showAddSourcePrompt {
+    UIAlertView *alert = [[[UIAlertView alloc]
+        initWithTitle:UCLocalize("ENTER_APT_URL")
+        message:nil
+        delegate:self
+        cancelButtonTitle:UCLocalize("CANCEL")
+        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
+    ] autorelease];
+
+    [alert setContext:@"source"];
+    [alert setTransform:CGAffineTransformTranslate([alert transform], 0.0, 100.0)];
+
+    [alert setNumberOfRows:1];
+    [alert addTextFieldWithValue:@"http://" label:@""];
+
+    UITextInputTraits *traits = [[alert textField] textInputTraits];
+    [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
+    [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
+    [traits setKeyboardType:UIKeyboardTypeURL];
+    // XXX: UIReturnKeyDone
+    [traits setReturnKeyType:UIReturnKeyNext];
 
-    [table_ reloadData];
+    [alert show];
 }
 
-@end
-/* }}} */
-/* Signature Controller {{{ */
-@interface SignatureController : CYBrowserController {
-    _transient Database *database_;
-    NSString *package_;
+- (void) addButtonClicked {
+    [self showAddSourcePrompt];
 }
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
-
-@end
+- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated {
+    [[self navigationItem] setLeftBarButtonItem:(editing ? [[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("ADD")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(addButtonClicked)
+    ] autorelease] : [[self navigationItem] backBarButtonItem]) animated:animated];
 
-@implementation SignatureController
+    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
+        style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
+        target:self
+        action:@selector(editButtonClicked)
+    ] autorelease] animated:animated];
 
-- (void) dealloc {
-    [package_ release];
-    [super dealloc];
+    if (IsWildcat_ && !editing)
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("SETTINGS")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(settingsButtonClicked)
+        ] autorelease]];
 }
 
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    // XXX: dude!
-    [super webView:view didClearWindowObject:window forFrame:frame];
+- (void) settingsButtonClicked {
+    [delegate_ showSettings];
 }
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        package_ = [package retain];
-        [self reloadData];
-    } return self;
-}
+- (void) editButtonClicked {
+    [list_ setEditing:![list_ isEditing] animated:YES];
 
-- (void) reloadData {
-    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
+    [self updateButtonsForEditingStatus:[list_ isEditing] animated:YES];
 }
 
 @end
 /* }}} */
 
-/* Role Controller {{{ */
-@interface CYSettingsController : CYViewController <
+/* Settings Controller {{{ */
+@interface SettingsController : CYViewController <
     UITableViewDataSource,
     UITableViewDelegate
 > {
@@ -7864,49 +8054,64 @@ freeing the view controllers on tab change */
 
 @end
 
-@implementation CYSettingsController
+@implementation SettingsController
+
 - (void) dealloc {
+    [self releaseSubviews];
+
+    [super dealloc];
+}
+
+- (void) loadView {
+    [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
+
+    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_];
+}
+
+- (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];
-    [container_ release];
+    segment_ = nil;
 
-    [super dealloc];
+    [container_ release];
+    container_ = nil;
 }
 
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate {
     if ((self = [super init])) {
         database_ = database;
         roledelegate_ = delegate;
-
-        [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")];
-
-        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];
-        }
-
-        [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
-        [self resizeSegmentedControl];
-
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [table_ setDelegate:self];
-        [table_ setDataSource:self];
-        [[self view] addSubview:table_];
-        [table_ reloadData];
     } return self;
 }
 
@@ -8021,79 +8226,87 @@ freeing the view controllers on tab change */
     return section == 3 ? container_ : nil;
 }
 
+- (void) reloadData {
+    [super reloadData];
+    [table_ reloadData];
+}
+
 @end
 /* }}} */
 /* Stash Controller {{{ */
-@interface CYStashController : CYViewController {
-    // XXX: just delete these things
-    _transient UIActivityIndicatorView *spinner_;
-    _transient UILabel *status_;
-    _transient UILabel *caption_;
+@interface StashController : CYViewController {
+    UIActivityIndicatorView *spinner_;
+    UILabel *status_;
+    UILabel *caption_;
 }
 @end
 
-@implementation CYStashController
-- (id) init {
-    if ((self = [super init])) {
-        [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]];
+@implementation StashController
 
-        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_];
 }
 
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    return IsWildcat_ || orientation == UIInterfaceOrientationPortrait;
+- (void) releaseSubviews {
+    [spinner_ release];
+    spinner_ = nil;
+
+    [status_ release];
+    status_ = nil;
+
+    [caption_ release];
+    caption_ = nil;
 }
+
 @end
 /* }}} */
 
-typedef enum {
-    kCydiaTag = 0,
-    kSectionsTag = 1,
-    kChangesTag = 2,
-    kManageTag = 3,
-    kInstalledTag = 4,
-    kSourcesTag = 5,
-    kSearchTag = 6
-} CYTabTag;
-
 @interface Cydia : UIApplication <
     ConfirmationControllerDelegate,
     ProgressControllerDelegate,
@@ -8112,37 +8325,19 @@ typedef enum {
     Database *database_;
 
     NSURL *starturl_;
-    int tag_;
 
     unsigned locked_;
     unsigned activity_;
 
-    CYSectionsController *sections_;
-    ChangesController *changes_;
-    ManageController *manage_;
-    SearchController *search_;
-    SourceController *sources_;
-    InstalledController *installed_;
-    id queueDelegate_;
-
-    CYStashController *stash_;
+    StashController *stash_;
 
     bool loaded_;
 }
 
-- (CYViewController *) _pageForURL:(NSURL *)url withClass:(Class)_class;
-- (void) setPage:(CYViewController *)page;
 - (void) loadData;
 
-// XXX: I hate prototypes
-- (id) queueBadgeController;
-
 @end
 
-static _finline void _setHomePage(Cydia *self) {
-    [self setPage:[self _pageForURL:[NSURL URLWithString:CydiaURL(@"")] withClass:[HomeController class]]];
-}
-
 @implementation Cydia
 
 - (void) beginUpdate {
@@ -8205,42 +8400,25 @@ static _finline void _setHomePage(Cydia *self) {
     }
 }
 
+// Navigation controller for the queuing badge.
+- (CYNavigationController *) queueNavigationController {
+    NSArray *controllers = [tabbar_ viewControllers];
+    return [controllers objectAtIndex:3];
+}
+
 - (void) _updateData {
     [self _saveConfig];
 
-    NSMutableSet *tabs([[[NSMutableSet alloc] initWithCapacity:10] autorelease]);
+    [tabbar_ reloadData];
 
-    [tabs addObject:[tabbar_ selectedViewController]];
-
-    if (sections_ != nil)
-        [tabs addObject:sections_];
-    if (changes_ != nil)
-        [tabs addObject:changes_];
-    if (manage_ != nil)
-        [tabs addObject:manage_];
-    if (search_ != nil)
-        [tabs addObject:search_];
-    if (sources_ != nil)
-        [tabs addObject:sources_];
-    if (installed_ != nil)
-        [tabs addObject:installed_];
-
-    for (CYNavigationController *tab in tabs)
-        [tab reloadData];
-
-    [queueDelegate_ queueStatusDidChange];
-    [[[self queueBadgeController] tabBarItem] setBadgeValue:(Queuing_ ? UCLocalize("Q_D") : nil)];
-}
+    CYNavigationController *navigation = [self queueNavigationController];
 
-- (int)indexOfTabWithTag:(int)tag {
-    int i = 0;
-    for (UINavigationController *controller in [tabbar_ viewControllers]) {
-        if ([[controller tabBarItem] tag] == tag)
-            return i;
-        i += 1;
-    }
+    id queuedelegate = nil;
+    if ([[navigation viewControllers] count] > 0)
+        queuedelegate = [[navigation viewControllers] objectAtIndex:0];
 
-    return -1;
+    [queuedelegate queueStatusDidChange];
+    [[navigation tabBarItem] setBadgeValue:(Queuing_ ? UCLocalize("Q_D") : nil)];
 }
 
 - (void) _refreshIfPossible {
@@ -8261,8 +8439,7 @@ static _finline void _setHomePage(Cydia *self) {
     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 {
@@ -8326,7 +8503,7 @@ static _finline void _setHomePage(Cydia *self) {
 
     NSLog(@"changes:#%u", changes);
 
-    UITabBarItem *changesItem = [[[tabbar_ viewControllers] objectAtIndex:[self indexOfTabWithTag:kChangesTag]] tabBarItem];
+    UITabBarItem *changesItem = [[[tabbar_ viewControllers] objectAtIndex:2] tabBarItem];
     if (changes != 0) {
         _trace();
         NSString *badge([[NSNumber numberWithInt:changes] stringValue]);
@@ -8497,94 +8674,8 @@ static _finline void _setHomePage(Cydia *self) {
     [self complete];
 }
 
-- (void) setPage:(CYViewController *)page {
-    [page setDelegate:self];
-
-    CYNavigationController *navController = (CYNavigationController *) [tabbar_ selectedViewController];
-    [navController setViewControllers:[NSArray arrayWithObject:page]];
-    for (CYNavigationController *page in [tabbar_ viewControllers])
-        if (page != navController)
-            [page setViewControllers:nil];
-}
-
-- (CYViewController *) _pageForURL:(NSURL *)url withClass:(Class)_class {
-    CYBrowserController *browser = [[[_class alloc] init] autorelease];
-    [browser loadURL:url];
-    return browser;
-}
-
-- (CYSectionsController *) sectionsController {
-    if (sections_ == nil)
-        sections_ = [[CYSectionsController alloc] initWithDatabase:database_];
-    return sections_;
-}
-
-- (ChangesController *) changesController {
-    if (changes_ == nil)
-        changes_ = [[ChangesController alloc] initWithDatabase:database_ delegate:self];
-    return changes_;
-}
-
-- (ManageController *) manageController {
-    if (manage_ == nil) {
-        manage_ = (ManageController *) [[self
-            _pageForURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"manage" ofType:@"html"]]
-            withClass:[ManageController class]
-        ] retain];
-        if (!IsWildcat_)
-            queueDelegate_ = manage_;
-    }
-    return manage_;
-}
-
-- (SearchController *) searchController {
-    if (search_ == nil)
-        search_ = [[SearchController alloc] initWithDatabase:database_];
-    return search_;
-}
-
-- (SourceController *) sourcesController {
-    if (sources_ == nil)
-        sources_ = [[SourceController alloc] initWithDatabase:database_];
-    return sources_;
-}
-
-- (InstalledController *) installedController {
-    if (installed_ == nil) {
-        installed_ = [[InstalledController alloc] initWithDatabase:database_];
-        if (IsWildcat_)
-            queueDelegate_ = installed_;
-    }
-    return installed_;
-}
-
-- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
-    int tag = [[viewController tabBarItem] tag];
-    if (tag == tag_) {
-        [(CYNavigationController *)[tabbar_ selectedViewController] popToRootViewControllerAnimated:YES];
-        return;
-    } else if (tag_ == 1) {
-        [[self sectionsController] resetView];
-    }
-
-    switch (tag) {
-        case kCydiaTag: _setHomePage(self); break;
-
-        case kSectionsTag: [self setPage:[self sectionsController]]; break;
-        case kChangesTag: [self setPage:[self changesController]]; break;
-        case kManageTag: [self setPage:[self manageController]]; break;
-        case kInstalledTag: [self setPage:[self installedController]]; break;
-        case kSourcesTag: [self setPage:[self sourcesController]]; break;
-        case kSearchTag: [self setPage:[self searchController]]; break;
-
-        _nodefault
-    }
-
-    tag_ = tag;
-}
-
 - (void) showSettings {
-    CYSettingsController *role = [[[CYSettingsController alloc] initWithDatabase:database_ delegate:self] autorelease];
+    SettingsController *role = [[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease];
     CYNavigationController *nav = [[[CYNavigationController alloc] initWithRootViewController:role] autorelease];
     if (IsWildcat_)
         [nav setModalPresentationStyle:UIModalPresentationFormSheet];
@@ -8601,29 +8692,6 @@ static _finline void _setHomePage(Cydia *self) {
         [self setNetworkActivityIndicatorVisible:NO];
 }
 
-- (void) setPackageController:(PackageController *)view {
-    WebThreadLock();
-    [view setPackage:nil];
-    WebThreadUnlock();
-}
-
-- (PackageController *) _packageController {
-    return [[[PackageController alloc] initWithDatabase:database_] autorelease];
-}
-
-- (PackageController *) packageController {
-    return [self _packageController];
-}
-
-// Returns the navigation controller for the queuing badge.
-- (id) queueBadgeController {
-    int index = [self indexOfTabWithTag:kManageTag];
-    if (index == -1)
-        index = [self indexOfTabWithTag:kInstalledTag];
-
-    return [[tabbar_ viewControllers] objectAtIndex:index];
-}
-
 - (void) cancelAndClear:(bool)clear {
     @synchronized (self) {
         if (clear) {
@@ -8745,76 +8813,140 @@ static _finline void _setHomePage(Cydia *self) {
 
 - (CYViewController *) pageForPackage:(NSString *)name {
     if (Package *package = [database_ packageWithName:name]) {
-        PackageController *view([self packageController]);
+        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]]];
-        return [self _pageForURL:url withClass:[CYBrowserController class]];
+        CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease];
+        [browser loadURL:url];
+        return browser;
     }
 }
 
-- (CYViewController *) pageForURL:(NSURL *)url hasTag:(int *)tag {
-    if (tag != NULL)
-        *tag = -1;
-
-    NSString *href([url absoluteString]);
-    if ([href hasPrefix:@"apptapp://package/"])
-        return [self pageForPackage:[href substringFromIndex:18]];
-
+- (CYViewController *) pageForURL:(NSURL *)url {
     NSString *scheme([[url scheme] lowercaseString]);
-    if (![scheme isEqualToString:@"cydia"])
+    if ([[url absoluteString] length] <= [scheme length] + 3)
         return nil;
-    NSString *path([url absoluteString]);
-    if ([path length] < 8)
+    NSString *path([[url absoluteString] substringFromIndex:[scheme length] + 3]);
+    NSArray *components([path pathComponents]);
+
+    if ([scheme isEqualToString:@"apptapp"] && [components count] > 0 && [[components objectAtIndex:0] isEqualToString:@"package"])
+        return [self pageForPackage:[components objectAtIndex:1]];
+
+    if ([components count] < 1 || ![scheme isEqualToString:@"cydia"])
         return nil;
-    path = [path substringFromIndex:8];
-    if (![path hasPrefix:@"/"])
-        path = [@"/" stringByAppendingString:path];
-
-    if ([path isEqualToString:@"/storage"])
-        return [self _pageForURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"storage" ofType:@"html"]] withClass:[CYBrowserController class]];
-    /*else if ([path isEqualToString:@"/add-source"])
-        return [[[AddSourceController alloc] initWithDatabase:database_] autorelease];*/
-    else if ([path isEqualToString:@"/sources"])
-        return [[[SourceController alloc] initWithDatabase:database_] autorelease];
-    else if ([path isEqualToString:@"/packages"])
-        return [[[InstalledController alloc] initWithDatabase:database_] autorelease];
-    else if ([path hasPrefix:@"/url/"])
-        return [self _pageForURL:[NSURL URLWithString:[path substringFromIndex:5]] withClass:[CYBrowserController class]];
-    else if ([path hasPrefix:@"/launch/"])
-        [self launchApplicationWithIdentifier:[path substringFromIndex:8] suspended:NO];
-    else if ([path hasPrefix:@"/package-settings/"])
-        return [[[CYPackageSettingsController alloc] initWithDatabase:database_ package:[path substringFromIndex:18]] autorelease];
-    else if ([path hasPrefix:@"/package-signature/"])
-        return [[[SignatureController alloc] initWithDatabase:database_ package:[path substringFromIndex:19]] autorelease];
-    else if ([path hasPrefix:@"/package/"])
-        return [self pageForPackage:[path substringFromIndex:9]];
-    else if ([path hasPrefix:@"/files/"]) {
-        NSString *name = [path substringFromIndex:7];
-
-        if (Package *package = [database_ packageWithName:name]) {
-            FileTable *files = [[[FileTable alloc] initWithDatabase:database_] autorelease];
-            [files setPackage:package];
-            return files;
+
+    NSString *base([components objectAtIndex:0]);
+
+    CYViewController *controller = nil;
+
+    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"]]];
+        }
+
+        if ([base isEqualToString:@"manage"]) {
+            controller = [[[ManageController alloc] init] autorelease];
+        }
+
+        if ([base isEqualToString:@"sources"]) {
+            controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
+        }
+
+        if ([base isEqualToString:@"home"]) {
+            controller = [[[HomeController alloc] init] autorelease];
+        }
+
+        if ([base isEqualToString:@"sections"]) {
+            controller = [[[SectionsController alloc] initWithDatabase:database_] autorelease];
+        }
+
+        if ([base isEqualToString:@"search"]) {
+            controller = [[[SearchController alloc] initWithDatabase:database_] autorelease];
+        }
+
+        if ([base isEqualToString:@"changes"]) {
+            controller = [[[ChangesController alloc] initWithDatabase:database_] autorelease];
+        }
+
+        if ([base isEqualToString:@"installed"]) {
+            controller = [[[InstalledController alloc] initWithDatabase:database_] autorelease];
+        }
+    } else if ([components count] == 2) {
+        NSString *argument = [components objectAtIndex:1];
+
+        if ([base isEqualToString:@"package"]) {
+            controller = [self pageForPackage:argument];
+        }
+
+        if ([base isEqualToString:@"search"]) {
+            controller = [[[SearchController alloc] initWithDatabase:database_] autorelease];
+            [(SearchController *)controller setSearchTerm:argument];
+        }
+
+        if ([base isEqualToString:@"sections"]) {
+            if ([argument isEqualToString:@"all"])
+                argument = nil;
+            controller = [[[SectionController alloc] initWithDatabase:database_ section:argument] autorelease];
+        }
+
+        if ([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;
+                    }
+                }
+            }
+        }
+
+        if ([base isEqualToString:@"launch"]) {
+            [self launchApplicationWithIdentifier:argument suspended:NO];
+            return nil;
+        }
+    } else if ([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:@"files"]) {
+                if (Package *package = [database_ packageWithName:arg1]) {
+                    controller = [[[FileTable alloc] initWithDatabase:database_] autorelease];
+                    [(FileTable *)controller setPackage:package];
+                }
+            }
         }
     }
 
-    return nil;
+    [controller setDelegate:self];
+    return controller;
 }
 
 - (BOOL) openCydiaURL:(NSURL *)url {
-    CYViewController *page = nil;
-    int tag = 0;
+    CYViewController *page([self pageForURL:url]);
 
-    if ((page = [self pageForURL:url hasTag:&tag])) {
-        [self setPage:page];
-        tag_ = tag;
-        [tabbar_ setSelectedViewController:(tag_ == -1 ? nil : [[tabbar_ viewControllers] objectAtIndex:tag_])];
+    if (page != nil) {
+        CYNavigationController *nav = [[[CYNavigationController alloc] init] autorelease];
+        [nav setViewControllers:[NSArray arrayWithObject:page]];
+        [tabbar_ setTransientViewController:nav];
     }
 
-    return !!page;
+    return page != nil;
 }
 
 - (void) applicationOpenURL:(NSURL *)url {
@@ -8833,9 +8965,18 @@ static _finline void _setHomePage(Cydia *self) {
         [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) addStashController {
     ++locked_;
-    stash_ = [[CYStashController alloc] init];
+    stash_ = [[StashController alloc] init];
     [window_ addSubview:[stash_ view]];
 }
 
@@ -8865,53 +9006,53 @@ static _finline void _setHomePage(Cydia *self) {
     }
 }
 
-- (void) setupTabBarController {
+- (void) setupViewControllers {
     tabbar_ = [[CYTabBarController alloc] initWithDatabase:database_];
     [tabbar_ setDelegate:self];
 
     NSMutableArray *items([NSMutableArray arrayWithObjects:
-        [[[UITabBarItem alloc] initWithTitle:@"Cydia" image:[UIImage applicationImageNamed:@"home.png"] tag:kCydiaTag] autorelease],
-        [[[UITabBarItem alloc] initWithTitle:UCLocalize("SECTIONS") image:[UIImage applicationImageNamed:@"install.png"] tag:kSectionsTag] autorelease],
-        [[[UITabBarItem alloc] initWithTitle:UCLocalize("CHANGES") image:[UIImage applicationImageNamed:@"changes.png"] tag:kChangesTag] autorelease],
-        [[[UITabBarItem alloc] initWithTitle:UCLocalize("SEARCH") image:[UIImage applicationImageNamed:@"search.png"] tag:kSearchTag] autorelease],
+        [[[UITabBarItem alloc] initWithTitle:@"Cydia" image:[UIImage applicationImageNamed:@"home.png"] tag:0] autorelease],
+        [[[UITabBarItem alloc] initWithTitle:UCLocalize("SECTIONS") image:[UIImage applicationImageNamed:@"install.png"] tag:0] autorelease],
+        [[[UITabBarItem alloc] initWithTitle:UCLocalize("CHANGES") image:[UIImage applicationImageNamed:@"changes.png"] tag:0] autorelease],
+        [[[UITabBarItem alloc] initWithTitle:UCLocalize("SEARCH") image:[UIImage applicationImageNamed:@"search.png"] tag:0] autorelease],
     nil]);
 
     if (IsWildcat_) {
-        [items insertObject:[[[UITabBarItem alloc] initWithTitle:UCLocalize("SOURCES") image:[UIImage applicationImageNamed:@"source.png"] tag:kSourcesTag] autorelease] atIndex:3];
-        [items insertObject:[[[UITabBarItem alloc] initWithTitle:UCLocalize("INSTALLED") image:[UIImage applicationImageNamed:@"manage.png"] tag:kInstalledTag] autorelease] atIndex:3];
+        [items insertObject:[[[UITabBarItem alloc] initWithTitle:UCLocalize("SOURCES") image:[UIImage applicationImageNamed:@"source.png"] tag:0] autorelease] atIndex:3];
+        [items insertObject:[[[UITabBarItem alloc] initWithTitle:UCLocalize("INSTALLED") image:[UIImage applicationImageNamed:@"manage.png"] tag:0] autorelease] atIndex:3];
     } else {
-        [items insertObject:[[[UITabBarItem alloc] initWithTitle:UCLocalize("MANAGE") image:[UIImage applicationImageNamed:@"manage.png"] tag:kManageTag] autorelease] atIndex:3];
+        [items insertObject:[[[UITabBarItem alloc] initWithTitle:UCLocalize("MANAGE") image:[UIImage applicationImageNamed:@"manage.png"] tag:0] autorelease] atIndex:3];
     }
 
     NSMutableArray *controllers([NSMutableArray array]);
-
     for (UITabBarItem *item in items) {
         CYNavigationController *controller([[[CYNavigationController alloc] initWithDatabase:database_] autorelease]);
         [controller setTabBarItem:item];
         [controllers addObject:controller];
     }
-
     [tabbar_ setViewControllers:controllers];
+
+    [tabbar_ setUpdateDelegate:self];
 }
 
-- (void)showFakeTabBarInView:(UIView *)view {
-    static UITabBar *fake = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)];
+- (CYEmulatedLoadingController *)showEmulatedLoadingControllerInView:(UIView *)view {
+    static CYEmulatedLoadingController *fake = [[CYEmulatedLoadingController alloc] init];
     if (view != nil) {
-        CGRect frame = [fake frame];
-        frame.origin.y = [view frame].size.height - frame.size.height;
-        frame.size.width = [view frame].size.width;
-        [fake setFrame:frame];
-        [fake setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin];
-        [view addSubview:fake];
+        [view addSubview:[fake view]];
     } else {
-        [fake removeFromSuperview];
+        [[fake view] removeFromSuperview];
     }
+
+    return fake;
 }
 
 - (void) applicationDidFinishLaunching:(id)unused {
 _trace();
     CydiaApp = self;
 
+    if ([self respondsToSelector:@selector(setApplicationSupportsShakeToEdit:)])
+        [self setApplicationSupportsShakeToEdit:NO];
+
     [NSURLCache setSharedURLCache:[[[SDURLCache alloc]
         initWithMemoryCapacity:524288
         diskCapacity:10485760
@@ -8928,8 +9069,6 @@ _trace();
     Font18Bold_ = [[UIFont boldSystemFontOfSize:18] retain];
     Font22Bold_ = [[UIFont boldSystemFontOfSize:22] retain];
 
-    tag_ = 0;
-
     essential_ = [[NSMutableArray alloc] initWithCapacity:4];
     broken_ = [[NSMutableArray alloc] initWithCapacity:4];
 
@@ -8960,13 +9099,9 @@ _trace();
 
     database_ = [Database sharedInstance];
 
-    [self setupTabBarController];
-    [tabbar_ setUpdateDelegate:self];
-    [window_ addSubview:[tabbar_ view]];
-
-    // Show pinstripes while loading data.
-    [[tabbar_ view] setBackgroundColor:[UIColor pinStripeColor]];
-    [self showFakeTabBarInView:[tabbar_ tabBar]];
+    [window_ setUserInteractionEnabled:NO];
+    [self setupViewControllers];
+    [self showEmulatedLoadingControllerInView:window_];
 
     [self performSelector:@selector(loadData) withObject:nil afterDelay:0];
 _trace();
@@ -8975,37 +9110,80 @@ _trace();
 - (void) loadData {
 _trace();
     if (Role_ == nil) {
-        [self showSettings];
+        [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];
+
         return;
+    } else {
+        if ([[self showEmulatedLoadingControllerInView:window_] modalViewController] != nil)
+            [[self showEmulatedLoadingControllerInView:window_] dismissModalViewControllerAnimated:YES];
+        [window_ setUserInteractionEnabled:NO];
     }
 
-    CGRect fixframe = [[tabbar_ view] frame];
-    if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]))
-        fixframe.size = CGSizeMake(fixframe.size.height, fixframe.size.width);
-    CYLoadingIndicator *loading = [[[CYLoadingIndicator alloc] initWithFrame:fixframe] autorelease];
-    [loading setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-    [[tabbar_ view] addSubview:loading];
-    [window_ setUserInteractionEnabled:NO];
-
     [self reloadData];
     PrintTimes();
 
-    // Show the initial page
-    if (starturl_ == nil || ![self openCydiaURL:starturl_]) {
-        [tabbar_ setSelectedIndex:0];
-        _setHomePage(self);
+    [window_ addSubview:[tabbar_ view]];
+    [self showEmulatedLoadingControllerInView:nil];
+    [window_ setUserInteractionEnabled:YES];
+
+    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;
+    }
+
+    if (recently && [Metadata_ objectForKey:@"InterfaceState"]) {
+        items = [[Metadata_ objectForKey:@"InterfaceState"] mutableCopy];
+        selectedIndex = [[Metadata_ objectForKey:@"InterfaceIndex"] intValue];
+    } else {
+        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"]];
     }
 
-    [self showFakeTabBarInView:nil];
+    [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];
 
-    [starturl_ release];
-    starturl_ = nil;
+        for (unsigned int nav = 0; nav < [stack count]; nav++) {
+            NSString *addr = [stack objectAtIndex:nav];
+            NSURL *url = [NSURL URLWithString:addr];
+            CYViewController *page = [self pageForURL:url];
+            if (page != nil)
+                [current addObject:page];
+        }
 
-    [window_ setUserInteractionEnabled:YES];
+        [navigation setViewControllers:current];
+    }
 
-    // XXX: does this actually slow anything down?
-    [[tabbar_ view] setBackgroundColor:[UIColor clearColor]];
-    [loading removeFromSuperview];
+    // (Try to) show the startup URL.
+    if (starturl_ != nil) {
+        [self openCydiaURL:starturl_];
+        [starturl_ release];
+        starturl_ = nil;
+    }
 }
 
 - (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item {
@@ -9061,6 +9239,33 @@ MSHook(void, UIHardware$_playSystemSound$, Class self, SEL _cmd, int sound) {
     }
 }
 
+Class $UIApplication;
+
+MSHook(void, UIApplication$_updateApplicationAccessibility, UIApplication *self, SEL _cmd) {
+    static BOOL initialized = NO;
+    static BOOL started = NO;
+
+    NSDictionary *dict([[[NSDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.apple.Accessibility.plist"] autorelease]);
+    BOOL enabled = [[dict objectForKey:@"VoiceOverTouchEnabled"] boolValue] || [[dict objectForKey:@"VoiceOverTouchEnabledByiTunes"] boolValue];
+
+    if ([self respondsToSelector:@selector(_accessibilityBundlePrincipalClass)]) {
+        id bundle = [self performSelector:@selector(_accessibilityBundlePrincipalClass)];
+        if (![bundle respondsToSelector:@selector(_accessibilityStopServer)]) return;
+        if (![bundle respondsToSelector:@selector(_accessibilityStartServer)]) return;
+
+        if (initialized && !enabled) {
+            initialized = NO;
+            [bundle performSelector:@selector(_accessibilityStopServer)];
+        } else if (enabled) {
+            initialized = YES;
+            if (!started) {
+                started = YES;
+                [bundle performSelector:@selector(_accessibilityStartServer)];
+            }
+        }
+    }
+}
+
 int main(int argc, char *argv[]) { _pooled
     _trace();
 
@@ -9088,6 +9293,13 @@ int main(int argc, char *argv[]) { _pooled
         _UIHardware$_playSystemSound$ = reinterpret_cast<void (*)(Class, SEL, int)>(method_getImplementation(UIHardware$_playSystemSound$));
         method_setImplementation(UIHardware$_playSystemSound$, reinterpret_cast<IMP>(&$UIHardware$_playSystemSound$));
     }
+
+    $UIApplication = objc_getClass("UIApplication");
+    Method UIApplication$_updateApplicationAccessibility(class_getInstanceMethod($UIApplication, @selector(_updateApplicationAccessibility)));
+    if (UIApplication$_updateApplicationAccessibility != NULL) {
+        _UIApplication$_updateApplicationAccessibility = reinterpret_cast<void (*)(UIApplication *, SEL)>(method_getImplementation(UIApplication$_updateApplicationAccessibility));
+        method_setImplementation(UIApplication$_updateApplicationAccessibility, reinterpret_cast<IMP>(&$UIApplication$_updateApplicationAccessibility));
+    }
     /* }}} */
     /* Set Locale {{{ */
     Locale_ = CFLocaleCopyCurrent();
@@ -9261,11 +9473,15 @@ 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];