]> git.saurik.com Git - cydia.git/commitdiff
Drastic upgrades to nearly everything.
authorJay Freeman (saurik) <saurik@saurk.com>
Sun, 3 Feb 2008 07:15:54 +0000 (07:15 +0000)
committerJay Freeman (saurik) <saurik@saurk.com>
Sun, 3 Feb 2008 07:15:54 +0000 (07:15 +0000)
21 files changed:
Cydia.mm
Preferences.mm [new file with mode: 0644]
data/changes-dn.png [new file with mode: 0644]
data/changes-up.png [new file with mode: 0644]
data/folder.png [new file with mode: 0644]
data/icon.png
data/install-dn.png
data/install-up.png
data/manage-dn.png [new file with mode: 0644]
data/manage-up.png [new file with mode: 0644]
data/reload.png [new file with mode: 0644]
data/search-dn.png [new file with mode: 0644]
data/search-up.png [new file with mode: 0644]
data/sources-dn.png [deleted file]
data/sources-up.png [deleted file]
data/uninstall-dn.png [deleted file]
data/uninstall-up.png [deleted file]
data/upgrade-dn.png [deleted file]
data/upgrade-up.png [deleted file]
internals.h [new file with mode: 0644]
makefile

index a8ef907db817e78a40d130dd5accc814a57a2050..470f276b9b4fa87afe5a71e8031b0146805c2d3f 100644 (file)
--- a/Cydia.mm
+++ b/Cydia.mm
 
 #include <sys/sysctl.h>
 
+extern "C" {
+#include <mach-o/nlist.h>
+}
+
+#include <objc/objc-class.h>
+
 #include <errno.h>
 #include <pcre.h>
 #include <string.h>
     } \
 while (false)
 /* }}} */
-
+/* Miscellaneous Messages {{{ */
 @interface WebView
 - (void) setApplicationNameForUserAgent:(NSString *)applicationName;
 @end
 
+@interface NSString (Cydia)
+- (NSString *) stringByAddingPercentEscapes;
+- (NSString *) stringByReplacingCharacter:(unsigned short)arg0 withCharacter:(unsigned short)arg1;
+@end
+/* }}} */
+
+/* Reset View (UIView) {{{ */
+@interface UIView (CYResetView)
+- (void) resetViewAnimated:(BOOL)animated;
+@end
+
+@implementation UIView (CYResetView)
+
+- (void) resetViewAnimated:(BOOL)animated {
+    fprintf(stderr, "%s\n", self->isa->name);
+    _assert(false);
+}
+
+@end
+/* }}} */
+/* Reset View (UITable) {{{ */
+@interface UITable (CYResetView)
+- (void) resetViewAnimated:(BOOL)animated;
+@end
+
+@implementation UITable (CYResetView)
+
+- (void) resetViewAnimated:(BOOL)animated {
+    [self selectRow:-1 byExtendingSelection:NO withFade:animated];
+}
+
+@end
+/* }}} */
+/* Reset View (UISectionList) {{{ */
+@interface UISectionList (CYResetView)
+- (void) resetViewAnimated:(BOOL)animated;
+@end
+
+@implementation UISectionList (CYResetView)
+
+- (void) resetViewAnimated:(BOOL)animated {
+    [[self table] resetViewAnimated:animated];
+}
+
+@end
+/* }}} */
+
+/* Perl-Compatible RegEx {{{ */
+class Pcre {
+  private:
+    pcre *code_;
+    pcre_extra *study_;
+    int capture_;
+    int *matches_;
+    const char *data_;
+
+  public:
+    Pcre(const char *regex) :
+        study_(NULL)
+    {
+        const char *error;
+        int offset;
+        code_ = pcre_compile(regex, 0, &error, &offset, NULL);
+
+        if (code_ == NULL) {
+            fprintf(stderr, "%d:%s\n", offset, error);
+            _assert(false);
+        }
+
+        pcre_fullinfo(code_, study_, PCRE_INFO_CAPTURECOUNT, &capture_);
+        matches_ = new int[(capture_ + 1) * 3];
+    }
+
+    ~Pcre() {
+        pcre_free(code_);
+        delete matches_;
+    }
+
+    NSString *operator [](size_t match) {
+        return [NSString
+            stringWithCString:(data_ + matches_[match * 2])
+            length:(matches_[match * 2 + 1] - matches_[match * 2])
+        ];
+    }
+
+    bool operator ()(const char *data, size_t size) {
+        data_ = data;
+        return pcre_exec(code_, study_, data, size, 0, 0, matches_, (capture_ + 1) * 3) >= 0;
+    }
+};
+/* }}} */
+/* CoreGraphicsServices Primitives {{{ */
+class CGColor {
+  private:
+    CGColorRef color_;
+
+  public:
+    CGColor(CGColorSpaceRef space, float red, float green, float blue, float alpha) {
+        float color[] = {red, green, blue, alpha};
+        color_ = CGColorCreate(space, color);
+    }
+
+    ~CGColor() {
+        CGColorRelease(color_);
+    }
+
+    operator CGColorRef() {
+        return color_;
+    }
+};
+
+class GSFont {
+  private:
+    GSFontRef font_;
+
+  public:
+    ~GSFont() {
+        /* XXX: no GSFontRelease()? */
+        CFRelease(font_);
+    }
+};
+/* }}} */
+
 static const int PulseInterval_ = 50000;
 const char *Machine_ = NULL;
 const char *SerialNumber_ = NULL;
 
-@interface NSString (CydiaBypass)
-- (NSString *) stringByAddingPercentEscapes;
-@end
+static NSMutableDictionary *Metadata_;
+static NSMutableDictionary *Packages_;
+static NSDate *now_;
 
 @protocol ProgressDelegate
 - (void) setError:(NSString *)error;
@@ -66,6 +195,32 @@ NSString *SizeString(double size) {
     return [NSString stringWithFormat:@"%.1f%s", size, powers_[power]];
 }
 
+static const float TextViewOffset_ = 22;
+
+UITextView *GetTextView(NSString *value, float left, bool html) {
+    UITextView *text([[[UITextView alloc] initWithFrame:CGRectMake(left, 3, 310 - left, 1000)] autorelease]);
+    [text setEditable:NO];
+    [text setTextSize:16];
+    if (html)
+        [text setHTML:value];
+    else
+        [text setText:value];
+    [text setEnabled:NO];
+
+    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+    CGColor clear(space, 0, 0, 0, 0);
+    [text setBackgroundColor:clear];
+    CGColorSpaceRelease(space);
+
+    CGRect frame = [text frame];
+    [text setFrame:frame];
+    CGRect rect = [text visibleTextRect];
+    frame.size.height = rect.size.height;
+    [text setFrame:frame];
+
+    return text;
+}
+
 /* Status Delegation {{{ */
 class Status :
     public pkgAcquireStatus
@@ -169,6 +324,8 @@ extern NSString *kUIButtonBarButtonTitleWidth;
 extern NSString *kUIButtonBarButtonType;
 /* }}} */
 /* Mime Addresses {{{ */
