]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Move Source{Cell,Controller} lower in MobileCydia.mm.
[cydia.git] / MobileCydia.mm
index 97fa45cff2160c80c556ad094c3a70ab1878fb32..afd02875176c8ed41183b5ba03a9c1a96bcd23de 100644 (file)
@@ -1346,6 +1346,7 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
     pkgSourceList *list_;
 
     SourceMap sources_;
+    CFMutableArrayRef deadSources_;
     CFMutableArrayRef packages_;
 
     _transient NSObject<ConfigurationDelegate, ProgressDelegate> *delegate_;
@@ -2478,12 +2479,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
 
@@ -3040,6 +3041,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_);
@@ -3157,6 +3160,7 @@ static NSString *Warning_;
             capacity += 1024;
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL);
+        deadSources_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
 
         int fds[2];
 
@@ -3231,6 +3235,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;
 }
 
@@ -3337,7 +3342,9 @@ static NSString *Warning_;
     ++era_;
 
     [self releasePackages];
+
     sources_.clear();
+    CFArrayRemoveAllValues(deadSources_);
 
     _error->Discard();
 
@@ -3427,14 +3434,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]);
     }
 
     {
@@ -3685,9 +3697,13 @@ static NSString *Warning_;
 }
 
 - (NSString *) mappedSectionForPointer:(const char *)section {
-    _H<NSString> &mapped(sections_[section]);
+    _H<NSString> *mapped;
+
+    _profile(Database$mappedSectionForPointer$Cache)
+        mapped = &sections_[section];
+    _end
 
-    if (mapped == NULL) {
+    if (*mapped == NULL) {
         size_t length(strlen(section));
         char spaced[length + 1];
 
@@ -3704,9 +3720,11 @@ static NSString *Warning_;
         _end
 
         _profile(Database$mappedSectionForPointer$Map)
-            mapped = [SectionMap_ objectForKey:string] ?: string;
+            string = [SectionMap_ objectForKey:string] ?: string;
         _end
-    } return mapped;
+
+        *mapped = string;
+    } return *mapped;
 }
 
 @end
@@ -4869,7 +4887,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (PackageCell *) init;
 - (void) setPackage:(Package *)package;
 
-+ (int) heightForPackage:(Package *)package;
 - (void) drawContentRect:(CGRect)rect;
 
 @end
@@ -5049,10 +5066,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [placard_ drawAtPoint:CGPointMake(width - 52, 9)];
 }
 
-+ (int) heightForPackage:(Package *)package {
-    return 73;
-}
-
 @end
 /* }}} */
 /* Section Cell {{{ */
@@ -5176,7 +5189,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) drawContentRect:(CGRect)rect {
-    bool highlighted(highlighted_);
+    bool highlighted(highlighted_ && !editing_);
 
     [icon_ drawInRect:CGRectMake(8, 7, 32, 32)];
 
@@ -5614,10 +5627,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [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 {
     Package *package([self packageAtIndexPath:path]);
     package = [database_ packageWithName:[package id]];
@@ -5655,7 +5664,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
         list_ = [[UITableView alloc] initWithFrame:[self bounds] style:UITableViewStylePlain];
         [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:73.0f];
+        [list_ setRowHeight:73];
         [self addSubview:list_];
 
         [list_ setDataSource:self];
@@ -5898,1989 +5907,1985 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 /* }}} */
 
-/* Source Cell {{{ */
-@interface SourceCell : CYTableViewCell <
-    ContentDelegate
-> {
-    UIImage *icon_;
-    NSString *origin_;
-    NSString *description_;
-    NSString *label_;
-}
-
-- (void) setSource:(Source *)source;
-
-@end
-
-@implementation SourceCell
-
-- (void) clearSource {
-    [icon_ release];
-    [origin_ release];
-    [description_ release];
-    [label_ release];
-
-    icon_ = nil;
-    origin_ = nil;
-    description_ = nil;
-    label_ = nil;
+/* Installed Controller {{{ */
+@interface InstalledController : FilteredPackageController {
+    BOOL expert_;
 }
 
-- (void) setSource:(Source *)source {
-    [self clearSource];
+- (id) initWithDatabase:(Database *)database;
 
-    if (icon_ == nil)
-        icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]];
-    if (icon_ == nil)
-        icon_ = [UIImage applicationImageNamed:@"unknown.png"];
-    icon_ = [icon_ retain];
+- (void) updateRoleButton;
+- (void) queueStatusDidChange;
 
-    origin_ = [[source name] retain];
-    label_ = [[source uri] retain];
-    description_ = [[source description] retain];
+@end
 
-    [content_ setNeedsDisplay];
-}
+@implementation InstalledController
 
 - (void) dealloc {
-    [self clearSource];
     [super dealloc];
 }
 
-- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
-    if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) {
-        UIView *content([self contentView]);
-        CGRect bounds([content bounds]);
-
-        content_ = [[ContentView alloc] initWithFrame:bounds];
-        [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [content_ setBackgroundColor:[UIColor whiteColor]];
-        [content addSubview:content_];
+- (NSString *) title { return UCLocalize("INSTALLED"); }
 
-        [content_ setDelegate:self];
-        [content_ setOpaque:YES];
+- (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;
 }
 
-- (void) drawContentRect:(CGRect)rect {
-    bool highlighted(highlighted_);
-    float width(rect.size.width);
+#if !AlwaysReload
+- (void) queueButtonClicked {
+    [delegate_ queue];
+}
+#endif
 
-    if (icon_ != nil)
-        [icon_ drawInRect:CGRectMake(10, 10, 30, 30)];
+- (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
+}
 
-    if (highlighted)
-        UISetColor(White_);
+- (void) reloadData {
+    [packages_ reloadData];
+}
 
-    if (!highlighted)
-        UISetColor(Black_);
-    [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - 80) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation];
+- (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]];
+}
 
-    if (!highlighted)
-        UISetColor(Blue_);
-    [label_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation];
+- (void) roleButtonClicked {
+    [packages_ setObject:[NSNumber numberWithBool:expert_]];
+    [packages_ reloadData];
+    expert_ = !expert_;
 
-    if (!highlighted)
-        UISetColor(Gray_);
-    [description_ drawAtPoint:CGPointMake(12, 46) forWidth:(width - 40) withFont:Font14_ lineBreakMode:UILineBreakModeTailTruncation];
+    [self updateRoleButton];
+}
+
+- (void) setDelegate:(id)delegate {
+    [super setDelegate:delegate];
+    [packages_ setDelegate:delegate];
 }
 
 @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_;
+/* Home Controller {{{ */
+@interface HomeController : CYBrowserController {
 }
 
-- (id) initWithDatabase:(Database *)database;
-
-- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
-
 @end
 
-@implementation SourceController
+@implementation HomeController
 
-- (void) _releaseConnection:(NSURLConnection *)connection {
-    if (connection != nil) {
-        [connection cancel];
-        //[connection setDelegate:nil];
-        [connection release];
-    }
++ (BOOL)shouldHideNavigationBar {
+    return NO;
 }
 
-- (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_];
+- (void) _setMoreHeaders:(NSMutableURLRequest *)request {
+    [super _setMoreHeaders:request];
 
-    [sources_ release];
-    [list_ release];
-    [super dealloc];
+    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"];
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
-}
+- (void) aboutButtonClicked {
+    UIAlertView *alert([[[UIAlertView alloc] init] autorelease]);
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
-    return offset_ == 0 ? 1 : 2;
-}
+    [alert setTitle:UCLocalize("ABOUT_CYDIA")];
+    [alert addButtonWithTitle:UCLocalize("CLOSE")];
+    [alert setCancelButtonIndex:0];
 
-- (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");
+    [alert setMessage:
+        @"Copyright (C) 2008-2010\n"
+        "Jay Freeman (saurik)\n"
+        "saurik@saurik.com\n"
+        "http://www.saurik.com/"
+    ];
 
-        _nodefault
-    }
+    [alert show];
 }
 
-- (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) viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
 
-        _nodefault
-    }
+    if ([[self class] shouldHideNavigationBar])
+        [[self navigationController] setNavigationBarHidden:YES animated:animated];
 }
 
-- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath {
-    unsigned idx = 0;
-    switch (indexPath.section) {
-        case 0: idx = indexPath.row; break;
-        case 1: idx = indexPath.row + offset_; break;
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
 
-        _nodefault
-    }
-    return [sources_ objectAtIndex:idx];
+    if ([[self class] shouldHideNavigationBar])
+        [[self navigationController] setNavigationBarHidden:NO animated:animated];
 }
 
-- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    return [source description] == nil ? 56 : 73;
+- (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;
 }
 
-- (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]];
+@end
+/* }}} */
+/* Manage Controller {{{ */
+@interface ManageController : CYBrowserController {
+}
 
