]> git.saurik.com Git - cydia.git/blobdiff - Cydia.mm
Fundamental performance improvements.
[cydia.git] / Cydia.mm
index aa809a7daa07bdfe53cd0adae27587e4a37207d9..8d48ac068dfff4925f4ede6a827fd023bf8c5c30 100644 (file)
--- a/Cydia.mm
+++ b/Cydia.mm
@@ -36,6 +36,8 @@
 */
 
 /* #include Directives {{{ */
+#import "UICaboodle.h"
+
 #include <objc/objc.h>
 #include <objc/runtime.h>
 
@@ -100,12 +102,20 @@ extern "C" {
 
 #import "BrowserView.h"
 #import "ResetView.h"
-#import "UICaboodle.h"
 /* }}} */
 
 //#define _finline __attribute__((force_inline))
 #define _finline inline
 
+struct timeval _ltv;
+bool _itv;
+
+#define _limit(count) do { \
+    static size_t _count(0); \
+    if (++_count == count) \
+        exit(0); \
+} while (false)
+
 /* Objective-C Handle<> {{{ */
 template <typename Type_>
 class _H {
@@ -242,6 +252,84 @@ extern NSString * const kCAFilterNearest;
 #define ForSaurik 1
 #define RecycleWebViews 0
 
+/* Radix Sort {{{ */
+@interface NSMutableArray (Radix)
+- (void) radixUsingSelector:(SEL)selector withObject:(id)object;
+@end
+
+@implementation NSMutableArray (Radix)
+
+- (void) radixUsingSelector:(SEL)selector withObject:(id)object {
+    NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:"L12@0:4@8"]]);
+    [invocation setSelector:selector];
+    [invocation setArgument:&object atIndex:2];
+
+    size_t count([self count]);
+
+    struct RadixItem {
+        size_t index;
+        uint32_t key;
+    } *swap(new RadixItem[count * 2]), *lhs(swap), *rhs(swap + count);
+
+    for (size_t i(0); i != count; ++i) {
+        RadixItem &item(lhs[i]);
+        item.index = i;
+
+        id object([self objectAtIndex:i]);
+        [invocation setTarget:object];
+
+        [invocation invoke];
+        [invocation getReturnValue:&item.key];
+    }
+
+    static const size_t bits = 11;
+    static const size_t slots = 1 << bits;
+    static const size_t passes = (32 + (bits - 1)) / bits;
+
+    size_t *hist(new size_t[slots]);
+
+    for (size_t pass(0); pass != passes; ++pass) {
+        memset(hist, 0, sizeof(size_t) * slots);
+
+        for (size_t i(0); i != count; ++i) {
+            uint32_t key(lhs[i].key);
+            key >>= pass * bits;
+            key &= _not(uint32_t) >> 32 - bits;
+            ++hist[key];
+        }
+
+        size_t offset(0);
+        for (size_t i(0); i != slots; ++i) {
+            size_t local(offset);
+            offset += hist[i];
+            hist[i] = local;
+        }
+
+        for (size_t i(0); i != count; ++i) {
+            uint32_t key(lhs[i].key);
+            key >>= pass * bits;
+            key &= _not(uint32_t) >> 32 - bits;
+            rhs[hist[key]++] = lhs[i];
+        }
+
+        RadixItem *tmp(lhs);
+        lhs = rhs;
+        rhs = tmp;
+    }
+
+    delete [] hist;
+
+    NSMutableArray *values([NSMutableArray arrayWithCapacity:count]);
+    for (size_t i(0); i != count; ++i)
+        [values addObject:[self objectAtIndex:lhs[i].index]];
+    [self setArray:values];
+
+    delete [] swap;
+}
+
+@end
+/* }}} */
+
 typedef enum {
     kUIControlEventMouseDown = 1 << 0,
     kUIControlEventMouseMovedInside = 1 << 2, // mouse moved inside control target
@@ -548,7 +636,7 @@ NSString *SizeString(double size) {
 
     static const char *powers_[] = {"B", "kB", "MB", "GB"};
 
-    return [NSString stringWithFormat:@"%s%.1f%s", (negative ? "-" : ""), size, powers_[power]];
+    return [NSString stringWithFormat:@"%s%.1f %s", (negative ? "-" : ""), size, powers_[power]];
 }
 
 NSString *StripVersion(NSString *version) {
@@ -758,6 +846,8 @@ class Progress :
     FILE *input_;
 }
 
++ (Database *) sharedInstance;
+
 - (void) _readCydia:(NSNumber *)fd;
 - (void) _readStatus:(NSNumber *)fd;
 - (void) _readOutput:(NSNumber *)fd;
@@ -766,7 +856,6 @@ class Progress :
 
 - (Package *) packageWithName:(NSString *)name;
 
-- (Database *) init;
 - (pkgCacheFile &) cache;
 - (pkgDepCache::Policy *) policy;
 - (pkgRecords *) records;
@@ -1056,6 +1145,8 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     Source *source_;
     bool cached_;
 
+    NSString *section_;
+
     NSString *latest_;
     NSString *installed_;
 
@@ -1084,7 +1175,10 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
 - (NSString *) description;
 - (NSString *) index;
 
+- (NSMutableDictionary *) metadata;
 - (NSDate *) seen;
+- (BOOL) subscribed;
+- (BOOL) ignored;
 
 - (NSString *) latest;
 - (NSString *) installed;
@@ -1105,7 +1199,7 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
 - (NSString *) id;
 - (NSString *) name;
 - (NSString *) tagline;
-- (NSString *) icon;
+- (UIImage *) icon;
 - (NSString *) homepage;
 - (NSString *) depiction;
 - (Address *) author;
@@ -1123,11 +1217,12 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
 - (bool) hasSupportingRole;
 - (BOOL) hasTag:(NSString *)tag;
 - (NSString *) primaryPurpose;
+- (NSArray *) purposes;
 
 - (NSComparisonResult) compareByName:(Package *)package;
 - (NSComparisonResult) compareBySection:(Package *)package;
-- (NSComparisonResult) compareBySectionAndName:(Package *)package;
-- (NSComparisonResult) compareForChanges:(Package *)package;
+
+- (uint32_t) compareForChanges;
 
 - (void) install;
 - (void) remove;
@@ -1145,6 +1240,9 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     if (source_ != nil)
         [source_ release];
 
+    if (section_ != nil)
+        [section_ release];
+
     [latest_ release];
     if (installed_ != nil)
         [installed_ release];
@@ -1175,7 +1273,7 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"applications", @"author", @"depiction", @"description", @"essential", @"homepage", @"icon", @"id", @"installed", @"latest", @"maintainer", @"name", @"section", @"size", @"source", @"sponsor", @"tagline", @"warnings", nil];
+    return [NSArray arrayWithObjects:@"applications", @"author", @"depiction", @"description", @"essential", @"homepage", @"icon", @"id", @"installed", @"latest", @"maintainer", @"name", @"purposes", @"section", @"size", @"source", @"sponsor", @"tagline", @"warnings", nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -1192,7 +1290,12 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
         database_ = database;
 
         version_ = [database_ policy]->GetCandidateVer(iterator_);
-        latest_ = version_.end() ? nil : [StripVersion([NSString stringWithUTF8String:version_.VerStr()]) retain];
+        NSString *latest = version_.end() ? nil : [NSString stringWithUTF8String:version_.VerStr()];
+        latest_ = [StripVersion(latest) retain];
+
+        pkgCache::VerIterator current = iterator_.CurrentVer();
+        NSString *installed = current.end() ? nil : [NSString stringWithUTF8String:current.VerStr()];
+        installed_ = [StripVersion(installed) retain];
 
         if (!version_.end())
             file_ = version_.FileList();
@@ -1201,10 +1304,7 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
             file_ = pkgCache::VerFileIterator(cache, cache.VerFileP);
         }
 
