]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
I cannot express how angry this mechanism makes me.
[cydia.git] / MobileCydia.mm
index 2b6bd1e011ab114b0b3a4bca47b356082b8945c6..999a708fcc28c74fbdf0f1e2e45c009ce8c97aa8 100644 (file)
@@ -125,6 +125,8 @@ extern "C" {
 #include "SDURLCache/SDURLCache.h"
 
 #include "substrate.h"
+
+#include "Version.h"
 /* }}} */
 
 /* Profiler {{{ */
@@ -218,12 +220,13 @@ union SplitHash {
 static const NSUInteger UIViewAutoresizingFlexibleBoth(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
 
 static _finline NSString *CydiaURL(NSString *path) {
-    char page[25];
-    page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = ':';
-    page[5] = '/'; page[6] = '/'; page[7] = 'c'; page[8] = 'y'; page[9] = 'd';
-    page[10] = 'i'; page[11] = 'a'; page[12] = '.'; page[13] = 's'; page[14] = 'a';
-    page[15] = 'u'; page[16] = 'r'; page[17] = 'i'; page[18] = 'k'; page[19] = '.';
-    page[20] = 'c'; page[21] = 'o'; page[22] = 'm'; page[23] = '/'; page[24] = '\0';
+    char page[26];
+    page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = 's';
+    page[5] = ':'; page[6] = '/'; page[7] = '/'; page[8] = 'c'; page[9] = 'y';
+    page[10] = 'd'; page[11] = 'i'; page[12] = 'a'; page[13] = '.'; page[14] = 's';
+    page[15] = 'a'; page[16] = 'u'; page[17] = 'r'; page[18] = 'i'; page[19] = 'k';
+    page[20] = '.'; page[21] = 'c'; page[22] = 'o'; page[23] = 'm'; page[24] = '/';
+    page[25] = '\0';
     return [[NSString stringWithUTF8String:page] stringByAppendingString:path];
 }
 
@@ -288,8 +291,9 @@ static _finline void UpdateExternalStatus(uint64_t newStatus) {
 
     NSRunLoop *loop([NSRunLoop currentRunLoop]);
     NSDate *future([NSDate distantFuture]);
+    NSString *mode([loop currentMode] ?: NSDefaultRunLoopMode);
 
-    while (!stopped && [loop runMode:NSDefaultRunLoopMode beforeDate:future]);
+    while (!stopped && [loop runMode:mode beforeDate:future]);
 
     return [context count] == 0 ? nil : [context objectAtIndex:0];
 }
@@ -307,12 +311,13 @@ static _finline void UpdateExternalStatus(uint64_t newStatus) {
 }
 
 - (int) yieldToPopupAlertAnimated:(BOOL)animated;
+
 @end
 
 @implementation CYAlertView
 
 - (id) initWithTitle:(NSString *)title buttons:(NSArray *)buttons defaultButtonIndex:(int)index {
-    if ((self = [super init])) {
+    if ((self = [super init]) != nil) {
         [self setTitle:title];
         [self setDelegate:self];
         for (NSString *button in buttons) [self addButtonWithTitle:button];
@@ -563,6 +568,21 @@ void CFArrayInsertionSortValues(CFMutableArrayRef array, CFRange range, CFCompar
 @end
 /* }}} */
 
+@interface NSInvocation (Cydia)
++ (NSInvocation *) invocationWithSelector:(SEL)selector forTarget:(id)target;
+@end
+
+@implementation NSInvocation (Cydia)
+
++ (NSInvocation *) invocationWithSelector:(SEL)selector forTarget:(id)target {
+    NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]]);
+    [invocation setTarget:target];
+    [invocation setSelector:selector];
+    return invocation;
+}
+
+@end
+
 @implementation WebScriptObject (NSFastEnumeration)
 
 - (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)objects count:(NSUInteger)count {
@@ -871,6 +891,7 @@ class Pcre {
 
 + (Address *) addressWithString:(NSString *)string;
 - (Address *) initWithString:(NSString *)string;
+
 @end
 
 @implementation Address
@@ -904,7 +925,10 @@ class Pcre {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"address", @"name", nil];
+    return [NSArray arrayWithObjects:
+        @"address",
+        @"name",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -982,6 +1006,7 @@ static const int PulseInterval_ = 50000;
 static const NSString *UI_;
 
 static int Finish_;
+static bool RestartSubstrate_;
 static NSArray *Finishes_;
 
 #define SpringBoard_ "/System/Library/LaunchDaemons/com.apple.SpringBoard.plist"
@@ -1018,7 +1043,7 @@ static const char *Machine_ = NULL;
 static NSString *System_ = nil;
 static NSString *SerialNumber_ = nil;
 static NSString *ChipID_ = nil;
-static NSString *Token_ = nil;
+static _H<NSString> Token_;
 static NSString *UniqueID_ = nil;
 static NSString *PLMN_ = nil;
 static NSString *Build_ = nil;
@@ -1048,23 +1073,6 @@ inline float Interpolate(float begin, float end, float fraction) {
     return (end - begin) * fraction + begin;
 }
 
-/* XXX: localize this! */
-NSString *SizeString(double size) {
-    bool negative = size < 0;
-    if (negative)
-        size = -size;
-
-    unsigned power = 0;
-    while (size > 1024) {
-        size /= 1024;
-        ++power;
-    }
-
-    static const char *powers_[] = {"B", "kB", "MB", "GB"};
-
-    return [NSString stringWithFormat:@"%s%.1f %s", (negative ? "-" : ""), size, powers_[power]];
-}
-
 static _finline const char *StripVersion_(const char *version) {
     const char *colon(strchr(version, ':'));
     return colon == NULL ? version : colon + 1;
@@ -1120,7 +1128,7 @@ NSString *GetLastUpdate() {
 }
 
 bool isSectionVisible(NSString *section) {
-    NSDictionary *metadata([Sections_ objectForKey:section]);
+    NSDictionary *metadata([Sections_ objectForKey:(section ?: @"")]);
     NSNumber *hidden(metadata == nil ? nil : [metadata objectForKey:@"Hidden"]);
     return hidden == nil || ![hidden boolValue];
 }
@@ -1130,22 +1138,12 @@ bool isSectionVisible(NSString *section) {
 /* Delegate Prototypes {{{ */
 @class Package;
 @class Source;
+@class CydiaProgressEvent;
 
-@interface NSObject (ProgressDelegate)
-@end
-
-@protocol ProgressDelegate
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)id;
-- (void) setProgressTitle:(NSString *)title;
-- (void) setProgressPercent:(float)percent;
-- (void) startProgress;
-- (void) addProgressOutput:(NSString *)output;
-- (bool) isCancelling:(size_t)received;
-@end
-
-@protocol ConfigurationDelegate
+@protocol DatabaseDelegate
 - (void) repairWithSelector:(SEL)selector;
 - (void) setConfigurationData:(NSString *)data;
+- (void) addProgressEventOnMainThread:(CydiaProgressEvent *)event forTask:(NSString *)task;
 @end
 
 @class CYPackageController;
@@ -1163,30 +1161,76 @@ bool isSectionVisible(NSString *section) {
 - (void) loadData;
 - (void) updateData;
 - (void) syncData;
+- (void) addTrivialSource:(NSString *)href;
 - (void) showSettings;
 - (UIProgressHUD *) addProgressHUD;
 - (void) removeProgressHUD:(UIProgressHUD *)hud;
 - (CYViewController *) pageForPackage:(NSString *)name;
 - (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item;
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
 @end
-
-static id<CydiaDelegate> CydiaApp;
 /* }}} */
 
+/* ProgressEvent Interface/Delegate {{{ */
+@interface CydiaProgressEvent : NSObject {
+    _H<NSString> message_;
+    _H<NSString> type_;
+
+    _H<NSArray> item_;
+    _H<NSString> package_;
+    _H<NSString> url_;
+    _H<NSString> version_;
+}
+
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type;
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package;
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forItem:(pkgAcquire::ItemDesc &)item;
+
+- (id) initWithMessage:(NSString *)message ofType:(NSString *)type;
+
+- (NSString *) message;
+- (NSString *) type;
+
+- (NSArray *) item;
+- (NSString *) package;
+- (NSString *) url;
+- (NSString *) version;
+
+- (void) setItem:(NSArray *)item;
+- (void) setPackage:(NSString *)package;
+- (void) setURL:(NSString *)url;
+- (void) setVersion:(NSString *)version;
+
+- (NSString *) compound:(NSString *)value;
+- (NSString *) compoundMessage;
+- (NSString *) compoundTitle;
+
+@end
+
+@protocol ProgressDelegate
+- (void) addProgressEvent:(CydiaProgressEvent *)event;
+- (void) setProgressPercent:(NSNumber *)percent;
+- (void) setProgressCancellable:(NSNumber *)cancellable;
+- (bool) isProgressCancelled;
+- (void) setTitle:(NSString *)title;
+@end
+/* }}} */
 /* Status Delegation {{{ */
 class Status :
     public pkgAcquireStatus
 {
   private:
     _transient NSObject<ProgressDelegate> *delegate_;
+    bool cancelled_;
 
   public:
     Status() :
-        delegate_(nil)
+        delegate_(nil),
+        cancelled_(false)
     {
     }
 
-    void setDelegate(id delegate) {
+    void setDelegate(NSObject<ProgressDelegate> *delegate) {
         delegate_ = delegate;
     }
 
@@ -1202,8 +1246,9 @@ class Status :
     }
 
     virtual void Fetch(pkgAcquire::ItemDesc &item) {
-        //NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
-        [delegate_ setProgressTitle:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), [NSString stringWithUTF8String:item.ShortDesc.c_str()]]];
+        NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
+        CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), name] ofType:@"STATUS" forItem:item]);
+        [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
     virtual void Done(pkgAcquire::ItemDesc &item) {
@@ -1220,17 +1265,8 @@ class Status :
         if (error.empty())
             return;
 
-        NSString *description([NSString stringWithUTF8String:item.Description.c_str()]);
-        NSArray *fields([description componentsSeparatedByString:@" "]);
-        NSString *source([fields count] == 0 ? nil : [fields objectAtIndex:0]);
-
-        [delegate_ performSelectorOnMainThread:@selector(_setProgressErrorPackage:)
-            withObject:[NSArray arrayWithObjects:
-                [NSString stringWithUTF8String:error.c_str()],
-                source,
-            nil]
-            waitUntilDone:YES
-        ];
+        CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:@"ERROR" forItem:item]);
+        [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
     virtual bool Pulse(pkgAcquire *Owner) {
@@ -1241,59 +1277,30 @@ class Status :
             double(TotalBytes + TotalItems)
         );
 
-        [delegate_ setProgressPercent:percent];
-        return [delegate_ isCancelling:CurrentBytes] ? false : value;
-    }
-
-    virtual void Start() {
-        [delegate_ startProgress];
-    }
-
-    virtual void Stop() {
-    }
-};
-/* }}} */
-/* Progress Delegation {{{ */
-class Progress :
-    public OpProgress
-{
-  private:
-    _transient id<ProgressDelegate> delegate_;
-    float percent_;
-
-  protected:
-    virtual void Update() {
-        /*if (abs(Percent - percent_) > 2)
-            //NSLog(@"%s:%s:%f", Op.c_str(), SubOp.c_str(), Percent);
-            percent_ = Percent;
-        }*/
-
-        /*[delegate_ setProgressTitle:[NSString stringWithUTF8String:Op.c_str()]];
-        [delegate_ setProgressPercent:(Percent / 100)];*/
-    }
-
-  public:
-    Progress() :
-        delegate_(nil),
-        percent_(0)
-    {
+        [delegate_ performSelectorOnMainThread:@selector(setProgressPercent:) withObject:[NSNumber numberWithFloat:percent] waitUntilDone:YES];
+        if (value && ![delegate_ isProgressCancelled])
+            return true;
+        else {
+            cancelled_ = true;
+            return false;
+        }
     }
 
-    void setDelegate(id delegate) {
-        delegate_ = delegate;
+    _finline bool WasCancelled() const {
+        return cancelled_;
     }
 
-    id getDelegate() const {
-        return delegate_;
+    virtual void Start() {
+        pkgAcquireStatus::Start();
+        [delegate_ performSelectorOnMainThread:@selector(setProgressCancellable:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
     }
 
-    virtual void Done() {
-        //NSLog(@"DONE");
-        //[delegate_ setProgressPercent:1];
+    virtual void Stop() {
+        pkgAcquireStatus::Stop();
+        [delegate_ performSelectorOnMainThread:@selector(setProgressCancellable:) withObject:[NSNumber numberWithBool:NO] waitUntilDone:YES];
     }
 };
 /* }}} */
-
 /* Database Interface {{{ */
 typedef std::map< unsigned long, _H<Source> > SourceMap;
 
@@ -1312,13 +1319,15 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
     SPtr<pkgPackageManager> manager_;
     pkgSourceList *list_;
 
-    SourceMap sources_;
-    CFMutableArrayRef deadSources_;
+    SourceMap sourceMap_;
+    NSMutableArray *sourceList_;
+
     CFMutableArrayRef packages_;
 
-    _transient NSObject<ConfigurationDelegate, ProgressDelegate> *delegate_;
+    _transient NSObject<DatabaseDelegate> *delegate_;
+    _transient NSObject<ProgressDelegate> *progress_;
+
     Status status_;
-    Progress progress_;
 
     int cydiafd_;
     int statusfd_;
@@ -1346,7 +1355,8 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 - (pkgSourceList &) list;
 - (NSArray *) packages;
 - (NSArray *) sources;
-- (void) reloadData;
+- (Source *) sourceWithKey:(NSString *)key;
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
 
 - (void) configure;
 - (bool) prepare;
@@ -1356,44 +1366,145 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 
 - (void) updateWithStatus:(Status &)status;
 
-- (void) setDelegate:(id)delegate;
+- (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate;
+
+- (void) setProgressDelegate:(NSObject<ProgressDelegate> *)delegate;
+- (NSObject<ProgressDelegate> *) progressDelegate;
+
 - (Source *) getSource:(pkgCache::PkgFileIterator)file;
 
 - (NSString *) mappedSectionForPointer:(const char *)pointer;
 
 @end
 /* }}} */
-/* Delegate Helpers {{{ */
-@implementation NSObject (ProgressDelegate)
+/* ProgressEvent Implementation {{{ */
+@implementation CydiaProgressEvent
 
-- (void) _setProgressErrorPackage:(NSArray *)args {
-    [self performSelector:@selector(setProgressError:forPackage:)
-        withObject:[args objectAtIndex:0]
-        withObject:([args count] == 1 ? nil : [args objectAtIndex:1])
-    ];
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type {
+    return [[[CydiaProgressEvent alloc] initWithMessage:message ofType:type] autorelease];
 }
 
-- (void) _setProgressErrorTitle:(NSArray *)args {
-    [self performSelector:@selector(setProgressError:withTitle:)
-        withObject:[args objectAtIndex:0]
-        withObject:([args count] == 1 ? nil : [args objectAtIndex:1])
-    ];
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package {
+    CydiaProgressEvent *event([self eventWithMessage:message ofType:type]);
+    [event setPackage:package];
+    return event;
 }
 
-- (void) _setProgressError:(NSString *)error withTitle:(NSString *)title {
-    [self performSelectorOnMainThread:@selector(_setProgressErrorTitle:)
-        withObject:[NSArray arrayWithObjects:error, title, nil]
-        waitUntilDone:YES
-    ];
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forItem:(pkgAcquire::ItemDesc &)item {
+    CydiaProgressEvent *event([self eventWithMessage:message ofType:type]);
+
+    NSString *description([NSString stringWithUTF8String:item.Description.c_str()]);
+    NSArray *fields([description componentsSeparatedByString:@" "]);
+    [event setItem:fields];
+
+    if ([fields count] > 3) {
+        [event setPackage:[fields objectAtIndex:2]];
+        [event setVersion:[fields objectAtIndex:3]];
+    }
+
+    [event setURL:[NSString stringWithUTF8String:item.URI.c_str()]];
+
+    return event;
+}
+
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"item",
+        @"message",
+        @"package",
+        @"type",
+        @"url",
+        @"version",
+    nil];
 }
 
-- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
-    Package *package = id == nil ? nil : [[Database sharedInstance] packageWithName:id];
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
 
-    [self performSelector:@selector(setProgressError:withTitle:)
-        withObject:error
-        withObject:(package == nil ? id : [package name])
-    ];
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (id) initWithMessage:(NSString *)message ofType:(NSString *)type {
+    if ((self = [super init]) != nil) {
+        message_ = message;
+        type_ = type;
+    } return self;
+}
+
+- (NSString *) message {
+    return message_;
+}
+
+- (NSString *) type {
+    return type_;
+}
+
+- (NSArray *) item {
+    return (id) item_ ?: [NSNull null];
+}
+
+- (void) setItem:(NSArray *)item {
+    item_ = item;
+}
+
+- (NSString *) package {
+    return (id) package_ ?: [NSNull null];
+}
+
+- (void) setPackage:(NSString *)package {
+    package_ = package;
+}
+
+- (NSString *) url {
+    return (id) url_ ?: [NSNull null];
+}
+
+- (void) setURL:(NSString *)url {
+    url_ = url;
+}
+
+- (void) setVersion:(NSString *)version {
+    version_ = version;
+}
+
+- (NSString *) version {
+    return (id) version_ ?: [NSNull null];
+}
+
+- (NSString *) compound:(NSString *)value {
+    if (value != nil) {
+        NSString *mode(nil); {
+            NSString *type([self type]);
+            if ([type isEqualToString:@"ERROR"])
+                mode = UCLocalize("ERROR");
+            else if ([type isEqualToString:@"WARNING"])
+                mode = UCLocalize("WARNING");
+        }
+
+        if (mode != nil)
+            value = [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), mode, value];
+    }
+
+    return value;
+}
+
+- (NSString *) compoundMessage {
+    return [self compound:[self message]];
+}
+
+- (NSString *) compoundTitle {
+    NSString *title;
+
+    if (package_ == nil)
+        title = nil;
+    else if (Package *package = [[Database sharedInstance] packageWithName:package_])
+        title = [package name];
+    else
+        title = package_;
+
+    return [self compound:title];
 }
 
 @end
@@ -1591,7 +1702,19 @@ static void PackageImport(const void *key, const void *value, void *context) {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"description", @"distribution", @"host", @"key", @"label", @"name", @"origin", @"trusted", @"type", @"uri", @"version", nil];
+    return [NSArray arrayWithObjects:
+        @"description",
+        @"distribution",
+        @"host",
+        @"key",
+        @"label",
+        @"name",
+        @"origin",
+        @"trusted",
+        @"type",
+        @"uri",
+        @"version",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -1756,37 +1879,176 @@ static void PackageImport(const void *key, const void *value, void *context) {
 
 @end
 /* }}} */
-/* Relationship Class {{{ */
-@interface Relationship : NSObject {
-    NSString *type_;
-    NSString *id_;
+/* CydiaOperation Class {{{ */
+@interface CydiaOperation : NSObject {
+    NSString *operator_;
+    NSString *value_;
 }
 
-- (NSString *) type;
-- (NSString *) id;
-- (NSString *) name;
+- (NSString *) operator;
+- (NSString *) value;
 
 @end
 
-@implementation Relationship
+@implementation CydiaOperation
 
 - (void) dealloc {
-    [type_ release];
-    [id_ release];
+    [operator_ release];
+    [value_ release];
     [super dealloc];
 }
 
-- (NSString *) type {
-    return type_;
+- (id) initWithOperator:(const char *)_operator value:(const char *)value {
+    if ((self = [super init]) != nil) {
+        operator_ = [[NSString alloc] initWithUTF8String:_operator];
+        value_ = [[NSString alloc] initWithUTF8String:value];
+    } return self;
 }
 
-- (NSString *) id {
-    return id_;
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"operator",
+        @"value",
+    nil];
 }
 
-- (NSString *) name {
-    _assert(false);
-    return nil;
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (NSString *) operator {
+    return operator_;
+}
+
+- (NSString *) value {
+    return value_;
+}
+
+@end
+/* }}} */
+/* CydiaClause Class {{{ */
+@interface CydiaClause : NSObject {
+    NSString *package_;
+    CydiaOperation *version_;
+}
+
+- (NSString *) package;
+- (CydiaOperation *) version;
+
+@end
+
+@implementation CydiaClause
+
+- (void) dealloc {
+    [package_ release];
+    [version_ release];
+    [super dealloc];
+}
+
+- (id) initWithIterator:(pkgCache::DepIterator &)dep {
+    if ((self = [super init]) != nil) {
+        package_ = [[NSString alloc] initWithUTF8String:dep.TargetPkg().Name()];
+
+        if (const char *version = dep.TargetVer())
+            version_ = [[CydiaOperation alloc] initWithOperator:dep.CompType() value:version];
+        else
+            version_ = [[NSNull null] retain];
+    } return self;
+}
+
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"package",
+        @"version",
+    nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (NSString *) package {
+    return package_;
+}
+
+- (CydiaOperation *) version {
+    return version_;
+}
+
+@end
+/* }}} */
+/* CydiaRelation Class {{{ */
+@interface CydiaRelation : NSObject {
+    NSString *relationship_;
+    NSMutableArray *clauses_;
+}
+
+- (NSString *) relationship;
+- (NSArray *) clauses;
+
+@end
+
+@implementation CydiaRelation
+
+- (void) dealloc {
+    [relationship_ release];
+    [clauses_ release];
+    [super dealloc];
+}
+
+- (id) initWithIterator:(pkgCache::DepIterator &)dep {
+    if ((self = [super init]) != nil) {
+        relationship_ = [[NSString alloc] initWithUTF8String:dep.DepType()];
+        clauses_ = [[NSMutableArray alloc] initWithCapacity:8];
+
+        pkgCache::DepIterator start;
+        pkgCache::DepIterator end;
+        dep.GlobOr(start, end); // ++dep
+
+        _forever {
+            [clauses_ addObject:[[[CydiaClause alloc] initWithIterator:start] autorelease]];
+
+            // yes, seriously. (wtf?)
+            if (start == end)
+                break;
+            ++start;
+        }
+    } return self;
+}
+
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"clauses",
+        @"relationship",
+    nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (NSString *) relationship {
+    return relationship_;
+}
+
+- (NSArray *) clauses {
+    return clauses_;
+}
+
+- (void) addClause:(CydiaClause *)clause {
+    [clauses_ addObject:clause];
 }
 
 @end
@@ -2066,8 +2328,17 @@ struct PackageNameOrdering :
 }
 
 + (NSString *) webScriptNameForSelector:(SEL)selector {
-    if (selector == @selector(hasTag:))
+    if (false);
+    else if (selector == @selector(clear))
+        return @"clear";
+    else if (selector == @selector(getField:))
+        return @"getField";
+    else if (selector == @selector(hasTag:))
         return @"hasTag";
+    else if (selector == @selector(install))
+        return @"install";
+    else if (selector == @selector(remove))
+        return @"remove";
     else
         return nil;
 }
@@ -2077,7 +2348,33 @@ struct PackageNameOrdering :
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"applications", @"author", @"depiction", @"longDescription", @"essential", @"homepage", @"icon", @"id", @"installed", @"latest", @"longSection", @"maintainer", @"mode", @"name", @"purposes", @"section", @"shortDescription", @"shortSection", @"simpleSection", @"size", @"source", @"sponsor", @"support", @"warnings", nil];
+    return [NSArray arrayWithObjects:
+        @"applications",
+        @"author",
+        @"depiction",
+        @"essential",
+        @"homepage",
+        @"icon",
+        @"id",
+        @"installed",
+        @"latest",
+        @"longDescription",
+        @"longSection",
+        @"maintainer",
+        @"mode",
+        @"name",
+        @"purposes",
+        @"relations",
+        @"section",
+        @"shortDescription",
+        @"shortSection",
+        @"simpleSection",
+        @"size",
+        @"source",
+        @"sponsor",
+        @"support",
+        @"warnings",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -2088,6 +2385,28 @@ struct PackageNameOrdering :
     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
 }
 
+- (NSArray *) relations {
+@synchronized (database_) {
+    NSMutableArray *relations([NSMutableArray arrayWithCapacity:16]);
+    for (pkgCache::DepIterator dep(version_.DependsList()); !dep.end(); ++dep)
+        [relations addObject:[[[CydiaRelation alloc] initWithIterator:dep] autorelease]];
+    return relations;
+} }
+
+- (NSString *) getField:(NSString *)name {
+@synchronized (database_) {
+    if ([database_ era] != era_ || file_.end())
+        return nil;
+
+    pkgRecords::Parser &parser([database_ records]->Lookup(file_));
+
+    const char *start, *end;
+    if (!parser.Find([name UTF8String], start, end))
+        return (NSString *) [NSNull null];
+
+    return [(NSString *) CYStringCreate(start, end - start) autorelease];
+} }
+
 - (void) parse {
     if (parsed_ != NULL)
         return;
@@ -2479,7 +2798,7 @@ struct PackageNameOrdering :
     _end
 
     _profile(Package$visible$isSectionVisible)
-        if (section != nil && !isSectionVisible(section))
+        if (!isSectionVisible(section))
             return false;
     _end
 
@@ -3020,8 +3339,7 @@ static NSString *Warning_;
 - (void) dealloc {
     // XXX: actually implement this thing
     _assert(false);
-    if (deadSources_)
-           CFRelease(deadSources_);
+    [sourceList_ release];
     [self releasePackages];
     apr_pool_destroy(pool_);
     NSRecycleZone(zone_);
@@ -3064,29 +3382,31 @@ static NSString *Warning_;
         size_t size(line.size());
         lprintf("S:%s\n", data);
 
-        if (conffile_r(data, size)) {
-            [delegate_ setConfigurationData:conffile_r[1]];
-        else if (strncmp(data, "status: ", 8) == 0) {
-            NSString *string = [NSString stringWithUTF8String:(data + 8)];
-            [delegate_ setProgressTitle:string];
+        if (conffile_r(data, size))
+            [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:conffile_r[1] waitUntilDone:YES];
+        else if (strncmp(data, "status: ", 8) == 0) {
+            CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 8)] ofType:@"STATUS"]);
+            [progress_ performSelectorOnMainThread:@selector(addProgressEvent) withObject:event waitUntilDone:YES];
         } else if (pmstatus_r(data, size)) {
             std::string type([pmstatus_r[1] UTF8String]);
-            NSString *id = pmstatus_r[2];
+
+            NSString *package = pmstatus_r[2];
+            if ([package isEqualToString:@"dpkg-exec"])
+                package = nil;
 
             float percent([pmstatus_r[3] floatValue]);
-            [delegate_ setProgressPercent:(percent / 100)];
+            [progress_ performSelectorOnMainThread:@selector(setProgressPercent:) withObject:[NSNumber numberWithFloat:(percent / 100)] waitUntilDone:YES];
 
             NSString *string = pmstatus_r[4];
 
-            if (type == "pmerror")
-                [delegate_ performSelectorOnMainThread:@selector(_setProgressErrorPackage:)
-                    withObject:[NSArray arrayWithObjects:string, id, nil]
-                    waitUntilDone:YES
-                ];
-            else if (type == "pmstatus") {
-                [delegate_ setProgressTitle:string];
+            if (type == "pmerror") {
+                CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:@"ERROR" forPackage:package]);
+                [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
+            } else if (type == "pmstatus") {
+                CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:@"STATUS" forPackage:package]);
+                [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
             } else if (type == "pmconffile")
-                [delegate_ setConfigurationData:string];
+                [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:string waitUntilDone:YES];
             else
                 lprintf("E:unknown pmstatus\n");
         } else
@@ -3103,7 +3423,9 @@ static NSString *Warning_;
 
     while (std::getline(is, line)) {
         lprintf("O:%s\n", line.c_str());
-        [delegate_ addProgressOutput:[NSString stringWithUTF8String:line.c_str()]];
+
+        CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:line.c_str()] ofType:@"INFORMATION"]);
+        [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
     _assume(false);
@@ -3139,7 +3461,7 @@ static NSString *Warning_;
             capacity += 1024;
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL);
-        deadSources_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+        sourceList_ = [[NSMutableArray alloc] initWithCapacity:16];
 
         int fds[2];
 
@@ -3211,104 +3533,37 @@ static NSString *Warning_;
 }
 
 - (NSArray *) sources {
-    NSMutableArray *sources([NSMutableArray arrayWithCapacity:sources_.size()]);
-    for (SourceMap::const_iterator i(sources_.begin()); i != sources_.end(); ++i)
-        [sources addObject:i->second];
-    [sources addObjectsFromArray:(NSArray *)deadSources_];
-    return sources;
+    return sourceList_;
 }
 
-- (NSArray *) issues {
-    if (cache_->BrokenCount() == 0)
-        return nil;
-
-    NSMutableArray *issues([NSMutableArray arrayWithCapacity:4]);
-
-    for (Package *package in [self packages]) {
-        if (![package broken])
-            continue;
-        pkgCache::PkgIterator pkg([package iterator]);
-
-        NSMutableArray *entry([NSMutableArray arrayWithCapacity:4]);
-        [entry addObject:[package name]];
-        [issues addObject:entry];
-
-        pkgCache::VerIterator ver(cache_[pkg].InstVerIter(cache_));
-        if (ver.end())
-            continue;
-
-        for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
-            pkgCache::DepIterator start;
-            pkgCache::DepIterator end;
-            dep.GlobOr(start, end); // ++dep
-
-            if (!cache_->IsImportantDep(end))
-                continue;
-            if ((cache_[end] & pkgDepCache::DepGInstall) != 0)
-                continue;
-
-            NSMutableArray *failure([NSMutableArray arrayWithCapacity:4]);
-            [entry addObject:failure];
-            [failure addObject:[NSString stringWithUTF8String:start.DepType()]];
-
-            NSString *name([NSString stringWithUTF8String:start.TargetPkg().Name()]);
-            if (Package *package = [self packageWithName:name])
-                name = [package name];
-            [failure addObject:name];
-
-            pkgCache::PkgIterator target(start.TargetPkg());
-            if (target->ProvidesList != 0)
-                [failure addObject:@"?"];
-            else {
-                pkgCache::VerIterator ver(cache_[target].InstVerIter(cache_));
-                if (!ver.end())
-                    [failure addObject:[NSString stringWithUTF8String:ver.VerStr()]];
-                else if (!cache_[target].CandidateVerIter(cache_).end())
-                    [failure addObject:@"-"];
-                else if (target->ProvidesList == 0)
-                    [failure addObject:@"!"];
-                else
-                    [failure addObject:@"%"];
-            }
-
-            _forever {
-                if (start.TargetVer() != 0)
-                    [failure addObject:[NSString stringWithFormat:@"%s %s", start.CompType(), start.TargetVer()]];
-                if (start == end)
-                    break;
-                ++start;
-            }
-        }
-    }
-
-    return issues;
+- (Source *) sourceWithKey:(NSString *)key {
+    for (Source *source in [self sources]) {
+        if ([[source key] isEqualToString:key])
+            return source;
+    } return nil;
 }
 
 - (bool) popErrorWithTitle:(NSString *)title {
     bool fatal(false);
-    std::string message;
 
     while (!_error->empty()) {
         std::string error;
         bool warning(!_error->PopMessage(error));
         if (!warning)
             fatal = true;
+
         for (;;) {
             size_t size(error.size());
             if (size == 0 || error[size - 1] != '\n')
                 break;
             error.resize(size - 1);
         }
+
         lprintf("%c:[%s]\n", warning ? 'W' : 'E', error.c_str());
 
-        if (!message.empty())
-            message += "\n\n";
-        message += error;
+        [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? @"WARNING" : @"ERROR")] forTask:title];
     }
 
-    if (fatal && !message.empty())
-        [delegate_ _setProgressError:[NSString stringWithUTF8String:message.c_str()] withTitle:[NSString stringWithFormat:Colon_, fatal ? Error_ : Warning_, title]];
-
     return fatal;
 }
 
@@ -3316,14 +3571,14 @@ static NSString *Warning_;
     return [self popErrorWithTitle:title] || !success;
 }
 
-- (void) reloadData { CYPoolStart() {
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation { CYPoolStart() {
 @synchronized (self) {
     ++era_;
 
     [self releasePackages];
 
-    sources_.clear();
-    CFArrayRemoveAllValues(deadSources_);
+    sourceMap_.clear();
+    [sourceList_ removeAllObjects];
 
     _error->Discard();
 
@@ -3352,10 +3607,14 @@ static NSString *Warning_;
     if (chk != -1)
         close(chk);
 
+    if (invocation != nil)
+        [invocation invoke];
+
     NSString *title(UCLocalize("DATABASE"));
 
     _trace();
-    while (!cache_.Open(progress_, true)) { pop:
+    OpProgress progress;
+    while (!cache_.Open(progress, true)) { pop:
         std::string error;
         bool warning(!_error->PopMessage(error));
         lprintf("cache_.Open():[%s]\n", error.c_str());
@@ -3364,12 +3623,12 @@ static NSString *Warning_;
             [delegate_ repairWithSelector:@selector(configure)];
         else if (error == "The package lists or status file could not be parsed or opened.")
             [delegate_ repairWithSelector:@selector(update)];
+        // else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
         // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
-        else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
-            [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, Error_, title]];
+        // else if (error == "Malformed Status line")
         // else if (error == "The list of sources could not be read.")
         else {
-            [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, warning ? Warning_ : Error_, title]];
+            [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? @"WARNING" : @"ERROR")] forTask:title];
             return;
         }
 
@@ -3394,7 +3653,7 @@ static NSString *Warning_;
         return;
 
     if (cache_->DelCount() != 0 || cache_->InstCount() != 0) {
-        [delegate_ _setProgressError:@"COUNTS_NONZERO_EX" withTitle:title];
+        [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("COUNTS_NONZERO_EX") ofType:@"ERROR"] forTask:title];
         return;
     }
 
@@ -3406,7 +3665,7 @@ static NSString *Warning_;
             return;
 
         if (cache_->BrokenCount() != 0) {
-            [delegate_ _setProgressError:@"STILL_BROKEN_EX" withTitle:title];
+            [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("STILL_BROKEN_EX") ofType:@"ERROR"] forTask:title];
             return;
         }
 
@@ -3415,19 +3674,17 @@ static NSString *Warning_;
     }
 
     for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
-        bool found = false;
+        Source *object([[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease]);
+        [sourceList_ addObject:object];
+
         std::vector<pkgIndexFile *> *indices = (*source)->GetIndexFiles();
         for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
             // XXX: this could be more intelligent
             if (dynamic_cast<debPackagesIndex *>(*index) != NULL) {
                 pkgCache::PkgFileIterator cached((*index)->FindInCache(cache_));
-                if (!cached.end()) {
-                    sources_[cached->ID] = [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease];
-                    found = true;
-                }
+                if (!cached.end())
+                    sourceMap_[cached->ID] = object;
             }
-        if (!found)
-            CFArrayAppendValue(deadSources_, [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease]);
     }
 
     {
@@ -3488,12 +3745,11 @@ static NSString *Warning_;
     delete resolver_;
     resolver_ = new pkgProblemResolver(cache_);
 
-    for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator) {
-        if (!cache_[iterator].Keep()) {
+    for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator)
+        if (!cache_[iterator].Keep())
             cache_->MarkKeep(iterator, false);
+        else if ((cache_[iterator].iFlags & pkgDepCache::ReInstall) != 0)
             cache_->SetReInstall(iterator, false);
-        }
-    }
 } }
 
 - (void) configure {
@@ -3559,6 +3815,9 @@ static NSString *Warning_;
 }
 
 - (void) perform {
+    bool substrate(RestartSubstrate_);
+    RestartSubstrate_ = false;
+
     NSString *title(UCLocalize("PERFORM_SELECTIONS"));
 
     NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
@@ -3569,13 +3828,14 @@ static NSString *Warning_;
             [before addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
     }
 
+    [delegate_ performSelectorOnMainThread:@selector(retainNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
+
     if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue) {
         _trace();
+        [self popErrorWithTitle:title];
         return;
     }
 
-    [CydiaApp retainNetworkActivityIndicator];
-
     bool failed = false;
     for (pkgAcquire::ItemIterator item = fetcher_->ItemsBegin(); item != fetcher_->ItemsEnd(); item++) {
         if ((*item)->Status == pkgAcquire::Item::StatDone && (*item)->Complete)
@@ -3583,27 +3843,19 @@ static NSString *Warning_;
         if ((*item)->Status == pkgAcquire::Item::StatIdle)
             continue;
 
-        std::string uri = (*item)->DescURI();
-        std::string error = (*item)->ErrorText;
-
-        lprintf("pAf:%s:%s\n", uri.c_str(), error.c_str());
         failed = true;
-
-        [delegate_ performSelectorOnMainThread:@selector(_setProgressErrorPackage:)
-            withObject:[NSArray arrayWithObjects:
-                [NSString stringWithUTF8String:error.c_str()],
-            nil]
-            waitUntilDone:YES
-        ];
     }
 
-    [CydiaApp releaseNetworkActivityIndicator];
+    [delegate_ performSelectorOnMainThread:@selector(releaseNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
 
     if (failed) {
         _trace();
         return;
     }
 
+    if (substrate)
+        RestartSubstrate_ = true;
+
     _system->UnLock();
     pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
 
@@ -3646,35 +3898,47 @@ static NSString *Warning_;
 }
 
 - (void) updateWithStatus:(Status &)status {
-    _transient NSObject<ProgressDelegate> *delegate(status.getDelegate());
     NSString *title(UCLocalize("REFRESHING_DATA"));
 
     pkgSourceList list;
-    if (!list.ReadMainList())
-        [delegate _setProgressError:@"Unable to read source list." withTitle:title];
+    if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
+        return;
 
     FileFd lock;
     lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
     if ([self popErrorWithTitle:title])
         return;
 
-    if ([self popErrorWithTitle:title forOperation:ListUpdate(status, list, PulseInterval_)])
-        /* XXX: ignore this because users suck and don't understand why refreshing is important: return */
-        /* XXX: why the hell is an empty if statement a clang error? */ (void) 0;
+    [delegate_ performSelectorOnMainThread:@selector(retainNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
+
+    bool success(ListUpdate(status, list, PulseInterval_));
+    if (status.WasCancelled())
+        _error->Discard();
+    else
+        [self popErrorWithTitle:title forOperation:success];
+
+    [delegate_ performSelectorOnMainThread:@selector(releaseNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
 
     [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"];
     Changed_ = true;
 }
 
-- (void) setDelegate:(id)delegate {
+- (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate {
     delegate_ = delegate;
+}
+
+- (void) setProgressDelegate:(NSObject<ProgressDelegate> *)delegate {
+    progress_ = delegate;
     status_.setDelegate(delegate);
-    progress_.setDelegate(delegate);
+}
+
+- (NSObject<ProgressDelegate> *) progressDelegate {
+    return progress_;
 }
 
 - (Source *) getSource:(pkgCache::PkgFileIterator)file {
-    SourceMap::const_iterator i(sources_.find(file->ID));
-    return i == sources_.end() ? nil : i->second;
+    SourceMap::const_iterator i(sourceMap_.find(file->ID));
+    return i == sourceMap_.end() ? nil : i->second;
 }
 
 - (NSString *) mappedSectionForPointer:(const char *)section {
@@ -3718,6 +3982,7 @@ static NSString *Warning_;
 }
 
 - (id) initWithDelegate:(IndirectDelegate *)indirect;
+
 @end
 
 @implementation CydiaObject
@@ -3738,7 +4003,19 @@ static NSString *Warning_;
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"device", @"firewire", @"imei", @"mac", @"serial", nil];
+    return [NSArray arrayWithObjects:
+        @"device",
+        @"ecid",
+        @"firmware",
+        @"hostname",
+        @"idiom",
+        @"model",
+        @"plmn",
+        @"role",
+        @"serial",
+        @"token",
+        @"version",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -3749,63 +4026,104 @@ static NSString *Warning_;
     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
 }
 
+- (NSString *) version {
+    return @ Cydia_;
+}
+
 - (NSString *) device {
     return [[UIDevice currentDevice] uniqueIdentifier];
 }
 
-#if 0 // XXX: implement!
-- (NSString *) mac {
-    if (![indirect_ promptForSensitive:@"Mac Address"])
-        return nil;
+- (NSString *) firmware {
+    return [[UIDevice currentDevice] systemVersion];
+}
+
+- (NSString *) hostname {
+    return [[UIDevice currentDevice] name];
+}
+
+- (NSString *) idiom {
+    UIDevice *device([UIDevice currentDevice]);
+    if (![device respondsToSelector:@selector(userInterfaceIdiom)])
+        return @"iphone";
+
+    UIUserInterfaceIdiom idiom([device userInterfaceIdiom]);
+    if (idiom == UIUserInterfaceIdiomPhone)
+        return @"iphone";
+    else if (idiom == UIUserInterfaceIdiomPad)
+        return @"ipad";
+    else
+        return @"unknown";
+}
+
+- (NSString *) plmn {
+    return (id) PLMN_ ?: [NSNull null];
+}
+
+- (NSString *) ecid {
+    return (id) ChipID_ ?: [NSNull null];
 }
 
 - (NSString *) serial {
-    if (![indirect_ promptForSensitive:@"Serial #"])
-        return nil;
+    return SerialNumber_;
 }
 
-- (NSString *) firewire {
-    if (![indirect_ promptForSensitive:@"Firewire GUID"])
-        return nil;
+- (NSString *) role {
+    return (id) Role_ ?: [NSNull null];
 }
 
-- (NSString *) imei {
-    if (![indirect_ promptForSensitive:@"IMEI"])
-        return nil;
+- (NSString *) model {
+    return [NSString stringWithUTF8String:Machine_];
+}
+
+- (NSString *) token {
+    return (id) Token_ ?: [NSNull null];
 }
-#endif
 
 + (NSString *) webScriptNameForSelector:(SEL)selector {
-    if (selector == @selector(close))
+    if (false);
+    else if (selector == @selector(addTrivialSource:))
+        return @"addTrivialSource";
+    else if (selector == @selector(close))
         return @"close";
+    else if (selector == @selector(du:))
+        return @"du";
+    else if (selector == @selector(stringWithFormat:arguments:))
+        return @"format";
+    else if (selector == @selector(getAllSources))
+        return @"getAllSourcs";
+    else if (selector == @selector(getKernelNumber:))
+        return @"getKernelNumber";
+    else if (selector == @selector(getKernelString:))
+        return @"getKernelString";
     else if (selector == @selector(getInstalledPackages))
         return @"getInstalledPackages";
     else if (selector == @selector(getPackageById:))
         return @"getPackageById";
     else if (selector == @selector(installPackages:))
         return @"installPackages";
+    else if (selector == @selector(localizedStringForKey:value:table:))
+        return @"localize";
+    else if (selector == @selector(refreshSources))
+        return @"refreshSources";
+    else if (selector == @selector(removeButton))
+        return @"removeButton";
+    else if (selector == @selector(scrollToBottom:))
+        return @"scrollToBottom";
     else if (selector == @selector(setButtonImage:withStyle:toFunction:))
         return @"setButtonImage";
     else if (selector == @selector(setButtonTitle:withStyle:toFunction:))
         return @"setButtonTitle";
     else if (selector == @selector(setPopupHook:))
         return @"setPopupHook";
-    else if (selector == @selector(setSpecial:))
-        return @"setSpecial";
     else if (selector == @selector(setToken:))
         return @"setToken";
     else if (selector == @selector(setViewportWidth:))
         return @"setViewportWidth";
-    else if (selector == @selector(supports:))
-        return @"supports";
-    else if (selector == @selector(stringWithFormat:arguments:))
-        return @"format";
-    else if (selector == @selector(localizedStringForKey:value:table:))
-        return @"localize";
-    else if (selector == @selector(du:))
-        return @"du";
     else if (selector == @selector(statfs:))
         return @"statfs";
+    else if (selector == @selector(supports:))
+        return @"supports";
     else
         return nil;
 }
@@ -3818,19 +4136,69 @@ static NSString *Warning_;
     return [feature isEqualToString:@"window.open"];
 }
 
+- (NSNumber *) getKernelNumber:(NSString *)name {
+    const char *string([name UTF8String]);
+
+    size_t size;
+    if (sysctlbyname(string, NULL, &size, NULL, 0) == -1)
+        return (id) [NSNull null];
+
+    if (size != sizeof(int))
+        return (id) [NSNull null];
+
+    int value;
+    if (sysctlbyname(string, &value, &size, NULL, 0) == -1)
+        return (id) [NSNull null];
+
+    return [NSNumber numberWithInt:value];
+}
+
+- (NSString *) getKernelString:(NSString *)name {
+    const char *string([name UTF8String]);
+
+    size_t size;
+    if (sysctlbyname(string, NULL, &size, NULL, 0) == -1)
+        return (id) [NSNull null];
+
+    char value[size + 1];
+    if (sysctlbyname(string, value, &size, NULL, 0) == -1)
+        return (id) [NSNull null];
+
+    // XXX: just in case you request something ludicrous
+    value[size] = '\0';
+
+    return [NSString stringWithCString:value];
+}
+
+- (void) addTrivialSource:(NSString *)href {
+    [delegate_ performSelectorOnMainThread:@selector(addTrivialSource:) withObject:href waitUntilDone:NO];
+}
+
+- (void) refreshSources {
+    [delegate_ performSelectorOnMainThread:@selector(syncData) withObject:nil waitUntilDone:NO];
+}
+
+- (NSArray *) getAllSources {
+    return [[Database sharedInstance] sources];
+}
+
 - (NSArray *) getInstalledPackages {
-    NSArray *packages([[Database sharedInstance] packages]);
+    Database *database([Database sharedInstance]);
+@synchronized (database) {
+    NSArray *packages([database packages]);
     NSMutableArray *installed([NSMutableArray arrayWithCapacity:1024]);
     for (Package *package in packages)
-        if ([package installed] != nil)
+        if (![package uninstalled])
             [installed addObject:package];
     return installed;
-}
+} }
 
 - (Package *) getPackageById:(NSString *)id {
-    Package *package([[Database sharedInstance] packageWithName:id]);
-    [package parse];
-    return package;
+    if (Package *package = [[Database sharedInstance] packageWithName:id]) {
+        [package parse];
+        return package;
+    } else
+        return (Package *) [NSNull null];
 }
 
 - (NSArray *) statfs:(NSString *)path {
@@ -3891,13 +4259,17 @@ static NSString *Warning_;
 }
 
 - (void) close {
-    [indirect_ close];
+    [indirect_ performSelectorOnMainThread:@selector(close) withObject:nil waitUntilDone:NO];
 }
 
 - (void) installPackages:(NSArray *)packages {
     [delegate_ performSelectorOnMainThread:@selector(installPackages:) withObject:packages waitUntilDone:NO];
 }
 
+- (void) removeButton {
+    [indirect_ removeButton];
+}
+
 - (void) setButtonImage:(NSString *)button withStyle:(NSString *)style toFunction:(id)function {
     [indirect_ setButtonImage:button withStyle:style toFunction:function];
 }
@@ -3906,25 +4278,31 @@ static NSString *Warning_;
     [indirect_ setButtonTitle:button withStyle:style toFunction:function];
 }
 
-- (void) setSpecial:(id)function {
-    [indirect_ setSpecial:function];
-}
+- (void) _setToken:(NSString *)token {
+    Token_ = token;
 
-- (void) setToken:(NSString *)token {
-    if (Token_ != nil)
-        [Token_ release];
-    Token_ = [token retain];
+    if (token == nil)
+        [Metadata_ removeObjectForKey:@"Token"];
+    else
+        [Metadata_ setObject:Token_ forKey:@"Token"];
 
-    [Metadata_ setObject:Token_ forKey:@"Token"];
     Changed_ = true;
 }
 
+- (void) setToken:(NSString *)token {
+    [self performSelectorOnMainThread:@selector(_setToken:) withObject:token waitUntilDone:NO];
+}
+
 - (void) setPopupHook:(id)function {
     [indirect_ setPopupHook:function];
 }
 
+- (void) scrollToBottom:(NSNumber *)animated {
+    [indirect_ performSelectorOnMainThread:@selector(scrollToBottomAnimated:) withObject:animated waitUntilDone:NO];
+}
+
 - (void) setViewportWidth:(float)width {
-    [indirect_ setViewportWidth:width];
+    [indirect_ setViewportWidthOnMainThread:width];
 }
 
 - (NSString *) stringWithFormat:(NSString *)format arguments:(WebScriptObject *)arguments {
@@ -3949,9 +4327,9 @@ static NSString *Warning_;
 
 /* @ Loading... Indicator {{{ */
 @interface CYLoadingIndicator : UIView {
-    UIActivityIndicatorView *spinner_;
-    UILabel *label_;
-    UIView *container_;
+    _H<UIActivityIndicatorView> spinner_;
+    _H<UILabel> label_;
+    _H<UIView> container_;
 }
 
 @property (readonly, nonatomic) UILabel *label;
@@ -3961,8 +4339,8 @@ static NSString *Warning_;
 
 @implementation CYLoadingIndicator
 
-- (id)initWithFrame:(CGRect)frame {
-    if ((self = [super initWithFrame:frame])) {
+- (id) initWithFrame:(CGRect)frame {
+    if ((self = [super initWithFrame:frame]) != nil) {
         container_ = [[[UIView alloc] init] autorelease];
         [container_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin];
 
@@ -4001,67 +4379,34 @@ static NSString *Warning_;
         [spinner_ setFrame:spinrect];
         [label_ setFrame:textrect];
         [self addSubview:container_];
-    }
+    } return self;
+}
 
-    return self;
+- (UILabel *) label {
+    return label_;
 }
 
-- (UILabel *)label { return label_; }
-- (UIActivityIndicatorView *)activityIndicatorView { return spinner_; }
+- (UIActivityIndicatorView *) activityIndicatorView {
+    return spinner_;
+}
 
 @end
 /* }}} */
 /* Emulated Loading Controller {{{ */
-@interface CYEmulatedLoadingController : CYViewController <
-    ProgressDelegate,
-    ConfigurationDelegate
-> {
+@interface CYEmulatedLoadingController : CYViewController {
     _transient Database *database_;
-    CYLoadingIndicator *indicator_;
-    UITabBar *tabbar_;
-    UINavigationBar *navbar_;
+    _H<CYLoadingIndicator> indicator_;
+    _H<UITabBar> tabbar_;
+    _H<UINavigationBar> navbar_;
 }
+
 @end
 
 @implementation CYEmulatedLoadingController
 
-- (void) dealloc {
-    [self releaseSubviews];
-    [database_ setDelegate:nil];
-
-    [super dealloc];
-}
-
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    CYAlertView *sheet([[[CYAlertView alloc]
-        initWithTitle:title
-        buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
-        defaultButtonIndex:0
-    ] autorelease]);
-
-    [sheet setMessage:error];
-    [sheet yieldToPopupAlertAnimated:YES];
-    [sheet dismiss];
-}
-
-- (void) setProgressTitle:(NSString *)title { }
-- (void) setProgressPercent:(float)percent { }
-- (void) startProgress { }
-- (void) addProgressOutput:(NSString *)output { }
-- (bool) isCancelling:(size_t)received { return NO; }
-- (void) setConfigurationData:(NSString *)data { }
-
-- (void) repairWithSelector:(SEL)selector {
-    [[indicator_ label] performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:Elision_, UCLocalize("REPAIRING"), nil] waitUntilDone:YES];
-    [database_ performSelector:selector];
-    sleep(10);
-    [[indicator_ label] performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:Elision_, UCLocalize("LOADING"), nil] waitUntilDone:YES];
-}
-
 - (id) initWithDatabase:(Database *)database {
-    if ((self = [super init])) {
+    if ((self = [super init]) != nil) {
         database_ = database;
-        [database_ setDelegate:self];
     } return self;
 }
 
@@ -4069,29 +4414,24 @@ static NSString *Warning_;
     [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]];
     [[self view] setBackgroundColor:[UIColor pinStripeColor]];
 
-    indicator_ = [[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]];
+    indicator_ = [[[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]] autorelease];
     [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
     [[self view] addSubview:indicator_];
 
-    tabbar_ = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)];
+    tabbar_ = [[[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)] autorelease];
     [tabbar_ setFrame:CGRectMake(0.0f, [[self view] bounds].size.height - [tabbar_ bounds].size.height, [[self view] bounds].size.width, [tabbar_ bounds].size.height)];
     [tabbar_ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth];
     [[self view] addSubview:tabbar_];
 
-    navbar_ = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)];
+    navbar_ = [[[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)] autorelease];
     [navbar_ setFrame:CGRectMake(0.0f, 0.0f, [[self view] bounds].size.width, [navbar_ bounds].size.height)];
     [navbar_ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth];
     [[self view] addSubview:navbar_];
 }
 
 - (void) releaseSubviews {
-    [indicator_ release];
     indicator_ = nil;
-
-    [tabbar_ release];
     tabbar_ = nil;
-
-    [navbar_ release];
     navbar_ = nil;
 }
 
@@ -4171,12 +4511,7 @@ static NSString *Warning_;
 
         WebView *webview([[webview_ _documentView] webView]);
 
-        Package *package([[Database sharedInstance] packageWithName:@"cydia"]);
-
-        NSString *application = package == nil ? @"Cydia" : [NSString
-            stringWithFormat:@"Cydia/%@",
-            [package installed]
-        ];
+        NSString *application([NSString stringWithFormat:@"Cydia/%@", @ Cydia_]);
 
         if (Safari_ != nil)
             application = [NSString stringWithFormat:@"Safari/%@ %@", Safari_, application];
@@ -4192,6 +4527,42 @@ static NSString *Warning_;
 @end
 /* }}} */
 
+// CydiaScript {{{
+@interface NSObject (CydiaScript)
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context;
+@end
+
+@implementation NSObject (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    return self;
+}
+
+@end
+
+@implementation NSArray (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    WebScriptObject *object([context evaluateWebScript:@"[]"]);
+    for (size_t i(0), e([self count]); i != e; ++i)
+        [object setWebScriptValueAtIndex:i value:[[self objectAtIndex:i] Cydia$webScriptObjectInContext:context]];
+    return object;
+}
+
+@end
+
+@implementation NSDictionary (CydiaScript)
+
+- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
+    WebScriptObject *object([context evaluateWebScript:@"({})"]);
+    for (id i in self)
+        [object setValue:[[self objectForKey:i] Cydia$webScriptObjectInContext:context] forKey:i];
+    return object;
+}
+
+@end
+// }}}
+
 /* Confirmation Controller {{{ */
 bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (!iterator.end())
@@ -4216,10 +4587,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @interface ConfirmationController : CYBrowserController {
     _transient Database *database_;
+
     UIAlertView *essential_;
-    NSArray *changes_;
-    NSArray *issues_;
-    NSArray *sizes_;
+
+    NSDictionary *changes_;
+    NSMutableArray *issues_;
+    NSDictionary *sizes_;
+
     BOOL substrate_;
 }
 
@@ -4231,24 +4605,29 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) dealloc {
     [changes_ release];
-    if (issues_ != nil)
-        [issues_ release];
+    [issues_ release];
     [sizes_ release];
+
     if (essential_ != nil)
         [essential_ release];
+
     [super dealloc];
 }
 
+- (void) complete {
+    if (substrate_)
+        RestartSubstrate_ = true;
+    [delegate_ confirmWithNavigationController:[self navigationController]];
+}
+
 - (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
     NSString *context([alert context]);
 
     if ([context isEqualToString:@"remove"]) {
-        if (button == [alert cancelButtonIndex]) {
+        if (button == [alert cancelButtonIndex])
             [self dismissModalViewControllerAnimated:YES];
-        } else if (button == [alert firstOtherButtonIndex]) {
-            if (substrate_)
-                Finish_ = 2;
-            [delegate_ confirmWithNavigationController:[self navigationController]];
+        else if (button == [alert firstOtherButtonIndex]) {
+            [self complete];
         }
 
         [alert dismissWithClickedButtonIndex:-1 animated:YES];
@@ -4272,47 +4651,140 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
     [super webView:view didClearWindowObject:window forFrame:frame];
-    [window setValue:changes_ forKey:@"changes"];
-    [window setValue:issues_ forKey:@"issues"];
-    [window setValue:sizes_ forKey:@"sizes"];
-    [window setValue:self forKey:@"queue"];
+
+    [window setValue:[[NSDictionary dictionaryWithObjectsAndKeys:
+        changes_, @"changes",
+        issues_, @"issues",
+        sizes_, @"sizes",
+        self, @"queue",
+    nil] Cydia$webScriptObjectInContext:window] forKey:@"cydiaConfirm"];
 }
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
 
-        NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *downgrading = [NSMutableArray arrayWithCapacity:16];
-        NSMutableArray *removing = [NSMutableArray arrayWithCapacity:16];
+        NSMutableArray *installs([NSMutableArray arrayWithCapacity:16]);
+        NSMutableArray *reinstalls([NSMutableArray arrayWithCapacity:16]);
+        NSMutableArray *upgrades([NSMutableArray arrayWithCapacity:16]);
+        NSMutableArray *downgrades([NSMutableArray arrayWithCapacity:16]);
+        NSMutableArray *removes([NSMutableArray arrayWithCapacity:16]);
 
         bool remove(false);
 
+        pkgCacheFile &cache([database_ cache]);
+        NSArray *packages([database_ packages]);
         pkgDepCache::Policy *policy([database_ policy]);
 
-        pkgCacheFile &cache([database_ cache]);
-        NSArray *packages = [database_ packages];
+        issues_ = [[NSMutableArray arrayWithCapacity:4] retain];
+
         for (Package *package in packages) {
-            pkgCache::PkgIterator iterator = [package iterator];
+            pkgCache::PkgIterator iterator([package iterator]);
+            NSString *name([package id]);
+
+            if ([package broken]) {
+                NSMutableArray *reasons([NSMutableArray arrayWithCapacity:4]);
+
+                [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                    name, @"package",
+                    reasons, @"reasons",
+                nil]];
+
+                pkgCache::VerIterator ver(cache[iterator].InstVerIter(cache));
+                if (ver.end())
+                    continue;
+
+                for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
+                    pkgCache::DepIterator start;
+                    pkgCache::DepIterator end;
+                    dep.GlobOr(start, end); // ++dep
+
+                    if (!cache->IsImportantDep(end))
+                        continue;
+                    if ((cache[end] & pkgDepCache::DepGInstall) != 0)
+                        continue;
+
+                    NSMutableArray *clauses([NSMutableArray arrayWithCapacity:4]);
+
+                    [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                        [NSString stringWithUTF8String:start.DepType()], @"relationship",
+                        clauses, @"clauses",
+                    nil]];
+
+                    _forever {
+                        NSString *reason, *installed((NSString *) [WebUndefined undefined]);
+
+                        pkgCache::PkgIterator target(start.TargetPkg());
+                        if (target->ProvidesList != 0)
+                            reason = @"missing";
+                        else {
+                            pkgCache::VerIterator ver(cache[target].InstVerIter(cache));
+                            if (!ver.end()) {
+                                reason = @"installed";
+                                installed = [NSString stringWithUTF8String:ver.VerStr()];
+                            } else if (!cache[target].CandidateVerIter(cache).end())
+                                reason = @"uninstalled";
+                            else if (target->ProvidesList == 0)
+                                reason = @"uninstallable";
+                            else
+                                reason = @"virtual";
+                        }
+
+                        NSDictionary *version(start.TargetVer() == 0 ? [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys:
+                            [NSString stringWithUTF8String:start.CompType()], @"operator",
+                            [NSString stringWithUTF8String:start.TargetVer()], @"value",
+                        nil]);
+
+                        [clauses addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                            [NSString stringWithUTF8String:start.TargetPkg().Name()], @"package",
+                            version, @"version",
+                            reason, @"reason",
+                            installed, @"installed",
+                        nil]];
+
+                        // yes, seriously. (wtf?)
+                        if (start == end)
+                            break;
+                        ++start;
+                    }
+                }
+            }
+
             pkgDepCache::StateCache &state(cache[iterator]);
 
-            NSString *name([package name]);
+            static Pcre special_r("^(firmware$|gsc\\.|cy\\+)");
 
             if (state.NewInstall())
-                [installing addObject:name];
+                [installs addObject:name];
             else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
-                [reinstalling addObject:name];
+                [reinstalls addObject:name];
             else if (state.Upgrade())
-                [upgrading addObject:name];
+                [upgrades addObject:name];
             else if (state.Downgrade())
-                [downgrading addObject:name];
-            else if (state.Delete()) {
+                [downgrades addObject:name];
+            else if (!state.Delete())
+                continue;
+            else if (special_r(name))
+                [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                    [NSNull null], @"package",
+                    [NSArray arrayWithObjects:
+                        [NSDictionary dictionaryWithObjectsAndKeys:
+                            @"Conflicts", @"relationship",
+                            [NSArray arrayWithObjects:
+                                [NSDictionary dictionaryWithObjectsAndKeys:
+                                    name, @"package",
+                                    [NSNull null], @"version",
+                                    @"installed", @"reason",
+                                nil],
+                            nil], @"clauses",
+                        nil],
+                    nil], @"reasons",
+                nil]];
+            else {
                 if ([package essential])
                     remove = true;
-                [removing addObject:name];
-            } else continue;
+                [removes addObject:name];
+            }
 
             substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
             substrate_ |= DepSubstrate(iterator.CurrentVer());
@@ -4346,21 +4818,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             [essential_ setContext:@"unable"];
         }
 
-        changes_ = [[NSArray alloc] initWithObjects:
-            installing,
-            reinstalling,
-            upgrading,
-            downgrading,
-            removing,
+        changes_ = [[NSDictionary alloc] initWithObjectsAndKeys:
+            installs, @"installs",
+            reinstalls, @"reinstalls",
+            upgrades, @"upgrades",
+            downgrades, @"downgrades",
+            removes, @"removes",
         nil];
 
-        issues_ = [database_ issues];
-        if (issues_ != nil)
-            issues_ = [issues_ retain];
-
-        sizes_ = [[NSArray alloc] initWithObjects:
-            SizeString([database_ fetcher].FetchNeeded()),
-            SizeString([database_ fetcher].PartialPresent()),
+        sizes_ = [[NSDictionary alloc] initWithObjectsAndKeys:
+            [NSNumber numberWithInteger:[database_ fetcher].FetchNeeded()], @"downloading",
+            [NSNumber numberWithInteger:[database_ fetcher].PartialPresent()], @"resuming",
         nil];
 
         [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/confirm/", UI_]]];
@@ -4374,9 +4842,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void) applyRightButton {
 #if !AlwaysReload
-    if (issues_ == nil && ![self isLoading])
+- (void) applyRightButton {
+    if ([issues_ count] == 0 && ![self isLoading])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CONFIRM")
             style:UIBarButtonItemStyleDone
@@ -4384,11 +4852,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             action:@selector(confirmButtonClicked)
         ] autorelease]];
     else
-        [super applyRightButton];
-#else
-    [[self navigationItem] setRightBarButtonItem:nil];
-#endif
+        [[self navigationItem] setRightBarButtonItem:nil];
 }
+#endif
 
 - (void) cancelButtonClicked {
     [self dismissModalViewControllerAnimated:YES];
@@ -4399,11 +4865,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) confirmButtonClicked {
     if (essential_ != nil)
         [essential_ show];
-    else {
-        if (substrate_)
-            Finish_ = 2;
-        [delegate_ confirmWithNavigationController:[self navigationController]];
-    }
+    else
+        [self complete];
 }
 #endif
 
@@ -4411,195 +4874,173 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* }}} */
 
 /* Progress Data {{{ */
-@interface ProgressData : NSObject {
-    SEL selector_;
-    // XXX: should these really both be _transient?
-    _transient id target_;
-    _transient id object_;
-}
+@interface CydiaProgressData : NSObject {
+    _transient id delegate_;
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
+    bool running_;
+    float progress_;
+
+    _H<NSMutableArray> events_;
+    _H<NSString> title_;
+
+    _H<NSString> status_;
+    _H<NSString> finish_;
+}
 
-- (SEL) selector;
-- (id) target;
-- (id) object;
 @end
 
-@implementation ProgressData
+@implementation CydiaProgressData
+
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"events",
+        @"finish",
+        @"progress",
+        @"running",
+        @"title",
+    nil];
+}
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (id) init {
     if ((self = [super init]) != nil) {
-        selector_ = selector;
-        target_ = target;
-        object_ = object;
+        events_ = [NSMutableArray arrayWithCapacity:32];
     } return self;
 }
 