-    return cell;
+- (void) queueStatusDidChange;
+@end
+
+@implementation ManageController
+
+- (id) init {
+    if ((self = [super init]) != nil) {
+        [[self navigationItem] setTitle:UCLocalize("MANAGE")];
+
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("SETTINGS")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(settingsButtonClicked)
+        ] autorelease]];
+
+        [self queueStatusDidChange];
+    } return self;
 }
 
-- (UITableViewCellAccessoryType) tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
-    return UITableViewCellAccessoryDisclosureIndicator;
+- (void) settingsButtonClicked {
+    [delegate_ showSettings];
 }
 
-- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
+#if !AlwaysReload
+- (void) queueButtonClicked {
+    [delegate_ queue];
+}
 
-    FilteredPackageController *packages = [[[FilteredPackageController alloc]
-        initWithDatabase:database_
-        title:[source label]
-        filter:@selector(isVisibleInSource:)
-        with:source
-    ] autorelease];
+- (void) applyLoadingTitle {
+    // No "Loading" title.
+}
 
-    [packages setDelegate:delegate_];
+- (void) applyRightButton {
+    // No right button.
+}
+#endif
 
-    [[self navigationController] pushViewController:packages animated:YES];
+- (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
 }
 
-- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    return [source record] != nil;
+- (bool) isLoading {
+    return false;
 }
 
-- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
-    Source *source = [self sourceAtIndexPath:indexPath];
-    [Sources_ removeObjectForKey:[source key]];
-    [delegate_ syncData];
+@end
+/* }}} */
+
+/* Refresh Bar {{{ */
+@interface RefreshBar : UINavigationBar {
+    UIProgressIndicator *indicator_;
+    UITextLabel *prompt_;
+    UIProgressBar *progress_;
+    UINavigationButton *cancel_;
 }
 
-- (void) complete {
-    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
-        @"deb", @"Type",
-        href_, @"URI",
-        @"./", @"Distribution",
-    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
+@end
 
-    [delegate_ syncData];
+@implementation RefreshBar
+
+- (void) dealloc {
+    [indicator_ release];
+    [prompt_ release];
+    [progress_ release];
+    [cancel_ release];
+    [super dealloc];
 }
 
-- (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];
+- (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];
 
-    NSURL *url([NSURL URLWithString:href]);
+    CGSize prgsize = {75, 100};
+    CGRect prgrect = {{
+        [self frame].size.width - prgsize.width - 10,
+        ([self frame].size.height - prgsize.height) / 2
+    } , prgsize};
+    [progress_ setFrame:prgrect];
 
-    NSStringEncoding encoding;
-    NSError *error(nil);
+    CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]);
+    unsigned indoffset = ([self frame].size.height - indsize.height) / 2;
+    CGRect indrect = {{indoffset, indoffset}, indsize};
+    [indicator_ setFrame:indrect];
 
-    if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error])
-        return [warning length] == 0 ? nil : warning;
-    return nil;
+    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) _endConnection:(NSURLConnection *)connection {
-    // XXX: the memory management in this method is horribly awkward
+- (void)setFrame:(CGRect)frame {
+    [super setFrame:frame];
 
-    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;
+    [self positionViews];
+}
 
-    if (
-        trivial_ == nil &&
-        trivial_bz2_ == nil &&
-        trivial_gz_ == nil
-    ) {
-        bool defer(false);
+- (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
+    if ((self = [super initWithFrame:frame])) {
+        [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
 
-        if (cydia_) {
-            if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) {
-                defer = true;
+        [self setBarStyle:UIBarStyleBlack];
 
-                UIAlertView *alert = [[[UIAlertView alloc]
-                    initWithTitle:UCLocalize("SOURCE_WARNING")
-                    message:warning
-                    delegate:self
-                    cancelButtonTitle:UCLocalize("CANCEL")
-                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
-                ] autorelease];
+        UIBarStyle barstyle([self _barStyle:NO]);
+        bool ugly(barstyle == UIBarStyleDefault);
 
-                [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];
+        UIProgressIndicatorStyle style = ugly ?
+            UIProgressIndicatorStyleMediumBrown :
+            UIProgressIndicatorStyleMediumWhite;
 
-            [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];
+        indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectZero];
+        [indicator_ setStyle:style];
+        [indicator_ startAnimation];
+        [self addSubview:indicator_];
 
-            [alert setContext:@"trivial"];
-            [alert show];
-        }
+        prompt_ = [[UITextLabel alloc] initWithFrame:CGRectZero];
+        [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
+        [prompt_ setBackgroundColor:[UIColor clearColor]];
+        [prompt_ setFont:[UIFont systemFontOfSize:15]];
+        [self addSubview:prompt_];
 
-        [delegate_ setStatusBarShowsProgress:NO];
-        [delegate_ removeProgressHUD:hud_];
+        progress_ = [[UIProgressBar alloc] initWithFrame:CGRectZero];
+        [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin];
+        [progress_ setStyle:0];
+        [self addSubview:progress_];
 
-        [hud_ autorelease];
-        hud_ = nil;
+        cancel_ = [[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted];
+        [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+        [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside];
+        [cancel_ setBarStyle:barstyle];
 
-        if (!defer) {
-            [href_ release];
-            href_ = nil;
-        }
+        [self positionViews];
+    } return self;
+}
 
-        if (error_ != nil) {
-            [error_ release];
-            error_ = nil;
-        }
-    }
+- (void) cancel {
+    [cancel_ removeFromSuperview];
 }
 
-- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
-    switch ([response statusCode]) {
-        case 200:
-            cydia_ = YES;
-    }
+- (void) start {
+    [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
+    [progress_ setProgress:0];
+    [self addSubview:cancel_];
 }
 
-- (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) stop {
+    [cancel_ removeFromSuperview];
 }
 
-- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
-    [self _endConnection:connection];
+- (void) setPrompt:(NSString *)prompt {
+    [prompt_ setText:prompt];
 }
 
-- (NSString *) title { return UCLocalize("SOURCES"); }
+- (void) setProgress:(float)progress {
+    [progress_ setProgress:progress];
+}
 
-- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
-    NSMutableURLRequest *request = [NSMutableURLRequest
-        requestWithURL:[NSURL URLWithString:href]
-        cachePolicy:NSURLRequestUseProtocolCachePolicy
-        timeoutInterval:120.0
-    ];
-
-    [request setHTTPMethod:method];
-
-    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 [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
-}
-
-- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
-    NSString *context([alert context]);
-
-    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];
+@end
+/* }}} */
 
-                cydia_ = false;
+@class CYNavigationController;
 
-                // XXX: this is stupid
-                hud_ = [[delegate_ addProgressHUD] retain];
-                [hud_ setText:UCLocalize("VERIFYING_URL")];
-            } break;
+/* Cydia Tab Bar Controller {{{ */
+@interface CYTabBarController : UITabBarController <
+    ProgressDelegate
+> {
+    _transient Database *database_;
+    RefreshBar *refreshbar_;
 
-            case 0:
-            break;
+    bool dropped_;
+    bool updating_;
+    // XXX: ok, "updatedelegate_"?...
+    _transient NSObject<CydiaDelegate> *updatedelegate_;
 
-            _nodefault
-        }
+    id root_;
+}
 
-        [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) dropBar:(BOOL)animated;
+- (void) beginUpdate;
+- (void) raiseBar:(BOOL)animated;
+- (BOOL) updating;
 
-            case 0:
-            break;
+@end
 
-            _nodefault
-        }
+@implementation CYTabBarController
 
-        [href_ release];
-        href_ = nil;
+/* XXX: some logic should probably go here related to
+freeing the view controllers on tab change */
 
-        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+- (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];
     }
 }
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("SOURCES")];
-        [self updateButtonsForEditingStatus:NO animated:NO];
-
         database_ = database;
-        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
-
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:list_];
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+        [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
 
-        [self reloadData];
+        refreshbar_ = [[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self];
     } return self;
 }
 
-- (void) 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];
+- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
+    return IsWildcat_ || orientation == UIInterfaceOrientationPortrait;
 }
 