-        pkgCache::VerIterator current = iterator_.CurrentVer();
-        installed_ = current.end() ? nil : [StripVersion([NSString stringWithUTF8String:current.VerStr()]) retain];
-
-        id_ = [[[NSString stringWithUTF8String:iterator_.Name()] lowercaseString] retain];
+        id_ = [[NSString stringWithUTF8String:iterator_.Name()] retain];
 
         if (!file_.end()) {
             pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
@@ -1249,13 +1349,45 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
                 }
             }
 
-        NSMutableDictionary *metadata = [Packages_ objectForKey:id_];
-        if (metadata == nil || [metadata count] == 0) {
-            metadata = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+        NSString *solid(latest == nil ? installed : latest);
+        bool changed(false);
+
+        NSString *key([id_ lowercaseString]);
+
+        NSMutableDictionary *metadata = [Packages_ objectForKey:key];
+        if (metadata == nil) {
+            metadata = [[NSMutableDictionary dictionaryWithObjectsAndKeys:
                 now_, @"FirstSeen",
-            nil];
+            nil] mutableCopy];
+
+            if (solid != nil)
+                [metadata setObject:solid forKey:@"LastVersion"];
+            changed = true;
+        } else {
+            NSDate *first([metadata objectForKey:@"FirstSeen"]);
+            NSDate *last([metadata objectForKey:@"LastSeen"]);
+            NSString *version([metadata objectForKey:@"LastVersion"]);
+
+            if (first == nil) {
+                first = last == nil ? now_ : last;
+                [metadata setObject:first forKey:@"FirstSeen"];
+                changed = true;
+            }
 
-            [Packages_ setObject:metadata forKey:id_];
+            if (solid != nil)
+                if (version == nil) {
+                    [metadata setObject:solid forKey:@"LastVersion"];
+                    changed = true;
+                } else if (![version isEqualToString:solid]) {
+                    [metadata setObject:solid forKey:@"LastVersion"];
+                    last = now_;
+                    [metadata setObject:last forKey:@"LastSeen"];
+                    changed = true;
+                }
+        }
+
+        if (changed) {
+            [Packages_ setObject:metadata forKey:key];
             Changed_ = true;
         }
     } return self;
@@ -1273,6 +1405,9 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
 }
 
 - (NSString *) section {
+    if (section_ != nil)
+        return section_;
+
     const char *section = iterator_.Section();
     if (section == NULL)
         return nil;
@@ -1286,7 +1421,8 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
             goto lookup;
         }
 
-    return [name stringByReplacingCharacter:'_' withCharacter:' '];
+    section_ = [[name stringByReplacingCharacter:'_' withCharacter:' '] retain];
+    return section_;
 }
 
 - (Address *) maintainer {
@@ -1325,8 +1461,32 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     return [index length] != 0 && isalpha([index characterAtIndex:0]) ? index : @"123";
 }
 
+- (NSMutableDictionary *) metadata {
+    return [Packages_ objectForKey:[id_ lowercaseString]];
+}
+
 - (NSDate *) seen {
-    return [[Packages_ objectForKey:id_] objectForKey:@"FirstSeen"];
+    NSDictionary *metadata([self metadata]);
+    if ([self subscribed])
+        if (NSDate *last = [metadata objectForKey:@"LastSeen"])
+            return last;
+    return [metadata objectForKey:@"FirstSeen"];
+}
+
+- (BOOL) subscribed {
+    NSDictionary *metadata([self metadata]);
+    if (NSNumber *subscribed = [metadata objectForKey:@"IsSubscribed"])
+        return [subscribed boolValue];
+    else
+        return false;
+}
+
+- (BOOL) ignored {
+    NSDictionary *metadata([self metadata]);
+    if (NSNumber *ignored = [metadata objectForKey:@"IsIgnored"])
+        return [ignored boolValue];
+    else
+        return false;
 }
 
 - (NSString *) latest {
@@ -1433,8 +1593,21 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     return tagline_;
 }
 
-- (NSString *) icon {
-    return icon_;
+- (UIImage *) icon {
+    NSString *section = [self section];
+    if (section != nil)
+        section = Simplify(section);
+
+    UIImage *icon(nil);
+    if (NSString *icon = icon_)
+        icon = [UIImage imageAtPath:[icon_ substringFromIndex:6]];
+    if (icon == nil) if (section != nil)
+        icon = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]];
+    if (icon == nil) if (source_ != nil) if (NSString *icon = [source_ defaultIcon])
+        icon = [UIImage imageAtPath:[icon substringFromIndex:6]];
+    if (icon == nil)
+        icon = [UIImage applicationImageNamed:@"unknown.png"];
+    return icon;
 }
 
 - (NSString *) homepage {
@@ -1605,6 +1778,14 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     return nil;
 }
 