-- (SEL) selector {
-    return selector_;
+- (void) setDelegate:(id)delegate {
+    delegate_ = delegate;
+}
+
+- (void) setProgress:(float)value {
+    progress_ = value;
+}
+
+- (NSNumber *) progress {
+    return [NSNumber numberWithFloat:progress_];
+}
+
+- (NSArray *) events {
+    return events_;
+}
+
+- (void) removeAllEvents {
+    [events_ removeAllObjects];
+}
+
+- (void) addEvent:(CydiaProgressEvent *)event {
+    [events_ addObject:event];
+}
+
+- (void) setTitle:(NSString *)text {
+    title_ = text;
 }
 
-- (id) target {
-    return target_;
+- (NSString *) title {
+    return title_;
 }
 
-- (id) object {
-    return object_;
+- (void) setFinish:(NSString *)text {
+    finish_ = text;
+}
+
+- (NSString *) finish {
+    return (id) finish_ ?: [NSNull null];
+}
+
+- (void) setRunning:(bool)running {
+    running_ = running;
+}
+
+- (NSNumber *) running {
+    return running_ ? (NSNumber *) kCFBooleanTrue : (NSNumber *) kCFBooleanFalse;
 }
 
 @end
 /* }}} */
 /* Progress Controller {{{ */
-@interface ProgressController : CYViewController <
-    ConfigurationDelegate,
+@interface ProgressController : CYBrowserController <
     ProgressDelegate
 > {
     _transient Database *database_;
-    UIProgressBar *progress_;
-    UITextView *output_;
-    UITextLabel *status_;
-    UIPushButton *close_;
-    BOOL running_;
-    SHA1SumValue springlist_;
-    SHA1SumValue notifyconf_;
-    NSString *title_;
+    _H<CydiaProgressData> progress_;
+    unsigned cancel_;
 }
 
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate;
 
-- (void) _retachThread;
-- (void) _detachNewThreadData:(ProgressData *)data;
-- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title;
+- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title;
 
-- (BOOL) isRunning;
+- (void) setTitle:(NSString *)title;
+- (void) setCancellable:(bool)cancellable;
 
 @end
 
-@protocol ProgressControllerDelegate
-- (void) progressControllerIsComplete:(ProgressController *)sender;
-@end
-
 @implementation ProgressController
 
 - (void) dealloc {
-    [database_ setDelegate:nil];
-    [progress_ release];
-    [output_ release];
-    [status_ release];
-    [close_ release];
-    if (title_ != nil)
-        [title_ release];
+    [database_ setProgressDelegate:nil];
+    [progress_ setDelegate:nil];
     [super dealloc];
 }
 
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate {
     if ((self = [super init]) != nil) {
         database_ = database;
-        [database_ setDelegate:self];
         delegate_ = delegate;
 
-        [[self view] setBackgroundColor:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]];
-
-        progress_ = [[UIProgressBar alloc] init];
-        [progress_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [progress_ setStyle:0];
+        [database_ setProgressDelegate:self];
 
-        status_ = [[UITextLabel alloc] init];
-        [status_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [status_ setColor:[UIColor whiteColor]];
-        [status_ setBackgroundColor:[UIColor clearColor]];
-        [status_ setCentersHorizontally:YES];
-        //[status_ setFont:font];
-
-        output_ = [[UITextView alloc] init];
-        [output_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        //[output_ setTextFont:@"Courier New"];
-        [output_ setFont:[[output_ font] fontWithSize:12]];
-        [output_ setTextColor:[UIColor whiteColor]];
-        [output_ setBackgroundColor:[UIColor clearColor]];
-        [output_ setMarginTop:0];
-        [output_ setAllowsRubberBanding:YES];
-        [output_ setEditable:NO];
-        [[self view] addSubview:output_];
-
-        close_ = [[UIPushButton alloc] init];
-        [close_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [close_ setAutosizesToFit:NO];
-        [close_ setDrawsShadow:YES];
-        [close_ setStretchBackground:YES];
-        [close_ setEnabled:YES];
-        [close_ setTitleFont:[UIFont boldSystemFontOfSize:22]];
-        [close_ addTarget:self action:@selector(closeButtonPushed) forEvents:UIControlEventTouchUpInside];
-        [close_ setBackground:[UIImage applicationImageNamed:@"green-up.png"] forState:0];
-        [close_ setBackground:[UIImage applicationImageNamed:@"green-dn.png"] forState:1];
+        progress_ = [[[CydiaProgressData alloc] init] autorelease];
+        [progress_ setDelegate:self];
     } return self;
 }
 
-- (void) positionViews {
-    CGRect bounds = [[self view] bounds];
-    CGSize prgsize = [UIProgressBar defaultSize];
-
-    CGRect prgrect = {{
-        (bounds.size.width - prgsize.width) / 2,
-        bounds.size.height - prgsize.height - 20
-    }, prgsize};
+- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+    [super webView:view didClearWindowObject:window forFrame:frame];
+    [window setValue:progress_ forKey:@"cydiaProgress"];
+}
 
-    float closewidth = std::min(bounds.size.width - 20, 300.0f);
+- (void) updateProgress {
+    [self dispatchEvent:@"CydiaProgressUpdate"];
+}
 
-    [progress_ setFrame:prgrect];
-    [status_ setFrame:CGRectMake(
-        10,
-        bounds.size.height - prgsize.height - 50,
-        bounds.size.width - 20,
-        24
-    )];
-    [output_ setFrame:CGRectMake(
-        10,
-        20,
-        bounds.size.width - 20,
-        bounds.size.height - 96
-    )];
-    [close_ setFrame:CGRectMake(
-        (bounds.size.width - closewidth) / 2,
-        bounds.size.height - prgsize.height - 50,
-        closewidth,
-        32 + prgsize.height
-    )];
+- (void) updateCancel {
+    [[self navigationItem] setLeftBarButtonItem:(cancel_ == 1 ? [[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("CANCEL")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(cancel)
+    ] autorelease] : nil)];
 }
 
 - (void) viewWillAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [[self navigationItem] setHidesBackButton:YES];
-    [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack];
+    if (![self hasLoaded]) {
+        [scroller_ setBackgroundColor:[UIColor blackColor]];
+        [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/progress/", UI_]]];
+    }
 
-    [self positionViews];
-}
+    [super viewDidAppear:animated];
 
-- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
-    [self positionViews];
-}
+    [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack];
 
-- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
-    NSString *context([alert context]);
+    [[self navigationItem] setHidesBackButton:YES];
 
-    if ([context isEqualToString:@"conffile"]) {
-        FILE *input = [database_ input];
-        if (button == [alert cancelButtonIndex])
-            fprintf(input, "N\n");
-        else if (button == [alert firstOtherButtonIndex])
-            fprintf(input, "Y\n");
-        fflush(input);
-    }
+    [self updateCancel];
 }
 
-- (void) closeButtonPushed {
-    running_ = NO;
-
+- (void) close {
     UpdateExternalStatus(0);
 
     switch (Finish_) {
         case 0:
-            [self dismissModalViewControllerAnimated:YES];
         break;
 
         case 1:
@@ -4631,19 +5072,32 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 reboot2(RB_AUTOBOOT);
         break;
     }
+
+    [super close];
 }
 
-- (void) _retachThread {
-    [[self navigationItem] setTitle:UCLocalize("COMPLETE")];
+- (void) setTitle:(NSString *)title {
+    [progress_ setTitle:title];
+    [self updateProgress];
+}
 
-    [[self view] addSubview:close_];
-    [progress_ removeFromSuperview];
-    [status_ removeFromSuperview];
+- (UIBarButtonItem *) rightButton {
+    return [[progress_ running] boolValue] ? nil : [[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("CLOSE")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(close)
+    ] autorelease];
+}
 
-    [database_ popErrorWithTitle:title_];
-    [delegate_ progressControllerIsComplete:self];
+- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title {
+    UpdateExternalStatus(1);
 
-    if (Finish_ < 4) {
+    [progress_ setRunning:true];
+    [self setTitle:title];
+    // implicit updateProgress
+
+    SHA1SumValue notifyconf; {
         FileFd file;
         if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
             _error->Discard();
@@ -4651,12 +5105,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             MMap mmap(file, MMap::ReadOnly);
             SHA1Summation sha1;
             sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
-            if (!(notifyconf_ == sha1.Result()))
-                Finish_ = 4;
+            notifyconf = sha1.Result();
         }
     }
 
-    if (Finish_ < 3) {
+    SHA1SumValue springlist; {
         FileFd file;
         if (!file.Open(SpringBoard_, FileFd::ReadOnly))
             _error->Discard();
@@ -4664,57 +5117,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             MMap mmap(file, MMap::ReadOnly);
             SHA1Summation sha1;
             sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
-            if (!(springlist_ == sha1.Result()))
-                Finish_ = 3;
+            springlist = sha1.Result();
         }
     }
 
-    switch (Finish_) {
-        case 0: [close_ setTitle:UCLocalize("RETURN_TO_CYDIA")]; break; /* XXX: Maybe UCLocalize("DONE")? */
-        case 1: [close_ setTitle:UCLocalize("CLOSE_CYDIA")]; break;
-        case 2: [close_ setTitle:UCLocalize("RESTART_SPRINGBOARD")]; break;
-        case 3: [close_ setTitle:UCLocalize("RELOAD_SPRINGBOARD")]; break;
-        case 4: [close_ setTitle:UCLocalize("REBOOT_DEVICE")]; break;
+    if (invocation != nil) {
+        [invocation yieldToSelector:@selector(invoke)];
+        [self setTitle:@"COMPLETE"];
     }
 
-    _trace();
-    system("su -c /usr/bin/uicache mobile");
-    _trace();
-
-    UpdateExternalStatus(Finish_ == 0 ? 2 : 0);
-
-    [delegate_ setStatusBarShowsProgress:NO];
-}
-
-- (void) _detachNewThreadData:(ProgressData *)data { _pooled
-    [[data target] performSelector:[data selector] withObject:[data object]];
-    [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES];
-}
-
-- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title {
-    UpdateExternalStatus(1);
-
-    if (title_ != nil)
-        [title_ release];
-    if (title == nil)
-        title_ = nil;
-    else
-        title_ = [title retain];
-
-    [[self navigationItem] setTitle:title_];
-
-    [status_ setText:nil];
-    [output_ setText:@""];
-    [progress_ setProgress:0];
-
-    [close_ removeFromSuperview];
-    [[self view] addSubview:progress_];
-    [[self view] addSubview:status_];
-
-    [delegate_ setStatusBarShowsProgress:YES];
-    running_ = YES;
-
-    {
+    if (Finish_ < 4) {
         FileFd file;
         if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
             _error->Discard();
@@ -4722,11 +5134,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             MMap mmap(file, MMap::ReadOnly);
             SHA1Summation sha1;
             sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
-            notifyconf_ = sha1.Result();
+            if (!(notifyconf == sha1.Result()))
+                Finish_ = 4;
         }
     }
 
-    {
+    if (Finish_ < 3) {
         FileFd file;
         if (!file.Open(SpringBoard_, FileFd::ReadOnly))
             _error->Discard();
@@ -4734,134 +5147,71 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             MMap mmap(file, MMap::ReadOnly);
             SHA1Summation sha1;
             sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
-            springlist_ = sha1.Result();
+            if (!(springlist == sha1.Result()))
+                Finish_ = 3;
         }
     }
 
-    [NSThread
-        detachNewThreadSelector:@selector(_detachNewThreadData:)
-        toTarget:self
-        withObject:[[[ProgressData alloc]
-            initWithSelector:selector
-            target:target
-            object:object
-        ] autorelease]
-    ];
-}
+    if (Finish_ < 2) {
+        if (RestartSubstrate_)
+            Finish_ = 2;
+    }
 
-- (void) repairWithSelector:(SEL)selector {
-    [self
-        detachNewThreadSelector:selector
-        toTarget:database_
-        withObject:nil
-        title:UCLocalize("REPAIRING")
-    ];
-}
+    RestartSubstrate_ = false;
 
-- (void) setConfigurationData:(NSString *)data {
-    [self
-        performSelectorOnMainThread:@selector(_setConfigurationData:)
-        withObject:data
-        waitUntilDone:YES
-    ];
-}
+    switch (Finish_) {
+        case 0: [progress_ setFinish:UCLocalize("RETURN_TO_CYDIA")]; break; /* XXX: Maybe UCLocalize("DONE")? */
+        case 1: [progress_ setFinish:UCLocalize("CLOSE_CYDIA")]; break;
+        case 2: [progress_ setFinish:UCLocalize("RESTART_SPRINGBOARD")]; break;
+        case 3: [progress_ setFinish:UCLocalize("RELOAD_SPRINGBOARD")]; break;
+        case 4: [progress_ setFinish:UCLocalize("REBOOT_DEVICE")]; break;
+    }
 
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    CYAlertView *sheet([[[CYAlertView alloc]
-        initWithTitle:title
-        buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
-        defaultButtonIndex:0
-    ] autorelease]);
+    _trace();
+    system("su -c /usr/bin/uicache mobile");
+    _trace();
 
-    [sheet setMessage:error];
-    [sheet yieldToPopupAlertAnimated:YES];
-    [sheet dismiss];
-}
+    UpdateExternalStatus(Finish_ == 0 ? 0 : 2);
 
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
-}
+    [progress_ setRunning:false];
+    [self updateProgress];
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
+    [self applyRightButton];
 }
 
-- (void) startProgress {
+- (void) addProgressEvent:(CydiaProgressEvent *)event {
+    [progress_ addEvent:event];
+    [self updateProgress];
 }
 
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
+- (bool) isProgressCancelled {
+    return cancel_ == 2;
 }
 
-- (bool) isCancelling:(size_t)received {
-    return false;
+- (void) cancel {
+    cancel_ = 2;
+    [self updateCancel];
 }
 
-- (void) _setConfigurationData:(NSString *)data {
-    static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
-
-    if (!conffile_r(data)) {
-        lprintf("E:invalid conffile\n");
-        return;
-    }
+- (void) setCancellable:(bool)cancellable {
+    unsigned cancel(cancel_);
 
-    NSString *ofile = conffile_r[1];
-    //NSString *nfile = conffile_r[2];
-
-    UIAlertView *alert = [[[UIAlertView alloc]
-        initWithTitle:UCLocalize("CONFIGURATION_UPGRADE")
-        message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
-        delegate:self
-        cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
-        otherButtonTitles:
-            UCLocalize("ACCEPT_NEW_COPY"),
-            // XXX: UCLocalize("SEE_WHAT_CHANGED"),
-        nil
-    ] autorelease];
+    if (!cancellable)
+        cancel_ = 0;
+    else if (cancel_ == 0)
+        cancel_ = 1;
 
-    [alert setContext:@"conffile"];
-    [alert show];
+    if (cancel != cancel_)
+        [self updateCancel];
 }
 
-- (void) _setProgressTitle:(NSString *)title {
-    NSMutableArray *words([[title componentsSeparatedByString:@" "] mutableCopy]);
-    for (size_t i(0), e([words count]); i != e; ++i) {
-        NSString *word([words objectAtIndex:i]);
-        if (Package *package = [database_ packageWithName:word])
-            [words replaceObjectAtIndex:i withObject:[package name]];
-    }
-
-    [status_ setText:[words componentsJoinedByString:@" "]];
+- (void) setProgressCancellable:(NSNumber *)cancellable {
+    [self setCancellable:[cancellable boolValue]];
 }
 
-- (void) _setProgressPercent:(NSNumber *)percent {
+- (void) setProgressPercent:(NSNumber *)percent {
     [progress_ setProgress:[percent floatValue]];
-}
-
-- (void) _addProgressOutput:(NSString *)output {
-    [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]];
-    CGSize size = [output_ contentSize];
-    CGPoint offset = [output_ contentOffset];
-    if (size.height - offset.y < [output_ frame].size.height + 20.f) {
-        CGRect rect = {{0, size.height-1}, {size.width, 1}};
-        [output_ scrollRectToVisible:rect animated:YES];
-    }
-}
-
-- (BOOL) isRunning {
-    return running_;
+    [self updateProgress];
 }
 
 @end
@@ -5564,12 +5914,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         database_ = database;
         buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
         name_ = [[NSString alloc] initWithString:name];
+        [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/package/#!/%@", UI_, name_]]];
     } return self;
 }
 
 - (void) reloadData {
-    [super reloadData];
-
     if (package_ != nil)
         [package_ autorelease];
     package_ = [database_ packageWithName:name_];
@@ -5612,7 +5961,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         action:@selector(customButtonClicked)
     ];
 
-    [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/package/#!/%@", UI_, name_]]];
+    [super reloadData];
 }
 
 - (bool) isLoading {
@@ -5986,7 +6335,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if ((self = [super initWithDatabase:database title:title]) != nil) {
         [self setFilter:filter];
         [self setObject:object];
-        [self reloadData];
     } return self;
 }
 
@@ -5996,6 +6344,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* Home Controller {{{ */
 @interface HomeController : CYBrowserController {
 }
+
 @end
 
 @implementation HomeController
@@ -6069,6 +6418,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) queueStatusDidChange;
+
 @end
 
 @implementation ManageController
@@ -6183,14 +6533,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [prompt_ setFrame:prmrect];
 }
 
-- (void)setFrame:(CGRect)frame {
+- (void) setFrame:(CGRect)frame {
     [super setFrame:frame];
-
     [self positionViews];
 }
 
 - (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
-    if ((self = [super initWithFrame:frame])) {
+    if ((self = [super initWithFrame:frame]) != nil) {
         [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
 
         [self setBarStyle:UIBarStyleBlack];
@@ -6227,18 +6576,20 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void) cancel {
-    [cancel_ removeFromSuperview];
+- (void) setCancellable:(bool)cancellable {
+    if (cancellable)
+        [self addSubview:cancel_];
+    else
+        [cancel_ removeFromSuperview];
 }
 
 - (void) start {
     [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
     [progress_ setProgress:0];
-    [self addSubview:cancel_];
 }
 
 - (void) stop {
-    [cancel_ removeFromSuperview];
+    [self setCancellable:NO];
 }
 
 - (void) setPrompt:(NSString *)prompt {
@@ -6410,52 +6761,22 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return updating_;
 }
 
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]];
-}
-
-- (void) startProgress {
-}
-
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
+- (void) addProgressEvent:(CydiaProgressEvent *)event {
+    [refreshbar_ setPrompt:[event compoundMessage]];
 }
 
-- (bool) isCancelling:(size_t)received {
+- (bool) isProgressCancelled {
     return !updating_;
 }
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
-}
-
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
-}
-
-- (void) _setProgressTitle:(NSString *)title {
-    [refreshbar_ setPrompt:title];
+- (void) setProgressCancellable:(NSNumber *)cancellable {
+    [refreshbar_ setCancellable:(updating_ && [cancellable boolValue])];
 }
 
-- (void) _setProgressPercent:(NSNumber *)percent {
+- (void) setProgressPercent:(NSNumber *)percent {
     [refreshbar_ setProgress:[percent floatValue]];
 }
 
-- (void) _addProgressOutput:(NSString *)output {
-}
-
 - (void) setUpdateDelegate:(id)delegate {
     updatedelegate_ = delegate;
 }
@@ -6629,10 +6950,14 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     NSURL *url([request URL]);
     if (url == nil)
         return NO;
+
     NSString *scheme([[url scheme] lowercaseString]);
-    if (scheme == nil || ![scheme isEqualToString:@"cydia"])
-        return NO;
-    return YES;
+    if (scheme != nil && [scheme isEqualToString:@"cydia"])
+        return YES;
+    if ([[url absoluteString] hasPrefix:@"about:cydia-"])
+        return YES;
+
+    return NO;
 }
 
 + (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
@@ -6659,8 +6984,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     NSURL *url([request URL]);
     NSString *href([url absoluteString]);
+    NSString *scheme([[url scheme] lowercaseString]);
+
+    NSString *path;
+
+    if ([scheme isEqualToString:@"cydia"])
+        path = [href substringFromIndex:8];
+    else if ([scheme isEqualToString:@"about"])
+        path = [href substringFromIndex:12];
+    else _assert(false);
 
-    NSString *path([href substringFromIndex:8]);
     NSRange slash([path rangeOfString:@"/"]);
 
     NSString *command;
@@ -6946,8 +7279,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _trace();
 }
 
-- (void)editButtonClicked {
-    [self setEditing:!editing_];
+- (void) editButtonClicked {
+    [self setEditing:(!editing_)];
 }
 
 @end
@@ -7090,6 +7423,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
+// this mostly works because reloadData (below) is @synchronized (database_)
+// XXX: that said, I've been running into problems with NSRangeExceptions :(
 - (void) _reloadPackages:(NSArray *)packages {
     CFRelease(packages_);
     packages_ = CFArrayCreateMutable(kCFAllocatorDefault, [packages count], NULL);
@@ -7257,7 +7592,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void)viewDidAppear:(BOOL)animated {
+- (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
 
     if (!searchloaded_) {
@@ -7329,22 +7664,29 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (package_ == nil)
         return 0;
 
-    return 1;
+    if ([package_ installed] == nil)
+        return 1;
+    else
+        return 2;
 }
 
 - (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
     if (package_ == nil)
         return 0;
 
-    return 2;
+    // both sections contain just one item right now.
+    return 1;
 }
 
 - (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
-    return UCLocalize("CHANGE_PACKAGE_SETTINGS");
+    return nil;
 }
 
 - (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
-    return UCLocalize("SHOW_ALL_CHANGES_EX");
+    if (section == 0)
+        return UCLocalize("SHOW_ALL_CHANGES_EX");
+    else
+        return UCLocalize("IGNORE_UPGRADES_EX");
 }
 
 - (void) onSubscribed:(id)control {
@@ -7355,15 +7697,50 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         [delegate_ updateData];
 }
 
+- (void) _updateIgnored {
+    const char *package([name_ UTF8String]);
+    bool on([ignoredSwitch_ isOn]);
+
+    pid_t pid(ExecFork());
+    if (pid == 0) {
+        FILE *dpkg(popen("dpkg --set-selections", "w"));
+        fwrite(package, strlen(package), 1, dpkg);
+
+        if (on)
+            fwrite(" hold\n", 6, 1, dpkg);
+        else
+            fwrite(" install\n", 9, 1, dpkg);
+
+        pclose(dpkg);
+
+        exit(0);
+        _assert(false);
+    }
+
+    _forever {
+        int status;
+        int result(waitpid(pid, &status, 0));
+
+        if (result != -1) {
+            _assert(result == pid);
+            break;
+        }
+    }
+}
+
 - (void) onIgnored:(id)control {
-    // TODO: set Held state - possibly call out to dpkg, etc.
+    NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(_updateIgnored)]]);
+    [invocation setTarget:self];
+    [invocation setSelector:@selector(_updateIgnored)];
+
+    [delegate_ reloadDataWithInvocation:invocation];
 }
 
 - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     if (package_ == nil)
         return nil;
 
-    switch ([indexPath row]) {
+    switch ([indexPath section]) {
         case 0: return subscribedCell_;
         case 1: return ignoredCell_;
 
@@ -7389,8 +7766,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)];
     [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
     [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
-    // Disable this switch, since it only reflects (not modifies) the ignored state.
-    [ignoredSwitch_ setUserInteractionEnabled:NO];
 
     subscribedCell_ = [[UITableViewCell alloc] init];
     [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
@@ -7401,8 +7776,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
     [ignoredCell_ setAccessoryView:ignoredSwitch_];
     [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
-    // FIXME: Ignored state is not saved.
-    [ignoredCell_ setUserInteractionEnabled:NO];
 }
 
 - (void) viewDidLoad {
@@ -7427,7 +7800,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithDatabase:(Database *)database package:(NSString *)package {
-    if ((self = [super init])) {
+    if ((self = [super init]) != nil) {
         database_ = database;
         name_ = [package retain];
     } return self;
@@ -7439,11 +7812,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     if (package_ != nil)
         [package_ autorelease];
     package_ = [database_ packageWithName:name_];
+
     if (package_ != nil) {
-        [package_ retain];
+        package_ = [package_ retain];
         [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO];
         [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO];
-    }
+    } // XXX: what now, G?
 
     [table_ reloadData];
 }
@@ -7611,7 +7985,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* }}} */
 /* Source Controller {{{ */
 @interface SourceController : FilteredPackageListController {
-    Source *source_;
+    _transient Source *source_;
+    NSString *key_;
 }
 
 - (id) initWithDatabase:(Database *)database source:(Source *)source;
@@ -7625,12 +8000,22 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithDatabase:(Database *)database source:(Source *)source {
-    source_ = source;
-
     if ((self = [super initWithDatabase:database title:[source label] filter:@selector(isVisibleInSource:) with:source]) != nil) {
+        source_ = source;
+        key_ = [[source key] retain];
     } return self;
 }
 
+- (void) reloadData {
+    source_ = [database_ sourceWithKey:key_];
+    [key_ release];
+    key_ = [[source_ key] retain];
+    [self setObject:source_];
+    [[self navigationItem] setTitle:[source_ label]];
+
+    [super reloadData];
+}
+
 @end
 /* }}} */
 /* Sources Controller {{{ */
@@ -7755,24 +8140,19 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[self navigationController] pushViewController:controller animated:YES];
 }
 
-- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+- (BOOL) tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
     Source *source = [self sourceAtIndexPath:indexPath];
     return [source record] != nil;
 }
 
-- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
+- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
     Source *source = [self sourceAtIndexPath:indexPath];
     [Sources_ removeObjectForKey:[source key]];
     [delegate_ syncData];
 }
 
 - (void) complete {
-    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
-        @"deb", @"Type",
-        href_, @"URI",
-        @"./", @"Distribution",
-    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
-
+    [delegate_ addTrivialSource:href_];
     [delegate_ syncData];
 }
 
@@ -7859,9 +8239,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             [alert show];
         }
 
-        [delegate_ setStatusBarShowsProgress:NO];
-        [delegate_ removeProgressHUD:hud_];
+        [delegate_ releaseNetworkActivityIndicator];
 
+        [delegate_ removeProgressHUD:hud_];
         [hud_ autorelease];
         hud_ = nil;
 
@@ -7914,7 +8294,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
 }
 
-- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
+- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
     NSString *context([alert context]);
 
     if ([context isEqualToString:@"source"]) {
@@ -7940,6 +8320,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 // XXX: this is stupid
                 hud_ = [[delegate_ addProgressHUD] retain];
                 [hud_ setText:UCLocalize("VERIFYING_URL")];
+                [delegate_ retainNetworkActivityIndicator];
             } break;
 
             case 0:
@@ -8004,7 +8385,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super reloadData];
 
     pkgSourceList list;
-    if (!list.ReadMainList())
+    if ([database_ popErrorWithTitle:UCLocalize("SOURCES") forOperation:list.ReadMainList()])
         return;
 
     [sources_ removeAllObjects];
@@ -8167,7 +8548,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate {
-    if ((self = [super init])) {
+    if ((self = [super init]) != nil) {
         database_ = database;
         roledelegate_ = delegate;
     } return self;
@@ -8259,7 +8640,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return 0; // :(
 }
 
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     return nil; // This method is required by the protocol.
 }
 
@@ -8297,6 +8678,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UILabel *status_;
     UILabel *caption_;
 }
+
 @end
 
 @implementation StashController
@@ -8367,7 +8749,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @interface Cydia : UIApplication <
     ConfirmationControllerDelegate,
-    ProgressControllerDelegate,
+    DatabaseDelegate,
     CydiaDelegate,
     UINavigationControllerDelegate,
     UITabBarControllerDelegate
@@ -8376,6 +8758,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     UIWindow *window_;
     CYTabBarController *tabbar_;
+    CYEmulatedLoadingController *emulated_;
 
     NSMutableArray *essential_;
     NSMutableArray *broken_;
@@ -8484,11 +8867,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [[navigation tabBarItem] setBadgeValue:(Queuing_ ? UCLocalize("Q_D") : nil)];
 }
 
-- (void) _refreshIfPossible {
+- (void) _refreshIfPossible:(NSDate *)update {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
     bool recently = false;
-    NSDate *update([Metadata_ objectForKey:@"LastUpdate"]);
     if (update != nil) {
         NSTimeInterval interval([update timeIntervalSinceNow]);
         if (interval <= 0 && interval > -(15*60))
@@ -8536,14 +8918,14 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) refreshIfPossible {
-    [NSThread detachNewThreadSelector:@selector(_refreshIfPossible) toTarget:self withObject:nil];
+    [NSThread detachNewThreadSelector:@selector(_refreshIfPossible:) toTarget:self withObject:[Metadata_ objectForKey:@"LastUpdate"]];
 }
 
-- (void) _reloadData {
+- (void) _reloadDataWithInvocation:(NSInvocation *)invocation {
     UIProgressHUD *hud(loaded_ ? [self addProgressHUD] : nil);
     [hud setText:UCLocalize("RELOADING_DATA")];
 
-    [database_ yieldToSelector:@selector(reloadData) withObject:nil];
+    [database_ yieldToSelector:@selector(reloadDataWithInvocation:) withObject:invocation];
 
     if (hud != nil)
         [self removeProgressHUD:hud];
@@ -8593,7 +8975,70 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [database_ update];
 }
 
+- (void) complete {
+    @synchronized (self) {
+        [self _reloadDataWithInvocation:nil];
+    }
+}
+
+- (void) disemulate {
+    if (emulated_ == nil)
+        return;
+
+    [window_ addSubview:[tabbar_ view]];
+    [[emulated_ view] removeFromSuperview];
+    [emulated_ release];
+    emulated_ = nil;
+    [window_ setUserInteractionEnabled:YES];
+}
+
+- (void) presentModalViewController:(UIViewController *)controller force:(BOOL)force {
+    UINavigationController *navigation([[[CYNavigationController alloc] initWithRootViewController:controller] autorelease]);
+    if (IsWildcat_)
+        [navigation setModalPresentationStyle:UIModalPresentationFormSheet];
+
+    UIViewController *parent;
+    if (emulated_ == nil)
+        parent = tabbar_;
+    else if (!force)
+        parent = emulated_;
+    else {
+        [self disemulate];
+        parent = tabbar_;
+    }
+
+    [parent presentModalViewController:navigation animated:YES];
+}
+
+- (ProgressController *) invokeNewProgress:(NSInvocation *)invocation forController:(UINavigationController *)navigation withTitle:(NSString *)title {
+    ProgressController *progress([[[ProgressController alloc] initWithDatabase:database_ delegate:self] autorelease]);
+
+    if (navigation != nil)
+        [navigation pushViewController:progress animated:YES];
+    else
+        [self presentModalViewController:progress force:YES];
+
+    [progress invoke:invocation withTitle:title];
+    return progress;
+}
+
+- (void) detachNewProgressSelector:(SEL)selector toTarget:(id)target forController:(UINavigationController *)navigation title:(NSString *)title {
+    [self invokeNewProgress:[NSInvocation invocationWithSelector:selector forTarget:target] forController:navigation withTitle:title];
+}
+
+- (void) repairWithInvocation:(NSInvocation *)invocation {
+    _trace();
+    [self invokeNewProgress:invocation forController:nil withTitle:UCLocalize("REPAIRING")];
+    _trace();
+}
+
+- (void) repairWithSelector:(SEL)selector {
+    [self performSelectorOnMainThread:@selector(repairWithInvocation:) withObject:[NSInvocation invocationWithSelector:selector forTarget:database_] waitUntilDone:YES];
+}
+
 - (void) syncData {
+    [self _saveConfig];
+
     FILE *file(fopen("/etc/apt/sources.list.d/cydia.list", "w"));
     _assert(file != NULL);
 
@@ -8609,28 +9054,31 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     fclose(file);
 
-    [self _saveConfig];
+    [self detachNewProgressSelector:@selector(update_) toTarget:self forController:nil title:UCLocalize("UPDATING_SOURCES")];
 
-    ProgressController *progress = [[[ProgressController alloc] initWithDatabase:database_ delegate:self] autorelease];
-    CYNavigationController *navigation = [[[CYNavigationController alloc] initWithRootViewController:progress] autorelease];
-    if (IsWildcat_)
-        [navigation setModalPresentationStyle:UIModalPresentationFormSheet];
-    [tabbar_ presentModalViewController:navigation animated:YES];
+    [self complete];
+}
 
-    [progress
-        detachNewThreadSelector:@selector(update_)
-        toTarget:self
-        withObject:nil
-        title:UCLocalize("UPDATING_SOURCES")
-    ];
+- (void) addTrivialSource:(NSString *)href {
+    [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
+        @"deb", @"Type",
+        href, @"URI",
+        @"./", @"Distribution",
+    nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href]];
+
+    Changed_ = true;
 }
 
-- (void) reloadData {
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation {
     @synchronized (self) {
-        [self _reloadData];
+        [self _reloadDataWithInvocation:invocation];
     }
 }
 
+- (void) reloadData {
+    [self reloadDataWithInvocation:nil];
+}
+
 - (void) resolve {
     pkgProblemResolver *resolver = [database_ resolver];
 
@@ -8640,6 +9088,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (bool) perform {
+    // XXX: this is a really crappy way of doing this.
+    // like, seriously: this state machine is still broken, and cancelling this here doesn't really /fix/ that.
+    // for one, the user can still /start/ a reloading data event while they have a queue, which is stupid
+    // for two, this just means there is a race condition between the refresh completing and the confirmation controller appearing.
+    if ([tabbar_ updating])
+        [tabbar_ cancelUpdate];
+
     if (![database_ prepare])
         return false;
 
@@ -8702,37 +9157,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     }
 }
 
-- (void) complete {
-    @synchronized (self) {
-        [self _reloadData];
-    }
-}
-
 - (void) confirmWithNavigationController:(UINavigationController *)navigation {
     Queuing_ = false;
-
-    ProgressController *progress = [[[ProgressController alloc] initWithDatabase:database_ delegate:self] autorelease];
-
-    if (navigation != nil) {
-        [navigation pushViewController:progress animated:YES];
-    } else {
-        navigation = [[[CYNavigationController alloc] initWithRootViewController:progress] autorelease];
-        if (IsWildcat_)
-            [navigation setModalPresentationStyle:UIModalPresentationFormSheet];
-        [tabbar_ presentModalViewController:navigation animated:YES];
-    }
-
-    [progress
-        detachNewThreadSelector:@selector(perform)
-        toTarget:database_
-        withObject:nil
-        title:UCLocalize("RUNNING")
-    ];
-
     ++locked_;
-}
-
-- (void) progressControllerIsComplete:(ProgressController *)progress {
+    [self detachNewProgressSelector:@selector(perform) toTarget:database_ forController:navigation title:UCLocalize("RUNNING")];
     --locked_;
     [self complete];
 }
@@ -8748,11 +9176,20 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) retainNetworkActivityIndicator {
     if (activity_++ == 0)
         [self setNetworkActivityIndicatorVisible:YES];
+
+#if TraceLogging
+    NSLog(@"retainNetworkActivityIndicator->%d", activity_);
+#endif
 }
 
 - (void) releaseNetworkActivityIndicator {
     if (--activity_ == 0)
         [self setNetworkActivityIndicatorVisible:NO];
+
+#if TraceLogging
+    NSLog(@"releaseNetworkActivityIndicator->%d", activity_);
+#endif
+
 }
 
 - (void) cancelAndClear:(bool)clear {
@@ -8771,7 +9208,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
     NSString *context([alert context]);
 
-    if ([context isEqualToString:@"fixhalf"]) {
+    if ([context isEqualToString:@"conffile"]) {
+        FILE *input = [database_ input];
+        if (button == [alert cancelButtonIndex])
+            fprintf(input, "N\n");
+        else if (button == [alert firstOtherButtonIndex])
+            fprintf(input, "Y\n");
+        fflush(input);
+
+        [alert dismissWithClickedButtonIndex:-1 animated:YES];
+    } else if ([context isEqualToString:@"fixhalf"]) {
         if (button == [alert cancelButtonIndex]) {
             @synchronized (self) {
                 for (Package *broken in broken_) {
@@ -8878,7 +9324,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return [[[CYPackageController alloc] initWithDatabase:database_ forPackage:name] autorelease];
 }
 
-- (CYViewController *) pageForURL:(NSURL *)url {
+- (CYViewController *) pageForURL:(NSURL *)url forExternal:(BOOL)external {
     NSString *scheme([[url scheme] lowercaseString]);
     if ([[url absoluteString] length] <= [scheme length] + 3)
         return nil;
@@ -8899,7 +9345,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         // This kind of URL can contain slashes in the argument, so we can't parse them below.
         NSString *destination = [[url absoluteString] substringFromIndex:([scheme length] + [@"://" length] + [base length] + [@"/" length])];
         controller = [[[CYBrowserController alloc] initWithURL:[NSURL URLWithString:destination]] autorelease];
-    } else if ([components count] == 1) {
+    } else if (!external && [components count] == 1) {
         if ([base isEqualToString:@"manage"]) {
             controller = [[[ManageController alloc] init] autorelease];
         }
@@ -8934,37 +9380,32 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             controller = [self pageForPackage:argument];
         }
 
-        if ([base isEqualToString:@"search"]) {
+        if (!external && [base isEqualToString:@"search"]) {
             controller = [[[SearchController alloc] initWithDatabase:database_] autorelease];
             [(SearchController *)controller setSearchTerm:argument];
         }
 
-        if ([base isEqualToString:@"sections"]) {
+        if (!external && [base isEqualToString:@"sections"]) {
             if ([argument isEqualToString:@"all"])
                 argument = nil;
             controller = [[[SectionController alloc] initWithDatabase:database_ section:argument] autorelease];
         }
 
-        if ([base isEqualToString:@"sources"]) {
+        if (!external && [base isEqualToString:@"sources"]) {
             if ([argument isEqualToString:@"add"]) {
                 controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
                 [(SourcesController *)controller showAddSourcePrompt];
             } else {
-                NSArray *sources = [database_ sources];
-                for (Source *source in sources) {
-                    if ([[source name] caseInsensitiveCompare:argument] == NSOrderedSame) {
-                        controller = [[[SourceController alloc] initWithDatabase:database_ source:source] autorelease];
-                        break;
-                    }
-                }
+                Source *source = [database_ sourceWithKey:argument];
+                controller = [[[SourceController alloc] initWithDatabase:database_ source:source] autorelease];
             }
         }
 
-        if ([base isEqualToString:@"launch"]) {
+        if (!external && [base isEqualToString:@"launch"]) {
             [self launchApplicationWithIdentifier:argument suspended:NO];
             return nil;
         }
-    } else if ([components count] == 3) {
+    } else if (!external && [components count] == 3) {
         NSString *arg1 = [components objectAtIndex:1];
         NSString *arg2 = [components objectAtIndex:2];
 
@@ -8984,8 +9425,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     return controller;
 }
 
-- (BOOL) openCydiaURL:(NSURL *)url {
-    CYViewController *page([self pageForURL:url]);
+- (BOOL) openCydiaURL:(NSURL *)url forExternal:(BOOL)external {
+    CYViewController *page([self pageForURL:url forExternal:external]);
 
     if (page != nil) {
         CYNavigationController *nav = [[[CYNavigationController alloc] init] autorelease];
@@ -9000,7 +9441,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [super applicationOpenURL:url];
 
     if (!loaded_) starturl_ = [url retain];
-    else [self openCydiaURL:url];
+    else [self openCydiaURL:url forExternal:YES];
 }
 
 - (void) applicationWillResignActive:(UIApplication *)application {
@@ -9021,6 +9462,32 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self _saveConfig];
 }
 
+- (void) setConfigurationData:(NSString *)data {
+    static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
+
+    if (!conffile_r(data)) {
+        lprintf("E:invalid conffile\n");
+        return;
+    }
+
+    NSString *ofile = conffile_r[1];
+    //NSString *nfile = conffile_r[2];
+
+    UIAlertView *alert = [[[UIAlertView alloc]
+        initWithTitle:UCLocalize("CONFIGURATION_UPGRADE")
+        message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
+        delegate:self
+        cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
+        otherButtonTitles:
+            UCLocalize("ACCEPT_NEW_COPY"),
+            // XXX: UCLocalize("SEE_WHAT_CHANGED"),
+        nil
+    ] autorelease];
+
+    [alert setContext:@"conffile"];
+    [alert show];
+}
+
 - (void) addStashController {
     ++locked_;
     stash_ = [[StashController alloc] init];
@@ -9037,13 +9504,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [self setIdleTimerDisabled:YES];
 
     [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque];
-    [self setStatusBarShowsProgress:YES];
     UpdateExternalStatus(1);
-
     [self yieldToSelector:@selector(system:) withObject:@"/usr/libexec/cydia/free.sh"];
-
     UpdateExternalStatus(0);
-    [self setStatusBarShowsProgress:NO];
 
     [self removeStashController];
 
@@ -9081,26 +9544,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [tabbar_ setUpdateDelegate:self];
 }
 
-- (CYEmulatedLoadingController *)showEmulatedLoadingControllerInView:(UIView *)view {
-    static CYEmulatedLoadingController *fake = nil;
-
-    if (view != nil) {
-        if (fake == nil)
-            fake = [[CYEmulatedLoadingController alloc] initWithDatabase:database_];
-        [view addSubview:[fake view]];
-    } else {
-        [[fake view] removeFromSuperview];
-        [fake release];
-        fake = nil;
-    }
-
-    return fake;
-}
-
 - (void) applicationDidFinishLaunching:(id)unused {
 _trace();
-    CydiaApp = self;
-
     if ([self respondsToSelector:@selector(setApplicationSupportsShakeToEdit:)])
         [self setApplicationSupportsShakeToEdit:NO];
 
@@ -9149,10 +9594,13 @@ _trace();
     }
 
     database_ = [Database sharedInstance];
+    [database_ setDelegate:self];
 
     [window_ setUserInteractionEnabled:NO];
     [self setupViewControllers];
-    [self showEmulatedLoadingControllerInView:window_];
+
+    emulated_ = [[CYEmulatedLoadingController alloc] initWithDatabase:database_];
+    [window_ addSubview:[emulated_ view]];
 
     [self performSelector:@selector(loadData) withObject:nil afterDelay:0];
 _trace();
@@ -9162,26 +9610,18 @@ _trace();
 _trace();
     if (Role_ == nil) {
         [window_ setUserInteractionEnabled:YES];
-
-        SettingsController *role = [[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease];
-        CYNavigationController *nav = [[[CYNavigationController alloc] initWithRootViewController:role] autorelease];
-        if (IsWildcat_)
-            [nav setModalPresentationStyle:UIModalPresentationFormSheet];
-        [[self showEmulatedLoadingControllerInView:window_] presentModalViewController:nav animated:YES];
-
+        [self presentModalViewController:[[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease] force:NO];
         return;
     } else {
-        if ([[self showEmulatedLoadingControllerInView:window_] modalViewController] != nil)
-            [[self showEmulatedLoadingControllerInView:window_] dismissModalViewControllerAnimated:YES];
+        if ([emulated_ modalViewController] != nil)
+            [emulated_ dismissModalViewControllerAnimated:YES];
         [window_ setUserInteractionEnabled:NO];
     }
 
     [self reloadData];
     PrintTimes();
 
-    [window_ addSubview:[tabbar_ view]];
-    [self showEmulatedLoadingControllerInView:nil];
-    [window_ setUserInteractionEnabled:YES];
+    [self disemulate];
 
     int selectedIndex = 0;
     NSMutableArray *items = nil;
@@ -9227,7 +9667,7 @@ _trace();
         for (unsigned int nav = 0; nav < [stack count]; nav++) {
             NSString *addr = [stack objectAtIndex:nav];
             NSURL *url = [NSURL URLWithString:addr];
-            CYViewController *page = [self pageForURL:url];
+            CYViewController *page = [self pageForURL:url forExternal:NO];
             if (page != nil)
                 [current addObject:page];
         }
@@ -9237,7 +9677,7 @@ _trace();
 
     // (Try to) show the startup URL.
     if (starturl_ != nil) {
-        [self openCydiaURL:starturl_];
+        [self openCydiaURL:starturl_ forExternal:NO];
         [starturl_ release];
         starturl_ = nil;
     }
@@ -9251,6 +9691,22 @@ _trace();
     }
 }
 
+- (void) addProgressEvent:(CydiaProgressEvent *)event forTask:(NSString *)task {
+    id<ProgressDelegate> progress([database_ progressDelegate] ?: [self invokeNewProgress:nil forController:nil withTitle:task]);
+    [progress setTitle:task];
+    [progress addProgressEvent:event];
+}
+
+- (void) addProgressEventForTask:(NSArray *)data {
+    CydiaProgressEvent *event([data objectAtIndex:0]);
+    NSString *task([data count] < 2 ? nil : [data objectAtIndex:1]);
+    [self addProgressEvent:event forTask:task];
+}
+
+- (void) addProgressEventOnMainThread:(CydiaProgressEvent *)event forTask:(NSString *)task {
+    [self performSelectorOnMainThread:@selector(addProgressEventForTask:) withObject:[NSArray arrayWithObjects:event, task, nil] waitUntilDone:YES];
+}
+
 @end
 
 /*IMP alloc_;
@@ -9326,6 +9782,8 @@ MSHook(void, UIApplication$_updateApplicationAccessibility, UIApplication *self,
 int main(int argc, char *argv[]) { _pooled
     _trace();
 
+    UpdateExternalStatus(0);
+
     if (Class $UIDevice = objc_getClass("UIDevice")) {
         UIDevice *device([$UIDevice currentDevice]);
         IsWildcat_ = [device respondsToSelector:@selector(isWildcat)] && [device isWildcat];
@@ -9555,10 +10013,15 @@ int main(int argc, char *argv[]) { _pooled
 
     Finishes_ = [NSArray arrayWithObjects:@"return", @"reopen", @"restart", @"reload", @"reboot", nil];
 
-    if (substrate && access("/Library/MobileSubstrate/DynamicLibraries/SimulatedKeyEvents.dylib", F_OK) == 0)
-        dlopen("/Library/MobileSubstrate/DynamicLibraries/SimulatedKeyEvents.dylib", RTLD_LAZY | RTLD_GLOBAL);
-    if (substrate && access("/Applications/WinterBoard.app/WinterBoard.dylib", F_OK) == 0)
-        dlopen("/Applications/WinterBoard.app/WinterBoard.dylib", RTLD_LAZY | RTLD_GLOBAL);
+#define MobileSubstrate_(name) \
+    if (substrate && access("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", F_OK) == 0) \
+        dlopen("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", RTLD_LAZY | RTLD_GLOBAL);
+
+    MobileSubstrate_(Activator)
+    MobileSubstrate_(libstatusbar)
+    MobileSubstrate_(SimulatedKeyEvents)
+    MobileSubstrate_(WinterBoard)
+
     /*if (substrate && access("/Library/MobileSubstrate/MobileSubstrate.dylib", F_OK) == 0)
         dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib", RTLD_LAZY | RTLD_GLOBAL);*/