-- (void) addButtonClicked {
-    /*[book_ pushPage:[[[AddSourceController alloc]
-        initWithBook:book_
-        database:database_
-    ] autorelease]];*/
+- (void) setUpdate:(NSDate *)date {
+    [self beginUpdate];
+}
 
-    UIAlertView *alert = [[[UIAlertView alloc]
-        initWithTitle:UCLocalize("ENTER_APT_URL")
-        message:nil
-        delegate:self
-        cancelButtonTitle:UCLocalize("CANCEL")
-        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
-    ] autorelease];
+- (void) beginUpdate {
+    [refreshbar_ start];
+    [self dropBar:YES];
 
-    [alert setContext:@"source"];
-    [alert setTransform:CGAffineTransformTranslate([alert transform], 0.0, 100.0)];
+    [updatedelegate_ retainNetworkActivityIndicator];
+    updating_ = true;
 
-    [alert setNumberOfRows:1];
-    [alert addTextFieldWithValue:@"http://" label:@""];
+    [NSThread
+        detachNewThreadSelector:@selector(performUpdate)
+        toTarget:self
+        withObject:nil
+    ];
+}
 
-    UITextInputTraits *traits = [[alert textField] textInputTraits];
-    [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
-    [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
-    [traits setKeyboardType:UIKeyboardTypeURL];
-    // XXX: UIReturnKeyDone
-    [traits setReturnKeyType:UIReturnKeyNext];
+- (void) performUpdate { _pooled
+    Status status;
+    status.setDelegate(self);
+    [database_ updateWithStatus:status];
 
-    [alert show];
+    [self
+        performSelectorOnMainThread:@selector(completeUpdate)
+        withObject:nil
+        waitUntilDone:NO
+    ];
 }
 
-- (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];
+- (void) stopUpdateWithSelector:(SEL)selector {
+    updating_ = false;
+    [updatedelegate_ releaseNetworkActivityIndicator];
 
-    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-        initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
-        style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
-        target:self
-        action:@selector(editButtonClicked)
-    ] autorelease] animated:animated];
+    [self raiseBar:YES];
+    [refreshbar_ stop];
 
-    if (IsWildcat_ && !editing)
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
+    [updatedelegate_ performSelector:selector withObject:nil afterDelay:0];
 }
 
-- (void) settingsButtonClicked {
-    [delegate_ showSettings];
+- (void) completeUpdate {
+    if (!updating_)
+        return;
+    [self stopUpdateWithSelector:@selector(reloadData)];
 }
 
-- (void) editButtonClicked {
-    [list_ setEditing:![list_ isEditing] animated:YES];
-
-    [self updateButtonsForEditingStatus:[list_ isEditing] animated:YES];
+- (void) cancelUpdate {
+    [self stopUpdateWithSelector:@selector(updateData)];
 }
 
-@end
-/* }}} */
-
-/* Installed Controller {{{ */
-@interface InstalledController : FilteredPackageController {
-    BOOL expert_;
+- (void) cancelPressed {
+    [self cancelUpdate];
 }
 
-- (id) initWithDatabase:(Database *)database;
-
-- (void) updateRoleButton;
-- (void) queueStatusDidChange;
-
-@end
-
-@implementation InstalledController
+- (BOOL) updating {
+    return updating_;
+}
 
-- (void) dealloc {
-    [super dealloc];
+- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
+    [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]];
 }
 
-- (NSString *) title { return UCLocalize("INSTALLED"); }
+- (void) startProgress {
+}
 
-- (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;
+- (void) setProgressTitle:(NSString *)title {
+    [self
+        performSelectorOnMainThread:@selector(_setProgressTitle:)
+        withObject:title
+        waitUntilDone:YES
+    ];
 }
 
-#if !AlwaysReload
-- (void) queueButtonClicked {
-    [delegate_ queue];
+- (bool) isCancelling:(size_t)received {
+    return !updating_;
 }
-#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
+- (void) setProgressPercent:(float)percent {
+    [self
+        performSelectorOnMainThread:@selector(_setProgressPercent:)
+        withObject:[NSNumber numberWithFloat:percent]
+        waitUntilDone:YES
+    ];
 }
 
-- (void) reloadData {
-    [packages_ reloadData];
+- (void) addProgressOutput:(NSString *)output {
+    [self
+        performSelectorOnMainThread:@selector(_addProgressOutput:)
+        withObject:output
+        waitUntilDone:YES
+    ];
 }
 
-- (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]];
+- (void) _setProgressTitle:(NSString *)title {
+    [refreshbar_ setPrompt:title];
 }
 
-- (void) roleButtonClicked {
-    [packages_ setObject:[NSNumber numberWithBool:expert_]];
-    [packages_ reloadData];
-    expert_ = !expert_;
-
-    [self updateRoleButton];
+- (void) _setProgressPercent:(NSNumber *)percent {
+    [refreshbar_ setProgress:[percent floatValue]];
 }
 
-- (void) setDelegate:(id)delegate {
-    [super setDelegate:delegate];
-    [packages_ setDelegate:delegate];
+- (void) _addProgressOutput:(NSString *)output {
 }
 
-@end
-/* }}} */
-
-/* Home Controller {{{ */
-@interface HomeController : CYBrowserController {
+- (void) setUpdateDelegate:(id)delegate {
+    updatedelegate_ = delegate;
 }
 
-@end
-
-@implementation HomeController
+- (CGFloat) statusBarHeight {
+    if (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) {
+        return [[UIApplication sharedApplication] statusBarFrame].size.height;
+    } else {
+        return [[UIApplication sharedApplication] statusBarFrame].size.width;
+    }
+}
 
-+ (BOOL)shouldHideNavigationBar {
-    return NO;
+- (UIView *) transitionView {
+    if ([self respondsToSelector:@selector(_transitionView)])
+        return [self _transitionView];
+    else
+        return MSHookIvar<id>(self, "_viewControllerTransitionView");
 }
 
-- (void) _setMoreHeaders:(NSMutableURLRequest *)request {
-    [super _setMoreHeaders:request];
+- (void) dropBar:(BOOL)animated {
+    if (dropped_)
+        return;
+    dropped_ = true;
 
-    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"];
-}
+    UIView *transition([self transitionView]);
+    [[self view] addSubview:refreshbar_];
 
-- (void) aboutButtonClicked {
-    UIAlertView *alert([[[UIAlertView alloc] init] autorelease]);
+    CGRect barframe([refreshbar_ frame]);
 
-    [alert setTitle:UCLocalize("ABOUT_CYDIA")];
-    [alert addButtonWithTitle:UCLocalize("CLOSE")];
-    [alert setCancelButtonIndex:0];
+    if (false) // XXX: _UIApplicationLinkedOnOrAfter(4)
+        barframe.origin.y = [self statusBarHeight];
+    else
+        barframe.origin.y = 0;
 
-    [alert setMessage:
-        @"Copyright (C) 2008-2010\n"
-        "Jay Freeman (saurik)\n"
-        "saurik@saurik.com\n"
-        "http://www.saurik.com/"
-    ];
+    [refreshbar_ setFrame:barframe];
 
-    [alert show];
-}
+    if (animated)
+        [UIView beginAnimations:nil context:NULL];
 
-- (void) viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
+    CGRect viewframe = [transition frame];
+    viewframe.origin.y += barframe.size.height;
+    viewframe.size.height -= barframe.size.height;
+    [transition setFrame:viewframe];
 
-    if ([[self class] shouldHideNavigationBar])
-        [[self navigationController] setNavigationBarHidden:YES animated:animated];
-}
+    if (animated)
+        [UIView commitAnimations];
 
-- (void) viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
+    // Ensure bar has the proper width for our view, it might have changed
+    barframe.size.width = viewframe.size.width;
+    [refreshbar_ setFrame:barframe];
 
-    if ([[self class] shouldHideNavigationBar])
-        [[self navigationController] setNavigationBarHidden:NO animated:animated];
+    // XXX: fix Apple's layout bug
+    [[root_ selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
 }
 
-- (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) raiseBar:(BOOL)animated {
+    if (!dropped_)
+        return;
+    dropped_ = false;
 
-@end
-/* }}} */
-/* Manage Controller {{{ */
-@interface ManageController : CYBrowserController {
-}
+    UIView *transition([self transitionView]);
+    [refreshbar_ removeFromSuperview];
 
-- (void) queueStatusDidChange;
-@end
+    CGRect barframe([refreshbar_ frame]);
 
-@implementation ManageController
+    if (animated)
+        [UIView beginAnimations:nil context:NULL];
 
-- (id) init {
-    if ((self = [super init]) != nil) {
-        [[self navigationItem] setTitle:UCLocalize("MANAGE")];
+    CGRect viewframe = [transition frame];
+    viewframe.origin.y -= barframe.size.height;
+    viewframe.size.height += barframe.size.height;
+    [transition setFrame:viewframe];
 
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("SETTINGS")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(settingsButtonClicked)
-        ] autorelease]];
+    if (animated)
+        [UIView commitAnimations];
 
-        [self queueStatusDidChange];
-    } return self;
+    // 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
 
-#if !AlwaysReload
-- (void) queueButtonClicked {
-    [delegate_ queue];
-}
+- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
+    bool dropped(dropped_);
 
-- (void) applyLoadingTitle {
-    // No "Loading" title.
-}
+    if (dropped)
+        [self raiseBar:NO];
 
-- (void) applyRightButton {
-    // No right button.
+    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
+
+    if (dropped)
+        [self dropBar:NO];
+
+    // XXX: fix Apple's layout bug
+    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
 }
-#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 {
-        [[self navigationItem] setRightBarButtonItem:nil];
+- (void) statusBarFrameChanged:(NSNotification *)notification {
+    if (dropped_) {
+        [self raiseBar:NO];
+        [self dropBar:NO];
     }
-#endif
 }
 
-- (bool) isLoading {
-    return false;
+- (void) dealloc {
+    [refreshbar_ release];
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    [super dealloc];
 }
 
 @end
 /* }}} */
 
-/* Refresh Bar {{{ */
-@interface RefreshBar : UINavigationBar {
-    UIProgressIndicator *indicator_;
-    UITextLabel *prompt_;
-    UIProgressBar *progress_;
-    UINavigationButton *cancel_;
+/* Cydia Navigation Controller {{{ */
+@interface CYNavigationController : UINavigationController {
+    _transient Database *database_;
+    _transient id<UINavigationControllerDelegate> delegate_;
 }
 
-@end
+- (id) initWithDatabase:(Database *)database;
+- (void) reloadData;
 
-@implementation RefreshBar
+@end
 
-- (void) dealloc {
-    [indicator_ release];
-    [prompt_ release];
-    [progress_ release];
-    [cancel_ release];
-    [super dealloc];
-}
 
-- (void) positionViews {
-    CGRect frame = [cancel_ frame];
-    frame.size = [cancel_ sizeThatFits:frame.size];
-    frame.origin.x = [self frame].size.width - frame.size.width - 5;
-    frame.origin.y = ([self frame].size.height - frame.size.height) / 2;
-    [cancel_ setFrame:frame];
+@implementation CYNavigationController
 
-    CGSize prgsize = {75, 100};
-    CGRect prgrect = {{
-        [self frame].size.width - prgsize.width - 10,
-        ([self frame].size.height - prgsize.height) / 2
-    } , prgsize};
-    [progress_ setFrame:prgrect];
+- (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];
+    }
+}
 
-    CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]);
-    unsigned indoffset = ([self frame].size.height - indsize.height) / 2;
-    CGRect indrect = {{indoffset, indoffset}, indsize};
-    [indicator_ setFrame:indrect];
+- (void) dealloc {
+    [super dealloc];
+}
 
-    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) 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)setFrame:(CGRect)frame {
-    [super setFrame:frame];
+- (void) setDelegate:(id<UINavigationControllerDelegate>)delegate {
+    delegate_ = delegate;
+}
 
-    [self positionViews];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+    } return self;
 }
 
-- (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
-    if ((self = [super initWithFrame:frame])) {
-        [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
+@end
+/* }}} */
+/* Cydia:// Protocol {{{ */
+@interface CydiaURLProtocol : NSURLProtocol {
+}
 
-        [self setBarStyle:UIBarStyleBlack];
+@end
 
-        UIBarStyle barstyle([self _barStyle:NO]);
-        bool ugly(barstyle == UIBarStyleDefault);
+@implementation CydiaURLProtocol
 
-        UIProgressIndicatorStyle style = ugly ?
-            UIProgressIndicatorStyleMediumBrown :
-            UIProgressIndicatorStyleMediumWhite;
++ (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;
+}
 
-        indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectZero];
-        [indicator_ setStyle:style];
-        [indicator_ startAnimation];
-        [self addSubview:indicator_];
++ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
+    return request;
+}
 
-        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) _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));
 
-        progress_ = [[UIProgressBar alloc] initWithFrame:CGRectZero];
-        [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin];
-        [progress_ setStyle:0];
-        [self addSubview:progress_];
+        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];
+    }
+}
 