+- (NSArray *) purposes {
+    NSMutableArray *purposes([NSMutableArray arrayWithCapacity:2]);
+    for (NSString *tag in tags_)
+        if ([tag hasPrefix:@"purpose::"])
+            [purposes addObject:[tag substringFromIndex:9]];
+    return [purposes count] == 0 ? nil : purposes;
+}
+
 - (NSComparisonResult) compareByName:(Package *)package {
     NSString *lhs = [self name];
     NSString *rhs = [package name];
@@ -1639,43 +1820,30 @@ NSString *Scour(const char *field, const char *begin, const char *end) {
     return NSOrderedSame;
 }
 
-- (NSComparisonResult) compareBySectionAndName:(Package *)package {
-    NSString *lhs = [self section];
-    NSString *rhs = [package section];
-
-    if (lhs == NULL && rhs != NULL)
-        return NSOrderedAscending;
-    else if (lhs != NULL && rhs == NULL)
-        return NSOrderedDescending;
-    else if (lhs != NULL && rhs != NULL) {
-        NSComparisonResult result = [lhs compare:rhs];
-        if (result != NSOrderedSame)
-            return result;
-    }
+- (uint32_t) compareForChanges {
+    union {
+        uint32_t key;
 
-    return [self compareByName:package];
-}
+        struct {
+            uint32_t timestamp : 30;
+            uint32_t ignored : 1;
+            uint32_t upgradable : 1;
+        } bits;
+    } value;
 
-- (NSComparisonResult) compareForChanges:(Package *)package {
-    BOOL lhs = [self upgradableAndEssential:YES];
-    BOOL rhs = [package upgradableAndEssential:YES];
+    value.bits.upgradable = [self upgradableAndEssential:YES] ? 1 : 0;
 
-    if (lhs != rhs)
-        return lhs ? NSOrderedAscending : NSOrderedDescending;
-    else if (!lhs) {
-        switch ([[self seen] compare:[package seen]]) {
-            case NSOrderedAscending:
-                return NSOrderedDescending;
-            case NSOrderedSame:
-                break;
-            case NSOrderedDescending:
-                return NSOrderedAscending;
-            default:
-                _assert(false);
-        }
+    if ([self upgradableAndEssential:YES]) {
+        value.bits.timestamp = 0;
+        value.bits.ignored = [self ignored] ? 0 : 1;
+        value.bits.upgradable = 1;
+    } else {
+        value.bits.timestamp = static_cast<uint32_t>([[self seen] timeIntervalSince1970]) >> 2;
+        value.bits.ignored = 0;
+        value.bits.upgradable = 0;
     }
 
-    return [self compareByName:package];
+    return _not(uint32_t) - value.key;
 }
 
 - (void) install {
@@ -1805,6 +1973,13 @@ static NSArray *Finishes_;
 /* Database Implementation {{{ */
 @implementation Database
 
++ (Database *) sharedInstance {
+    static Database *instance;
+    if (instance == nil)
+        instance = [[Database alloc] init];
+    return instance;
+}
+
 - (void) dealloc {
     _assert(false);
     [super dealloc];
@@ -2352,8 +2527,6 @@ static NSArray *Finishes_;
     [super dealloc];
 }
 
-#include "internals.h"
-
 - (void) mailComposeControllerWillAttemptToSend:(MailComposeController *)controller {
     NSLog(@"will");
 }
@@ -2378,6 +2551,9 @@ UIActionSheet *mailAlertSheet = [[UIActionSheet alloc] initWithTitle:@"Error" bu
 }
 
 - (void) deliverMessage { _pooled
+    setuid(501);
+    setgid(501);
+
     if (![controller_ deliverMessage])
         [self performSelectorOnMainThread:@selector(showError) withObject:nil waitUntilDone:NO];
 }
@@ -2425,6 +2601,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 
 @interface ConfirmationView : BrowserView {
+    _transient Database *database_;
     UIActionSheet *essential_;
     NSArray *changes_;
     NSArray *issues_;
@@ -2483,7 +2660,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithBook:(RVBook *)book database:(Database *)database {
-    if ((self = [super initWithBook:book database:database]) != nil) {
+    if ((self = [super initWithBook:book]) != nil) {
+        database_ = database;
+
         NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16];
         NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16];
         NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16];
@@ -2573,11 +2752,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-// XXX: replace with <title/>
-- (NSString *) title {
-    return issues_ == nil ? @"Confirm Changes" : @"Cannot Comply";
-}
-
 - (NSString *) backButtonTitle {
     return @"Confirm";
 }
@@ -3196,17 +3370,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (section != nil)
         section = Simplify(section);
 
-    icon_ = nil;
-    if (NSString *icon = [package icon])
-        icon_ = [UIImage imageAtPath:[icon substringFromIndex:6]];
-    if (icon_ == nil) if (section != nil)
-        icon_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]];
-    if (icon_ == nil) if (NSString *icon = [source defaultIcon])
-        icon_ = [UIImage imageAtPath:[icon substringFromIndex:6]];
-    if (icon_ == nil)
-        icon_ = [UIImage applicationImageNamed:@"unknown.png"];
-
-    icon_ = [icon_ retain];
+    icon_ = [[package icon] retain];
 
     name_ = [[package name] retain];
     description_ = [[package tagline] retain];
@@ -3386,7 +3550,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         count_ = [[NSString stringWithFormat:@"%d", [section count]] retain];
 
         if (editing_)
-            [switch_ setValue:isSectionVisible(section_) animated:NO];
+            [switch_ setValue:(isSectionVisible(section_) ? 1 : 0) animated:NO];
     }
 }
 
@@ -3549,6 +3713,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* }}} */
 /* Package View {{{ */
 @interface PackageView : BrowserView {
+    _transient Database *database_;
     Package *package_;
     NSString *name_;
     NSMutableArray *buttons_;
@@ -3603,7 +3768,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super webView:sender didClearWindowObject:window forFrame:frame];
 }
 
-#if 0
 - (void) _rightButtonClicked {
     /*[super _rightButtonClicked];
     return;*/
@@ -3627,7 +3791,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         ] autorelease]];
     }
 }