+Pcre email_r("^\"?(.*)\"? <([^>]*)>$");
+
 @interface Address : NSObject {
     NSString *name_;
     NSString *email_;
@@ -206,26 +363,12 @@ extern NSString *kUIButtonBarButtonType;
 
 - (Address *) initWithString:(NSString *)string {
     if ((self = [super init]) != nil) {
-        const char *error;
-        int offset;
-        pcre *code = pcre_compile("^\"?(.*)\"? <([^>]*)>$", 0, &error, &offset, NULL);
-
-        if (code == NULL) {
-            fprintf(stderr, "%d:%s\n", offset, error);
-            _assert(false);
-        }
-
-        pcre_extra *study = NULL;
-        int capture;
-        pcre_fullinfo(code, study, PCRE_INFO_CAPTURECOUNT, &capture);
-        int matches[(capture + 1) * 3];
-
-        size_t size = [string length];
         const char *data = [string UTF8String];
+        size_t size = [string length];
 
-        if (pcre_exec(code, study, data, size, 0, 0, matches, sizeof(matches) / sizeof(matches[0])) >= 0) {
-            name_ = [[NSString stringWithCString:(data + matches[2]) length:(matches[3] - matches[2])] retain];
-            email_ = [[NSString stringWithCString:(data + matches[4]) length:(matches[5] - matches[4])] retain];
+        if (email_r(data, size)) {
+            name_ = [email_r[1] retain];
+            email_ = [email_r[2] retain];
         } else {
             name_ = [[NSString stringWithCString:data length:size] retain];
             email_ = nil;
@@ -286,6 +429,7 @@ inline float interpolate(float begin, float end, float fraction) {
 
 @class Package;
 
+/* Database Interface {{{ */
 @interface Database : NSObject {
     pkgCacheFile cache_;
     pkgRecords *records_;
@@ -300,6 +444,8 @@ inline float interpolate(float begin, float end, float fraction) {
     int statusfd_;
 }
 
+- (void) dealloc;
+
 - (void) _readStatus:(NSNumber *)fd;
 - (void) _readOutput:(NSNumber *)fd;
 
@@ -319,53 +465,139 @@ inline float interpolate(float begin, float end, float fraction) {
 
 - (void) setDelegate:(id)delegate;
 @end
+/* }}} */
 
 /* Reset View {{{ */
 @interface ResetView : UIView {
+    UIPushButton *reload_;
+    NSMutableArray *views_;
     UINavigationBar *navbar_;
+    UITransitionView *transition_;
     bool resetting_;
+    id delegate_;
 }
 
+- (void) dealloc;
+
 - (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item;
 
-- (void) dealloc;
+- (id) initWithFrame:(CGRect)frame;
+- (void) setDelegate:(id)delegate;
+- (void) reloadPushed;
+
+- (void) pushView:(UIView *)view withTitle:(NSString *)title backButtonTitle:(NSString *)back rightButton:(NSString *)right;
+- (void) popViews:(unsigned)views;
 - (void) resetView;
 - (void) _resetView;
-- (NSString *) leftTitle;
-- (NSString *) rightTitle;
+- (void) setPrompt;
 @end
 
 @implementation ResetView
 
+- (void) dealloc {
+    [reload_ release];
+    [transition_ release];
+    [navbar_ release];
+    [views_ release];
+    [super dealloc];
+}
+
 - (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item {
-    if ([[navbar_ navigationItems] count] == 1)
+    [views_ removeLastObject];
+    UIView *view([views_ lastObject]);
+    [view resetViewAnimated:!resetting_];
+    if (!resetting_)
+        [transition_ transition:2 toView:view];
+
+    if ([views_ count] == 1)
         [self _resetView];
 }
 
-- (void) dealloc {
-    [navbar_ release];
-    [super dealloc];
+- (id) initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
+        views_ = [[NSMutableArray arrayWithCapacity:4] retain];
+
+        struct CGRect bounds = [self bounds];
+        CGSize navsize = [UINavigationBar defaultSizeWithPrompt];
+        CGRect navrect = {{0, 0}, navsize};
+
+        navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
+        [self addSubview:navbar_];
+
+        [navbar_ setBarStyle:1];
+        [navbar_ setDelegate:self];
+
+        transition_ = [[UITransitionView alloc] initWithFrame:CGRectMake(
+            bounds.origin.x, bounds.origin.y + navsize.height, bounds.size.width, bounds.size.height - navsize.height
+        )];
+
+        //reload_ = [[UIPushButton alloc] initWithFrame:CGRectMake(284, 8, 29, 23)];
+        reload_ = [[UIPushButton alloc] initWithFrame:CGRectMake(282, 5, 29, 23)];
+        [reload_ setShowPressFeedback:YES];
+        [reload_ setImage:[UIImage applicationImageNamed:@"reload.png"]];
+        [reload_ addTarget:self action:@selector(reloadPushed) forEvents:1];
+
+        [navbar_ addSubview:reload_];
+
+        [self addSubview:transition_];
+    } return self;
+}
+
+- (void) setDelegate:(id)delegate {
+    delegate_ = delegate;
+}
+
+- (void) reloadPushed {
+    [delegate_ update];
+}
+
+- (void) pushView:(UIView *)view withTitle:(NSString *)title backButtonTitle:(NSString *)back rightButton:(NSString *)right {
+    UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:title] autorelease];
+    [navbar_ pushNavigationItem:navitem];
+    [navitem setBackButtonTitle:back];
+
+    [navbar_ showButtonsWithLeftTitle:nil rightTitle:right];
+
+    [transition_ transition:([views_ count] == 0 ? 0 : 1) toView:view];
+    [views_ addObject:view];
+}
+
+- (void) popViews:(unsigned)views {
+    resetting_ = true;
+    for (unsigned i(0); i != views; ++i)
+        [navbar_ popNavigationItem];
+    resetting_ = false;
+
+    [transition_ transition:2 toView:[views_ lastObject]];
 }
 
 - (void) resetView {
     resetting_ = true;
     if ([[navbar_ navigationItems] count] == 1)
         [self _resetView];
-    else while ([[navbar_ navigationItems] count] != 1)
+    else do
         [navbar_ popNavigationItem];
+    while ([[navbar_ navigationItems] count] != 1);
     resetting_ = false;
+
+    [transition_ transition:0 toView:[views_ lastObject]];
 }
 
 - (void) _resetView {
-    [navbar_ showButtonsWithLeftTitle:[self leftTitle] rightTitle:[self rightTitle]];
 }
 
-- (NSString *) leftTitle {
-    return nil;
-}
+- (void) setPrompt {
+    NSDate *update = [Metadata_ objectForKey:@"LastUpdate"];
 
-- (NSString *) rightTitle {
-    return nil;
+    CFLocaleRef locale = CFLocaleCopyCurrent();
+    CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
+    CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) update);
+
+    [navbar_ setPrompt:[NSString stringWithFormat:@"Last Updated: %@", (NSString *) formatted]];
+
+    CFRelease(formatter);
+    CFRelease(formatted);
+    CFRelease(locale);
 }
 
 @end
@@ -375,24 +607,13 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     if ([packages count] == 0)
         return;
 
-    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
-    float clear[] = {0, 0, 0, 0};
-    float blue[] = {0, 0, 0.4, 1};
-
-    UITextView *text([[[UITextView alloc] initWithFrame: CGRectMake(110, 3, 200, 60)] autorelease]);
-    [text setEditable:NO];
-    [text setTextSize:16];
-    [text setBackgroundColor:CGColorCreate(space, clear)];
-    [text setTextColor:CGColorCreate(space, blue)];
-    [text setText:([packages count] == 0 ? @"n/a" : [packages componentsJoinedByString:@", "])];
-    [text setEnabled:NO];
-
-    CGRect frame([text frame]);
-    CGSize size([text contentSize]);
-    frame.size.height = size.height;
-    [text setFrame:frame];
-
+    UITextView *text = GetTextView([packages count] == 0 ? @"n/a" : [packages componentsJoinedByString:@", "], 110, false);
     [fields setObject:text forKey:key];
+
+    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+    CGColor blue(space, 0, 0, 0.4, 1);
+    [text setTextColor:blue];
+    CGColorSpaceRelease(space);
 }
 
 @protocol ConfirmationViewDelegate
@@ -498,7 +719,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
         return proposed;
     else {
         _assert(size_t(row) < [fields_ count]);
-        return [[[fields_ allValues] objectAtIndex:row] contentSize].height;
+        return [[[fields_ allValues] objectAtIndex:row] visibleTextRect].size.height + TextViewOffset_;
     }
 }
 
@@ -632,26 +853,78 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 /* }}} */
 
 /* Package Class {{{ */
+NSString *Scour(const char *field, const char *begin, const char *end) {
+    size_t i(0), l(strlen(field));
+
+    for (;;) {
+        const char *name = begin + i;
+        const char *colon = name + l;
+        const char *value = colon + 1;
+
+        if (
+            value < end &&
+            *colon == ':' &&
+            memcmp(name, field, l) == 0
+        ) {
+            while (value != end && value[0] == ' ')
+                ++value;
+            const char *line = std::find(value, end, '\n');
+            while (line != value && line[-1] == ' ')
+                --line;
+            return [NSString stringWithCString:value length:(line - value)];
+        } else {
+            begin = std::find(begin, end, '\n');
+            if (begin == end)
+                return nil;
+            ++begin;
+        }
+    }
+}
+
 @interface Package : NSObject {
     pkgCache::PkgIterator iterator_;
     Database *database_;
-    pkgRecords::Parser *parser_;
     pkgCache::VerIterator version_;
     pkgCache::VerFileIterator file_;
+
+    NSString *latest_;
+    NSString *installed_;
+
+    NSString *id_;
+    NSString *name_;
+    NSString *tagline_;
+    NSString *icon_;
+    NSString *bundle_;
 }
 
+- (void) dealloc;
+
 - (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database version:(pkgCache::VerIterator)version file:(pkgCache::VerFileIterator)file;
 + (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database;
 
-- (NSString *) name;
 - (NSString *) section;
-- (NSString *) latest;
-- (NSString *) installed;
 - (Address *) maintainer;
 - (size_t) size;
-- (NSString *) tagline;
 - (NSString *) description;
+- (NSString *) index;
+
+- (NSDate *) seen;
+
+- (NSString *) latest;
+- (NSString *) installed;
+- (BOOL) upgradable;
+
+- (NSString *) id;
+- (NSString *) name;
+- (NSString *) tagline;
+- (NSString *) icon;
+- (NSString *) bundle;
+
+- (BOOL) matches:(NSString *)text;
+
+- (NSComparisonResult) compareByName:(Package *)package;
 - (NSComparisonResult) compareBySectionAndName:(Package *)package;
+- (NSComparisonResult) compareForChanges:(Package *)package;
 
 - (void) install;
 - (void) remove;
@@ -659,6 +932,22 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 @implementation Package
 
+- (void) dealloc {
+    [latest_ release];
+    if (installed_ != nil)
+        [installed_ release];
+
+    [id_ release];
+    if (name_ != nil)
+        [name_ release];
+    [tagline_ release];
+    if (icon_ != nil)
+        [icon_ release];
+    if (bundle_ != nil)
+        [bundle_ release];
+    [super dealloc];
+}
+
 - (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database version:(pkgCache::VerIterator)version file:(pkgCache::VerFileIterator)file {
     if ((self = [super init]) != nil) {
         iterator_ = iterator;
@@ -666,7 +955,35 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
         version_ = version;
         file_ = file;
-        parser_ = &[database_ records]->Lookup(file);
+
+        pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
+
+        const char *begin, *end;
+        parser->GetRec(begin, end);
+
+        latest_ = [[NSString stringWithCString:version_.VerStr()] retain];
+        installed_ = iterator_.CurrentVer().end() ? nil : [[NSString stringWithCString:iterator_.CurrentVer().VerStr()] retain];
+
+        id_ = [[[NSString stringWithCString:iterator_.Name()] lowercaseString] retain];
+        name_ = Scour("Name", begin, end);
+        if (name_ != nil)
+            name_ = [name_ retain];
+        tagline_ = [[NSString stringWithCString:parser->ShortDesc().c_str()] retain];
+        icon_ = Scour("Icon", begin, end);
+        if (icon_ != nil)
+            icon_ = [icon_ retain];
+        bundle_ = Scour("Bundle", begin, end);
+        if (bundle_ != nil)
+            bundle_ = [bundle_ retain];
+
+        NSMutableDictionary *metadata = [Packages_ objectForKey:id_];
+        if (metadata == nil) {
+            metadata = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+                now_, @"FirstSeen",
+            nil];
+
+            [Packages_ setObject:metadata forKey:id_];
+        }
     } return self;
 }
 
@@ -682,43 +999,126 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     return nil;
 }
 
-- (NSString *) name {
-    return [[NSString stringWithCString:iterator_.Name()] lowercaseString];
+- (NSString *) section {
+    return [[NSString stringWithCString:iterator_.Section()] stringByReplacingCharacter:'_' withCharacter:' '];
 }
 
-- (NSString *) section {
-    return [NSString stringWithCString:iterator_.Section()];
+- (Address *) maintainer {
+    pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
+    return [Address addressWithString:[NSString stringWithCString:parser->Maintainer().c_str()]];
+}
+
+- (size_t) size {
+    return version_->InstalledSize;
+}
+
+- (NSString *) description {
+    pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
+    NSString *description([NSString stringWithCString:parser->LongDesc().c_str()]);
+
+    NSArray *lines = [description componentsSeparatedByString:@"\n"];
+    NSMutableArray *trimmed = [NSMutableArray arrayWithCapacity:([lines count] - 1)];
+    if ([lines count] < 2)
+        return nil;
+
+    NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
+    for (size_t i(1); i != [lines count]; ++i) {
+        NSString *trim = [[lines objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace];
+        [trimmed addObject:trim];
+    }
+
+    return [trimmed componentsJoinedByString:@"\n"];
+}
+
+- (NSString *) index {
+    return [[[self name] substringToIndex:1] uppercaseString];
+}
+
+- (NSDate *) seen {
+    return [[Packages_ objectForKey:id_] objectForKey:@"FirstSeen"];
 }
 
 - (NSString *) latest {
-    return [NSString stringWithCString:version_.VerStr()];
+    return latest_;
 }
 
 - (NSString *) installed {
-    return iterator_.CurrentVer().end() ? nil : [NSString stringWithCString:iterator_.CurrentVer().VerStr()];
+    return installed_;
 }
 
-- (Address *) maintainer {
-    return [Address addressWithString:[NSString stringWithCString:parser_->Maintainer().c_str()]];
+- (BOOL) upgradable {
+    NSString *installed = [self installed];
+    return installed != nil && [[self latest] compare:installed] != NSOrderedSame ? YES : NO;
 }
 
-- (size_t) size {
-    return version_->InstalledSize;
+- (NSString *) id {
+    return id_;
+}
+
+- (NSString *) name {
+    return name_ == nil ? id_ : name_;
 }
 
 - (NSString *) tagline {
-    return [NSString stringWithCString:parser_->ShortDesc().c_str()];
+    return tagline_;
 }
 
-- (NSString *) description {
-    return [NSString stringWithCString:parser_->LongDesc().c_str()];
+- (NSString *) icon {
+    return icon_;
+}
+
+- (NSString *) bundle {
+    return bundle_;
+}
+
+- (BOOL) matches:(NSString *)text {
+    if (text == nil)
+        return NO;
+
+    NSRange range;
+
+    range = [[self name] rangeOfString:text options:NSCaseInsensitiveSearch];
+    if (range.location != NSNotFound)
+        return YES;
+
+    range = [[self tagline] rangeOfString:text options:NSCaseInsensitiveSearch];
+    if (range.location != NSNotFound)
+        return YES;
+
+    return NO;
+}
+
+- (NSComparisonResult) compareByName:(Package *)package {
+    return [[self name] caseInsensitiveCompare:[package name]];
 }
 
 - (NSComparisonResult) compareBySectionAndName:(Package *)package {
-    NSComparisonResult result = [[self section] compare:[package section]];
+    NSComparisonResult result = [[self section] caseInsensitiveCompare:[package section]];
     if (result != NSOrderedSame)
         return result;
-    return [[self name] compare:[package name]];
+    return [self compareByName:package];
+}
+
+- (NSComparisonResult) compareForChanges:(Package *)package {
+    BOOL lhs = [self upgradable];
+    BOOL rhs = [package upgradable];
+
+    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);
+        }
+    }
+
+    return [self compareByName:package];
 }
 
 - (void) install {
@@ -750,6 +1150,8 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 - (Section *) initWithName:(NSString *)name row:(size_t)row;
 - (NSString *) name;
 - (size_t) row;
+- (NSArray *) packages;
+- (size_t) count;
 - (void) addPackage:(Package *)package;
 @end
 
@@ -777,6 +1179,14 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     return row_;
 }
 
+- (NSArray *) packages {
+    return packages_;
+}
+
+- (size_t) count {
+    return [packages_ count];
+}
+
 - (void) addPackage:(Package *)package {
     [packages_ addObject:package];
 }
@@ -785,11 +1195,14 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 /* }}} */
 
 /* Package View {{{ */
+@protocol PackageViewDelegate
+- (void) performPackage:(Package *)package;
+@end
+
 @interface PackageView : UIView {
     UIPreferencesTable *table_;
     Package *package_;
-    Database *database_;
-    NSMutableArray *cells_;
+    UITextView *description_;
     id delegate_;
 }
 
@@ -797,13 +1210,16 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 - (int) numberOfGroupsInPreferencesTable:(UIPreferencesTable *)table;
 - (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group;
+- (float) preferencesTable:(UIPreferencesTable *)table heightForRow:(int)row inGroup:(int)group withProposedHeight:(float)proposed;
 - (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group;
 - (UIPreferencesTableCell *) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group;
 
 - (BOOL) canSelectRow:(int)row;
 - (void) tableRowSelected:(NSNotification *)notification;
 
-- (id) initWithFrame:(struct CGRect)frame database:(Database *)database;
+- (Package *) package;
+
+- (id) initWithFrame:(struct CGRect)frame;
 - (void) setPackage:(Package *)package;
 - (void) setDelegate:(id)delegate;
 @end
@@ -813,9 +1229,9 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 - (void) dealloc {
     if (package_ != nil)
         [package_ release];
+    if (description_ != nil)
+        [description_ release];
     [table_ release];
-    [database_ release];
-    [cells_ release];
     [super dealloc];
 }
 
@@ -825,60 +1241,72 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 - (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group {
     switch (group) {
-        case 0: return @"Specifics";
-        case 1: return @"Description";
+        case 0: return nil;
+        case 1: return @"Details";
+        case 2: return @"Source";
 
         default: _assert(false);
     }
 }
 
+- (float) preferencesTable:(UIPreferencesTable *)table heightForRow:(int)row inGroup:(int)group withProposedHeight:(float)proposed {
+    if (group != 0 || row != 1)
+        return proposed;
+    else
+        return [description_ visibleTextRect].size.height + TextViewOffset_;
+}
+
 - (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group {
     switch (group) {
-        case 0: return 6;
-        case 1: return 1;
+        case 0: return 2;
+        case 1: return 5;
+        case 2: return 0;
 
         default: _assert(false);
     }
 }
 
 - (UIPreferencesTableCell *) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group {
-    UIPreferencesTableCell *cell;
+    UIPreferencesTableCell *cell = [[[UIPreferencesTableCell alloc] init] autorelease];
+    [cell setShowSelection:NO];
 
     switch (group) {
         case 0: switch (row) {
             case 0:
-                cell = [cells_ objectAtIndex:0];
-                [cell setTitle:@"Name"];
-                [cell setValue:[package_ name]];
+                [cell setTitle:[package_ name]];
+                [cell setValue:[package_ latest]];
+            break;
+
+            case 1:
+                [cell addSubview:description_];
+            break;
+
+            default: _assert(false);
+        } break;
+
+        case 1: switch (row) {
+            case 0:
+                [cell setTitle:@"Identifier"];
+                [cell setValue:[package_ id]];
             break;
 
             case 1: {
-                cell = [cells_ objectAtIndex:1];
-                [cell setTitle:@"Installed"];
+                [cell setTitle:@"Installed Version"];
                 NSString *installed([package_ installed]);
                 [cell setValue:(installed == nil ? @"n/a" : installed)];
             } break;
 
             case 2:
-                cell = [cells_ objectAtIndex:2];
-                [cell setTitle:@"Latest"];
-                [cell setValue:[package_ latest]];
-            break;
-
-            case 3:
-                cell = [cells_ objectAtIndex:3];
                 [cell setTitle:@"Section"];
                 [cell setValue:[package_ section]];
             break;
 
-            case 4:
-                cell = [cells_ objectAtIndex:4];
-                [cell setTitle:@"Size"];
+            case 3:
+                [cell setTitle:@"Expanded Size"];
                 [cell setValue:SizeString([package_ size])];
             break;
 
-            case 5:
-                cell = [cells_ objectAtIndex:5];
+            case 4:
                 [cell setTitle:@"Maintainer"];
                 [cell setValue:[[package_ maintainer] name]];
                 [cell setShowDisclosure:YES];
@@ -888,18 +1316,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
             default: _assert(false);
         } break;
 
-        case 1: switch (row) {
-            case 0:
-                cell = [cells_ objectAtIndex:6];
-                [cell setTitle:nil];
-                [cell setValue:[package_ tagline]];
-            break;
-
-            case 1:
-                cell = [cells_ objectAtIndex:7];
-                [cell setTitle:@"Description"];
-                [cell setValue:[package_ description]];
-            break;
+        case 2: switch (row) {
         } break;
 
         default: _assert(false);
@@ -913,8 +1330,9 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 }
 
 - (void) tableRowSelected:(NSNotification *)notification {
+    printf("%d\n", [table_ selectedRow]);
     switch ([table_ selectedRow]) {
-        case 5:
+        case 8:
             [delegate_ openURL:[NSURL URLWithString:[NSString stringWithFormat:@"mailto:%@?subject=%@",
                 [[package_ maintainer] email],
                 [[NSString stringWithFormat:@"regarding apt package \"%@\"", [package_ name]] stringByAddingPercentEscapes]
@@ -923,29 +1341,46 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     }
 }
 
-- (id) initWithFrame:(struct CGRect)frame database:(Database *)database {
-    if ((self = [super initWithFrame:frame]) != nil) {
-        database_ = [database retain];
+- (Package *) package {
+    return package_;
+}
 
+- (id) initWithFrame:(struct CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
         table_ = [[UIPreferencesTable alloc] initWithFrame:[self bounds]];
         [self addSubview:table_];
 
         [table_ setDataSource:self];
         [table_ setDelegate:self];
-
-        cells_ = [[NSMutableArray arrayWithCapacity:16] retain];
-
-        for (unsigned i = 0; i != 8; ++i) {
-            UIPreferencesTableCell *cell = [[[UIPreferencesTableCell alloc] init] autorelease];
-            [cell setShowSelection:NO];
-            [cells_ addObject:cell];
-        }
     } return self;
 }
 
 - (void) setPackage:(Package *)package {
-    package_ = [package retain];
-    [table_ reloadData];
+    if (package_ != nil) {
+        [package_ autorelease];
+        package_ = nil;
+    }
+
+    if (description_ != nil) {
+        [description_ release];
+        description_ = nil;
+    }
+
+    if (package != nil) {
+        package_ = [package retain];
+
+        NSString *description([package description]);
+        if (description == nil)
+            description = [package tagline];
+        description_ = [GetTextView(description, 12, true) retain];
+
+        CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+        CGColor black(space, 0, 0, 0, 1);
+        [description_ setTextColor:black];
+        CGColorSpaceRelease(space);
+
+        [table_ reloadData];
+    }
 }
 
 - (void) setDelegate:(id)delegate {
@@ -955,20 +1390,16 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 @end
 /* }}} */
 /* Package Cell {{{ */
-@protocol PackageCellDelegate
-- (NSString *) versionWithPackage:(Package *)package;
-@end
-
 @interface PackageCell : UITableCell {
     UITextLabel *name_;
-    UIRightTextLabel *version_;
+    UITextLabel *version_;
     UITextLabel *description_;
-    id delegate_;
+    SEL versioner_;
 }
 
 - (void) dealloc;
 
-- (PackageCell *) initWithDelegate:(id)delegate;
+- (PackageCell *) initWithVersioner:(SEL)versioner;
 - (void) setPackage:(Package *)package;
 
 - (void) _setSelected:(float)fraction;
@@ -987,33 +1418,36 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     [super dealloc];
 }
 
-- (PackageCell *) initWithDelegate:(id)delegate {
+- (PackageCell *) initWithVersioner:(SEL)versioner {
     if ((self = [super init]) != nil) {
-        delegate_ = delegate;
+        versioner_ = versioner;
 
         GSFontRef bold = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 22);
         GSFontRef large = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 16);
         GSFontRef small = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 14);
 
         CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
-        float clear[] = {0, 0, 0, 0};
+
+        CGColor clear(space, 0, 0, 0, 0);
 
         name_ = [[UITextLabel alloc] initWithFrame:CGRectMake(12, 7, 250, 25)];
-        [name_ setBackgroundColor:CGColorCreate(space, clear)];
+        [name_ setBackgroundColor:clear];
         [name_ setFont:bold];
 
-        version_ = [[UIRightTextLabel alloc] initWithFrame:CGRectMake(290, 7, 70, 25)];
-        [version_ setBackgroundColor:CGColorCreate(space, clear)];
+        version_ = [[UIRightTextLabel alloc] initWithFrame:CGRectMake(286, 7, 70, 25)];
+        [version_ setBackgroundColor:clear];
         [version_ setFont:large];
 
         description_ = [[UITextLabel alloc] initWithFrame:CGRectMake(13, 35, 315, 20)];
-        [description_ setBackgroundColor:CGColorCreate(space, clear)];
+        [description_ setBackgroundColor:clear];
         [description_ setFont:small];
 
         [self addSubview:name_];
         [self addSubview:version_];
         [self addSubview:description_];
 
+        CGColorSpaceRelease(space);
+
         CFRelease(small);
         CFRelease(large);
         CFRelease(bold);
@@ -1022,34 +1456,36 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 - (void) setPackage:(Package *)package {
     [name_ setText:[package name]];
-    [version_ setText:[delegate_ versionWithPackage:package]];
+    [version_ setText:[package latest]];
     [description_ setText:[package tagline]];
 }
 
 - (void) _setSelected:(float)fraction {
     CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
 
-    float black[] = {
+    CGColor black(space,
         interpolate(0.0, 1.0, fraction),
         interpolate(0.0, 1.0, fraction),
         interpolate(0.0, 1.0, fraction),
-    1.0};
+    1.0);
 
-    float blue[] = {
+    CGColor blue(space,
         interpolate(0.2, 1.0, fraction),
         interpolate(0.2, 1.0, fraction),
         interpolate(1.0, 1.0, fraction),
-    1.0};
+    1.0);
 
-    float gray[] = {
+    CGColor gray(space,
         interpolate(0.4, 1.0, fraction),
         interpolate(0.4, 1.0, fraction),
         interpolate(0.4, 1.0, fraction),
-    1.0};
+    1.0);
+
+    [name_ setColor:black];
+    [version_ setColor:blue];
+    [description_ setColor:gray];
 
-    [name_ setColor:CGColorCreate(space, black)];
-    [version_ setColor:CGColorCreate(space, blue)];
-    [description_ setColor:CGColorCreate(space, gray)];
+    CGColorSpaceRelease(space);
 }
 
 - (void) setSelected:(BOOL)selected {
@@ -1071,905 +1507,991 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 @end
 /* }}} */
 
-/* Source {{{ */
-@interface Source : NSObject {
-    NSString *description_;
-    NSString *label_;
-    NSString *origin_;
-
-    NSString *uri_;
-    NSString *distribution_;
-    NSString *type_;
+/* Database Implementation {{{ */
+@implementation Database
 
-    BOOL trusted_;
+- (void) dealloc {
+    _assert(false);
+    [super dealloc];
 }
 
-- (void) dealloc;
+- (void) _readStatus:(NSNumber *)fd {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+    __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
+    std::istream is(&ib);
+    std::string line;
 
-- (Source *) initWithMetaIndex:(metaIndex *)index;
+    const char *error;
+    int offset;
+    pcre *code = pcre_compile("^([^:]*):([^:]*):([^:]*):(.*)$", 0, &error, &offset, NULL);
 
-- (BOOL) trusted;
+    pcre_extra *study = NULL;
+    int capture;
+    pcre_fullinfo(code, study, PCRE_INFO_CAPTURECOUNT, &capture);
+    int matches[(capture + 1) * 3];
 
-- (NSString *) uri;
-- (NSString *) distribution;
-- (NSString *) type;
+    while (std::getline(is, line)) {
+        const char *data(line.c_str());
 
-- (NSString *) description;
-- (NSString *) label;
-- (NSString *) origin;
-@end
+        _assert(pcre_exec(code, study, data, line.size(), 0, 0, matches, sizeof(matches) / sizeof(matches[0])) >= 0);
 
-@implementation Source
+        std::istringstream buffer(line.substr(matches[6], matches[7] - matches[6]));
+        float percent;
+        buffer >> percent;
+        [delegate_ setPercent:(percent / 100)];
 
-- (void) dealloc {
-    [uri_ release];
-    [distribution_ release];
-    [type_ release];
+        NSString *string = [NSString stringWithCString:(data + matches[8]) length:(matches[9] - matches[8])];
+        std::string type(line.substr(matches[2], matches[3] - matches[2]));
 
-    if (description_ != nil)
-        [description_ release];
-    if (label_ != nil)
-        [label_ release];
-    if (origin_ != nil)
-        [origin_ release];
+        if (type == "pmerror")
+            [delegate_ setError:string];
+        else if (type == "pmstatus")
+            [delegate_ setTitle:string];
+        else if (type == "pmconffile")
+            ;
+        else _assert(false);
+    }
 
-    [super dealloc];
+    [pool release];
+    _assert(false);
 }
 
-- (Source *) initWithMetaIndex:(metaIndex *)index {
-    if ((self = [super init]) != nil) {
-        trusted_ = index->IsTrusted();
+- (void) _readOutput:(NSNumber *)fd {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+    __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
+    std::istream is(&ib);
+    std::string line;
 
-        uri_ = [[NSString stringWithCString:index->GetURI().c_str()] retain];
-        distribution_ = [[NSString stringWithCString:index->GetDist().c_str()] retain];
-        type_ = [[NSString stringWithCString:index->GetType()] retain];
+    while (std::getline(is, line))
+        [delegate_ addOutput:[NSString stringWithCString:line.c_str()]];
 
-        description_ = nil;
-        label_ = nil;
-        origin_ = nil;
-
-        debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index));
-        if (dindex != NULL) {
-            std::ifstream release(dindex->MetaIndexFile("Release").c_str());
-            std::string line;
-            while (std::getline(release, line)) {
-                std::string::size_type colon(line.find(':'));
-                if (colon == std::string::npos)
-                    continue;
-
-                std::string name(line.substr(0, colon));
-                std::string value(line.substr(colon + 1));
-                while (!value.empty() && value[0] == ' ')
-                    value = value.substr(1);
-
-                if (name == "Description")
-                    description_ = [[NSString stringWithCString:value.c_str()] retain];
-                else if (name == "Label")
-                    label_ = [[NSString stringWithCString:value.c_str()] retain];
-                else if (name == "Origin")
-                    origin_ = [[NSString stringWithCString:value.c_str()] retain];
-            }
-        }
-    } return self;
+    [pool release];
+    _assert(false);
 }
 
-- (BOOL) trusted {
-    return trusted_;
+- (Package *) packageWithName:(NSString *)name {
+    pkgCache::PkgIterator iterator(cache_->FindPkg([name cString]));
+    return iterator.end() ? nil : [Package packageWithIterator:iterator database:self];
 }
 
-- (NSString *) uri {
-    return uri_;
-}
+- (Database *) init {
+    if ((self = [super init]) != nil) {
+        records_ = NULL;
+        resolver_ = NULL;
+        fetcher_ = NULL;
+        lock_ = NULL;
 
-- (NSString *) distribution {
-    return distribution_;
-}
+        int fds[2];
 
-- (NSString *) type {
-    return type_;
-}
+        _assert(pipe(fds) != -1);
+        statusfd_ = fds[1];
 
-- (NSString *) description {
-    return description_;
+        [NSThread
+            detachNewThreadSelector:@selector(_readStatus:)
+            toTarget:self
+            withObject:[[NSNumber numberWithInt:fds[0]] retain]
+        ];
+
+        _assert(pipe(fds) != -1);
+        _assert(dup2(fds[1], 1) != -1);
+        _assert(close(fds[1]) != -1);
+
+        [NSThread
+            detachNewThreadSelector:@selector(_readOutput:)
+            toTarget:self
+            withObject:[[NSNumber numberWithInt:fds[0]] retain]
+        ];
+    } return self;
 }
 
-- (NSString *) label {
-    return label_;
+- (pkgCacheFile &) cache {
+    return cache_;
 }
 
-- (NSString *) origin {
-    return origin_;
+- (pkgRecords *) records {
+    return records_;
 }
 
-@end
-/* }}} */
-/* Source Cell {{{ */
-@interface SourceCell : UITableCell {
-    UITextLabel *description_;
-    UIRightTextLabel *label_;
-    UITextLabel *origin_;
+- (pkgProblemResolver *) resolver {
+    return resolver_;
 }
 
-- (void) dealloc;
+- (pkgAcquire &) fetcher {
+    return *fetcher_;
+}
 
-- (SourceCell *) initWithSource:(Source *)source;
+- (void) reloadData {
+    _error->Discard();
+    manager_ = NULL;
+    delete lock_;
+    delete fetcher_;
+    delete resolver_;
+    delete records_;
+    cache_.Close();
+    _assert(cache_.Open(progress_, true));
+    records_ = new pkgRecords(cache_);
+    resolver_ = new pkgProblemResolver(cache_);
+    fetcher_ = new pkgAcquire(&status_);
+    lock_ = NULL;
+}
 
-- (void) _setSelected:(float)fraction;
-- (void) setSelected:(BOOL)selected;
-- (void) setSelected:(BOOL)selected withFade:(BOOL)fade;
-- (void) _setSelectionFadeFraction:(float)fraction;
+- (void) prepare {
+    pkgRecords records(cache_);
 
-@end
+    lock_ = new FileFd();
+    lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
+    _assert(!_error->PendingError());
 
-@implementation SourceCell
+    pkgSourceList list;
+    _assert(list.ReadMainList());
 
-- (void) dealloc {
-    [description_ release];
-    [label_ release];
-    [origin_ release];
-    [super dealloc];
+    manager_ = (_system->CreatePM(cache_));
+    _assert(manager_->GetArchives(fetcher_, &list, &records));
+    _assert(!_error->PendingError());
 }
 
-- (SourceCell *) initWithSource:(Source *)source {
-    if ((self = [super init]) != nil) {
-        GSFontRef bold = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 20);
-        GSFontRef small = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 14);
-
-        CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
-        float clear[] = {0, 0, 0, 0};
+- (void) perform {
+    if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue)
+        return;
 
-        NSString *description = [source description];
-        if (description == nil)
-            description = [source uri];
+    _system->UnLock();
+    pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
 
-        description_ = [[UITextLabel alloc] initWithFrame:CGRectMake(12, 7, 270, 25)];
-        [description_ setBackgroundColor:CGColorCreate(space, clear)];
-        [description_ setFont:bold];
-        [description_ setText:description];
+    if (result == pkgPackageManager::Failed)
+        return;
+    if (_error->PendingError())
+        return;
+    if (result != pkgPackageManager::Completed)
+        return;
+}
 
-        NSString *label = [source label];
-        if (label == nil)
-            label = [source type];
+- (void) update {
+    pkgSourceList list;
+    _assert(list.ReadMainList());
 
-        label_ = [[UIRightTextLabel alloc] initWithFrame:CGRectMake(290, 32, 90, 25)];
-        [label_ setBackgroundColor:CGColorCreate(space, clear)];
-        [label_ setFont:small];
-        [label_ setText:label];
+    FileFd lock;
+    lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
+    _assert(!_error->PendingError());
 
-        NSString *origin = [source origin];
-        if (origin == nil)
-            origin = [source distribution];
+    pkgAcquire fetcher(&status_);
+    _assert(list.GetIndexes(&fetcher));
+    _assert(fetcher.Run(PulseInterval_) != pkgAcquire::Failed);
 
-        origin_ = [[UITextLabel alloc] initWithFrame:CGRectMake(13, 35, 315, 20)];
-        [origin_ setBackgroundColor:CGColorCreate(space, clear)];
-        [origin_ setFont:small];
-        [origin_ setText:origin];
+    bool failed = false;
+    for (pkgAcquire::ItemIterator item = fetcher.ItemsBegin(); item != fetcher.ItemsEnd(); item++)
+        if ((*item)->Status != pkgAcquire::Item::StatDone) {
+            (*item)->Finished();
+            failed = true;
+        }
 
-        [self addSubview:description_];
-        [self addSubview:label_];
-        [self addSubview:origin_];
+    if (!failed && _config->FindB("APT::Get::List-Cleanup", true) == true) {
+        _assert(fetcher.Clean(_config->FindDir("Dir::State::lists")));
+        _assert(fetcher.Clean(_config->FindDir("Dir::State::lists") + "partial/"));
+    }
 
-        CFRelease(small);
-        CFRelease(bold);
-    } return self;
+    [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"];
 }
 
-- (void) _setSelected:(float)fraction {
-    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
-
-    float black[] = {
-        interpolate(0.0, 1.0, fraction),
-        interpolate(0.0, 1.0, fraction),
-        interpolate(0.0, 1.0, fraction),
-    1.0};
-
-    float blue[] = {
-        interpolate(0.2, 1.0, fraction),
-        interpolate(0.2, 1.0, fraction),
-        interpolate(1.0, 1.0, fraction),
-    1.0};
+- (void) upgrade {
+    _assert(cache_->DelCount() == 0 && cache_->InstCount() == 0);
+    _assert(pkgApplyStatus(cache_));
 
-    float gray[] = {
-        interpolate(0.4, 1.0, fraction),
-        interpolate(0.4, 1.0, fraction),
-        interpolate(0.4, 1.0, fraction),
-    1.0};
+    if (cache_->BrokenCount() != 0) {
+        _assert(pkgFixBroken(cache_));
+        _assert(cache_->BrokenCount() == 0);
+        _assert(pkgMinimizeUpgrade(cache_));
+    }
 
-    [description_ setColor:CGColorCreate(space, black)];
-    [label_ setColor:CGColorCreate(space, blue)];
-    [origin_ setColor:CGColorCreate(space, gray)];
+    _assert(pkgDistUpgrade(cache_));
 }
 
-- (void) setSelected:(BOOL)selected {
-    [self _setSelected:(selected ? 1.0 : 0.0)];
-    [super setSelected:selected];
-}
-
-- (void) setSelected:(BOOL)selected withFade:(BOOL)fade {
-    if (!fade)
-        [self _setSelected:(selected ? 1.0 : 0.0)];
-    [super setSelected:selected withFade:fade];
-}
-
-- (void) _setSelectionFadeFraction:(float)fraction {
-    [self _setSelected:fraction];
-    [super _setSelectionFadeFraction:fraction];
+- (void) setDelegate:(id)delegate {
+    delegate_ = delegate;
+    status_.setDelegate(delegate);
+    progress_.setDelegate(delegate);
 }
 
 @end
 /* }}} */
-/* Sources View {{{ */
-@interface SourcesView : ResetView {
-    UISectionList *list_;
-    Database *database_;
-    id delegate_;
-    NSMutableArray *sources_;
-    UIAlertSheet *alert_;
-}
-
-- (int) numberOfSectionsInSectionList:(UISectionList *)list;
-- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section;
-- (int) sectionList:(UISectionList *)list rowForSection:(int)section;
 
-- (int) numberOfRowsInTable:(UITable *)table;
-- (float) table:(UITable *)table heightForRow:(int)row;
-- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col;
-- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row;
-- (void) tableRowSelected:(NSNotification*)notification;
+/* Progress Data {{{ */
+@interface ProgressData : NSObject {
+    SEL selector_;
+    id target_;
+    id object_;
+}
 
-- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
+- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
 
-- (void) dealloc;
-- (id) initWithFrame:(CGRect)frame database:(Database *)database;
-- (void) setDelegate:(id)delegate;
-- (void) reloadData;
-- (NSString *) leftTitle;
-- (NSString *) rightTitle;
+- (SEL) selector;
+- (id) target;
+- (id) object;
 @end
 
-@implementation SourcesView
+@implementation ProgressData
 
-- (int) numberOfSectionsInSectionList:(UISectionList *)list {
-    return 1;
+- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
+    if ((self = [super init]) != nil) {
+        selector_ = selector;
+        target_ = target;
+        object_ = object;
+    } return self;
 }
 
-- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section {
-    return @"sources";
+- (SEL) selector {
+    return selector_;
 }
 
-- (int) sectionList:(UISectionList *)list rowForSection:(int)section {
-    return 0;
+- (id) target {
+    return target_;
 }
 
-- (int) numberOfRowsInTable:(UITable *)table {
-    return [sources_ count];
+- (id) object {
+    return object_;
 }
 
-- (float) table:(UITable *)table heightForRow:(int)row {
-    return 64;
+@end
+/* }}} */
+/* Progress View {{{ */
+@interface ProgressView : UIView <
+    ProgressDelegate
+> {
+    UIView *view_;
+    UIView *background_;
+    UITransitionView *transition_;
+    UIView *overlay_;
+    UINavigationBar *navbar_;
+    UIProgressBar *progress_;
+    UITextView *output_;
+    UITextLabel *status_;
+    id delegate_;
 }
 
-- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col {
-    return [[[SourceCell alloc] initWithSource:[sources_ objectAtIndex:row]] autorelease];
-}
+- (void) dealloc;
 
-- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
-    return NO;
-}
+- (ProgressView *) initWithFrame:(struct CGRect)frame delegate:(id)delegate;
+- (void) setContentView:(UIView *)view;
+- (void) resetView;
 
-- (void) tableRowSelected:(NSNotification*)notification {
-    UITable *table([list_ table]);
-    int row([table selectedRow]);
-    if (row == INT_MAX)
-        return;
+- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button;
 
-    [table selectRow:-1 byExtendingSelection:NO withFade:YES];
-}
+- (void) _retachThread;
+- (void) _detachNewThreadData:(ProgressData *)data;
+- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title;
 
-- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button {
-    [alert_ dismiss];
-    [alert_ release];
-    alert_ = nil;
-}
+- (void) setError:(NSString *)error;
+- (void) _setError:(NSString *)error;
 
-- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button {
-    switch (button) {
-        case 0:
-            alert_ = [[UIAlertSheet alloc]
-                initWithTitle:@"Unimplemented"
-                buttons:[NSArray arrayWithObjects:@"Okay", nil]
-                defaultButtonIndex:0
-                delegate:self
-                context:self
-            ];
+- (void) setTitle:(NSString *)title;
+- (void) _setTitle:(NSString *)title;
 
-            [alert_ setBodyText:@"This feature will be implemented soon. In the mean time, you may add sources by adding .list files to '/etc/apt/sources.list.d'. If you'd like to be in the default list, please contact the author of Packager."];
-            [alert_ popupAlertAnimated:YES];
-        break;
+- (void) setPercent:(float)percent;
+- (void) _setPercent:(NSNumber *)percent;
 
-        case 1:
-            [delegate_ update];
-        break;
-    }
-}
+- (void) addOutput:(NSString *)output;
+- (void) _addOutput:(NSString *)output;
+@end
+
+@protocol ProgressViewDelegate
+- (void) progressViewIsComplete:(ProgressView *)sender;
+@end
+
+@implementation ProgressView
 
 - (void) dealloc {
-    if (sources_ != nil)
-        [sources_ release];
-    [list_ release];
+    [view_ release];
+    [background_ release];
+    [transition_ release];
+    [overlay_ release];
+    [navbar_ release];
+    [progress_ release];
+    [output_ release];
+    [status_ release];
     [super dealloc];
 }
 
-- (id) initWithFrame:(CGRect)frame database:(Database *)database {
+- (ProgressView *) initWithFrame:(struct CGRect)frame delegate:(id)delegate {
     if ((self = [super initWithFrame:frame]) != nil) {
-        database_ = database;
-        sources_ = nil;
+        delegate_ = delegate;
+
+        CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+
+        CGColor black(space, 0.0, 0.0, 0.0, 1.0);
+        CGColor white(space, 1.0, 1.0, 1.0, 1.0);
+        CGColor clear(space, 0.0, 0.0, 0.0, 0.0);
+
+        background_ = [[UIView alloc] initWithFrame:[self bounds]];
+        [background_ setBackgroundColor:black];
+        [self addSubview:background_];
+
+        transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
+        [self addSubview:transition_];
+
+        overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]];
 
         CGSize navsize = [UINavigationBar defaultSize];
         CGRect navrect = {{0, 0}, navsize};
-        CGRect bounds = [self bounds];
 
         navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
-        [self addSubview:navbar_];
+        [overlay_ addSubview:navbar_];
 
         [navbar_ setBarStyle:1];
         [navbar_ setDelegate:self];
 
-        UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Sources"] autorelease];
+        UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:nil] autorelease];
         [navbar_ pushNavigationItem:navitem];
 
-        list_ = [[UISectionList alloc] initWithFrame:CGRectMake(
-            0, navsize.height, bounds.size.width, bounds.size.height - navsize.height
+        CGRect bounds = [overlay_ bounds];
+        CGSize prgsize = [UIProgressBar defaultSize];
+
+        CGRect prgrect = {{
+            (bounds.size.width - prgsize.width) / 2,
+            bounds.size.height - prgsize.height - 20
+        }, prgsize};
+
+        progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
+        [overlay_ addSubview:progress_];
+
+        status_ = [[UITextLabel alloc] initWithFrame:CGRectMake(
+            10,
+            bounds.size.height - prgsize.height - 50,
+            bounds.size.width - 20,
+            24
         )];
 
-        [self addSubview:list_];
+        [status_ setColor:white];
+        [status_ setBackgroundColor:clear];
 
-        [list_ setDataSource:self];
-        [list_ setShouldHideHeaderInShortLists:NO];
+        [status_ setCentersHorizontally:YES];
+        //[status_ setFont:font];
 
-        UITableColumn *column = [[UITableColumn alloc]
-            initWithTitle:@"Name"
-            identifier:@"name"
-            width:frame.size.width
-        ];
+        output_ = [[UITextView alloc] initWithFrame:CGRectMake(
+            10,
+            navrect.size.height + 20,
+            bounds.size.width - 20,
+            bounds.size.height - navsize.height - 62 - navrect.size.height
+        )];
 
-        UITable *table = [list_ table];
-        [table setSeparatorStyle:1];
-        [table addTableColumn:column];
-        [table setDelegate:self];
-    } return self;
-}
+        //[output_ setTextFont:@"Courier New"];
+        [output_ setTextSize:12];
 
-- (void) setDelegate:(id)delegate {
-    delegate_ = delegate;
-}
+        [output_ setTextColor:white];
+        [output_ setBackgroundColor:clear];
 
-- (void) reloadData {
-    pkgSourceList list;
-    _assert(list.ReadMainList());
+        [output_ setMarginTop:0];
+        [output_ setAllowsRubberBanding:YES];
 
-    if (sources_ != nil)
-        [sources_ release];
+        [overlay_ addSubview:output_];
+        [overlay_ addSubview:status_];
 
-    sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
-    for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
-        [sources_ addObject:[[[Source alloc] initWithMetaIndex:*source] autorelease]];
+        [progress_ setStyle:0];
 
-    [self resetView];
-    [list_ reloadData];
+        CGColorSpaceRelease(space);
+    } return self;
 }
 
-- (NSString *) leftTitle {
-    return @"Refresh All";
+- (void) setContentView:(UIView *)view {
+    view_ = [view retain];
 }
 
-- (NSString *) rightTitle {
-    return @"Edit";
+- (void) resetView {
+    [transition_ transition:6 toView:view_];
 }
 
-@end
-/* }}} */
+- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button {
+    [sheet dismiss];
+}
 
-@implementation Database
+- (void) _retachThread {
+    [delegate_ progressViewIsComplete:self];
+    [self resetView];
+}
 
-- (void) _readStatus:(NSNumber *)fd {
+- (void) _detachNewThreadData:(ProgressData *)data {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
-    __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
-    std::istream is(&ib);
-    std::string line;
-
-    const char *error;
-    int offset;
-    pcre *code = pcre_compile("^([^:]*):([^:]*):([^:]*):(.*)$", 0, &error, &offset, NULL);
-
-    pcre_extra *study = NULL;
-    int capture;
-    pcre_fullinfo(code, study, PCRE_INFO_CAPTURECOUNT, &capture);
-    int matches[(capture + 1) * 3];
-
-    while (std::getline(is, line)) {
-        const char *data(line.c_str());
-
-        _assert(pcre_exec(code, study, data, line.size(), 0, 0, matches, sizeof(matches) / sizeof(matches[0])) >= 0);
-
-        std::istringstream buffer(line.substr(matches[6], matches[7] - matches[6]));
-        float percent;
-        buffer >> percent;
-        [delegate_ setPercent:(percent / 100)];
-
-        NSString *string = [NSString stringWithCString:(data + matches[8]) length:(matches[9] - matches[8])];
-        std::string type(line.substr(matches[2], matches[3] - matches[2]));
+    [[data target] performSelector:[data selector] withObject:[data object]];
+    [data release];
 
-        if (type == "pmerror")
-            [delegate_ setError:string];
-        else if (type == "pmstatus")
-            [delegate_ setTitle:string];
-        else if (type == "pmconffile")
-            ;
-        else _assert(false);
-    }
+    [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES];
 
     [pool release];
 }
 
-- (void) _readOutput:(NSNumber *)fd {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
-    __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
-    std::istream is(&ib);
-    std::string line;
+- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title {
+    [navbar_ popNavigationItem];
+    UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:title] autorelease];
+    [navbar_ pushNavigationItem:navitem];
 
-    while (std::getline(is, line))
-        [delegate_ addOutput:[NSString stringWithCString:line.c_str()]];
+    [status_ setText:nil];
+    [output_ setText:@""];
+    [progress_ setProgress:0];
 
-    [pool release];
-}
+    [transition_ transition:6 toView:overlay_];
 
-- (Package *) packageWithName:(NSString *)name {
-    pkgCache::PkgIterator iterator(cache_->FindPkg([name cString]));
-    return iterator.end() ? nil : [Package packageWithIterator:iterator database:self];
+    [NSThread
+        detachNewThreadSelector:@selector(_detachNewThreadData:)
+        toTarget:self
+        withObject:[[ProgressData alloc]
+            initWithSelector:selector
+            target:target
+            object:object
+        ]
+    ];
 }
 
-- (Database *) init {
-    if ((self = [super init]) != nil) {
-        records_ = NULL;
-        resolver_ = NULL;
-        fetcher_ = NULL;
-        lock_ = NULL;
-
-        int fds[2];
+- (void) setError:(NSString *)error {
+    [self
+        performSelectorOnMainThread:@selector(_setError:)
+        withObject:error
+        waitUntilDone:YES
+    ];
+}
 
-        _assert(pipe(fds) != -1);
-        statusfd_ = fds[1];
+- (void) _setError:(NSString *)error {
+    UIAlertSheet *sheet = [[[UIAlertSheet alloc]
+        initWithTitle:@"Package Error"
+        buttons:[NSArray arrayWithObjects:@"Okay", nil]
+        defaultButtonIndex:0
+        delegate:self
+        context:self
+    ] autorelease];
 
-        [NSThread
-            detachNewThreadSelector:@selector(_readStatus:)
-            toTarget:self
-            withObject:[[NSNumber numberWithInt:fds[0]] retain]
-        ];
+    [sheet setBodyText:error];
+    [sheet popupAlertAnimated:YES];
+}
 
-        _assert(pipe(fds) != -1);
-        _assert(dup2(fds[1], 1) != -1);
-        _assert(close(fds[1]) != -1);
+- (void) setTitle:(NSString *)title {
+    [self
+        performSelectorOnMainThread:@selector(_setTitle:)
+        withObject:title
+        waitUntilDone:YES
+    ];
+}
 
-        [NSThread
-            detachNewThreadSelector:@selector(_readOutput:)
-            toTarget:self
-            withObject:[[NSNumber numberWithInt:fds[0]] retain]
-        ];
-    } return self;
+- (void) _setTitle:(NSString *)title {
+    [status_ setText:[title stringByAppendingString:@"..."]];
 }
 
-- (pkgCacheFile &) cache {
-    return cache_;
+- (void) setPercent:(float)percent {
+    [self
+        performSelectorOnMainThread:@selector(_setPercent:)
+        withObject:[NSNumber numberWithFloat:percent]
+        waitUntilDone:YES
+    ];
 }
 
-- (pkgRecords *) records {
-    return records_;
+- (void) _setPercent:(NSNumber *)percent {
+    [progress_ setProgress:[percent floatValue]];
 }
 
-- (pkgProblemResolver *) resolver {
-    return resolver_;
+- (void) addOutput:(NSString *)output {
+    [self
+        performSelectorOnMainThread:@selector(_addOutput:)
+        withObject:output
+        waitUntilDone:YES
+    ];
 }
 
-- (pkgAcquire &) fetcher {
-    return *fetcher_;
+- (void) _addOutput:(NSString *)output {
+    [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]];
+    CGSize size = [output_ contentSize];
+    CGRect rect = {{0, size.height}, {size.width, 0}};
+    [output_ scrollRectToVisible:rect animated:YES];
 }
 
-- (void) reloadData {
-    _error->Discard();
-    manager_ = NULL;
-    delete lock_;
-    delete fetcher_;
-    delete resolver_;
-    delete records_;
-    cache_.Close();
-    cache_.Open(progress_, true);
-    records_ = new pkgRecords(cache_);
-    resolver_ = new pkgProblemResolver(cache_);
-    fetcher_ = new pkgAcquire(&status_);
-    lock_ = NULL;
+@end
+/* }}} */
+
+/* Package Table {{{ */
+@protocol PackageTableDelegate
+- (void) packageTable:(id)table packageSelected:(Package *)package;
+@end
+
+@interface PackageTable : UIView {
+    SEL versioner_;
+    UISectionList *list_;
+
+    id delegate_;
+    NSArray *packages_;
+    NSMutableArray *sections_;
 }
 
-- (void) prepare {
-    pkgRecords records(cache_);
+- (void) dealloc;
 
-    lock_ = new FileFd();
-    lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
-    _assert(!_error->PendingError());
+- (int) numberOfSectionsInSectionList:(UISectionList *)list;
+- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section;
+- (int) sectionList:(UISectionList *)list rowForSection:(int)section;
 
-    pkgSourceList list;
-    _assert(list.ReadMainList());
+- (int) numberOfRowsInTable:(UITable *)table;
+- (float) table:(UITable *)table heightForRow:(int)row;
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing;
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row;
+- (void) tableRowSelected:(NSNotification *)notification;
 
-    manager_ = (_system->CreatePM(cache_));
-    _assert(manager_->GetArchives(fetcher_, &list, &records));
-    _assert(!_error->PendingError());
+- (id) initWithFrame:(CGRect)frame versioner:(SEL)versioner;
+
+- (void) setDelegate:(id)delegate;
+- (void) setPackages:(NSArray *)packages;
+
+- (void) resetViewAnimated:(BOOL)animated;
+- (UITable *) table;
+@end
+
+@implementation PackageTable
+
+- (void) dealloc {
+    [list_ release];
+    [sections_ release];
+    if (packages_ != nil)
+        [packages_ release];
+    [super dealloc];
 }
 
-- (void) perform {
-    if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue)
-        return;
+- (int) numberOfSectionsInSectionList:(UISectionList *)list {
+    return [sections_ count];
+}
 
-    _system->UnLock();
-    pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
+- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section {
+    return [[sections_ objectAtIndex:section] name];
+}
 
-    if (result == pkgPackageManager::Failed)
-        return;
-    if (_error->PendingError())
-        return;
-    if (result != pkgPackageManager::Completed)
-        return;
+- (int) sectionList:(UISectionList *)list rowForSection:(int)section {
+    return [[sections_ objectAtIndex:section] row];
 }
 
-- (void) update {
-    pkgSourceList list;
-    _assert(list.ReadMainList());
+- (int) numberOfRowsInTable:(UITable *)table {
+    return [packages_ count];
+}
 
-    FileFd lock;
-    lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
-    _assert(!_error->PendingError());
+- (float) table:(UITable *)table heightForRow:(int)row {
+    return 64;
+}
 
-    pkgAcquire fetcher(&status_);
-    _assert(list.GetIndexes(&fetcher));
-    _assert(fetcher.Run(PulseInterval_) != pkgAcquire::Failed);
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
+    if (reusing == nil)
+        reusing = [[[PackageCell alloc] initWithVersioner:versioner_] autorelease];
+    [(PackageCell *)reusing setPackage:[packages_ objectAtIndex:row]];
+    return reusing;
+}
 
-    bool failed = false;
-    for (pkgAcquire::ItemIterator item = fetcher.ItemsBegin(); item != fetcher.ItemsEnd(); item++)
-        if ((*item)->Status != pkgAcquire::Item::StatDone) {
-            (*item)->Finished();
-            failed = true;
-        }
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
+    return NO;
+}
 
-    if (!failed && _config->FindB("APT::Get::List-Cleanup", true) == true) {
-        _assert(fetcher.Clean(_config->FindDir("Dir::State::lists")));
-        _assert(fetcher.Clean(_config->FindDir("Dir::State::lists") + "partial/"));
-    }
+- (void) tableRowSelected:(NSNotification *)notification {
+    int row = [[notification object] selectedRow];
+    [delegate_ packageTable:self packageSelected:(row == INT_MAX ? nil : [packages_ objectAtIndex:row])];
 }
 
-- (void) upgrade {
-    _assert(cache_->DelCount() == 0 && cache_->InstCount() == 0);
-    _assert(pkgApplyStatus(cache_));
+- (id) initWithFrame:(CGRect)frame versioner:(SEL)versioner {
+    if ((self = [super initWithFrame:frame]) != nil) {
+        versioner_ = versioner;
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-    if (cache_->BrokenCount() != 0) {
-        _assert(pkgFixBroken(cache_));
-        _assert(cache_->BrokenCount() == 0);
-        _assert(pkgMinimizeUpgrade(cache_));
-    }
+        list_ = [[UISectionList alloc] initWithFrame:[self bounds] showSectionIndex:YES];
+        [list_ setDataSource:self];
 
-    _assert(pkgDistUpgrade(cache_));
+        UITableColumn *column = [[[UITableColumn alloc]
+            initWithTitle:@"Name"
+            identifier:@"name"
+            width:frame.size.width
+        ] autorelease];
+
+        UITable *table = [list_ table];
+        [table setSeparatorStyle:1];
+        [table addTableColumn:column];
+        [table setDelegate:self];
+        [table setReusesTableCells:YES];
+
+        [self addSubview:list_];
+    } return self;
 }
 
 - (void) setDelegate:(id)delegate {
     delegate_ = delegate;
-    status_.setDelegate(delegate);
-    progress_.setDelegate(delegate);
+}
+
+- (void) setPackages:(NSArray *)packages {
+    if (packages_ != nil)
+        [packages_ autorelease];
+    _assert(packages != nil);
+    packages_ = [packages retain];
+
+    [sections_ removeAllObjects];
+
+    Section *section = nil;
+
+    for (size_t offset(0); offset != [packages_ count]; ++offset) {
+        Package *package = [packages_ objectAtIndex:offset];
+        NSString *name = [package index];
+
+        if (section == nil || ![[section name] isEqual:name]) {
+            section = [[[Section alloc] initWithName:name row:offset] autorelease];
+            [sections_ addObject:section];
+        }
+
+        [section addPackage:package];
+    }
+
+    [list_ reloadData];
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+    [[list_ table] selectRow:-1 byExtendingSelection:NO withFade:animated];
+}
+
+- (UITable *) table {
+    return [list_ table];
 }
 
 @end
+/* }}} */
 
-/* Progress Data {{{ */
-@interface ProgressData : NSObject {
-    SEL selector_;
-    id target_;
-    id object_;
+/* Section Cell {{{ */
+@interface SectionCell : UITableCell {
+    UITextLabel *name_;
+    UITextLabel *count_;
 }
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
+- (void) dealloc;
+
+- (id) init;
+- (void) setSection:(Section *)section;
+
+- (void) _setSelected:(float)fraction;
+- (void) setSelected:(BOOL)selected;
+- (void) setSelected:(BOOL)selected withFade:(BOOL)fade;
+- (void) _setSelectionFadeFraction:(float)fraction;
 
-- (SEL) selector;
-- (id) target;
-- (id) object;
 @end
 
-@implementation ProgressData
+@implementation SectionCell
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
+- (void) dealloc {
+    [name_ release];
+    [count_ release];
+    [super dealloc];
+}
+
+- (id) init {
     if ((self = [super init]) != nil) {
-        selector_ = selector;
-        target_ = target;
-        object_ = object;
+        GSFontRef bold = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 22);
+        GSFontRef small = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 12);
+
+        CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+        CGColor clear(space, 0, 0, 0, 0);
+        CGColor white(space, 1, 1, 1, 1);
+
+        name_ = [[UITextLabel alloc] initWithFrame:CGRectMake(47, 9, 250, 25)];
+        [name_ setBackgroundColor:clear];
+        [name_ setFont:bold];
+
+        count_ = [[UITextLabel alloc] initWithFrame:CGRectMake(11, 7, 29, 32)];
+        [count_ setCentersHorizontally:YES];
+        [count_ setBackgroundColor:clear];
+        [count_ setFont:small];
+        [count_ setColor:white];
+
+        UIImageView *folder = [[[UIImageView alloc] initWithFrame:CGRectMake(8, 7, 32, 32)] autorelease];
+        [folder setImage:[UIImage applicationImageNamed:@"folder.png"]];
+
+        [self addSubview:folder];
+        [self addSubview:name_];
+        [self addSubview:count_];
+
+        [self _setSelected:0];
+
+        CGColorSpaceRelease(space);
+
+        CFRelease(small);
+        CFRelease(bold);
     } return self;
 }
 
-- (SEL) selector {
-    return selector_;
+- (void) setSection:(Section *)section {
+    if (section == nil) {
+        [name_ setText:@"All Packages"];
+        [count_ setText:nil];
+    } else {
+        [name_ setText:[section name]];
+        [count_ setText:[NSString stringWithFormat:@"%d", [section count]]];
+    }
 }
 
-- (id) target {
-    return target_;
+- (void) _setSelected:(float)fraction {
+    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+
+    CGColor black(space,
+        interpolate(0.0, 1.0, fraction),
+        interpolate(0.0, 1.0, fraction),
+        interpolate(0.0, 1.0, fraction),
+    1.0);
+
+    [name_ setColor:black];
+
+    CGColorSpaceRelease(space);
 }
 
-- (id) object {
-    return object_;
+- (void) setSelected:(BOOL)selected {
+    [self _setSelected:(selected ? 1.0 : 0.0)];
+    [super setSelected:selected];
 }
 
-@end
-/* }}} */
-/* Progress View {{{ */
-@interface ProgressView : UIView <
-    ProgressDelegate
-> {
-    UIView *view_;
-    UIView *background_;
-    UITransitionView *transition_;
-    UIView *overlay_;
-    UINavigationBar *navbar_;
-    UIProgressBar *progress_;
-    UITextView *output_;
-    UITextLabel *status_;
-    id delegate_;
+- (void) setSelected:(BOOL)selected withFade:(BOOL)fade {
+    if (!fade)
+        [self _setSelected:(selected ? 1.0 : 0.0)];
+    [super setSelected:selected withFade:fade];
 }
 
-- (void) dealloc;
-
-- (ProgressView *) initWithFrame:(struct CGRect)frame delegate:(id)delegate;
-- (void) setContentView:(UIView *)view;
-- (void) resetView;
-
-- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button;
+- (void) _setSelectionFadeFraction:(float)fraction {
+    [self _setSelected:fraction];
+    [super _setSelectionFadeFraction:fraction];
+}
 
-- (void) _retachThread;
-- (void) _detachNewThreadData:(ProgressData *)data;
-- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object;
+@end
+/* }}} */
+/* Install View {{{ */
+@interface InstallView : ResetView <
+    PackageTableDelegate
+> {
+    NSArray *sections_;
+    UITable *list_;
+    PackageTable *table_;
+    PackageView *view_;
+    NSString *section_;
+    NSString *package_;
+    NSMutableArray *packages_;
+}
 
-- (void) setError:(NSString *)error;
-- (void) _setError:(NSString *)error;
+- (void) dealloc;
 
-- (void) setTitle:(NSString *)title;
-- (void) _setTitle:(NSString *)title;
+- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
 
-- (void) setPercent:(float)percent;
-- (void) _setPercent:(NSNumber *)percent;
+- (int) numberOfRowsInTable:(UITable *)table;
+- (float) table:(UITable *)table heightForRow:(int)row;
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing;
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row;
+- (void) tableRowSelected:(NSNotification *)notification;
 
-- (void) addOutput:(NSString *)output;
-- (void) _addOutput:(NSString *)output;
-@end
+- (void) packageTable:(id)table packageSelected:(Package *)package;
 
-@protocol ProgressViewDelegate
-- (void) progressViewIsComplete:(ProgressView *)sender;
+- (id) initWithFrame:(CGRect)frame;
+- (void) setPackages:(NSArray *)packages;
+- (void) setDelegate:(id)delegate;
 @end
 
-@implementation ProgressView
+@implementation InstallView
 
 - (void) dealloc {
-    [view_ release];
-    [background_ release];
-    [transition_ release];
-    [overlay_ release];
-    [navbar_ release];
-    [progress_ release];
-    [output_ release];
-    [status_ release];
+    [packages_ release];
+    if (sections_ != nil)
+        [sections_ release];
+    if (list_ != nil)
+        [list_ release];
+    if (table_ != nil)
+        [table_ release];
+    if (view_ != nil)
+        [view_ release];
+    if (section_ != nil)
+        [section_ release];
+    if (package_ != nil)
+        [package_ release];
     [super dealloc];
 }
 
-- (ProgressView *) initWithFrame:(struct CGRect)frame delegate:(id)delegate {
-    if ((self = [super initWithFrame:frame]) != nil) {
-        delegate_ = delegate;
-
-        CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
-        float black[] = {0.0, 0.0, 0.0, 1.0};
-        float white[] = {1.0, 1.0, 1.0, 1.0};
-        float clear[] = {0.0, 0.0, 0.0, 0.0};
+- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button {
+    if (button == 0) {
+        [[view_ package] install];
+        [delegate_ resolve];
+        [delegate_ perform];
+    }
+}
 
-        background_ = [[UIView alloc] initWithFrame:[self bounds]];
-        [background_ setBackgroundColor:CGColorCreate(space, black)];
-        [self addSubview:background_];
+- (int) numberOfRowsInTable:(UITable *)table {
+    return [sections_ count] + 1;
+}
 
-        transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
-        [self addSubview:transition_];
+- (float) table:(UITable *)table heightForRow:(int)row {
+    return 45;
+}
 
-        overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]];
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
+    if (reusing == nil)
+        reusing = [[[SectionCell alloc] init] autorelease];
+    if (row == 0)
+        [(SectionCell *)reusing setSection:nil];
+    else
+        [(SectionCell *)reusing setSection:[sections_ objectAtIndex:(row - 1)]];
+    return reusing;
+}
 
-        CGSize navsize = [UINavigationBar defaultSize];
-        CGRect navrect = {{0, 0}, navsize};
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
+    return YES;
+}
 
-        navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
-        [overlay_ addSubview:navbar_];
+- (void) tableRowSelected:(NSNotification *)notification {
+    int row = [[notification object] selectedRow];
 
-        [navbar_ setBarStyle:1];
-        [navbar_ setDelegate:self];
+    if (row == INT_MAX) {
+        [section_ release];
+        section_ = nil;
 
-        UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Running..."] autorelease];
-        [navbar_ pushNavigationItem:navitem];
+        [table_ release];
+        table_ = nil;
+    } else {
+        _assert(section_ == nil);
+        _assert(table_ == nil);
 
-        CGRect bounds = [overlay_ bounds];
-        CGSize prgsize = [UIProgressBar defaultSize];
+        Section *section;
+        NSString *name;
 
-        CGRect prgrect = {{
-            (bounds.size.width - prgsize.width) / 2,
-            bounds.size.height - prgsize.height - 20
-        }, prgsize};
+        if (row == 0) {
+            section = nil;
+            section_ = nil;
+            name = @"All Packages";
+        } else {
+            section = [sections_ objectAtIndex:(row - 1)];
+            name = [section name];
+            section_ = [name retain];
+        }
 
-        progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
-        [overlay_ addSubview:progress_];
+        table_ = [[PackageTable alloc] initWithFrame:[transition_ bounds] versioner:@selector(latest)];
+        [table_ setDelegate:self];
+        [table_ setPackages:(section == nil ? packages_ : [section packages])];
 
-        status_ = [[UITextLabel alloc] initWithFrame:CGRectMake(
-            10,
-            bounds.size.height - prgsize.height - 50,
-            bounds.size.width - 20,
-            24
-        )];
+        [self pushView:table_ withTitle:name backButtonTitle:@"Packages" rightButton:nil];
+    }
+}
 
-        [status_ setColor:CGColorCreate(space, white)];
-        [status_ setBackgroundColor:CGColorCreate(space, clear)];
+- (void) packageTable:(id)table packageSelected:(Package *)package {
+    if (package == nil) {
+        [package_ release];
+        package_ = nil;
 
-        [status_ setCentersHorizontally:YES];
-        //[status_ setFont:font];
+        [view_ release];
+        view_ = nil;
+    } else {
+        _assert(package_ == nil);
+        _assert(view_ == nil);
 
-        output_ = [[UITextView alloc] initWithFrame:CGRectMake(
-            10,
-            navrect.size.height + 20,
-            bounds.size.width - 20,
-            bounds.size.height - navsize.height - 62 - navrect.size.height
-        )];
+        package_ = [[package name] retain];
 
-        //[output_ setTextFont:@"Courier New"];
-        [output_ setTextSize:12];
+        view_ = [[PackageView alloc] initWithFrame:[transition_ bounds]];
+        [view_ setDelegate:delegate_];
 
-        [output_ setTextColor:CGColorCreate(space, white)];
-        [output_ setBackgroundColor:CGColorCreate(space, clear)];
+        [view_ setPackage:package];
 
-        [output_ setMarginTop:0];
-        [output_ setAllowsRubberBanding:YES];
+        [self pushView:view_ withTitle:[package name] backButtonTitle:nil rightButton:@"Install"];
+    }
+}
 
-        [overlay_ addSubview:output_];
-        [overlay_ addSubview:status_];
+- (id) initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
+        packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
-        [progress_ setStyle:0];
-    } return self;
-}
+        list_ = [[UITable alloc] initWithFrame:[transition_ bounds]];
+        [self pushView:list_ withTitle:@"Install" backButtonTitle:@"Sections" rightButton:nil];
 
-- (void) setContentView:(UIView *)view {
-    view_ = view;
-}
+        UITableColumn *column = [[[UITableColumn alloc]
+            initWithTitle:@"Name"
+            identifier:@"name"
+            width:frame.size.width
+        ] autorelease];
 
-- (void) resetView {
-    [transition_ transition:6 toView:view_];
-}
+        [list_ setDataSource:self];
+        [list_ setSeparatorStyle:1];
+        [list_ addTableColumn:column];
+        [list_ setDelegate:self];
+        [list_ setReusesTableCells:YES];
 
-- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button {
-    [sheet dismiss];
+        [transition_ transition:0 toView:list_];
+    } return self;
 }
 
-- (void) _retachThread {
-    [delegate_ progressViewIsComplete:self];
-    [self resetView];
-}
+- (void) setPackages:(NSArray *)packages {
+    [packages_ removeAllObjects];
 
-- (void) _detachNewThreadData:(ProgressData *)data {
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    for (size_t i(0); i != [packages count]; ++i) {
+        Package *package([packages objectAtIndex:i]);
+        if ([package installed] == nil)
+            [packages_ addObject:package];
+    }
 
-    [[data target] performSelector:[data selector] withObject:[data object]];
-    [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES];
+    [packages_ sortUsingSelector:@selector(compareBySectionAndName:)];
+    NSMutableArray *sections = [NSMutableArray arrayWithCapacity:16];
 
-    [data release];
-    [pool release];
-}
+    Section *nsection = nil;
+    Package *npackage = nil;
 
-- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object {
-    [status_ setText:nil];
-    [output_ setText:@""];
-    [progress_ setProgress:0];
+    Section *section = nil;
+    for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) {
+        Package *package = [packages_ objectAtIndex:offset];
+        NSString *name = [package section];
 
-    [transition_ transition:6 toView:overlay_];
+        if (section == nil || ![[section name] isEqual:name]) {
+            section = [[[Section alloc] initWithName:name row:offset] autorelease];
 
-    [NSThread
-        detachNewThreadSelector:@selector(_detachNewThreadData:)
-        toTarget:self
-        withObject:[[ProgressData alloc]
-            initWithSelector:selector
-            target:target
-            object:object
-        ]
-    ];
-}
+            if ([name isEqualToString:section_])
+                nsection = section;
+            [sections addObject:section];
+        }
 
-- (void) setError:(NSString *)error {
-    [self
-        performSelectorOnMainThread:@selector(_setError:)
-        withObject:error
-        waitUntilDone:YES
-    ];
-}
+        if ([[package name] isEqualToString:package_])
+            npackage = package;
+        [section addPackage:package];
+    }
 
-- (void) _setError:(NSString *)error {
-    UIAlertSheet *sheet = [[[UIAlertSheet alloc]
-        initWithTitle:@"Package Error"
-        buttons:[NSArray arrayWithObjects:@"Okay", nil]
-        defaultButtonIndex:0
-        delegate:self
-        context:self
-    ] autorelease];
+    if (sections_ != nil)
+        [sections_ release];
+    sections_ = [sections retain];
 
-    [sheet setBodyText:error];
-    [sheet popupAlertAnimated:YES];
-}
+    [packages_ sortUsingSelector:@selector(compareByName:)];
 
-- (void) setTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
-}
+    [list_ reloadData];
 
-- (void) _setTitle:(NSString *)title {
-    [status_ setText:[title stringByAppendingString:@"..."]];
-}
+    unsigned views(0);
 
-- (void) setPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
-}
+    if (npackage != nil)
+        [view_ setPackage:npackage];
+    else if (package_ != nil)
+        ++views;
 
-- (void) _setPercent:(NSNumber *)percent {
-    [progress_ setProgress:[percent floatValue]];
-}
+    if (nsection != nil)
+        [table_ setPackages:[nsection packages]];
+    else if (section_ != nil)
+        ++views;
 
-- (void) addOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
+    [self popViews:views];
+    [self setPrompt];
 }
 
-- (void) _addOutput:(NSString *)output {
-    [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]];
-    CGSize size = [output_ contentSize];
-    CGRect rect = {{0, size.height}, {size.width, 0}};
-    [output_ scrollRectToVisible:rect animated:YES];
+- (void) setDelegate:(id)delegate {
+    if (view_ != nil)
+        [view_ setDelegate:delegate];
+    [super setDelegate:delegate];
 }
 
 @end
 /* }}} */
-
-@protocol PackagesViewDelegate
-- (void) perform;
-- (void) update;
-- (void) openURL:(NSString *)url;
-@end
-
-/* PackagesView {{{ */
-@interface PackagesView : ResetView <
-    PackageCellDelegate
+/* Changes View {{{ */
+@interface ChangesView : ResetView <
+    PackageTableDelegate
 > {
-    Database *database_;
+    UISectionList *list_;
     NSMutableArray *packages_;
     NSMutableArray *sections_;
-    id delegate_;
-    UISectionList *list_;
-    UITransitionView *transition_;
-    Package *package_;
-    NSString *pkgname_;
-    PackageView *pkgview_;
+    PackageView *view_;
+    NSString *package_;
+    size_t count_;
 }
 
+- (void) dealloc;
+
+- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
+
 - (int) numberOfSectionsInSectionList:(UISectionList *)list;
 - (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section;
 - (int) sectionList:(UISectionList *)list rowForSection:(int)section;
@@ -1978,24 +2500,28 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 - (float) table:(UITable *)table heightForRow:(int)row;
 - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing;
 - (BOOL) table:(UITable *)table showDisclosureForRow:(int)row;
-- (void) tableRowSelected:(NSNotification*)notification;
+- (void) tableRowSelected:(NSNotification *)notification;
 
-- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
-- (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item;
+- (id) initWithFrame:(CGRect)frame;
+- (void) setPackages:(NSArray *)packages;
+- (void) _resetView;
+- (size_t) count;
 
-- (id) initWithFrame:(struct CGRect)frame database:(Database *)database;
 - (void) setDelegate:(id)delegate;
-- (void) deselect;
-- (void) reloadData:(BOOL)reset;
-
-- (NSMutableArray *) packages;
-- (NSString *) title;
-- (void) perform:(Package *)package;
-- (void) addPackage:(Package *)package;
-- (NSString *) versionWithPackage:(Package *)package;
 @end
 
-@implementation PackagesView
+@implementation ChangesView
+
+- (void) dealloc {
+    [list_ release];
+    [packages_ release];
+    [sections_ release];
+    if (view_ != nil)
+        [view_ release];
+    if (package_ != nil)
+        [package_ release];
+    [super dealloc];
+}
 
 - (int) numberOfSectionsInSectionList:(UISectionList *)list {
     return [sections_ count];
@@ -2019,86 +2545,64 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
     if (reusing == nil)
-        reusing = [[PackageCell alloc] initWithDelegate:self];
+        reusing = [[[PackageCell alloc] initWithVersioner:NULL] autorelease];
     [(PackageCell *)reusing setPackage:[packages_ objectAtIndex:row]];
     return reusing;
 }
 
 - (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
-    return YES;
+    return NO;
 }
 
-- (void) tableRowSelected:(NSNotification*)notification {
-    int row = [[list_ table] selectedRow];
-    if (row == INT_MAX)
-        return;
-
-    package_ = [packages_ objectAtIndex:row];
-    pkgname_ = [[package_ name] retain];
-
-    UINavigationItem *navitem = [[UINavigationItem alloc] initWithTitle:[package_ name]];
-    [navbar_ pushNavigationItem:navitem];
-
-    [navbar_ showButtonsWithLeftTitle:nil rightTitle:[self title]];
-
-    [pkgview_ setPackage:package_];
-    [transition_ transition:1 toView:pkgview_];
+- (void) tableRowSelected:(NSNotification *)notification {
+    int row = [[notification object] selectedRow];
+    [self packageTable:self packageSelected:(row == INT_MAX ? nil : [packages_ objectAtIndex:row])];
 }
 
 - (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button {
-    if (button == 0) {
-        [self perform:package_];
-
-        pkgProblemResolver *resolver = [database_ resolver];
-
-        resolver->InstallProtect();
-        if (!resolver->Resolve(true))
-            _error->Discard();
-
-        [delegate_ perform];
-    }
-}
-
-- (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item {
-    [self deselect];
-    [super navigationBar:navbar poppedItem:item];
+    if (button == 1)
+        [delegate_ upgrade];
 }
 
-- (id) initWithFrame:(struct CGRect)frame database:(Database *)database {
-    if ((self = [super initWithFrame:frame]) != nil) {
-        database_ = [database retain];
+- (void) packageTable:(id)table packageSelected:(Package *)package {
+    if (package == nil) {
+        [package_ release];
+        package_ = nil;
 
-        struct CGRect bounds = [self bounds];
-        CGSize navsize = [UINavigationBar defaultSize];
-        CGRect navrect = {{0, 0}, navsize};
+        [view_ release];
+        view_ = nil;
+    } else {
+        _assert(package_ == nil);
+        _assert(view_ == nil);
 
-        navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
-        [self addSubview:navbar_];
+        package_ = [[package name] retain];
 
-        [navbar_ setBarStyle:1];
-        [navbar_ setDelegate:self];
+        view_ = [[PackageView alloc] initWithFrame:[transition_ bounds]];
+        [view_ setDelegate:delegate_];
 
-        UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:[self title]] autorelease];
-        [navbar_ pushNavigationItem:navitem];
-        [navitem setBackButtonTitle:@"Packages"];
+        [view_ setPackage:package];
 
-        transition_ = [[UITransitionView alloc] initWithFrame:CGRectMake(
-            bounds.origin.x, bounds.origin.y + navsize.height, bounds.size.width, bounds.size.height - navsize.height
+        [self pushView:view_ withTitle:[package name] backButtonTitle:nil rightButton:(
+            [package upgradable] ? @"Upgrade" : @"Install"
         )];
+    }
+}
 
-        [self addSubview:transition_];
+- (id) initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
+        packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
+        sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
 
         list_ = [[UISectionList alloc] initWithFrame:[transition_ bounds] showSectionIndex:NO];
-        [list_ setDataSource:self];
         [list_ setShouldHideHeaderInShortLists:NO];
+        [list_ setDataSource:self];
+        //[list_ setSectionListStyle:1];
 
-        [transition_ transition:0 toView:list_];
-
-        UITableColumn *column = [[UITableColumn alloc]
+        UITableColumn *column = [[[UITableColumn alloc]
             initWithTitle:@"Name"
             identifier:@"name"
             width:frame.size.width
-        ];
+        ] autorelease];
 
         UITable *table = [list_ table];
         [table setSeparatorStyle:1];
@@ -2106,187 +2610,397 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
         [table setDelegate:self];
         [table setReusesTableCells:YES];
 
-        pkgview_ = [[PackageView alloc] initWithFrame:[transition_ bounds] database:database_];
+        [self pushView:list_ withTitle:@"Changes" backButtonTitle:nil rightButton:nil];
     } return self;
 }
 
-- (void) setDelegate:(id)delegate {
-    delegate_ = delegate;
-    [pkgview_ setDelegate:delegate];
-}
-
-- (void) deselect {
-    [transition_ transition:(resetting_ ? 0 : 2) toView:list_];
-    UITable *table = [list_ table];
-    [table selectRow:-1 byExtendingSelection:NO withFade:(resetting_ ? NO : YES)];
-    package_ = nil;
-}
-
-- (void) reloadData:(BOOL)reset {
-    if (sections_ != nil)
-        [sections_ release];
-    if (packages_ != nil)
-        [packages_ release];
-
-    packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
+- (void) setPackages:(NSArray *)packages {
+    [packages_ removeAllObjects];
+    for (size_t i(0); i != [packages count]; ++i) {
+        Package *package([packages objectAtIndex:i]);
+        if ([package installed] == nil || [package upgradable])
+            [packages_ addObject:package];
+    }
 
-    for (pkgCache::PkgIterator iterator = [database_ cache]->PkgBegin(); !iterator.end(); ++iterator)
-        if (Package *package = [Package packageWithIterator:iterator database:database_])
-            [self addPackage:package];
+    [packages_ sortUsingSelector:@selector(compareForChanges:)];
 
-    [packages_ sortUsingSelector:@selector(compareBySectionAndName:)];
-    sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    [sections_ removeAllObjects];
 
+    Section *upgradable = [[[Section alloc] initWithName:@"Available Upgrades" row:0] autorelease];
     Section *section = nil;
+
+    count_ = 0;
+    Package *npackage = nil;
     for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) {
         Package *package = [packages_ objectAtIndex:offset];
-        NSString *name = [package section];
+        if ([[package name] isEqualToString:package_])
+            npackage = package;
 
-        if (section == nil || ![[section name] isEqual:name]) {
-            section = [[Section alloc] initWithName:name row:offset];
-            [sections_ addObject:section];
-        }
+        if ([package upgradable])
+            [upgradable addPackage:package];
+        else {
+            NSDate *seen = [package seen];
 
-        [section addPackage:package];
+            CFLocaleRef locale = CFLocaleCopyCurrent();
+            CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
+            CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) seen);
+
+            NSString *name = (NSString *) formatted;
+
+            if (section == nil || ![[section name] isEqual:name]) {
+                section = [[[Section alloc] initWithName:name row:offset] autorelease];
+                [sections_ addObject:section];
+            }
+
+            [section addPackage:package];
+
+            CFRelease(formatter);
+            CFRelease(formatted);
+            CFRelease(locale);
+        }
     }
 
+    count_ = [[upgradable packages] count];
+    if (count_ != 0)
+        [sections_ insertObject:upgradable atIndex:0];
+
     [list_ reloadData];
-    if (reset)
-        [self resetView];
-    else if (package_ != nil) {
-        package_ = [database_ packageWithName:pkgname_];
-        [pkgview_ setPackage:package_];
-    }
-}
 
-- (NSMutableArray *) packages {
-    return packages_;
-}
+    if (npackage != nil)
+        [view_ setPackage:npackage];
+    else if (package_ != nil)
+        [self popViews:1];
+    if ([views_ count] == 1)
+        [self _resetView];
 
-- (NSString *) title {
-    return nil;
+    [self setPrompt];
 }
 
-- (void) perform:(Package *)package {
+- (void) _resetView {
+    [navbar_ showButtonsWithLeftTitle:(count_ == 0 ? nil : @"Upgrade All") rightTitle:nil];
+    [super _resetView];
 }
 
-- (void) addPackage:(Package *)package {
-    [packages_ addObject:package];
+- (size_t) count {
+    return count_;
 }
 
-- (NSString *) versionWithPackage:(Package *)package {
-    return nil;
+- (void) setDelegate:(id)delegate {
+    if (view_ != nil)
+        [view_ setDelegate:delegate];
+    [super setDelegate:delegate];
 }
 
 @end
 /* }}} */
-
-/* InstallView {{{ */
-@interface InstallView : PackagesView {
+/* Manage View {{{ */
+@interface ManageView : ResetView <
+    PackageTableDelegate
+> {
+    PackageTable *table_;
+    PackageView *view_;
+    NSString *package_;
 }
 
-- (NSString *) title;
-- (void) addPackage:(Package *)package;
-- (void) perform:(Package *)package;
-- (NSString *) versionWithPackage:(Package *)package;
+- (void) dealloc;
+
+- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
+
+- (void) packageTable:(id)table packageSelected:(Package *)package;
+
+- (id) initWithFrame:(CGRect)frame;
+- (void) setPackages:(NSArray *)packages;
+
+- (void) setDelegate:(id)delegate;
 @end
 
-@implementation InstallView
+@implementation ManageView
 
-- (NSString *) title {
-    return @"Install";
+- (void) dealloc {
+    [table_ release];
+    if (view_ != nil)
+        [view_ release];
+    if (package_ != nil)
+        [package_ release];
+    [super dealloc];
 }
 
-- (void) addPackage:(Package *)package {
-    if ([package installed] == nil)
-        [super addPackage:package];
+- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button {
+    if (button == 0) {
+        [[view_ package] remove];
+        [delegate_ resolve];
+        [delegate_ perform];
+    }
+}
+
+- (void) packageTable:(id)table packageSelected:(Package *)package {
+    if (package == nil) {
+        [package_ release];
+        package_ = nil;
+
+        [view_ release];
+        view_ = nil;
+    } else {
+        _assert(package_ == nil);
+        _assert(view_ == nil);
+
+        package_ = [[package name] retain];
+
+        view_ = [[PackageView alloc] initWithFrame:[transition_ bounds]];
+        [view_ setDelegate:delegate_];
+
+        [view_ setPackage:package];
+
+        [self pushView:view_ withTitle:[package name] backButtonTitle:nil rightButton:@"Uninstall"];
+    }
+}
+
+- (id) initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
+        table_ = [[PackageTable alloc] initWithFrame:[transition_ bounds] versioner:@selector(latest)];
+        [table_ setDelegate:self];
+
+        [self pushView:table_ withTitle:@"Uninstall" backButtonTitle:@"Packages" rightButton:nil];
+    } return self;
 }
 
-- (void) perform:(Package *)package {
-    [package install];
+- (void) setPackages:(NSArray *)packages {
+    NSMutableArray *local = [NSMutableArray arrayWithCapacity:16];
+    for (size_t i(0); i != [packages count]; ++i) {
+        Package *package([packages objectAtIndex:i]);
+        if ([package installed] != nil)
+            [local addObject:package];
+    }
+
+    [local sortUsingSelector:@selector(compareByName:)];
+
+    Package *npackage = nil;
+    for (size_t offset = 0, count = [local count]; offset != count; ++offset) {
+        Package *package = [local objectAtIndex:offset];
+        if ([[package name] isEqualToString:package_])
+            npackage = package;
+    }
+
+    [table_ setPackages:local];
+
+    if (npackage != nil)
+        [view_ setPackage:npackage];
+    else if (package_ != nil)
+        [self popViews:1];
+
+    [self setPrompt];
 }
 
-- (NSString *) versionWithPackage:(Package *)package {
-    return [package latest];
+- (void) setDelegate:(id)delegate {
+    if (view_ != nil)
+        [view_ setDelegate:delegate];
+    [super setDelegate:delegate];
 }
 
 @end
 /* }}} */
-/* UpgradeView {{{ */
-@interface UpgradeView : PackagesView {
+/* Search View {{{ */
+@protocol SearchViewDelegate
+- (void) showKeyboard:(BOOL)show;
+@end
+
+@interface SearchView : ResetView <
+    PackageTableDelegate
+> {
+    NSMutableArray *packages_;
+    UIView *accessory_;
+    UISearchField *field_;
+    PackageTable *table_;
+    PackageView *view_;
+    NSString *package_;
 }
 
+- (void) dealloc;
+
 - (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
+- (void) packageTable:(id)table packageSelected:(Package *)package;
+
+- (void) textFieldDidBecomeFirstResponder:(UITextField *)field;
+- (void) textFieldDidResignFirstResponder:(UITextField *)field;
+
+- (void) keyboardInputChanged:(UIFieldEditor *)editor;
+- (BOOL) keyboardInput:(id)input shouldInsertText:(NSString *)text isMarkedText:(int)marked;
+
+- (id) initWithFrame:(CGRect)frame;
+- (void) setPackages:(NSArray *)packages;
+
+- (void) setDelegate:(id)delegate;
+- (void) resetPackage:(Package *)package;
+- (void) searchPackages;
 
-- (NSString *) title;
-- (NSString *) leftTitle;
-- (void) addPackage:(Package *)package;
-- (void) perform:(Package *)package;
-- (NSString *) versionWithPackage:(Package *)package;
 @end
 
-@implementation UpgradeView
+@implementation SearchView
+
+- (void) dealloc {
+    [packages_ release];
+    [accessory_ release];
+    [field_ release];
+    [table_ release];
+    if (view_ != nil)
+        [view_ release];
+    if (package_ != nil)
+        [package_ release];
+    [super dealloc];
+}
 
 - (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button {
-    if (button != 1)
-        [super navigationBar:navbar buttonClicked:button];
-    else {
-        [database_ upgrade];
+    if (button == 0) {
+        Package *package = [view_ package];
+        if ([package installed] == nil)
+            [package install];
+        else
+            [package remove];
+        [delegate_ resolve];
         [delegate_ perform];
     }
 }
 
-- (NSString *) title {
-    return @"Upgrade";
+- (void) packageTable:(id)table packageSelected:(Package *)package {
+    if (package == nil) {
+        [navbar_ setAccessoryView:accessory_ animate:(resetting_ ? NO : YES) goingBack:YES];
+
+        [package_ release];
+        package_ = nil;
+
+        [view_ release];
+        view_ = nil;
+    } else {
+        [navbar_ setAccessoryView:nil animate:YES goingBack:NO];
+
+        _assert(package_ == nil);
+        _assert(view_ == nil);
+
+        package_ = [[package name] retain];
+
+        view_ = [[PackageView alloc] initWithFrame:[transition_ bounds]];
+        [view_ setDelegate:delegate_];
+
+        [self pushView:view_ withTitle:[package name] backButtonTitle:nil rightButton:nil];
+        [self resetPackage:package];
+    }
 }
 
-- (NSString *) leftTitle {
-    return [packages_ count] == 0 ? nil : @"Upgrade All";
+- (void) textFieldDidBecomeFirstResponder:(UITextField *)field {
+    [delegate_ showKeyboard:YES];
+    [table_ setEnabled:NO];
+
+    /*CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+    CGColor dimmed(alpha, 0, 0, 0, 0.5);
+    [editor_ setBackgroundColor:dimmed];
+    CGColorSpaceRelease(space);*/
 }
 
-- (void) addPackage:(Package *)package {
-    NSString *installed = [package installed];
-    if (installed != nil && [[package latest] compare:installed] != NSOrderedSame)
-        [super addPackage:package];
+- (void) textFieldDidResignFirstResponder:(UITextField *)field {
+    [table_ setEnabled:YES];
+    [delegate_ showKeyboard:NO];
 }
 
-- (void) perform:(Package *)package {
-    [package install];
+- (void) keyboardInputChanged:(UIFieldEditor *)editor {
+    NSString *text([field_ text]);
+    [field_ setClearButtonStyle:(text == nil || [text length] == 0 ? 0 : 2)];
 }
 
-- (NSString *) versionWithPackage:(Package *)package {
-    return [package latest];
+- (BOOL) keyboardInput:(id)input shouldInsertText:(NSString *)text isMarkedText:(int)marked {
+    if ([text length] != 1 || [text characterAtIndex:0] != '\n')
+        return YES;
+
+    [self searchPackages];
+    [field_ resignFirstResponder];
+    return NO;
 }
 
-@end
-/* }}} */
-/* UninstallView {{{ */
-@interface UninstallView : PackagesView {
+- (id) initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
+        packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
+
+        table_ = [[PackageTable alloc] initWithFrame:[transition_ bounds] versioner:@selector(latest)];
+        [table_ setDelegate:self];
+
+        CGRect area = [self bounds];
+        area.origin.y = 30;
+        area.origin.x = 0;
+        area.size.width -= 12;
+        area.size.height = [UISearchField defaultHeight];
+
+        field_ = [[UISearchField alloc] initWithFrame:area];
+
+        GSFontRef font = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 16);
+        [field_ setFont:font];
+        CFRelease(font);
+
+        [field_ setPlaceholder:@"Package Names & Descriptions"];
+        [field_ setPaddingTop:5];
+        [field_ setDelegate:self];
+
+        UITextTraits *traits = [field_ textTraits];
+        [traits setEditingDelegate:self];
+        [traits setReturnKeyType:6];
+
+        accessory_ = [[UIView alloc] initWithFrame:CGRectMake(6, 6, area.size.width, area.size.height + 30)];
+        [accessory_ addSubview:field_];
+
+        [navbar_ setAccessoryView:accessory_];
+        [self pushView:table_ withTitle:nil backButtonTitle:@"Search" rightButton:nil];
+
+        /* XXX: for the love of god just fix this */
+        [navbar_ removeFromSuperview];
+        [self addSubview:navbar_];
+    } return self;
 }
 
-- (NSString *) title;
-- (void) addPackage:(Package *)package;
-- (void) perform:(Package *)package;
-- (NSString *) versionWithPackage:(Package *)package;
-@end
+- (void) setPackages:(NSArray *)packages {
+    [packages_ removeAllObjects];
+    [packages_ addObjectsFromArray:packages];
+    [packages_ sortUsingSelector:@selector(compareByName:)];
 
-@implementation UninstallView
+    Package *npackage = nil;
+    for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) {
+        Package *package = [packages_ objectAtIndex:offset];
+        if ([[package name] isEqualToString:package_])
+            npackage = package;
+    }
+
+    [self searchPackages];
 
-- (NSString *) title {
-    return @"Uninstall";
+    if (npackage != nil)
+        [self resetPackage:npackage];
+    else if (package_ != nil)
+        [self popViews:1];
+
+    [self setPrompt];
 }
 
-- (void) addPackage:(Package *)package {
-    if ([package installed] != nil)
-        [super addPackage:package];
+- (void) setDelegate:(id)delegate {
+    if (view_ != nil)
+        [view_ setDelegate:delegate];
+    [super setDelegate:delegate];
 }
 
-- (void) perform:(Package *)package {
-    [package remove];
+- (void) resetPackage:(Package *)package {
+    [view_ setPackage:package];
+    NSString *right = [package installed] == nil ? @"Install" : @"Uninstall";
+    [navbar_ showButtonsWithLeftTitle:nil rightTitle:right];
 }
 
-- (NSString *) versionWithPackage:(Package *)package {
-    return [package installed];
+- (void) searchPackages {
+    NSString *text([field_ text]);
+
+    NSMutableArray *packages([NSMutableArray arrayWithCapacity:16]);
+
+    for (size_t offset(0), count([packages_ count]); offset != count; ++offset) {
+        Package *package = [packages_ objectAtIndex:offset];
+        if ([package matches:text])
+            [packages addObject:package];
+    }
+
+    [table_ setPackages:packages];
+    [[table_ table] scrollPointVisibleAtTopLeft:CGPointMake(0, 0) animated:NO];
 }
 
 @end
@@ -2294,8 +3008,8 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 @interface Cydia : UIApplication <
     ConfirmationViewDelegate,
-    PackagesViewDelegate,
-    ProgressViewDelegate
+    ProgressViewDelegate,
+    SearchViewDelegate
 > {
     UIWindow *window_;
     UIView *underlay_;
@@ -2316,17 +3030,26 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     UIProgressIndicator *indicator_;
 
     InstallView *install_;
-    UpgradeView *upgrade_;
-    UninstallView *uninstall_;
-    SourcesView *sources_;
+    ChangesView *changes_;
+    ManageView *manage_;
+    SearchView *search_;
+
+    bool restart_;
+
+    UIKeyboard *keyboard_;
 }
 
 - (void) loadNews;
 - (void) reloadData:(BOOL)reset;
+- (void) setPrompt;
+
+- (void) resolve;
 - (void) perform;
+- (void) upgrade;
+- (void) update;
+
 - (void) cancel;
 - (void) confirm;
-- (void) update;
 
 - (void) progressViewIsComplete:(ProgressView *)progress;
 
@@ -2336,11 +3059,10 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 - (void) view:(UIView *)sender didSetFrame:(CGRect)frame oldFrame:(CGRect)old;
 
+- (void) applicationWillSuspend;
 - (void) applicationDidFinishLaunching:(id)unused;
 @end
 
-#include <objc/objc-class.h>
-
 @implementation Cydia
 
 - (void) loadNews {
@@ -2359,12 +3081,28 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 - (void) reloadData:(BOOL)reset {
     [database_ reloadData];
-    [install_ reloadData:reset];
-    [upgrade_ reloadData:reset];
-    [uninstall_ reloadData:reset];
-    [sources_ reloadData];
 
-    if (size_t count = [[upgrade_ packages] count]) {
+    size_t count = 16;
+
+    if (Packages_ == nil) {
+        Packages_ = [[NSMutableDictionary alloc] initWithCapacity:count];
+        [Metadata_ setObject:Packages_ forKey:@"Packages"];
+    }
+
+    now_ = [NSDate date];
+
+    NSMutableArray *packages = [NSMutableArray arrayWithCapacity:count];
+    for (pkgCache::PkgIterator iterator = [database_ cache]->PkgBegin(); !iterator.end(); ++iterator)
+        if (Package *package = [Package packageWithIterator:iterator database:database_])
+            [packages addObject:package];
+
+    [install_ setPackages:packages];
+    [changes_ setPackages:packages];
+    [manage_ setPackages:packages];
+    [search_ setPackages:packages];
+    //[self setPrompt];
+
+    if (size_t count = [changes_ count]) {
         NSString *badge([[NSNumber numberWithInt:count] stringValue]);
         [buttonbar_ setBadgeValue:badge forButton:3];
         [buttonbar_ setBadgeAnimated:YES forButton:3];
@@ -2374,6 +3112,30 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
         [buttonbar_ setBadgeAnimated:NO forButton:3];
         [self removeApplicationBadge];
     }
+
+    _assert([Metadata_ writeToFile:@"/var/lib/cydia/metadata.plist" atomically:YES] == YES);
+}
+
+- (void) setPrompt {
+    NSDate *update = [Metadata_ objectForKey:@"LastUpdate"];
+
+    CFLocaleRef locale = CFLocaleCopyCurrent();
+    CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, locale, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
+    CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) update);
+
+    [navbar_ setPrompt:[NSString stringWithFormat:@"Last Updated: %@", (NSString *) formatted]];
+
+    CFRelease(formatter);
+    CFRelease(formatted);
+    CFRelease(locale);
+}
+
+- (void) resolve {
+    pkgProblemResolver *resolver = [database_ resolver];
+
+    resolver->InstallProtect();
+    if (!resolver->Resolve(true))
+        _error->Discard();
 }
 
 - (void) perform {
@@ -2381,6 +3143,11 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     confirm_ = [[ConfirmationView alloc] initWithView:underlay_ database:database_ delegate:self];
 }
 
+- (void) upgrade {
+    [database_ upgrade];
+    [self perform];
+}
+
 - (void) cancel {
     [self reloadData:NO];
     [confirm_ release];
@@ -2389,11 +3156,13 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
 - (void) confirm {
     [overlay_ removeFromSuperview];
+    restart_ = true;
 
     [progress_
         detachNewThreadSelector:@selector(perform)
         toTarget:database_
         withObject:nil
+        title:@"Running..."
     ];
 }
 
@@ -2402,6 +3171,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
         detachNewThreadSelector:@selector(update)
         toTarget:database_
         withObject:nil
+        title:@"Refreshing Sources..."
     ];
 }
 
@@ -2432,7 +3202,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
             ] autorelease];
 
             [sheet setBodyText:
-                @"Copyright (C) 2007\n"
+                @"Copyright (C) 2008\n"
                 "Jay Freeman (saurik)\n"
                 "saurik@saurik.com\n"
                 "http://www.saurik.com/\n"
@@ -2446,10 +3216,11 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
                 "http://www.ccs.ucsb.edu/\n"
                 "\n"
                 "Special Thanks:\n"
-                "bad_, BHSPitMonkey, Cobra, core,\n"
-                "Corona, cromas, Darken, dtzWill,\n"
-                "francis, Godores, jerry, Kingstone,\n"
-                "lounger, rockabilly, tman, Wbiggs"
+                "bad_, BHSPitMonkey, cash, Cobra,\n"
+                "core, Corona, crashx, cromas,\n"
+                "Darken, dtzWill, Erica, francis,\n"
+                "Godores, jerry, Kingstone, lounger,\n"
+                "mbranes, rockabilly, tman, Wbiggs"
             ];
 
             [sheet presentSheetFromButtonBar:buttonbar_];
@@ -2467,9 +3238,9 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     switch ([sender tag]) {
         case 1: view = featured_; break;
         case 2: view = install_; break;
-        case 3: view = upgrade_; break;
-        case 4: view = uninstall_; break;
-        case 5: view = sources_; break;
+        case 3: view = changes_; break;
+        case 4: view = manage_; break;
+        case 5: view = search_; break;
 
         default:
             _assert(false);
@@ -2485,12 +3256,19 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     [indicator_ stopAnimation];
 }
 
+- (void) applicationWillSuspend {
+    if (restart_)
+        system("launchctl stop com.apple.SpringBoard");
+}
+
 - (void) applicationDidFinishLaunching:(id)unused {
     _assert(pkgInitConfig(*_config));
     _assert(pkgInitSystem(*_config, _system));
 
     confirm_ = nil;
+    restart_ = false;
 
+    _trace();
     CGRect screenrect = [UIHardware fullScreenApplicationContentRect];
     window_ = [[UIWindow alloc] initWithContentRect:screenrect];
 
@@ -2515,7 +3293,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
     featured_ = [[UIView alloc] initWithFrame:[transition_ bounds]];
 
-    CGSize navsize = [UINavigationBar defaultSize];
+    CGSize navsize = [UINavigationBar defaultSizeWithPrompt];
     CGRect navrect = {{0, 0}, navsize};
 
     navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
@@ -2523,17 +3301,18 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
     [navbar_ setBarStyle:1];
     [navbar_ setDelegate:self];
+    [navbar_ setPrompt:@"Welcome to Cydia Packager"];
 
     [navbar_ showButtonsWithLeftTitle:@"About" rightTitle:@"Reload"];
 
-    UINavigationItem *navitem = [[UINavigationItem alloc] initWithTitle:@"Featured"];
+    UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Featured"] autorelease];
     [navbar_ pushNavigationItem:navitem];
 
     struct CGRect subbounds = [featured_ bounds];
     subbounds.origin.y += navsize.height;
     subbounds.size.height -= navsize.height;
 
-    UIImageView *pinstripe = [[UIImageView alloc] initWithFrame:subbounds];
+    UIImageView *pinstripe = [[[UIImageView alloc] initWithFrame:subbounds] autorelease];
     [pinstripe setImage:[UIImage applicationImageNamed:@"pinstripe.png"]];
     [featured_ addSubview:pinstripe];
 
@@ -2556,7 +3335,7 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     [webview_ setDelegate:self];
 
     CGSize indsize = [UIProgressIndicator defaultSizeForStyle:2];
-    indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectMake(87, 15, indsize.width, indsize.height)];
+    indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectMake(87, 45, indsize.width, indsize.height)];
     [indicator_ setStyle:2];
     [featured_ addSubview:indicator_];
 
@@ -2583,18 +3362,18 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
         [NSDictionary dictionaryWithObjectsAndKeys:
             @"buttonBarItemTapped:", kUIButtonBarButtonAction,
-            @"upgrade-up.png", kUIButtonBarButtonInfo,
-            @"upgrade-dn.png", kUIButtonBarButtonSelectedInfo,
+            @"changes-up.png", kUIButtonBarButtonInfo,
+            @"changes-dn.png", kUIButtonBarButtonSelectedInfo,
             [NSNumber numberWithInt:3], kUIButtonBarButtonTag,
             self, kUIButtonBarButtonTarget,
-            @"Upgrade", kUIButtonBarButtonTitle,
+            @"Changes", kUIButtonBarButtonTitle,
             @"0", kUIButtonBarButtonType,
         nil],
 
         [NSDictionary dictionaryWithObjectsAndKeys:
             @"buttonBarItemTapped:", kUIButtonBarButtonAction,
-            @"uninstall-up.png", kUIButtonBarButtonInfo,
-            @"uninstall-dn.png", kUIButtonBarButtonSelectedInfo,
+            @"manage-up.png", kUIButtonBarButtonInfo,
+            @"manage-dn.png", kUIButtonBarButtonSelectedInfo,
             [NSNumber numberWithInt:4], kUIButtonBarButtonTag,
             self, kUIButtonBarButtonTarget,
             @"Uninstall", kUIButtonBarButtonTitle,
@@ -2603,11 +3382,11 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
 
         [NSDictionary dictionaryWithObjectsAndKeys:
             @"buttonBarItemTapped:", kUIButtonBarButtonAction,
-            @"sources-up.png", kUIButtonBarButtonInfo,
-            @"sources-dn.png", kUIButtonBarButtonSelectedInfo,
+            @"search-up.png", kUIButtonBarButtonInfo,
+            @"search-dn.png", kUIButtonBarButtonSelectedInfo,
             [NSNumber numberWithInt:5], kUIButtonBarButtonTag,
             self, kUIButtonBarButtonTarget,
-            @"Sources", kUIButtonBarButtonTitle,
+            @"Search", kUIButtonBarButtonTitle,
             @"0", kUIButtonBarButtonType,
         nil],
     nil];
@@ -2637,38 +3416,131 @@ void AddTextView(NSMutableDictionary *fields, NSMutableArray *packages, NSString
     [buttonbar_ showSelectionForButton:1];
     [transition_ transition:0 toView:featured_];
 
+    _trace();
     [overlay_ addSubview:buttonbar_];
 
+    _trace();
+    [UIKeyboard initImplementationNow];
+
+    _trace();
+    CGRect edtrect = [overlay_ bounds];
+    edtrect.origin.y += navsize.height;
+    edtrect.size.height -= navsize.height;
+
+    _trace();
+    CGSize keysize = [UIKeyboard defaultSize];
+    CGRect keyrect = {{0, [overlay_ bounds].size.height - keysize.height}, keysize};
+    keyboard_ = [[UIKeyboard alloc] initWithFrame:keyrect];
+
+    _trace();
     database_ = [[Database alloc] init];
     [database_ setDelegate:progress_];
 
-    install_ = [[InstallView alloc] initWithFrame:[transition_ bounds] database:database_];
+    _trace();
+    install_ = [[InstallView alloc] initWithFrame:[transition_ bounds]];
     [install_ setDelegate:self];
 
-    upgrade_ = [[UpgradeView alloc] initWithFrame:[transition_ bounds] database:database_];
-    [upgrade_ setDelegate:self];
+    _trace();
+    changes_ = [[ChangesView alloc] initWithFrame:[transition_ bounds]];
+    [changes_ setDelegate:self];
 
-    uninstall_ = [[UninstallView alloc] initWithFrame:[transition_ bounds] database:database_];
-    [uninstall_ setDelegate:self];
+    _trace();
+    manage_ = [[ManageView alloc] initWithFrame:[transition_ bounds]];
+    [manage_ setDelegate:self];
 
-    sources_ = [[SourcesView alloc] initWithFrame:[transition_ bounds] database:database_];
-    [sources_ setDelegate:self];
+    _trace();
+    search_ = [[SearchView alloc] initWithFrame:[transition_ bounds]];
+    [search_ setDelegate:self];
 
+    _trace();
     [self reloadData:NO];
+    _trace();
     [progress_ resetView];
 
+    _trace();
     Package *package([database_ packageWithName:@"cydia"]);
     NSString *application = package == nil ? @"Cydia" : [NSString stringWithFormat:@"Cydia/%@", [package installed]];
     WebView *webview = [webview_ webView];
     [webview setApplicationNameForUserAgent:application];
 
+    _trace();
     url_ = [NSURL URLWithString:@"http://cydia.saurik.com/"];
     [self loadNews];
+    _trace();
+}
+
+- (void) showKeyboard:(BOOL)show {
+    if (show)
+        [overlay_ addSubview:keyboard_];
+    else
+        [keyboard_ removeFromSuperview];
 }
 
 @end
 
+void AddPreferences(NSString *plist) {
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+    NSMutableDictionary *settings = [[[NSMutableDictionary alloc] initWithContentsOfFile:plist] autorelease];
+    _assert(settings != NULL);
+    NSMutableArray *items = [settings objectForKey:@"items"];
+
+    bool cydia(false);
+
+    for (size_t i(0); i != [items count]; ++i) {
+        NSMutableDictionary *item([items objectAtIndex:i]);
+        NSString *label = [item objectForKey:@"label"];
+        if (label != nil && [label isEqualToString:@"Cydia"]) {
+            cydia = true;
+            break;
+        }
+    }
+
+    if (!cydia) {
+        for (size_t i(0); i != [items count]; ++i) {
+            NSDictionary *item([items objectAtIndex:i]);
+            NSString *label = [item objectForKey:@"label"];
+            if (label != nil && [label isEqualToString:@"General"]) {
+                [items insertObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                    @"CydiaSettings", @"bundle",
+                    @"PSLinkCell", @"cell",
+                    [NSNumber numberWithBool:YES], @"hasIcon",
+                    [NSNumber numberWithBool:YES], @"isController",
+                    @"Cydia", @"label",
+                nil] atIndex:(i + 1)];
+
+                break;
+            }
+        }
+
+        _assert([settings writeToFile:plist atomically:YES] == YES);
+    }
+
+    [pool release];
+}
+
+/*IMP alloc_;
+id Alloc_(id self, SEL selector) {
+    id object = alloc_(self, selector);
+    fprintf(stderr, "[%s]A-%p\n", self->isa->name, object);
+    return object;
+}*/
+
 int main(int argc, char *argv[]) {
+    struct nlist nl[2];
+    memset(nl, 0, sizeof(nl));
+    nl[0].n_un.n_name = "_useMDNSResponder";
+    nlist("/usr/lib/libc.dylib", nl);
+    if (nl[0].n_type != N_UNDF)
+        *(int *) nl[0].n_value = 0;
+
+    setuid(0);
+    setgid(0);
+
+    /*Method alloc = class_getClassMethod([NSObject class], @selector(alloc));
+    alloc_ = alloc->method_imp;
+    alloc->method_imp = (IMP) &Alloc_;*/
+
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
     size_t size;
@@ -2687,6 +3559,15 @@ int main(int argc, char *argv[]) {
             IOObjectRelease(service);
         }
 
+    AddPreferences(@"/Applications/Preferences.app/Settings-iPhone.plist");
+    AddPreferences(@"/Applications/Preferences.app/Settings-iPod.plist");
+
+    if ((Metadata_ = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/lib/cydia/metadata.plist"]) == NULL)
+        Metadata_ = [[NSMutableDictionary alloc] initWithCapacity:2];
+    else
+        Packages_ = [Metadata_ objectForKey:@"Packages"];
+
     UIApplicationMain(argc, argv, [Cydia class]);
     [pool release];
+    return 0;
 }
diff --git a/Preferences.mm b/Preferences.mm
new file mode 100644 (file)
index 0000000..b7ccfff
--- /dev/null
@@ -0,0 +1,438 @@
+/* Source {{{ */
+@interface Source : NSObject {
+    NSString *description_;
+    NSString *label_;
+    NSString *origin_;
+
+    NSString *uri_;
+    NSString *distribution_;
+    NSString *type_;
+
+    BOOL trusted_;
+}
+
+- (void) dealloc;
+
+- (Source *) initWithMetaIndex:(metaIndex *)index;
+
+- (BOOL) trusted;
+
+- (NSString *) uri;
+- (NSString *) distribution;
+- (NSString *) type;
+
+- (NSString *) description;
+- (NSString *) label;
+- (NSString *) origin;
+@end
+
+@implementation Source
+
+- (void) dealloc {
+    [uri_ release];
+    [distribution_ release];
+    [type_ release];
+
+    if (description_ != nil)
+        [description_ release];
+    if (label_ != nil)
+        [label_ release];
+    if (origin_ != nil)
+        [origin_ release];
+
+    [super dealloc];
+}
+
+- (Source *) initWithMetaIndex:(metaIndex *)index {
+    if ((self = [super init]) != nil) {
+        trusted_ = index->IsTrusted();
+
+        uri_ = [[NSString stringWithCString:index->GetURI().c_str()] retain];
+        distribution_ = [[NSString stringWithCString:index->GetDist().c_str()] retain];
+        type_ = [[NSString stringWithCString:index->GetType()] retain];
+
+        description_ = nil;
+        label_ = nil;
+        origin_ = nil;
+
+        debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index));
+        if (dindex != NULL) {
+            std::ifstream release(dindex->MetaIndexFile("Release").c_str());
+            std::string line;
+            while (std::getline(release, line)) {
+                std::string::size_type colon(line.find(':'));
+                if (colon == std::string::npos)
+                    continue;
+
+                std::string name(line.substr(0, colon));
+                std::string value(line.substr(colon + 1));
+                while (!value.empty() && value[0] == ' ')
+                    value = value.substr(1);
+
+                if (name == "Description")
+                    description_ = [[NSString stringWithCString:value.c_str()] retain];
+                else if (name == "Label")
+                    label_ = [[NSString stringWithCString:value.c_str()] retain];
+                else if (name == "Origin")
+                    origin_ = [[NSString stringWithCString:value.c_str()] retain];
+            }
+        }
+    } return self;
+}
+
+- (BOOL) trusted {
+    return trusted_;
+}
+
+- (NSString *) uri {
+    return uri_;
+}
+
+- (NSString *) distribution {
+    return distribution_;
+}
+
+- (NSString *) type {
+    return type_;
+}
+
+- (NSString *) description {
+    return description_;
+}
+
+- (NSString *) label {
+    return label_;
+}
+
+- (NSString *) origin {
+    return origin_;
+}
+
+@end
+/* }}} */
+/* Source Cell {{{ */
+@interface SourceCell : UITableCell {
+    UITextLabel *description_;
+    UIRightTextLabel *label_;
+    UITextLabel *origin_;
+}
+
+- (void) dealloc;
+
+- (SourceCell *) initWithSource:(Source *)source;
+
+- (void) _setSelected:(float)fraction;
+- (void) setSelected:(BOOL)selected;
+- (void) setSelected:(BOOL)selected withFade:(BOOL)fade;
+- (void) _setSelectionFadeFraction:(float)fraction;
+
+@end
+
+@implementation SourceCell
+
+- (void) dealloc {
+    [description_ release];
+    [label_ release];
+    [origin_ release];
+    [super dealloc];
+}
+
+- (SourceCell *) initWithSource:(Source *)source {
+    if ((self = [super init]) != nil) {
+        GSFontRef bold = GSFontCreateWithName("Helvetica", kGSFontTraitBold, 20);
+        GSFontRef small = GSFontCreateWithName("Helvetica", kGSFontTraitNone, 14);
+
+        CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+        float clear[] = {0, 0, 0, 0};
+
+        NSString *description = [source description];
+        if (description == nil)
+            description = [source uri];
+
+        description_ = [[UITextLabel alloc] initWithFrame:CGRectMake(12, 7, 270, 25)];
+        [description_ setBackgroundColor:CGColorCreate(space, clear)];
+        [description_ setFont:bold];
+        [description_ setText:description];
+
+        NSString *label = [source label];
+        if (label == nil)
+            label = [source type];
+
+        label_ = [[UIRightTextLabel alloc] initWithFrame:CGRectMake(290, 32, 90, 25)];
+        [label_ setBackgroundColor:CGColorCreate(space, clear)];
+        [label_ setFont:small];
+        [label_ setText:label];
+
+        NSString *origin = [source origin];
+        if (origin == nil)
+            origin = [source distribution];
+
+        origin_ = [[UITextLabel alloc] initWithFrame:CGRectMake(13, 35, 315, 20)];
+        [origin_ setBackgroundColor:CGColorCreate(space, clear)];
+        [origin_ setFont:small];
+        [origin_ setText:origin];
+
+        [self addSubview:description_];
+        [self addSubview:label_];
+        [self addSubview:origin_];
+
+        CFRelease(small);
+        CFRelease(bold);
+    } return self;
+}
+
+- (void) _setSelected:(float)fraction {
+    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+
+    float black[] = {
+        interpolate(0.0, 1.0, fraction),
+        interpolate(0.0, 1.0, fraction),
+        interpolate(0.0, 1.0, fraction),
+    1.0};
+
+    float blue[] = {
+        interpolate(0.2, 1.0, fraction),
+        interpolate(0.2, 1.0, fraction),
+        interpolate(1.0, 1.0, fraction),
+    1.0};
+
+    float gray[] = {
+        interpolate(0.4, 1.0, fraction),
+        interpolate(0.4, 1.0, fraction),
+        interpolate(0.4, 1.0, fraction),
+    1.0};
+
+    [description_ setColor:CGColorCreate(space, black)];
+    [label_ setColor:CGColorCreate(space, blue)];
+    [origin_ setColor:CGColorCreate(space, gray)];
+}
+
+- (void) setSelected:(BOOL)selected {
+    [self _setSelected:(selected ? 1.0 : 0.0)];
+    [super setSelected:selected];
+}
+
+- (void) setSelected:(BOOL)selected withFade:(BOOL)fade {
+    if (!fade)
+        [self _setSelected:(selected ? 1.0 : 0.0)];
+    [super setSelected:selected withFade:fade];
+}
+
+- (void) _setSelectionFadeFraction:(float)fraction {
+    [self _setSelected:fraction];
+    [super _setSelectionFadeFraction:fraction];
+}
+
+@end
+/* }}} */
+
+/* Sources View {{{ */
+@interface SourcesView : UIView {
+    UISectionList *list_;
+    Database *database_;
+    id delegate_;
+    NSMutableArray *sources_;
+    UIAlertSheet *alert_;
+}
+
+- (int) numberOfSectionsInSectionList:(UISectionList *)list;
+- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section;
+- (int) sectionList:(UISectionList *)list rowForSection:(int)section;
+
+- (int) numberOfRowsInTable:(UITable *)table;
+- (float) table:(UITable *)table heightForRow:(int)row;
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col;
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row;
+- (void) tableRowSelected:(NSNotification*)notification;
+
+- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
+
+- (void) dealloc;
+- (id) initWithFrame:(CGRect)frame database:(Database *)database;
+- (void) setDelegate:(id)delegate;
+- (void) reloadData;
+- (NSString *) leftTitle;
+- (NSString *) rightTitle;
+@end
+
+@implementation SourcesView
+
+- (int) numberOfSectionsInSectionList:(UISectionList *)list {
+    return 1;
+}
+
+- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section {
+    return @"sources";
+}
+
+- (int) sectionList:(UISectionList *)list rowForSection:(int)section {
+    return 0;
+}
+
+- (int) numberOfRowsInTable:(UITable *)table {
+    return [sources_ count];
+}
+
+- (float) table:(UITable *)table heightForRow:(int)row {
+    return 64;
+}
+
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col {
+    return [[[SourceCell alloc] initWithSource:[sources_ objectAtIndex:row]] autorelease];
+}
+
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
+    return NO;
+}
+
+- (void) tableRowSelected:(NSNotification*)notification {
+    UITable *table([list_ table]);
+    int row([table selectedRow]);
+    if (row == INT_MAX)
+        return;
+
+    [table selectRow:-1 byExtendingSelection:NO withFade:YES];
+}
+
+- (void) alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button {
+    [alert_ dismiss];
+    [alert_ release];
+    alert_ = nil;
+}
+
+- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button {
+    switch (button) {
+        case 0:
+            alert_ = [[UIAlertSheet alloc]
+                initWithTitle:@"Unimplemented"
+                buttons:[NSArray arrayWithObjects:@"Okay", nil]
+                defaultButtonIndex:0
+                delegate:self
+                context:self
+            ];
+
+            [alert_ setBodyText:@"This feature will be implemented soon. In the mean time, you may add sources by adding .list files to '/etc/apt/sources.list.d'. If you'd like to be in the default list, please contact the author of Packager."];
+            [alert_ popupAlertAnimated:YES];
+        break;
+
+        case 1:
+            [delegate_ update];
+        break;
+    }
+}
+
+- (void) dealloc {
+    if (sources_ != nil)
+        [sources_ release];
+    [list_ release];
+    [super dealloc];
+}
+
+- (id) initWithFrame:(CGRect)frame database:(Database *)database {
+    if ((self = [super initWithFrame:frame]) != nil) {
+        database_ = database;
+        sources_ = nil;
+
+        CGSize navsize = [UINavigationBar defaultSize];
+        CGRect navrect = {{0, 0}, navsize};
+        CGRect bounds = [self bounds];
+
+        navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
+        [self addSubview:navbar_];
+
+        [navbar_ setBarStyle:1];
+        [navbar_ setDelegate:self];
+
+        UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Sources"] autorelease];
+        [navbar_ pushNavigationItem:navitem];
+
+        list_ = [[UISectionList alloc] initWithFrame:CGRectMake(
+            0, navsize.height, bounds.size.width, bounds.size.height - navsize.height
+        )];
+
+        [self addSubview:list_];
+
+        [list_ setDataSource:self];
+        [list_ setShouldHideHeaderInShortLists:NO];
+
+        UITableColumn *column = [[UITableColumn alloc]
+            initWithTitle:@"Name"
+            identifier:@"name"
+            width:frame.size.width
+        ];
+
+        UITable *table = [list_ table];
+        [table setSeparatorStyle:1];
+        [table addTableColumn:column];
+        [table setDelegate:self];
+    } return self;
+}
+
+- (void) setDelegate:(id)delegate {
+    delegate_ = delegate;
+}
+
+- (void) reloadData {
+    pkgSourceList list;
+    _assert(list.ReadMainList());
+
+    if (sources_ != nil)
+        [sources_ release];
+
+    sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
+    for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
+        [sources_ addObject:[[[Source alloc] initWithMetaIndex:*source] autorelease]];
+
+    [list_ reloadData];
+}
+
+- (NSString *) leftTitle {
+    return @"Refresh All";
+}
+
+- (NSString *) rightTitle {
+    return @"Edit";
+}
+
+@end
+/* }}} */
+/* Settings View {{{ */
+@interface SettingsView : ResetView {
+}
+
+- (void) dealloc;
+- (void) reloadData;
+@end
+
+@implementation SettingsView
+
+- (void) dealloc {
+    [super dealloc];
+}
+
+- (id) initWithFrame:(CGRect)frame database:(Database *)database {
+    if ((self = [super initWithFrame:frame]) != nil) {
+        database_ = database;
+        sources_ = nil;
+
+        CGSize navsize = [UINavigationBar defaultSize];
+        CGRect navrect = {{0, 0}, navsize};
+        CGRect bounds = [self bounds];
+
+        navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
+        [self addSubview:navbar_];
+
+        [navbar_ setBarStyle:1];
+        [navbar_ setDelegate:self];
+
+        UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:@"Settings"] autorelease];
+        [navbar_ pushNavigationItem:navitem];
+    } return self;
+}
+
+- (void) reloadData {
+    [self resetView];
+}
+
+@end
+/* }}} */
diff --git a/data/changes-dn.png b/data/changes-dn.png
new file mode 100644 (file)
index 0000000..cfd31f1
Binary files /dev/null and b/data/changes-dn.png differ
diff --git a/data/changes-up.png b/data/changes-up.png
new file mode 100644 (file)
index 0000000..654208f
Binary files /dev/null and b/data/changes-up.png differ
diff --git a/data/folder.png b/data/folder.png
new file mode 100644 (file)
index 0000000..b628e95
Binary files /dev/null and b/data/folder.png differ
index 840c4e16820cb03acab168a80afd73b38bc28503..5048c8fe96f1c85722a07f7f0c4551ec46607afe 100644 (file)
Binary files a/data/icon.png and b/data/icon.png differ
index c555fd14fa021517b6dce189f56334d1e35c189a..7e11a95f0770d1bbb6980facb5934cfdacff1822 100644 (file)
Binary files a/data/install-dn.png and b/data/install-dn.png differ
index 650f2ac8c82357d17fa9258cd7c65d20d085f325..1b7b1d97e820b7f4d184009c89010de6fb2edbc3 100644 (file)
Binary files a/data/install-up.png and b/data/install-up.png differ
diff --git a/data/manage-dn.png b/data/manage-dn.png
new file mode 100644 (file)
index 0000000..727d067
Binary files /dev/null and b/data/manage-dn.png differ
diff --git a/data/manage-up.png b/data/manage-up.png
new file mode 100644 (file)
index 0000000..9513a7c
Binary files /dev/null and b/data/manage-up.png differ
diff --git a/data/reload.png b/data/reload.png
new file mode 100644 (file)
index 0000000..71dc424
Binary files /dev/null and b/data/reload.png differ
diff --git a/data/search-dn.png b/data/search-dn.png
new file mode 100644 (file)
index 0000000..56ce631
Binary files /dev/null and b/data/search-dn.png differ
diff --git a/data/search-up.png b/data/search-up.png
new file mode 100644 (file)
index 0000000..4083f1b
Binary files /dev/null and b/data/search-up.png differ
diff --git a/data/sources-dn.png b/data/sources-dn.png
deleted file mode 100644 (file)
index 08b26da..0000000
Binary files a/data/sources-dn.png and /dev/null differ
diff --git a/data/sources-up.png b/data/sources-up.png
deleted file mode 100644 (file)
index 4dee828..0000000
Binary files a/data/sources-up.png and /dev/null differ
diff --git a/data/uninstall-dn.png b/data/uninstall-dn.png
deleted file mode 100644 (file)
index 0533f2c..0000000
Binary files a/data/uninstall-dn.png and /dev/null differ
diff --git a/data/uninstall-up.png b/data/uninstall-up.png
deleted file mode 100644 (file)
index 3c51c69..0000000
Binary files a/data/uninstall-up.png and /dev/null differ
diff --git a/data/upgrade-dn.png b/data/upgrade-dn.png
deleted file mode 100644 (file)
index 4ee5a82..0000000
Binary files a/data/upgrade-dn.png and /dev/null differ
diff --git a/data/upgrade-up.png b/data/upgrade-up.png
deleted file mode 100644 (file)
index 8aa50b0..0000000
Binary files a/data/upgrade-up.png and /dev/null differ
diff --git a/internals.h b/internals.h
new file mode 100644 (file)
index 0000000..90b3103
--- /dev/null
@@ -0,0 +1,9 @@
+- (NSMethodSignature *) methodSignatureForSelector:(SEL)selector {
+    fprintf(stderr, "[%s]S-%s\n", self->isa->name, sel_getName(selector));
+    return [super methodSignatureForSelector:selector];
+}
+
+- (BOOL) respondsToSelector:(SEL)selector {
+    fprintf(stderr, "[%s]R-%s\n", self->isa->name, sel_getName(selector));
+    return [super respondsToSelector:selector];
+}
index b0ea315055cac28dc3def7f2e6b9abb74d835490..ad0db8a75f192eba0d81c4767af559bf070be08d 100644 (file)
--- a/makefile
+++ b/makefile
@@ -6,5 +6,5 @@ test: all
        scp -p Cydia saurik@$(iPhone):/dat
        ssh saurik@$(iPhone) /dat/Cydia
 
-Cydia: *.mm makefile
+Cydia: *.mm *.h makefile
        arm-apple-darwin-g++ -fobjc-call-cxx-cdtors -g3 -O2 -Wall -Werror -o $@ $< -framework UIKit -framework IOKit -framework Foundation -framework CoreFoundation -framework CoreGraphics -framework GraphicsServices -lobjc -lapt-pkg -lpcre -fobjc-exceptions