-        cancel_ = [[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted];
-        [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside];
-        [cancel_ setBarStyle:barstyle];
+- (void) startLoading {
+    id<NSURLProtocolClient> client([self client]);
+    NSURLRequest *request([self request]);
 
-        [self positionViews];
-    } return self;
-}
+    NSURL *url([request URL]);
+    NSString *href([url absoluteString]);
 
-- (void) cancel {
-    [cancel_ removeFromSuperview];
-}
+    NSString *path([href substringFromIndex:8]);
+    NSRange slash([path rangeOfString:@"/"]);
 
-- (void) start {
-    [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
-    [progress_ setProgress:0];
-    [self addSubview:cancel_];
-}
+    NSString *command;
+    if (slash.location == NSNotFound) {
+        command = path;
+        path = nil;
+    } else {
+        command = [path substringToIndex:slash.location];
+        path = [path substringFromIndex:(slash.location + 1)];
+    }
 
-- (void) stop {
-    [cancel_ removeFromSuperview];
-}
+    Database *database([Database sharedInstance]);
 
-- (void) setPrompt:(NSString *)prompt {
-    [prompt_ setText:prompt];
+    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) setProgress:(float)progress {
-    [progress_ setProgress:progress];
+- (void) stopLoading {
 }
 
 @end
 /* }}} */
 
-@class CYNavigationController;
-
-/* Cydia Tab Bar Controller {{{ */
-@interface CYTabBarController : UITabBarController <
-    ProgressDelegate
+/* Sections Controller {{{ */
+@interface CYSectionsController : CYViewController <
+    UITableViewDataSource,
+    UITableViewDelegate
 > {
     _transient Database *database_;
-    RefreshBar *refreshbar_;
-
-    bool dropped_;
-    bool updating_;
-    // XXX: ok, "updatedelegate_"?...
-    _transient NSObject<CydiaDelegate> *updatedelegate_;
-
-    id root_;
+    NSMutableArray *sections_;
+    NSMutableArray *filtered_;
+    UITableView *list_;
+    UIView *accessory_;
+    BOOL editing_;
 }
 
-- (void) dropBar:(BOOL)animated;
-- (void) beginUpdate;
-- (void) raiseBar:(BOOL)animated;
-- (BOOL) updating;
+- (id) initWithDatabase:(Database *)database;
+- (void) reloadData;
+- (void) resetView;
+
+- (void) editButtonClicked;
 
 @end
 
-@implementation CYTabBarController
+@implementation CYSectionsController
 
-/* XXX: some logic should probably go here related to
-freeing the view controllers on tab change */
+- (void) dealloc {
+    [list_ setDataSource:nil];
+    [list_ setDelegate:nil];
 
-- (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];
-    }
+    [sections_ release];
+    [filtered_ release];
+    [list_ release];
+    [accessory_ release];
+    [super dealloc];
 }
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        database_ = database;
+- (void) setEditing:(BOOL)editing {
+    if ((editing_ = editing))
+        [list_ reloadData];
+    else
+        [delegate_ updateData];
 
-        [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
+    [[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];
+}
 
-        refreshbar_ = [[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self];
-    } return self;
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
 }
 
-- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
-    return IsWildcat_ || orientation == UIInterfaceOrientationPortrait;
+- (void) viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    if (editing_) [self setEditing:NO];
 }
 
-- (void) setUpdate:(NSDate *)date {
-    [self beginUpdate];
+- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath {
+    Section *section = (editing_ ? [sections_ objectAtIndex:[indexPath row]] : ([indexPath row] == 0 ? nil : [filtered_ objectAtIndex:([indexPath row] - 1)]));
+    return section;
 }
 
-- (void) beginUpdate {
-    [refreshbar_ start];
-    [self dropBar:YES];
+- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    return editing_ ? [sections_ count] : [filtered_ count] + 1;
+}
 
-    [updatedelegate_ retainNetworkActivityIndicator];
-    updating_ = true;
+/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+    return 45.0f;
+}*/
 
-    [NSThread
-        detachNewThreadSelector:@selector(performUpdate)
-        toTarget:self
-        withObject:nil
-    ];
-}
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    static NSString *reuseIdentifier = @"SectionCell";
 
-- (void) performUpdate { _pooled
-    Status status;
-    status.setDelegate(self);
-    [database_ updateWithStatus:status];
+    SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
+    if (cell == nil)
+        cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
 
-    [self
-        performSelectorOnMainThread:@selector(completeUpdate)
-        withObject:nil
-        waitUntilDone:NO
-    ];
+    [cell setSection:[self sectionAtIndexPath:indexPath] editing:editing_];
+
+    return cell;
 }
 
-- (void) stopUpdateWithSelector:(SEL)selector {
-    updating_ = false;
-    [updatedelegate_ releaseNetworkActivityIndicator];
+- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (editing_)
+        return;
 
-    [self raiseBar:YES];
-    [refreshbar_ stop];
+    Section *section = [self sectionAtIndexPath:indexPath];
+    NSString *name = [section name];
+    NSString *title;
 
-    [updatedelegate_ performSelector:selector withObject:nil afterDelay:0];
-}
+    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");
+        }
+    }
 
-- (void) completeUpdate {
-    if (!updating_)
-        return;
-    [self stopUpdateWithSelector:@selector(reloadData)];
-}
+    FilteredPackageController *table = [[[FilteredPackageController alloc]
+        initWithDatabase:database_
+        title:title
+        filter:@selector(isVisibleInSection:)
+        with:name
+    ] autorelease];
 