-#endif
 
 - (NSString *) _rightButtonTitle {
     int count = [buttons_ count];
@@ -3639,7 +3802,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithBook:(RVBook *)book database:(Database *)database {
-    if ((self = [super initWithBook:book database:database]) != nil) {
+    if ((self = [super initWithBook:book]) != nil) {
         database_ = database;
         buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
     } return self;
@@ -4248,7 +4411,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     [sources_ removeAllObjects];
     [sources_ addObjectsFromArray:[database_ sources]];
+    _trace();
     [sources_ sortUsingSelector:@selector(compareByNameAndType:)];
+    _trace();
 
     int count = [sources_ count];
     for (offset_ = 0; offset_ != count; ++offset_) {
@@ -4616,9 +4781,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     if ([href hasPrefix:@"apptapp://package/"])
         page = [delegate_ pageForPackage:[href substringFromIndex:18]];
-    else if ([scheme isEqualToString:@"cydia"])
+    else if ([scheme isEqualToString:@"cydia"]) {
         page = [delegate_ pageForURL:url hasTag:NULL];
-    else if (![scheme isEqualToString:@"apptapp"])
+        if (page == nil)
+            return false;
+    } else if (![scheme isEqualToString:@"apptapp"])
         return false;
 
     if (page != nil)
@@ -4644,6 +4811,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [listener use];
 }
 
+- (void) webView:(WebView *)webView decidePolicyForMIMEType:(NSString *)type request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
+    if ([WebView canShowMIMEType:type])
+        [listener use];
+    else {
+        // XXX: handle more mime types!
+        [listener ignore];
+        if (frame == [webView mainFrame])
+            [UIApp openURL:[request URL]];
+    }
+}
+
 - (void) webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)action request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
     NSURL *url([request URL]);
 
@@ -4667,6 +4845,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     int store(_not(int));
     if (NSURL *itms = [url itmsURL:&store]) {
+        NSLog(@"itms#%@#%u#%@", url, store, itms);
         if (
             store == 1 && [capability containsObject:@"com.apple.MobileStore"] ||
             store == 2 && [capability containsObject:@"com.apple.AppStore"]
@@ -4720,7 +4899,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (WebView *) _createWebViewWithRequest:(NSURLRequest *)request pushed:(BOOL)pushed {
     [self setBackButtonTitle:title_];
 
-    BrowserView *browser = [[[BrowserView alloc] initWithBook:book_ database:database_] autorelease];
+    BrowserView *browser = [[[BrowserView alloc] initWithBook:book_] autorelease];
     [browser setDelegate:delegate_];
 
     if (pushed) {
@@ -4825,9 +5004,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 #endif
 }
 
-- (id) initWithBook:(RVBook *)book database:(Database *)database {
+- (id) initWithBook:(RVBook *)book {
     if ((self = [super initWithBook:book]) != nil) {
-        database_ = database;
         loading_ = false;
 
         struct CGRect bounds = [self bounds];
@@ -4905,7 +5083,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectMake(281, 12, indsize.width, indsize.height)];
         [indicator_ setStyle:UIProgressIndicatorStyleMediumWhite];
 
-        Package *package([database_ packageWithName:@"cydia"]);
+        Package *package([[Database sharedInstance] packageWithName:@"cydia"]);
         NSString *application = package == nil ? @"Cydia" : [NSString
             stringWithFormat:@"Cydia/%@",
             [package installed]
@@ -4969,6 +5147,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 
+/* Cydia Book {{{ */
 @interface CYBook : RVBook <
     ProgressDelegate
 > {
@@ -4990,112 +5169,413 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @end
 
-/* Install View {{{ */
-@interface InstallView : RVPage {
-    _transient Database *database_;
-    NSMutableArray *sections_;
-    NSMutableArray *filtered_;
-    UITransitionView *transition_;
-    UITable *list_;
-    UIView *accessory_;
-    BOOL editing_;
-}
-
-- (id) initWithBook:(RVBook *)book database:(Database *)database;
-- (void) reloadData;
-- (void) resetView;
-
-@end
-
-@implementation InstallView
+@implementation CYBook
 
 - (void) dealloc {
-    [list_ setDataSource:nil];
-    [list_ setDelegate:nil];
-
-    [sections_ release];
-    [filtered_ release];
-    [transition_ release];
-    [list_ release];
-    [accessory_ release];
+    [overlay_ release];
+    [indicator_ release];
+    [prompt_ release];
+    [progress_ release];
+    [cancel_ release];
     [super dealloc];
 }
 
-- (int) numberOfRowsInTable:(UITable *)table {
-    return editing_ ? [sections_ count] : [filtered_ count] + 1;
+- (NSString *) getTitleForPage:(RVPage *)page {
+    return Simplify([super getTitleForPage:page]);
 }
 
-- (float) table:(UITable *)table heightForRow:(int)row {
-    return 45;
+- (BOOL) updating {
+    return updating_;
 }
 
-- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
-    if (reusing == nil)
-        reusing = [[[SectionCell alloc] init] autorelease];
-    [(SectionCell *)reusing setSection:(editing_ ?
-        [sections_ objectAtIndex:row] :
-        (row == 0 ? nil : [filtered_ objectAtIndex:(row - 1)])
-    ) editing:editing_];
-    return reusing;
-}
+- (void) update {
+    [UIView beginAnimations:nil context:NULL];
 
-- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
-    return !editing_;
-}
+    CGRect ovrframe = [overlay_ frame];
+    ovrframe.origin.y = 0;
+    [overlay_ setFrame:ovrframe];
 
-- (BOOL) table:(UITable *)table canSelectRow:(int)row {
-    return !editing_;
+    CGRect barframe = [navbar_ frame];
+    barframe.origin.y += ovrframe.size.height;
+    [navbar_ setFrame:barframe];
+
+    CGRect trnframe = [transition_ frame];
+    trnframe.origin.y += ovrframe.size.height;
+    trnframe.size.height -= ovrframe.size.height;
+    [transition_ setFrame:trnframe];
+
+    [UIView endAnimations];
+
+    [indicator_ startAnimation];
+    [prompt_ setText:@"Updating Database"];
+    [progress_ setProgress:0];
+
+    received_ = 0;
+    last_ = [NSDate timeIntervalSinceReferenceDate];
+    updating_ = true;
+    [overlay_ addSubview:cancel_];
+
+    [NSThread
+        detachNewThreadSelector:@selector(_update)
+        toTarget:self
+        withObject:nil
+    ];
 }
 
-- (void) tableRowSelected:(NSNotification *)notification {
-    int row = [[notification object] selectedRow];
-    if (row == INT_MAX)
-        return;
+- (void) _update_ {
+    updating_ = false;
 
-    Section *section;
-    NSString *name;
-    NSString *title;
+    [indicator_ stopAnimation];
 
-    if (row == 0) {
-        section = nil;
-        name = nil;
-        title = @"All Packages";
-    } else {
-        section = [filtered_ objectAtIndex:(row - 1)];
-        name = [section name];
+    [UIView beginAnimations:nil context:NULL];
 
-        if (name != nil)
-            title = name;
-        else {
-            name = @"";
-            title = @"(No Section)";
-        }
-    }
+    CGRect ovrframe = [overlay_ frame];
+    ovrframe.origin.y = -ovrframe.size.height;
+    [overlay_ setFrame:ovrframe];
 
-    PackageTable *table = [[[PackageTable alloc]
-        initWithBook:book_
-        database:database_
-        title:title
-        filter:@selector(isVisiblyUninstalledInSection:)
-        with:name
-    ] autorelease];
+    CGRect barframe = [navbar_ frame];
+    barframe.origin.y -= ovrframe.size.height;
+    [navbar_ setFrame:barframe];
 
-    [table setDelegate:delegate_];
+    CGRect trnframe = [transition_ frame];
+    trnframe.origin.y -= ovrframe.size.height;
+    trnframe.size.height += ovrframe.size.height;
+    [transition_ setFrame:trnframe];
 
-    [book_ pushPage:table];
+    [UIView commitAnimations];
+
+    [delegate_ performSelector:@selector(reloadData) withObject:nil afterDelay:0];
 }
 
-- (id) initWithBook:(RVBook *)book database:(Database *)database {
-    if ((self = [super initWithBook:book]) != nil) {
+- (id) initWithFrame:(CGRect)frame database:(Database *)database {
+    if ((self = [super initWithFrame:frame]) != nil) {
         database_ = database;
 
-        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
-        filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
+        CGRect ovrrect = [navbar_ bounds];
+        ovrrect.size.height = [UINavigationBar defaultSize].height;
+        ovrrect.origin.y = -ovrrect.size.height;
 
-        transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
-        [self addSubview:transition_];
+        overlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
+        [self addSubview:overlay_];
 
-        list_ = [[UITable alloc] initWithFrame:[transition_ bounds]];
+        ovrrect.origin.y = frame.size.height;
+        underlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
+        [underlay_ setTintColor:[UIColor colorWithRed:0.23 green:0.23 blue:0.23 alpha:1]];
+        [self addSubview:underlay_];
+
+        [overlay_ setBarStyle:1];
+        [underlay_ setBarStyle:1];
+
+        int barstyle = [overlay_ _barStyle:NO];
+        bool ugly = barstyle == 0;
+
+        UIProgressIndicatorStyle style = ugly ?
+            UIProgressIndicatorStyleMediumBrown :
+            UIProgressIndicatorStyleMediumWhite;
+
+        CGSize indsize = [UIProgressIndicator defaultSizeForStyle:style];
+        unsigned indoffset = (ovrrect.size.height - indsize.height) / 2;
+        CGRect indrect = {{indoffset, indoffset}, indsize};
+
+        indicator_ = [[UIProgressIndicator alloc] initWithFrame:indrect];
+        [indicator_ setStyle:style];
+        [overlay_ addSubview:indicator_];
+
+        CGSize prmsize = {215, indsize.height + 4};
+
+        CGRect prmrect = {{
+            indoffset * 2 + indsize.width,
+#ifdef __OBJC2__
+            -1 +
+#endif
+            unsigned(ovrrect.size.height - prmsize.height) / 2
+        }, prmsize};
+
+        UIFont *font = [UIFont systemFontOfSize:15];
+
+        prompt_ = [[UITextLabel alloc] initWithFrame:prmrect];
+
+        [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
+        [prompt_ setBackgroundColor:[UIColor clearColor]];
+        [prompt_ setFont:font];
+
+        [overlay_ addSubview:prompt_];
+
+        CGSize prgsize = {75, 100};
+
+        CGRect prgrect = {{
+            ovrrect.size.width - prgsize.width - 10,
+            (ovrrect.size.height - prgsize.height) / 2
+        } , prgsize};
+
+        progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
+        [progress_ setStyle:0];
+        [overlay_ addSubview:progress_];
+
+        cancel_ = [[UINavigationButton alloc] initWithTitle:@"Cancel" style:UINavigationButtonStyleHighlighted];
+        [cancel_ addTarget:self action:@selector(_onCancel) forControlEvents:UIControlEventTouchUpInside];
+
+        CGRect frame = [cancel_ frame];
+        frame.size.width = 65;
+        frame.origin.x = ovrrect.size.width - frame.size.width - 5;
+        frame.origin.y = (ovrrect.size.height - frame.size.height) / 2;
+        [cancel_ setFrame:frame];
+
+        [cancel_ setBarStyle:barstyle];
+    } return self;
+}
+
+- (void) _onCancel {
+    updating_ = false;
+    [cancel_ removeFromSuperview];
+}
+
+- (void) _update { _pooled
+    Status status;
+    status.setDelegate(self);
+
+    [database_ updateWithStatus:status];
+
+    [self
+        performSelectorOnMainThread:@selector(_update_)
+        withObject:nil
+        waitUntilDone:NO
+    ];
+}
+
+- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
+    [prompt_ setText:[NSString stringWithFormat:@"Error: %@", error]];
+}
+
+- (void) setProgressTitle:(NSString *)title {
+    [self
+        performSelectorOnMainThread:@selector(_setProgressTitle:)
+        withObject:title
+        waitUntilDone:YES
+    ];
+}
+
+- (void) setProgressPercent:(float)percent {
+    [self
+        performSelectorOnMainThread:@selector(_setProgressPercent:)
+        withObject:[NSNumber numberWithFloat:percent]
+        waitUntilDone:YES
+    ];
+}
+
+- (void) startProgress {
+}
+
+- (void) addProgressOutput:(NSString *)output {
+    [self
+        performSelectorOnMainThread:@selector(_addProgressOutput:)
+        withObject:output
+        waitUntilDone:YES
+    ];
+}
+
+- (bool) isCancelling:(size_t)received {
+    NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
+    if (received_ != received) {
+        received_ = received;
+        last_ = now;
+    } else if (now - last_ > 15)
+        return true;
+    return !updating_;
+}
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+    [sheet dismiss];
+}
+
+- (void) _setProgressTitle:(NSString *)title {
+    [prompt_ setText:title];
+}
+
+- (void) _setProgressPercent:(NSNumber *)percent {
+    [progress_ setProgress:[percent floatValue]];
+}
+
+- (void) _addProgressOutput:(NSString *)output {
+}
+
+@end
+/* }}} */
+/* Cydia:// Protocol {{{ */
+@interface CydiaURLProtocol : NSURLProtocol {
+}
+
+@end
+
+@implementation CydiaURLProtocol
+
++ (BOOL) canInitWithRequest:(NSURLRequest *)request {
+    NSURL *url([request URL]);
+    if (url == nil)
+        return NO;
+    NSString *scheme([[url scheme] lowercaseString]);
+    if (scheme == nil || ![scheme isEqualToString:@"cydia"])
+        return NO;
+    return YES;
+}
+
++ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
+    return request;
+}
+
+- (void) startLoading {
+    id<NSURLProtocolClient> client([self client]);
+    NSURLRequest *request([self request]);
+
+    NSURL *url([request URL]);
+    NSString *href([url absoluteString]);
+
+    NSString *path([href substringFromIndex:8]);
+    NSRange slash([path rangeOfString:@"/"]);
+
+    NSString *command;
+    if (slash.location == NSNotFound) {
+        command = path;
+        path = nil;
+    } else {
+        command = [path substringToIndex:slash.location];
+        path = [path substringFromIndex:(slash.location + 1)];
+    }
+
+    Database *database([Database sharedInstance]);
+
+    if ([command isEqualToString:@"package-icon"]) {
+        if (path == nil)
+            goto fail;
+        Package *package([database packageWithName:path]);
+        if (package == nil)
+            goto fail;
+
+        NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
+
+        UIImage *icon([package icon]);
+        NSData *data(UIImagePNGRepresentation(icon));
+
+        [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+        [client URLProtocol:self didLoadData:data];
+        [client URLProtocolDidFinishLoading:self];
+    } else fail: {
+        [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
+    }
+}
+
+- (void) stopLoading {
+}
+
+@end
+/* }}} */
+
+/* Install View {{{ */
+@interface InstallView : RVPage {
+    _transient Database *database_;
+    NSMutableArray *sections_;
+    NSMutableArray *filtered_;
+    UITransitionView *transition_;
+    UITable *list_;
+    UIView *accessory_;
+    BOOL editing_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+- (void) reloadData;
+- (void) resetView;
+
+@end
+
+@implementation InstallView
+
+- (void) dealloc {
+    [list_ setDataSource:nil];
+    [list_ setDelegate:nil];
+
+    [sections_ release];
+    [filtered_ release];
+    [transition_ release];
+    [list_ release];
+    [accessory_ release];
+    [super dealloc];
+}
+
+- (int) numberOfRowsInTable:(UITable *)table {
+    return editing_ ? [sections_ count] : [filtered_ count] + 1;
+}
+
+- (float) table:(UITable *)table heightForRow:(int)row {
+    return 45;
+}
+
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
+    if (reusing == nil)
+        reusing = [[[SectionCell alloc] init] autorelease];
+    [(SectionCell *)reusing setSection:(editing_ ?
+        [sections_ objectAtIndex:row] :
+        (row == 0 ? nil : [filtered_ objectAtIndex:(row - 1)])
+    ) editing:editing_];
+    return reusing;
+}
+
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
+    return !editing_;
+}
+
+- (BOOL) table:(UITable *)table canSelectRow:(int)row {
+    return !editing_;
+}
+
+- (void) tableRowSelected:(NSNotification *)notification {
+    int row = [[notification object] selectedRow];
+    if (row == INT_MAX)
+        return;
+
+    Section *section;
+    NSString *name;
+    NSString *title;
+
+    if (row == 0) {
+        section = nil;
+        name = nil;
+        title = @"All Packages";
+    } else {
+        section = [filtered_ objectAtIndex:(row - 1)];
+        name = [section name];
+
+        if (name != nil)
+            title = name;
+        else {
+            name = @"";
+            title = @"(No Section)";
+        }
+    }
+
+    PackageTable *table = [[[PackageTable alloc]
+        initWithBook:book_
+        database:database_
+        title:title
+        filter:@selector(isVisiblyUninstalledInSection:)
+        with:name
+    ] autorelease];
+
+    [table setDelegate:delegate_];
+
+    [book_ pushPage:table];
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+    if ((self = [super initWithBook:book]) != nil) {
+        database_ = database;
+
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+        filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
+
+        transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
+        [self addSubview:transition_];
+
+        list_ = [[UITable alloc] initWithFrame:[transition_ bounds]];
         [transition_ transition:0 toView:list_];
 
         UITableColumn *column = [[[UITableColumn alloc]
@@ -5126,6 +5606,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     NSMutableArray *filtered = [NSMutableArray arrayWithCapacity:[packages count]];
     NSMutableDictionary *sections = [NSMutableDictionary dictionaryWithCapacity:32];
 
+    _trace();
     for (size_t i(0); i != [packages count]; ++i) {
         Package *package([packages objectAtIndex:i]);
         NSString *name([package section]);
@@ -5141,11 +5622,14 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         if ([package valid] && [package installed] == nil && [package visible])
             [filtered addObject:package];
     }
+    _trace();
 
     [sections_ addObjectsFromArray:[sections allValues]];
     [sections_ sortUsingSelector:@selector(compareByName:)];
 
+    _trace();
     [filtered sortUsingSelector:@selector(compareBySection:)];
+    _trace();
 
     Section *section = nil;
     for (size_t offset = 0, count = [filtered count]; offset != count; ++offset) {
@@ -5161,8 +5645,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
         [section addToCount];
     }
+    _trace();
 
     [list_ reloadData];
+    _trace();
 }
 
 - (void) resetView {
@@ -5177,10 +5663,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) _rightButtonClicked {
     if ((editing_ = !editing_))
         [list_ reloadData];
-    else {
+    else
         [delegate_ updateData];
-    }
-
     [book_ reloadTitleForPage:self];
     [book_ reloadButtonsForPage:self];
 }
@@ -5323,6 +5807,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [packages_ removeAllObjects];
     [sections_ removeAllObjects];
 
+    _trace();
     for (size_t i(0); i != [packages count]; ++i) {
         Package *package([packages objectAtIndex:i]);
 
@@ -5333,43 +5818,46 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             [packages_ addObject:package];
     }
 
-    [packages_ sortUsingSelector:@selector(compareForChanges:)];
+    _trace();
+    [packages_ radixUsingSelector:@selector(compareForChanges) withObject:nil];
+    _trace();
 
     Section *upgradable = [[[Section alloc] initWithName:@"Available Upgrades"] autorelease];
+    Section *ignored = [[[Section alloc] initWithName:@"Ignored Upgrades"] autorelease];
     Section *section = nil;
+    NSDate *last = nil;
 
     upgrades_ = 0;
     bool unseens = false;
 
     CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
 
+    _trace();
     for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) {
         Package *package = [packages_ objectAtIndex:offset];
 
-        if ([package upgradableAndEssential:YES]) {
-            ++upgrades_;
-            [upgradable addToCount];
-        } else {
+        if (![package upgradableAndEssential:YES]) {
             unseens = true;
             NSDate *seen = [package seen];
 
-            NSString *name;
+            if (section == nil || last != seen && (seen == nil || [seen compare:last] != NSOrderedSame)) {
+                last = seen;
 
-            if (seen == nil)
-                name = [@"n/a ?" retain];
-            else {
-                name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) seen);
-            }
-
-            if (section == nil || ![[section name] isEqualToString:name]) {
+                NSString *name(seen == nil ? [@"n/a ?" retain] : (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) seen));
                 section = [[[Section alloc] initWithName:name row:offset] autorelease];
                 [sections_ addObject:section];
+                [name release];
             }
 
-            [name release];
             [section addToCount];
+        } else if ([package ignored])
+            [ignored addToCount];
+        else {
+            ++upgrades_;
+            [upgradable addToCount];
         }
     }
+    _trace();
 
     CFRelease(formatter);
 
@@ -5380,6 +5868,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [sections_ removeLastObject];
     }
 
+    if ([ignored count] != 0)
+        [sections_ insertObject:ignored atIndex:0];
     if (upgrades_ != 0)
         [sections_ insertObject:upgradable atIndex:0];
 
@@ -5667,237 +6157,236 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 @end
 /* }}} */
 
-@implementation CYBook
-
-- (void) dealloc {
-    [overlay_ release];
-    [indicator_ release];
-    [prompt_ release];
-    [progress_ release];
-    [cancel_ release];
-    [super dealloc];
-}
-
-- (NSString *) getTitleForPage:(RVPage *)page {
-    return Simplify([super getTitleForPage:page]);
-}
-
-- (BOOL) updating {
-    return updating_;
+@interface SettingsView : RVPage {
+    _transient Database *database_;
+    NSString *name_;
+    Package *package_;
+    UIPreferencesTable *table_;
+    _UISwitchSlider *subscribedSwitch_;
+    _UISwitchSlider *ignoredSwitch_;
+    UIPreferencesControlTableCell *subscribedCell_;
+    UIPreferencesControlTableCell *ignoredCell_;
 }
 
-- (void) update {
-    [UIView beginAnimations:nil context:NULL];
-
-    CGRect ovrframe = [overlay_ frame];
-    ovrframe.origin.y = 0;
-    [overlay_ setFrame:ovrframe];
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package;
 
-    CGRect barframe = [navbar_ frame];
-    barframe.origin.y += ovrframe.size.height;
-    [navbar_ setFrame:barframe];
+@end
 
-    CGRect trnframe = [transition_ frame];
-    trnframe.origin.y += ovrframe.size.height;
-    trnframe.size.height -= ovrframe.size.height;
-    [transition_ setFrame:trnframe];
+@implementation SettingsView
 
-    [UIView endAnimations];
+- (void) dealloc {
+    [table_ setDataSource:nil];
 
-    [indicator_ startAnimation];
-    [prompt_ setText:@"Updating Database"];
-    [progress_ setProgress:0];
+    [name_ release];
+    if (package_ != nil)
+        [package_ release];
+    [table_ release];
+    [subscribedSwitch_ release];
+    [ignoredSwitch_ release];
+    [subscribedCell_ release];
+    [ignoredCell_ release];
+    [super dealloc];
+}
 
-    received_ = 0;
-    last_ = [NSDate timeIntervalSinceReferenceDate];
-    updating_ = true;
-    [overlay_ addSubview:cancel_];
+- (int) numberOfGroupsInPreferencesTable:(UIPreferencesTable *)table {
+    if (package_ == nil)
+        return 0;
 
-    [NSThread
-        detachNewThreadSelector:@selector(_update)
-        toTarget:self
-        withObject:nil
-    ];
+    return 2;
 }
 
-- (void) _update_ {
-    updating_ = false;
+- (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group {
+    if (package_ == nil)
+        return nil;
 
-    [indicator_ stopAnimation];
+    switch (group) {
+        case 0: return nil;
+        case 1: return nil;
 
-    [UIView beginAnimations:nil context:NULL];
+        default: _assert(false);
+    }
 
-    CGRect ovrframe = [overlay_ frame];
-    ovrframe.origin.y = -ovrframe.size.height;
-    [overlay_ setFrame:ovrframe];
+    return nil;
+}
 
-    CGRect barframe = [navbar_ frame];
-    barframe.origin.y -= ovrframe.size.height;
-    [navbar_ setFrame:barframe];
+- (BOOL) preferencesTable:(UIPreferencesTable *)table isLabelGroup:(int)group {
+    if (package_ == nil)
+        return NO;
 
-    CGRect trnframe = [transition_ frame];
-    trnframe.origin.y -= ovrframe.size.height;
-    trnframe.size.height += ovrframe.size.height;
-    [transition_ setFrame:trnframe];
+    switch (group) {
+        case 0: return NO;
+        case 1: return YES;
 
-    [UIView commitAnimations];
+        default: _assert(false);
+    }
 
-    [delegate_ performSelector:@selector(reloadData) withObject:nil afterDelay:0];
+    return NO;
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database {
-    if ((self = [super initWithFrame:frame]) != nil) {
-        database_ = database;
+- (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group {
+    if (package_ == nil)
+        return 0;
 
-        CGRect ovrrect = [navbar_ bounds];
-        ovrrect.size.height = [UINavigationBar defaultSize].height;
-        ovrrect.origin.y = -ovrrect.size.height;
+    switch (group) {
+        case 0: return 1;
+        case 1: return 1;
 
-        overlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
-        [self addSubview:overlay_];
+        default: _assert(false);
+    }
 
-        ovrrect.origin.y = frame.size.height;
-        underlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
-        [underlay_ setTintColor:[UIColor colorWithRed:0.23 green:0.23 blue:0.23 alpha:1]];
-        [self addSubview:underlay_];
+    return 0;
+}
 
-        [overlay_ setBarStyle:1];
-        [underlay_ setBarStyle:1];
+- (void) onSomething:(UIPreferencesControlTableCell *)cell withKey:(NSString *)key {
+    if (package_ == nil)
+        return;
 
-        int barstyle = [overlay_ _barStyle:NO];
-        bool ugly = barstyle == 0;
+    _UISwitchSlider *slider([cell control]);
+    // XXX: this is just weird
+    BOOL value([slider value] != 0);
+    NSMutableDictionary *metadata([package_ metadata]);
 
-        UIProgressIndicatorStyle style = ugly ?
-            UIProgressIndicatorStyleMediumBrown :
-            UIProgressIndicatorStyleMediumWhite;
+    BOOL before;
+    if (NSNumber *number = [metadata objectForKey:key])
+        before = [number boolValue];
+    else
+        before = NO;
+    NSLog(@"%@:%@ %@:%@ : %u:%u", cell, slider, name_, key, value, before);
 
-        CGSize indsize = [UIProgressIndicator defaultSizeForStyle:style];
-        unsigned indoffset = (ovrrect.size.height - indsize.height) / 2;
-        CGRect indrect = {{indoffset, indoffset}, indsize};
+    if (value != before) {
+        [metadata setObject:[NSNumber numberWithBool:value] forKey:key];
+        Changed_ = true;
+        //[delegate_ performSelector:@selector(updateData) withObject:nil afterDelay:0];
+        [delegate_ updateData];
+    }
+}
 
-        indicator_ = [[UIProgressIndicator alloc] initWithFrame:indrect];
-        [indicator_ setStyle:style];
-        [overlay_ addSubview:indicator_];
+- (void) onSubscribed:(UIPreferencesControlTableCell *)cell {
+    [self onSomething:cell withKey:@"IsSubscribed"];
+}
 
-        CGSize prmsize = {215, indsize.height + 4};
+- (void) onIgnored:(UIPreferencesControlTableCell *)cell {
+    [self onSomething:cell withKey:@"IsIgnored"];
+}
 
-        CGRect prmrect = {{
-            indoffset * 2 + indsize.width,
-#ifdef __OBJC2__
-            -1 +
-#endif
-            unsigned(ovrrect.size.height - prmsize.height) / 2
-        }, prmsize};
+- (id) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group {
+    if (package_ == nil)
+        return nil;
 
-        UIFont *font = [UIFont systemFontOfSize:15];
+    switch (group) {
+        case 0: switch (row) {
+            case 0:
+                return subscribedCell_;
+            case 1:
+                return ignoredCell_;
+            default: _assert(false);
+        } break;
+
+        case 1: switch (row) {
+            case 0: {
+                UIPreferencesControlTableCell *cell([[[UIPreferencesControlTableCell alloc] init] autorelease]);
+                [cell setTitle:@"Changes only shows upgrades to installed packages. This minimizes spam from packagers. Activate this to see upgrades to this package even when it is not installed."];
+                return cell;
+            }
 
-        prompt_ = [[UITextLabel alloc] initWithFrame:prmrect];
+            default: _assert(false);
+        } break;
 
-        [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
-        [prompt_ setBackgroundColor:[UIColor clearColor]];
-        [prompt_ setFont:font];
+        default: _assert(false);
+    }
 
-        [overlay_ addSubview:prompt_];
+    return nil;
+}
 
-        CGSize prgsize = {75, 100};
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package {
+    if ((self = [super initWithBook:book])) {
+        database_ = database;
+        name_ = [package retain];
 
-        CGRect prgrect = {{
-            ovrrect.size.width - prgsize.width - 10,
-            (ovrrect.size.height - prgsize.height) / 2
-        } , prgsize};
+        table_ = [[UIPreferencesTable alloc] initWithFrame:[self bounds]];
+        [self addSubview:table_];
 
-        progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
-        [progress_ setStyle:0];
-        [overlay_ addSubview:progress_];
+        subscribedSwitch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(200, 10, 50, 20)];
+        [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:kUIControlEventMouseUpInside];
 
-        cancel_ = [[UINavigationButton alloc] initWithTitle:@"Cancel" style:UINavigationButtonStyleHighlighted];
-        [cancel_ addTarget:self action:@selector(_onCancel) forControlEvents:UIControlEventTouchUpInside];
+        ignoredSwitch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(200, 10, 50, 20)];
+        [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:kUIControlEventMouseUpInside];
 
-        CGRect frame = [cancel_ frame];
-        frame.size.width = 65;
-        frame.origin.x = ovrrect.size.width - frame.size.width - 5;
-        frame.origin.y = (ovrrect.size.height - frame.size.height) / 2;
-        [cancel_ setFrame:frame];
+        subscribedCell_ = [[UIPreferencesControlTableCell alloc] init];
+        [subscribedCell_ setTitle:@"Show All Changes"];
+        [subscribedCell_ setControl:subscribedSwitch_];
 
-        [cancel_ setBarStyle:barstyle];
+        ignoredCell_ = [[UIPreferencesControlTableCell alloc] init];
+        [ignoredCell_ setTitle:@"Ignore Upgrades"];
+        [ignoredCell_ setControl:ignoredSwitch_];
+
+        [table_ setDataSource:self];
+        [self reloadData];
     } return self;
 }
 
-- (void) _onCancel {
-    updating_ = false;
-    [cancel_ removeFromSuperview];
+- (void) resetViewAnimated:(BOOL)animated {
+    [table_ resetViewAnimated:animated];
 }
 
-- (void) _update { _pooled
-    Status status;
-    status.setDelegate(self);
-
-    [database_ updateWithStatus:status];
+- (void) reloadData {
+    if (package_ != nil)
+        [package_ autorelease];
+    package_ = [database_ packageWithName:name_];
+    if (package_ != nil) {
+        [package_ retain];
+        [subscribedSwitch_ setValue:([package_ subscribed] ? 1 : 0) animated:NO];
+        [ignoredSwitch_ setValue:([package_ ignored] ? 1 : 0) animated:NO];
+    }
 
-    [self
-        performSelectorOnMainThread:@selector(_update_)
-        withObject:nil
-        waitUntilDone:NO
-    ];
+    [table_ reloadData];
 }
 
-- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
-    [prompt_ setText:[NSString stringWithFormat:@"Error: %@", error]];
+- (NSString *) title {
+    return @"Settings";
 }
 
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
-}
+@end
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
+/* Signature View {{{ */
+@interface SignatureView : BrowserView {
+    _transient Database *database_;
+    NSString *package_;
 }
 
-- (void) startProgress {
-}
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package;
 
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
-}
+@end
 
-- (bool) isCancelling:(size_t)received {
-    NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
-    if (received_ != received) {
-        received_ = received;
-        last_ = now;
-    } else if (now - last_ > 15)
-        return true;
-    return !updating_;
+@implementation SignatureView
+
+- (void) dealloc {
+    [package_ release];
+    [super dealloc];
 }
 
-- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
-    [sheet dismiss];
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+    // XXX: dude!
+    [super webView:sender didClearWindowObject:window forFrame:frame];
 }
 
-- (void) _setProgressTitle:(NSString *)title {
-    [prompt_ setText:title];
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package {
+    if ((self = [super initWithBook:book]) != nil) {
+        database_ = database;
+        package_ = [package retain];
+        [self reloadData];
+    } return self;
 }
 
-- (void) _setProgressPercent:(NSNumber *)percent {
-    [progress_ setProgress:[percent floatValue]];
+- (void) resetViewAnimated:(BOOL)animated {
 }
 
-- (void) _addProgressOutput:(NSString *)output {
+- (void) reloadData {
+    [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
 }
 
 @end
+/* }}} */
 
 @interface Cydia : UIApplication <
     ConfirmationViewDelegate,
@@ -5978,7 +6467,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [overlay_ addSubview:hud];
     [hud show:YES];*/
 
+    _trace();
     [database_ reloadData];
+    _trace();
 
     size_t changes(0);
 
@@ -6029,7 +6520,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) _saveConfig {
     if (Changed_) {
+        _trace();
         _assert([Metadata_ writeToFile:@"/var/lib/cydia/metadata.plist" atomically:YES] == YES);
+        _trace();
         Changed_ = false;
     }
 }
@@ -6038,11 +6531,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self _saveConfig];
 
     /* XXX: this is just stupid */
-    if (tag_ != 2)
+    if (tag_ != 2 && install_ != nil)
         [install_ reloadData];
-    if (tag_ != 3)
+    if (tag_ != 3 && changes_ != nil)
         [changes_ reloadData];
-    if (tag_ != 5)
+    if (tag_ != 5 && search_ != nil)
         [search_ reloadData];
 
     [book_ reloadData];
@@ -6186,7 +6679,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (RVPage *) _pageForURL:(NSURL *)url withClass:(Class)_class {
-    BrowserView *browser = [[[_class alloc] initWithBook:book_ database:database_] autorelease];
+    BrowserView *browser = [[[_class alloc] initWithBook:book_] autorelease];
     [browser loadURL:url];
     return browser;
 }
@@ -6357,6 +6850,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[UIKeyboardImpl sharedInstance] setSoundsEnabled:(Sounds_Keyboard_ ? YES : NO)];
     [overlay_ addSubview:keyboard_];
 
+    if (!bootstrap_)
+        [underlay_ addSubview:overlay_];
+
+    [self reloadData];
+
     install_ = [[InstallView alloc] initWithBook:book_ database:database_];
     changes_ = [[ChangesView alloc] initWithBook:book_ database:database_];
     search_ = [[SearchView alloc] initWithBook:book_ database:database_];
@@ -6366,11 +6864,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         withClass:[ManageView class]
     ] retain];
 
-    if (!bootstrap_)
-        [underlay_ addSubview:overlay_];
-
-    [self reloadData];
-
     if (bootstrap_)
         [self bootstrap];
     else
@@ -6489,7 +6982,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) openMailToURL:(NSURL *)url {
+// XXX: this makes me sad
+#if 0
     [[[MailToView alloc] initWithView:underlay_ delegate:self url:url] autorelease];
+#else
+    [UIApp openURL:url];
+#endif
 }
 
 - (RVPage *) pageForPackage:(NSString *)name {
@@ -6531,6 +7029,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         return [self _pageForURL:[NSURL URLWithString:[href substringFromIndex:12]] withClass:[BrowserView class]];
     else if ([href hasPrefix:@"cydia://launch/"])
         [self launchApplicationWithIdentifier:[href substringFromIndex:15] suspended:NO];
+    else if ([href hasPrefix:@"cydia://package-settings/"])
+        return [[[SettingsView alloc] initWithBook:book_ database:database_ package:[href substringFromIndex:25]] autorelease];
+    else if ([href hasPrefix:@"cydia://package-signature/"])
+        return [[[SignatureView alloc] initWithBook:book_ database:database_ package:[href substringFromIndex:26]] autorelease];
     else if ([href hasPrefix:@"cydia://package/"])
         return [self pageForPackage:[href substringFromIndex:16]];
     else if ([href hasPrefix:@"cydia://files/"]) {
@@ -6571,6 +7073,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     essential_ = [[NSMutableArray alloc] initWithCapacity:4];
     broken_ = [[NSMutableArray alloc] initWithCapacity:4];
 
+    [NSURLProtocol registerClass:[CydiaURLProtocol class]];
+
     CGRect screenrect = [UIHardware fullScreenApplicationContentRect];
     window_ = [[UIWindow alloc] initWithContentRect:screenrect];
 
@@ -6578,7 +7082,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [window_ makeKey:self];
     [window_ setHidden:NO];
 
-    database_ = [[Database alloc] init];
+    database_ = [Database sharedInstance];
     progress_ = [[ProgressView alloc] initWithFrame:[window_ bounds] database:database_ delegate:self];
     [database_ setDelegate:progress_];
     [window_ setContentView:progress_];
@@ -6621,7 +7125,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 + (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
-    NSLog(@"exc:%s", sel_getName(selector));
     return selector != @selector(supports:);
 }