-- (void) cancelUpdate {
-    [self stopUpdateWithSelector:@selector(updateData)];
-}
+    [table setDelegate:delegate_];
 
-- (void) cancelPressed {
-    [self cancelUpdate];
+    [[self navigationController] pushViewController:table animated:YES];
 }
 
-- (BOOL) updating {
-    return updating_;
-}
+- (NSString *) title { return UCLocalize("SECTIONS"); }
 
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]];
-}
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        database_ = database;
 
-- (void) startProgress {
-}
+        [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
 
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
-}
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+        filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-- (bool) isCancelling:(size_t)received {
-    return !updating_;
-}
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
+        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [list_ setRowHeight:45.0f];
+        [[self view] addSubview:list_];
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
-}
+        [list_ setDataSource:self];
+        [list_ setDelegate:self];
 
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
+        [self reloadData];
+    } return self;
 }
 
-- (void) _setProgressTitle:(NSString *)title {
-    [refreshbar_ setPrompt:title];
-}
+- (void) reloadData {
+    NSArray *packages = [database_ packages];
 
-- (void) _setProgressPercent:(NSNumber *)percent {
-    [refreshbar_ setProgress:[percent floatValue]];
-}
+    [sections_ removeAllObjects];
+    [filtered_ removeAllObjects];
 
-- (void) _addProgressOutput:(NSString *)output {
-}
+    NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]);
 
-- (void) setUpdateDelegate:(id)delegate {
-    updatedelegate_ = delegate;
-}
+    _trace();
+    for (Package *package in packages) {
+        NSString *name([package section]);
+        NSString *key(name == nil ? @"" : name);
 
-- (CGFloat) statusBarHeight {
-    if (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) {
-        return [[UIApplication sharedApplication] statusBarFrame].size.height;
-    } else {
-        return [[UIApplication sharedApplication] statusBarFrame].size.width;
-    }
-}
+        Section *section;
 
-- (UIView *) transitionView {
-    if ([self respondsToSelector:@selector(_transitionView)])
-        return [self _transitionView];
-    else
-        return MSHookIvar<id>(self, "_viewControllerTransitionView");
-}
+        _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
 
-- (void) dropBar:(BOOL)animated {
-    if (dropped_)
-        return;
-    dropped_ = true;
+        [section addToCount];
 
-    UIView *transition([self transitionView]);
-    [[self view] addSubview:refreshbar_];
+        _profile(SectionsView$reloadData$Filter)
+            if (![package valid] || ![package visible])
+                continue;
+        _end
 
-    CGRect barframe([refreshbar_ frame]);
+        [section addToRow];
+    }
+    _trace();
 
-    if (false) // XXX: _UIApplicationLinkedOnOrAfter(4)
-        barframe.origin.y = [self statusBarHeight];
-    else
-        barframe.origin.y = 0;
+    [sections_ addObjectsFromArray:[sections allValues]];
 
-    [refreshbar_ setFrame:barframe];
+    [sections_ sortUsingSelector:@selector(compareByLocalized:)];
 
-    if (animated)
-        [UIView beginAnimations:nil context:NULL];
+    for (Section *section in sections_) {
+        size_t count([section row]);
+        if (count == 0)
+            continue;
 
-    CGRect viewframe = [transition frame];
-    viewframe.origin.y += barframe.size.height;
-    viewframe.size.height -= barframe.size.height;
-    [transition setFrame:viewframe];
+        section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease];
+        [section setCount:count];
+        [filtered_ addObject:section];
+    }
 
-    if (animated)
-        [UIView commitAnimations];
+    [[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)];
 
-    // Ensure bar has the proper width for our view, it might have changed
-    barframe.size.width = viewframe.size.width;
-    [refreshbar_ setFrame:barframe];
+    [list_ reloadData];
+    _trace();
+}
 
-    // XXX: fix Apple's layout bug
-    [[root_ selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
+- (void) resetView {
+    if (editing_)
+        [self editButtonClicked];
 }
 
-- (void) raiseBar:(BOOL)animated {
-    if (!dropped_)
-        return;
-    dropped_ = false;
+- (void)editButtonClicked {
+    [self setEditing:!editing_];
+}
 
-    UIView *transition([self transitionView]);
-    [refreshbar_ removeFromSuperview];
+- (UIView *) accessoryView {
+    return accessory_;
+}
 
-    CGRect barframe([refreshbar_ frame]);
+@end
+/* }}} */
+/* Changes Controller {{{ */
+@interface ChangesController : CYViewController <
+    UITableViewDataSource,
+    UITableViewDelegate
+> {
+    _transient Database *database_;
+    unsigned era_;
+    CFMutableArrayRef packages_;
+    NSMutableArray *sections_;
+    UITableView *list_;
+    unsigned upgrades_;
+    BOOL hasSentFirstLoad_;
+}
 
-    if (animated)
-        [UIView beginAnimations:nil context:NULL];
+- (id) initWithDatabase:(Database *)database delegate:(id)delegate;
+- (void) reloadData;
 
-    CGRect viewframe = [transition frame];
-    viewframe.origin.y -= barframe.size.height;
-    viewframe.size.height += barframe.size.height;
-    [transition setFrame:viewframe];
+@end
 
-    if (animated)
-        [UIView commitAnimations];
+@implementation ChangesController
 
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
-
-#if 0
-- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
-}
-#endif
-
-- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
-    bool dropped(dropped_);
-
-    if (dropped)
-        [self raiseBar:NO];
-
-    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
+- (void) dealloc {
+    [list_ setDelegate:nil];
+    [list_ setDataSource:nil];
 
-    if (dropped)
-        [self dropBar:NO];
+    CFRelease(packages_);
 
-    // XXX: fix Apple's layout bug
-    // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation];
+    [sections_ release];
+    [list_ release];
+    [super dealloc];
 }
 
-- (void) statusBarFrameChanged:(NSNotification *)notification {
-    if (dropped_) {
-        [self raiseBar:NO];
-        [self dropBar:NO];
+- (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) dealloc {
-    [refreshbar_ release];
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
-    [super dealloc];
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
+    NSInteger count([sections_ count]);
+    return count == 0 ? 1 : count;
 }
 
-@end
-/* }}} */
-
-/* Cydia Navigation Controller {{{ */
-@interface CYNavigationController : UINavigationController {
-    _transient Database *database_;
-    _transient id<UINavigationControllerDelegate> delegate_;
+- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
+    if ([sections_ count] == 0)
+        return nil;
+    return [[sections_ objectAtIndex:section] name];
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
+- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section {
+    if ([sections_ count] == 0)
+        return 0;
+    return [[sections_ objectAtIndex:section] count];
+}
 
-@end
+- (Package *) packageAtIndex:(NSUInteger)index {
+    return (Package *) CFArrayGetValueAtIndex(packages_, index);
+}
 
+- (Package *) packageAtIndexPath:(NSIndexPath *)path {
+@synchronized (database_) {
+    if ([database_ era] != era_)
+        return nil;
 
-@implementation CYNavigationController
+    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];
+} }
 
-- (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];
-    }
+- (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) dealloc {
-    [super dealloc];
+- (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;
 }
 
-- (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) refreshButtonClicked {
+    [delegate_ beginUpdate];
+    [[self navigationItem] setLeftBarButtonItem:nil animated:YES];
 }
 
-- (void) setDelegate:(id<UINavigationControllerDelegate>)delegate {
-    delegate_ = delegate;
+- (void) upgradeButtonClicked {
+    [delegate_ distUpgrade];
 }
 
-- (id) initWithDatabase:(Database *)database {
+- (NSString *) title { return UCLocalize("CHANGES"); }
+
+- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
     if ((self = [super init]) != nil) {
         database_ = database;
-    } return self;
-}
+        [[self navigationItem] setTitle:UCLocalize("CHANGES")];
 
-@end
-/* }}} */
-/* Cydia:// Protocol {{{ */
-@interface CydiaURLProtocol : NSURLProtocol {
-}
+        packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
 
-@end
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-@implementation CydiaURLProtocol
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [list_ setRowHeight:73];
+        [[self view] addSubview:list_];
 
-+ (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;
-}
+        [list_ setDataSource:self];
+        [list_ setDelegate:self];
 
-+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
-    return request;
+        delegate_ = delegate;
+    } return self;
 }
 
-- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request {
-    id<NSURLProtocolClient> client([self client]);
-    if (icon == nil)
-        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
-    else {
-        NSData *data(UIImagePNGRepresentation(icon));
+- (void) _reloadPackages:(NSArray *)packages {
+    CFRelease(packages_);
+    packages_ = CFArrayCreateMutable(kCFAllocatorDefault, [packages count], NULL);
 
-        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];
-    }
+    _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();
 }
 
-- (void) startLoading {
-    id<NSURLProtocolClient> client([self client]);
-    NSURLRequest *request([self request]);
+- (void) reloadData {
+@synchronized (database_) {
+    era_ = [database_ era];
+    NSArray *packages = [database_ packages];
 
-    NSURL *url([request URL]);
-    NSString *href([url absoluteString]);
+    [sections_ removeAllObjects];
 
-    NSString *path([href substringFromIndex:8]);
-    NSRange slash([path rangeOfString:@"/"]);
+#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
 
-    NSString *command;
-    if (slash.location == NSNotFound) {
-        command = path;
-        path = nil;
-    } else {
-        command = [path substringToIndex:slash.location];
-        path = [path substringFromIndex:(slash.location + 1)];
-    }
+    Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
+    Section *ignored = nil;
+    Section *section = nil;
+    time_t last = 0;
 
-    Database *database([Database sharedInstance]);
+    upgrades_ = 0;
+    bool unseens = false;
 
-    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]];
-    }
-}
+    CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
 
-- (void) stopLoading {
-}
+    for (size_t offset = 0, count = CFArrayGetCount(packages_); offset != count; ++offset) {
+        Package *package = [self packageAtIndex:offset];
 
-@end
-/* }}} */
+        BOOL uae = [package upgradableAndEssential:YES];
 
-/* Sections Controller {{{ */
-@interface CYSectionsController : CYViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
-> {
-    _transient Database *database_;
-    NSMutableArray *sections_;
-    NSMutableArray *filtered_;
-    UITableView *list_;
-    UIView *accessory_;
-    BOOL editing_;
+        if (!uae) {
+            unseens = true;
+            time_t seen([package seen]);
+
+            if (section == nil || last != seen) {
+                last = seen;
+
+                NSString *name;
+                name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]);
+                [name autorelease];
+
+                _profile(ChangesController$reloadData$Allocate)
+                    name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name];
+                    section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease];
+                    [sections_ addObject:section];
+                _end
+            }
+
+            [section addToCount];
+        } else if ([package ignored]) {
+            if (ignored == nil) {
+                ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease];
+            }
+            [ignored addToCount];
+        } else {
+            ++upgrades_;
+            [upgradable addToCount];
+        }
+    }
+    _trace();
+
+    CFRelease(formatter);
+
+    if (unseens) {
+        Section *last = [sections_ lastObject];
+        size_t count = [last count];
+        CFArrayReplaceValues(packages_, CFRangeMake(CFArrayGetCount(packages_) - count, count), NULL, 0);
+        [sections_ removeLastObject];
+    }
+
+    if ([ignored count] != 0)
+        [sections_ insertObject:ignored atIndex:0];
+    if (upgrades_ != 0)
+        [sections_ insertObject:upgradable atIndex:0];
+
+    [list_ reloadData];
+
+    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]];
+
+    if (![delegate_ updating])
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("REFRESH")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(refreshButtonClicked)
+        ] autorelease]];
+
+    PrintTimes();
+} }
+
+@end
+/* }}} */
+/* Search Controller {{{ */
+@interface SearchController : FilteredPackageController <
+    UISearchBarDelegate
+> {
+    UISearchBar *search_;
 }
 
 - (id) initWithDatabase:(Database *)database;
 - (void) reloadData;
-- (void) resetView;
-
-- (void) editButtonClicked;
 
 @end
 
-@implementation CYSectionsController
+@implementation SearchController
 
 - (void) dealloc {
-    [list_ setDataSource:nil];
-    [list_ setDelegate:nil];
-
-    [sections_ release];
-    [filtered_ release];
-    [list_ release];
-    [accessory_ release];
+    [search_ release];
     [super dealloc];
 }
 
-- (void) setEditing:(BOOL)editing {
-    if ((editing_ = editing))
-        [list_ reloadData];
-    else
-        [delegate_ updateData];
-
-    [[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];
+- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
+    [packages_ setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
+    [search_ resignFirstResponder];
+    [self reloadData];
 }
 
-- (void) viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
+    [packages_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
+    [self reloadData];
 }
 
-- (void) viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
-    if (editing_) [self setEditing:NO];
-}
+- (NSString *) title { return nil; }
 
-- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath {
-    Section *section = (editing_ ? [sections_ objectAtIndex:[indexPath row]] : ([indexPath row] == 0 ? nil : [filtered_ objectAtIndex:([indexPath row] - 1)]));
-    return section;
+- (id) initWithDatabase:(Database *)database {
+    return [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil];
 }
 
-- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    return editing_ ? [sections_ count] : [filtered_ count] + 1;
-}
+- (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")];
 
-/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-    return 45.0f;
-}*/
+        UITextField *textField;
+        if ([search_ respondsToSelector:@selector(searchField)])
+            textField = [search_ searchField];
+        else
+            textField = MSHookIvar<UITextField *>(search_, "_searchField");
 
-- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    static NSString *reuseIdentifier = @"SectionCell";
+        [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
+        [search_ setDelegate:self];
+        [textField setEnablesReturnKeyAutomatically:NO];
+        [[self navigationItem] setTitleView:textField];
+    }
+}
 
-    SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
-    if (cell == nil)
-        cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
+- (void) _reloadData {
+}
 
-    [cell setSection:[self sectionAtIndexPath:indexPath] editing:editing_];
+- (void) reloadData {
+    _profile(SearchController$reloadData)
+        [packages_ reloadData];
+    _end
+    PrintTimes();
+    [packages_ resetCursor];
+}
 
-    return cell;
+- (void) didSelectPackage:(Package *)package {
+    [search_ resignFirstResponder];
+    [super didSelectPackage:package];
 }
 
-- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    if (editing_)
-        return;
+@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_;
+}
 
-    Section *section = [self sectionAtIndexPath:indexPath];
-    NSString *name = [section name];
-    NSString *title;
+- (id) initWithDatabase:(Database *)database package:(NSString *)package;
 
-    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");
-        }
-    }
+@end
 
-    FilteredPackageController *table = [[[FilteredPackageController alloc]
-        initWithDatabase:database_
-        title:title
-        filter:@selector(isVisibleInSection:)
-        with:name
-    ] autorelease];
+@implementation CYPackageSettingsController
 
-    [table setDelegate:delegate_];
+- (void) dealloc {
+    [name_ release];
+    if (package_ != nil)
+        [package_ release];
+    [table_ release];
+    [subscribedSwitch_ release];
+    [ignoredSwitch_ release];
+    [subscribedCell_ release];
+    [ignoredCell_ release];
 
-    [[self navigationController] pushViewController:table animated:YES];
+    [super dealloc];
 }
 
-- (NSString *) title { return UCLocalize("SECTIONS"); }
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
+    if (package_ == nil)
+        return 0;
 
-- (id) initWithDatabase:(Database *)database {
-    if ((self = [super init]) != nil) {
-        database_ = database;
+    return 1;
+}
 
-        [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
+- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    if (package_ == nil)
+        return 0;
 
-        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
-        filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    return 2;
+}
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:45.0f];
-        [[self view] addSubview:list_];
+- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
+    return UCLocalize("SHOW_ALL_CHANGES_EX");
+}
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+- (void) onSubscribed:(id)control {
+    bool value([control isOn]);
+    if (package_ == nil)
+        return;
+    if ([package_ setSubscribed:value])
+        [delegate_ updateData];
+}
 
-        [self reloadData];
-    } return self;
+- (void) onIgnored:(id)control {
+    // TODO: set Held state - possibly call out to dpkg, etc.
 }
 
-- (void) reloadData {
-    NSArray *packages = [database_ packages];
-
-    [sections_ removeAllObjects];
-    [filtered_ removeAllObjects];
-
-    NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]);
-
-    _trace();
-    for (Package *package in packages) {
-        NSString *name([package section]);
-        NSString *key(name == nil ? @"" : name);
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (package_ == nil)
+        return nil;
 
-        Section *section;
+    switch ([indexPath row]) {
+        case 0: return subscribedCell_;
+        case 1: return ignoredCell_;
 
-        _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
+        _nodefault
+    }
 
-        [section addToCount];
+    return nil;
+}
 
-        _profile(SectionsView$reloadData$Filter)
-            if (![package valid] || ![package visible])
-                continue;
-        _end
+- (NSString *) title { return UCLocalize("SETTINGS"); }
 
-        [section addToRow];
-    }
-    _trace();
+- (id) initWithDatabase:(Database *)database package:(NSString *)package {
+    if ((self = [super init])) {
+        database_ = database;
+        name_ = [package retain];
 
-    [sections_ addObjectsFromArray:[sections allValues]];
+        [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
 
-    [sections_ sortUsingSelector:@selector(compareByLocalized:)];
+        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
+        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [[self view] addSubview:table_];
 
-    for (Section *section in sections_) {
-        size_t count([section row]);
-        if (count == 0)
-            continue;
+        subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+        [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
 
-        section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease];
-        [section setCount:count];
-        [filtered_ addObject:section];
-    }
+        ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
+        [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
+        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
 
-    [[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)];
+        subscribedCell_ = [[UITableViewCell alloc] init];
+        [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
+        [subscribedCell_ setAccessoryView:subscribedSwitch_];
+        [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
 
-    [list_ reloadData];
-    _trace();
-}
+        ignoredCell_ = [[UITableViewCell alloc] init];
+        [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
+        [ignoredCell_ setAccessoryView:ignoredSwitch_];
+        [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+        // FIXME: Ignored state is not saved.
+        [ignoredCell_ setUserInteractionEnabled:NO];
 
-- (void) resetView {
-    if (editing_)
-        [self editButtonClicked];
+        [table_ setDataSource:self];
+        [table_ setDelegate:self];
+        [self reloadData];
+    } return self;
 }
 
-- (void)editButtonClicked {
-    [self setEditing:!editing_];
-}
+- (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];
+    }
 
-- (UIView *) accessoryView {
-    return accessory_;
+    [table_ reloadData];
 }
 
 @end
 /* }}} */
-/* Changes Controller {{{ */
-@interface ChangesController : CYViewController <
-    UITableViewDataSource,
-    UITableViewDelegate
-> {
+/* Signature Controller {{{ */
+@interface SignatureController : CYBrowserController {
     _transient Database *database_;
-    unsigned era_;
-    CFMutableArrayRef packages_;
-    NSMutableArray *sections_;
-    UITableView *list_;
-    unsigned upgrades_;
-    BOOL hasSentFirstLoad_;
+    NSString *package_;
 }
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate;
-- (void) reloadData;
+- (id) initWithDatabase:(Database *)database package:(NSString *)package;
 
 @end
 
-@implementation ChangesController
+@implementation SignatureController
 
 - (void) dealloc {
-    [list_ setDelegate:nil];
-    [list_ setDataSource:nil];
-
-    CFRelease(packages_);
-
-    [sections_ release];
-    [list_ release];
+    [package_ release];
     [super dealloc];
 }
 
-- (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) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+    // XXX: dude!
+    [super webView:view didClearWindowObject:window forFrame:frame];
 }
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
-    NSInteger count([sections_ count]);
-    return count == 0 ? 1 : count;
+- (id) initWithDatabase:(Database *)database package:(NSString *)package {
+    if ((self = [super init]) != nil) {
+        database_ = database;
+        package_ = [package retain];
+        [self reloadData];
+    } return self;
 }
 
-- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
-        return nil;
-    return [[sections_ objectAtIndex:section] name];
+- (void) reloadData {
+    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
 }
 
-- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section {
-    if ([sections_ count] == 0)
-        return 0;
-    return [[sections_ objectAtIndex:section] count];
-}
+@end
+/* }}} */
 
-- (Package *) packageAtIndex:(NSUInteger)index {
-    return (Package *) CFArrayGetValueAtIndex(packages_, index);
+/* Source Cell {{{ */
+@interface SourceCell : CYTableViewCell <
+    ContentDelegate
+> {
+    UIImage *icon_;
+    NSString *origin_;
+    NSString *label_;
 }
 
-- (Package *) packageAtIndexPath:(NSIndexPath *)path {
-@synchronized (database_) {
-    if ([database_ era] != era_)
-        return nil;
-
-    Section *section([sections_ objectAtIndex:[path section]]);
-    NSInteger row([path row]);
-    return [[[self packageAtIndex:([section row] + row)] retain] autorelease];
-} }
+- (void) setSource:(Source *)source;
 
-- (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;
-}
+@end
 
-/*- (CGFloat) tableView:(UITableView *)table heightForRowAtIndexPath:(NSIndexPath *)path {
-    return [PackageCell heightForPackage:[self packageAtIndexPath:path]];
-}*/
+@implementation SourceCell
 
-- (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;
-}
+- (void) clearSource {
+    [icon_ release];
+    [origin_ release];
+    [label_ release];
 
-- (void) refreshButtonClicked {
-    [delegate_ beginUpdate];
-    [[self navigationItem] setLeftBarButtonItem:nil animated:YES];
+    icon_ = nil;
+    origin_ = nil;
+    label_ = nil;
 }
 
-- (void) upgradeButtonClicked {
-    [delegate_ distUpgrade];
-}
+- (void) setSource:(Source *)source {
+    [self clearSource];
 
-- (NSString *) title { return UCLocalize("CHANGES"); }
+    if (icon_ == nil)
+        icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]];
+    if (icon_ == nil)
+        icon_ = [UIImage applicationImageNamed:@"unknown.png"];
+    icon_ = [icon_ retain];
 
-- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        [[self navigationItem] setTitle:UCLocalize("CHANGES")];
+    origin_ = [[source name] retain];
+    label_ = [[source uri] retain];
 
-        packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
+    [content_ setNeedsDisplay];
+}
 
-        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+- (void) dealloc {
+    [self clearSource];
+    [super dealloc];
+}
 
-        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
-        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [list_ setRowHeight:73.0f];
-        [[self view] addSubview:list_];
+- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
+    if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) {
+        UIView *content([self contentView]);
+        CGRect bounds([content bounds]);
 
-        [list_ setDataSource:self];
-        [list_ setDelegate:self];
+        content_ = [[ContentView alloc] initWithFrame:bounds];
+        [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [content_ setBackgroundColor:[UIColor whiteColor]];
+        [content addSubview:content_];
 
-        delegate_ = delegate;
+        [content_ setDelegate:self];
+        [content_ setOpaque:YES];
     } return self;
 }
 
-- (void) _reloadPackages:(NSArray *)packages {
-    _trace();
-    for (Package *package in packages)
-        if ([package upgradableAndEssential:YES] || [package visible])
-            CFArrayAppendValue(packages_, package);
+- (void) drawContentRect:(CGRect)rect {
+    bool highlighted(highlighted_);
+    float width(rect.size.width);
 
-    _trace();
-    [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackageChangesRadix) withContext:NULL];
-    _trace();
+    if (icon_ != nil)
+        [icon_ drawInRect:CGRectMake(10, 10, 30, 30)];
+
+    if (highlighted)
+        UISetColor(White_);
+
+    if (!highlighted)
+        UISetColor(Black_);
+    [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - 80) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation];
+
+    if (!highlighted)
+        UISetColor(Blue_);
+    [label_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation];
 }
 
-- (void) reloadData {
-    era_ = [database_ era];
-    NSArray *packages = [database_ packages];
+@end
+/* }}} */
+/* Source Table {{{ */
+@interface SourceController : 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;
 
-    upgrades_ = 0;
-    bool unseens = false;
+- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated;
 
-    CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
+@end
 
-    for (size_t offset = 0, count = CFArrayGetCount(packages_); offset != count; ++offset) {
-        Package *package = [self packageAtIndex:offset];
+@implementation SourceController
 
-        BOOL uae = [package upgradableAndEssential:YES];
+- (void) _releaseConnection:(NSURLConnection *)connection {
+    if (connection != nil) {
+        [connection cancel];
+        //[connection setDelegate:nil];
+        [connection release];
+    }
+}
 
-        if (!uae) {
-            unseens = true;
-            time_t seen([package seen]);
+- (void) dealloc {
+    if (href_ != nil)
+        [href_ release];
+    if (hud_ != nil)
+        [hud_ release];
+    if (error_ != nil)
+        [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];
+    [list_ 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
-            }
+- (void) viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
+}
 
-            [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];
-        }
+- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
+    return offset_ == 0 ? 1 : 2;
+}
+
+- (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
     }
-    _trace();
+}
 
-    CFRelease(formatter);
+- (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_;
 
-    if (unseens) {
-        Section *last = [sections_ lastObject];
-        size_t count = [last count];
-        CFArrayReplaceValues(packages_, CFRangeMake(CFArrayGetCount(packages_) - count, count), NULL, 0);
-        [sections_ removeLastObject];
+        _nodefault
     }
+}
 
-    if ([ignored count] != 0)
-        [sections_ insertObject:ignored atIndex:0];
-    if (upgrades_ != 0)
-        [sections_ insertObject:upgradable atIndex:0];
+- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath {
+    unsigned idx = 0;
+    switch (indexPath.section) {
+        case 0: idx = indexPath.row; break;
+        case 1: idx = indexPath.row + offset_; break;
 
-    [list_ reloadData];
+        _nodefault
+    }
+    return [sources_ objectAtIndex:idx];
+}
 
-    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]];
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    static NSString *cellIdentifier = @"SourceCell";
 
-    if (![delegate_ updating])
-        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
-            initWithTitle:UCLocalize("REFRESH")
-            style:UIBarButtonItemStylePlain
-            target:self
-            action:@selector(refreshButtonClicked)
-        ] autorelease]];
+    SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
+    if(cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease];
+    [cell setSource:[self sourceAtIndexPath:indexPath]];
+
+    return cell;
 }
 
-@end
-/* }}} */
-/* Search Controller {{{ */
-@interface SearchController : FilteredPackageController <
-    UISearchBarDelegate
-> {
-    UISearchBar *search_;
+- (UITableViewCellAccessoryType) tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {
+    return UITableViewCellAccessoryDisclosureIndicator;
 }
 
-- (id) initWithDatabase:(Database *)database;
-- (void) reloadData;
+- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    Source *source = [self sourceAtIndexPath:indexPath];
 
-@end
+    FilteredPackageController *packages = [[[FilteredPackageController alloc]
+        initWithDatabase:database_
+        title:[source label]
+        filter:@selector(isVisibleInSource:)
+        with:source
+    ] autorelease];
 
-@implementation SearchController
+    [packages setDelegate:delegate_];
 
-- (void) dealloc {
-    [search_ release];
-    [super dealloc];
+    [[self navigationController] pushViewController:packages 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;
+
+    if (
+        trivial_ == nil &&
+        trivial_bz2_ == nil &&
+        trivial_gz_ == nil
+    ) {
+        bool defer(false);
+
+        if (cydia_) {
+            if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) {
+                defer = true;
+
+                UIAlertView *alert = [[[UIAlertView alloc]
+                    initWithTitle:UCLocalize("SOURCE_WARNING")
+                    message:warning
+                    delegate:self
+                    cancelButtonTitle:UCLocalize("CANCEL")
+                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
+                ] autorelease];
+
+                [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];
+
+            [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];
+
+            [alert setContext:@"trivial"];
+            [alert show];
+        }
+
+        [delegate_ setStatusBarShowsProgress:NO];
+        [delegate_ removeProgressHUD:hud_];
+
+        [hud_ autorelease];
+        hud_ = nil;
+
+        if (!defer) {
+            [href_ release];
+            href_ = nil;
+        }
+
+        if (error_ != nil) {
+            [error_ release];
+            error_ = 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_;
+- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
+    switch ([response statusCode]) {
+        case 200:
+            cydia_ = YES;
+    }
 }
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
+- (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];
+}
 
-@end
+- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
+    [self _endConnection:connection];
+}
 
-@implementation CYPackageSettingsController
+- (NSString *) title { return UCLocalize("SOURCES"); }
 
-- (void) dealloc {
-    [name_ release];
-    if (package_ != nil)
-        [package_ release];
-    [table_ release];
-    [subscribedSwitch_ release];
-    [ignoredSwitch_ release];
-    [subscribedCell_ release];
-    [ignoredCell_ release];
+- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
+    NSMutableURLRequest *request = [NSMutableURLRequest
+        requestWithURL:[NSURL URLWithString:href]
+        cachePolicy:NSURLRequestUseProtocolCachePolicy
+        timeoutInterval:120.0
+    ];
 
-    [super dealloc];
-}
+    [request setHTTPMethod:method];
 
-- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
-    if (package_ == nil)
-        return 0;
+    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 1;
+    return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
 }
 
-- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if (package_ == nil)
-        return 0;
+- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
+    NSString *context([alert context]);
 
-    return 2;
-}
+    if ([context isEqualToString:@"source"]) {
+        switch (button) {
+            case 1: {
+                NSString *href = [[alert textField] text];
 
-- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
-    return UCLocalize("SHOW_ALL_CHANGES_EX");
-}
+                //installer_ = [[self _requestHRef:href method:@"GET"] retain];
 
-- (void) onSubscribed:(id)control {
-    bool value([control isOn]);
-    if (package_ == nil)
-        return;
-    if ([package_ setSubscribed:value])
-        [delegate_ updateData];
-}
+                if (![href hasSuffix:@"/"])
+                    href_ = [href stringByAppendingString:@"/"];
+                else
+                    href_ = href;
+                href_ = [href_ retain];
 
-- (void) onIgnored:(id)control {
-    // TODO: set Held state - possibly call out to dpkg, etc.
-}
+                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];
 
-- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    if (package_ == nil)
-        return nil;
+                cydia_ = false;
 
-    switch ([indexPath row]) {
-        case 0: return subscribedCell_;
-        case 1: return ignoredCell_;
+                // XXX: this is stupid
+                hud_ = [[delegate_ addProgressHUD] retain];
+                [hud_ setText:UCLocalize("VERIFYING_URL")];
+            } break;
 
-        _nodefault
-    }
+            case 0:
+            break;
 
-    return nil;
-}
+            _nodefault
+        }
 
-- (NSString *) title { return UCLocalize("SETTINGS"); }
+        [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;
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init])) {
-        database_ = database;
-        name_ = [package retain];
+            case 0:
+            break;
 
-        [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
+            _nodefault
+        }
 
-        table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped];
-        [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[self view] addSubview:table_];
+        [href_ release];
+        href_ = nil;
 
-        subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    }
+}
 
-        ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
-        [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
+- (id) initWithDatabase:(Database *)database {
+    if ((self = [super init]) != nil) {
+        [[self navigationItem] setTitle:UCLocalize("SOURCES")];
+        [self updateButtonsForEditingStatus:NO animated:NO];
 
-        subscribedCell_ = [[UITableViewCell alloc] init];
-        [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
-        [subscribedCell_ setAccessoryView:subscribedSwitch_];
-        [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+        database_ = database;
+        sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-        ignoredCell_ = [[UITableViewCell alloc] init];
-        [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
-        [ignoredCell_ setAccessoryView:ignoredSwitch_];
-        [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
+        list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain];
+        [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+        [list_ setRowHeight:56];
+        [[self view] addSubview:list_];
+
+        [list_ setDataSource:self];
+        [list_ setDelegate:self];
 
-        [table_ setDataSource:self];
-        [table_ setDelegate:self];
         [self reloadData];
     } 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];
+    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_++;
     }
 
-    [table_ reloadData];
+    [list_ setEditing:NO];
+    [self updateButtonsForEditingStatus:NO animated:NO];
+    [list_ reloadData];
 }
 
-@end
-/* }}} */
-/* Signature Controller {{{ */
-@interface SignatureController : CYBrowserController {
-    _transient Database *database_;
-    NSString *package_;
-}
+- (void) addButtonClicked {
+    /*[book_ pushPage:[[[AddSourceController alloc]
+        initWithBook:book_
+        database:database_
+    ] autorelease]];*/
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package;
+    UIAlertView *alert = [[[UIAlertView alloc]
+        initWithTitle:UCLocalize("ENTER_APT_URL")
+        message:nil
+        delegate:self
+        cancelButtonTitle:UCLocalize("CANCEL")
+        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
+    ] autorelease];
 
-@end
+    [alert setContext:@"source"];
+    [alert setTransform:CGAffineTransformTranslate([alert transform], 0.0, 100.0)];
 
-@implementation SignatureController
+    [alert setNumberOfRows:1];
+    [alert addTextFieldWithValue:@"http://" label:@""];
 
-- (void) dealloc {
-    [package_ release];
-    [super dealloc];
+    UITextInputTraits *traits = [[alert textField] textInputTraits];
+    [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
+    [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
+    [traits setKeyboardType:UIKeyboardTypeURL];
+    // XXX: UIReturnKeyDone
+    [traits setReturnKeyType:UIReturnKeyNext];
+
+    [alert show];
 }
 
-- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
-    // XXX: dude!
-    [super webView:view didClearWindowObject:window forFrame:frame];
+- (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];
+
+    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
+        style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
+        target:self
+        action:@selector(editButtonClicked)
+    ] autorelease] animated:animated];
+
+    if (IsWildcat_ && !editing)
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("SETTINGS")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(settingsButtonClicked)
+        ] autorelease]];
 }
 
-- (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init]) != nil) {
-        database_ = database;
-        package_ = [package retain];
-        [self reloadData];
-    } return self;
+- (void) settingsButtonClicked {
+    [delegate_ showSettings];
 }
 
-- (void) reloadData {
-    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
+- (void) editButtonClicked {
+    [list_ setEditing:![list_ isEditing] animated:YES];
+
+    [self updateButtonsForEditingStatus:[list_ isEditing] animated:YES];
 }
 
 @end
@@ -8952,6 +8957,9 @@ static _finline void _setHomePage(Cydia *self) {
 _trace();
     CydiaApp = self;
 
+    if ([self respondsToSelector:@selector(setApplicationSupportsShakeToEdit:)])
+        [self setApplicationSupportsShakeToEdit:NO];
+
     [NSURLCache setSharedURLCache:[[[SDURLCache alloc]
         initWithMemoryCapacity:524288
         diskCapacity:10485760