]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Do not show ProgressController Close if running.
[cydia.git] / MobileCydia.mm
index da4f6da2ebf0e25808e88e816c7f12a940a5b03a..b6a51d54c1b40ce5a788e292220762cc02577d98 100644 (file)
@@ -288,8 +288,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 +308,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];
@@ -366,7 +368,6 @@ static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive |
 #define TrackResize (0 && !ForRelease)
 #define ManualRefresh (1 && !ForRelease)
 #define ShowInternals (0 && !ForRelease)
-#define IgnoreInstall (0 && !ForRelease)
 #define AlwaysReload (0 && !ForRelease)
 #define TryIndexedCollation (0 && !ForRelease)
 
@@ -564,6 +565,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 {
@@ -872,6 +888,7 @@ class Pcre {
 
 + (Address *) addressWithString:(NSString *)string;
 - (Address *) initWithString:(NSString *)string;
+
 @end
 
 @implementation Address
@@ -905,7 +922,10 @@ class Pcre {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"address", @"name", nil];
+    return [NSArray arrayWithObjects:
+        @"address",
+        @"name",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -983,6 +1003,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"
@@ -1049,23 +1070,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;
@@ -1121,7 +1125,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];
 }
@@ -1131,22 +1135,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;
@@ -1164,30 +1158,66 @@ 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<NSString> package_;
+    _H<NSString> uri_;
+}
+
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type;
++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package;
+
+- (id) initWithMessage:(NSString *)message ofType:(NSString *)type;
+
+- (NSString *) message;
+- (NSString *) type;
+- (NSString *) package;
+- (NSString *) uri;
+
+- (void) setPackage:(NSString *)package;
+
+- (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;
     }
 
@@ -1203,8 +1233,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"]);
+        [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
     virtual void Done(pkgAcquire::ItemDesc &item) {
@@ -1221,17 +1252,12 @@ 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]);
+        //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"]);
+        [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
     virtual bool Pulse(pkgAcquire *Owner) {
@@ -1242,59 +1268,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;
 
@@ -1313,13 +1310,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_;
@@ -1347,7 +1346,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;
@@ -1357,44 +1357,106 @@ 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
-    ];
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"message",
+        @"package",
+        @"type",
+        @"uri",
+    nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
 }
 
-- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
-    Package *package = id == nil ? nil : [[Database sharedInstance] packageWithName:id];
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
 
-    [self performSelector:@selector(setProgressError:withTitle:)
-        withObject:error
-        withObject:(package == nil ? id : [package 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_;
+}
+
+- (NSString *) package {
+    return package_;
+}
+
+- (NSString *) uri {
+    return uri_;
+}
+
+- (void) setPackage:(NSString *)package {
+    package_ = package;
+}
+
+- (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
@@ -1592,7 +1654,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 {
@@ -1757,37 +1831,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
@@ -2067,8 +2280,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;
 }
@@ -2078,7 +2300,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 {
@@ -2089,6 +2337,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;
@@ -2480,7 +2750,7 @@ struct PackageNameOrdering :
     _end
 
     _profile(Package$visible$isSectionVisible)
-        if (section != nil && !isSectionVisible(section))
+        if (!isSectionVisible(section))
             return false;
     _end
 
@@ -3021,8 +3291,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_);
@@ -3065,29 +3334,28 @@ 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];
 
             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
@@ -3104,7 +3372,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);
@@ -3140,7 +3410,7 @@ static NSString *Warning_;
             capacity += 1024;
 
         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL);
-        deadSources_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+        sourceList_ = [[NSMutableArray alloc] initWithCapacity:16];
 
         int fds[2];
 
@@ -3212,103 +3482,36 @@ 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;
+- (Source *) sourceWithKey:(NSString *)key {
+    for (Source *source in [self sources]) {
+        if ([[source key] isEqualToString:key])
+            return source;
+    } return nil;
+}
 
-    NSMutableArray *issues([NSMutableArray arrayWithCapacity:4]);
+- (bool) popErrorWithTitle:(NSString *)title {
+    bool fatal(false);
 
-    for (Package *package in [self packages]) {
-        if (![package broken])
-            continue;
-        pkgCache::PkgIterator pkg([package iterator]);
+    while (!_error->empty()) {
+        std::string error;
+        bool warning(!_error->PopMessage(error));
+        if (!warning)
+            fatal = true;
 
-        NSMutableArray *entry([NSMutableArray arrayWithCapacity:4]);
-        [entry addObject:[package name]];
-        [issues addObject:entry];
+        for (;;) {
+            size_t size(error.size());
+            if (size == 0 || error[size - 1] != '\n')
+                break;
+            error.resize(size - 1);
+        }
 
-        pkgCache::VerIterator ver(cache_[pkg].InstVerIter(cache_));
-        if (ver.end())
-            continue;
+        lprintf("%c:[%s]\n", warning ? 'W' : 'E', error.c_str());
 
-        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;
-}
-
-- (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;
-    }
-
-    if (fatal && !message.empty())
-        [delegate_ _setProgressError:[NSString stringWithUTF8String:message.c_str()] withTitle:[NSString stringWithFormat:Colon_, fatal ? Error_ : Warning_, title]];
+        [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? @"WARNING" : @"ERROR")] forTask:title];
+    }
 
     return fatal;
 }
@@ -3317,14 +3520,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();
 
@@ -3353,10 +3556,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());
@@ -3366,11 +3573,10 @@ static NSString *Warning_;
         else if (error == "The package lists or status file could not be parsed or opened.")
             [delegate_ repairWithSelector:@selector(update)];
         // 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 == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
         // 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;
         }
 
@@ -3395,7 +3601,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;
     }
 
@@ -3407,7 +3613,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;
         }
 
@@ -3416,19 +3622,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]);
     }
 
     {
@@ -3489,12 +3693,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 {
@@ -3560,6 +3763,9 @@ static NSString *Warning_;
 }
 
 - (void) perform {
+    bool substrate(RestartSubstrate_);
+    RestartSubstrate_ = false;
+
     NSString *title(UCLocalize("PERFORM_SELECTIONS"));
 
     NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
@@ -3570,13 +3776,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)
@@ -3584,27 +3791,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_);
 
@@ -3647,35 +3846,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 {
@@ -3719,6 +3930,7 @@ static NSString *Warning_;
 }
 
 - (id) initWithDelegate:(IndirectDelegate *)indirect;
+
 @end
 
 @implementation CydiaObject
@@ -3739,7 +3951,14 @@ static NSString *Warning_;
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"device", @"firewire", @"imei", @"mac", @"serial", nil];
+    return [NSArray arrayWithObjects:
+        @"device",
+        @"ecid",
+        @"model",
+        @"plmn",
+        @"role",
+        @"serial",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -3754,59 +3973,70 @@ static NSString *Warning_;
     return [[UIDevice currentDevice] uniqueIdentifier];
 }
 
-#if 0 // XXX: implement!
-- (NSString *) mac {
-    if (![indirect_ promptForSensitive:@"Mac Address"])
-        return nil;
+- (NSString *) plmn {
+    return PLMN_;
+}
+
+- (NSString *) ecid {
+    return ChipID_;
 }
 
 - (NSString *) serial {
-    if (![indirect_ promptForSensitive:@"Serial #"])
-        return nil;
+    return SerialNumber_;
 }
 
-- (NSString *) firewire {
-    if (![indirect_ promptForSensitive:@"Firewire GUID"])
-        return nil;
+- (NSString *) role {
+    return Role_;
 }
 
-- (NSString *) imei {
-    if (![indirect_ promptForSensitive:@"IMEI"])
-        return nil;
+- (NSString *) model {
+    return [NSString stringWithUTF8String:Machine_];
 }
-#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;
 }
@@ -3819,14 +4049,62 @@ 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]);
@@ -3892,13 +4170,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];
 }
@@ -3907,11 +4189,7 @@ static NSString *Warning_;
     [indirect_ setButtonTitle:button withStyle:style toFunction:function];
 }
 
-- (void) setSpecial:(id)function {
-    [indirect_ setSpecial:function];
-}
-
-- (void) setToken:(NSString *)token {
+- (void) _setToken:(NSString *)token {
     if (Token_ != nil)
         [Token_ release];
     Token_ = [token retain];
@@ -3920,12 +4198,20 @@ static NSString *Warning_;
     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 {
@@ -3950,9 +4236,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;
@@ -3962,8 +4248,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];
 
@@ -4002,67 +4288,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;
 }
 
@@ -4070,29 +4323,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;
 }
 
@@ -4193,6 +4441,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())
@@ -4217,10 +4501,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_;
 }
 
@@ -4232,24 +4519,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];
@@ -4273,49 +4565,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;
 
-        [[self navigationItem] setTitle:UCLocalize("CONFIRM")];
-
-        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());
@@ -4331,7 +4714,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                 message:UCLocalize("REMOVING_ESSENTIALS_EX")
                 delegate:self
                 cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")]
-                otherButtonTitles:[NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")], nil
+                otherButtonTitles:
+                    [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")],
+                nil
             ];
 
             [essential_ setContext:@"remove"];
@@ -4347,21 +4732,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_]]];
@@ -4375,9 +4756,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
+#if !AlwaysReload
 - (void) applyRightButton {
-#if !AlwaysReload && !IgnoreInstall
-    if (issues_ == nil && ![self isLoading])
+    if ([issues_ count] == 0 && ![self isLoading])
         [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
             initWithTitle:UCLocalize("CONFIRM")
             style:UIBarButtonItemStyleDone
@@ -4385,11 +4766,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];
@@ -4398,16 +4777,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 #if !AlwaysReload
 - (void) confirmButtonClicked {
-#if IgnoreInstall
-    return;
-#endif
     if (essential_ != nil)
         [essential_ show];
-    else {
-        if (substrate_)
-            Finish_ = 2;
-        [delegate_ confirmWithNavigationController:[self navigationController]];
-    }
+    else
+        [self complete];
 }
 #endif
 
@@ -4415,195 +4788,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_;
+
+    bool running_;
+    float progress_;
+
+    _H<NSMutableArray> events_;
+    _H<NSString> title_;
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
+    _H<NSString> status_;
+    _H<NSString> finish_;
+}
 
-- (SEL) selector;
-- (id) target;
-- (id) object;
 @end
 
-@implementation ProgressData
+@implementation CydiaProgressData
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"events",
+        @"finish",
+        @"progress",
+        @"running",
+        @"title",
+    nil];
+}
+
+- (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;
-
-@end
+- (void) setTitle:(NSString *)title;
+- (void) setCancellable:(bool)cancellable;
 
-@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:
@@ -4635,19 +4986,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();
@@ -4655,12 +5019,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();
@@ -4668,57 +5031,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();
@@ -4726,11 +5048,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();
@@ -4738,133 +5061,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]
-    ];
-}
+    }
 
-- (void) repairWithSelector:(SEL)selector {
-    [self
-        detachNewThreadSelector:selector
-        toTarget:database_
-        withObject:nil
-        title:UCLocalize("REPAIRING")
-    ];
-}
+    if (Finish_ < 2) {
+        if (RestartSubstrate_)
+            Finish_ = 2;
+    }
 
-- (void) setConfigurationData:(NSString *)data {
-    [self
-        performSelectorOnMainThread:@selector(_setConfigurationData:)
-        withObject:data
-        waitUntilDone:YES
-    ];
-}
+    RestartSubstrate_ = false;
 
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    CYAlertView *sheet([[[CYAlertView alloc]
-        initWithTitle:title
-        buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
-        defaultButtonIndex:0
-    ] autorelease]);
+    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;
+    }
 
-    [sheet setMessage:error];
-    [sheet yieldToPopupAlertAnimated:YES];
-    [sheet dismiss];
-}
+    _trace();
+    system("su -c /usr/bin/uicache mobile");
+    _trace();
 
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
-}
+    UpdateExternalStatus(Finish_ == 0 ? 0 : 2);
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
-}
+    [progress_ setRunning:false];
+    [self updateProgress];
 
-- (void) startProgress {
+    [self applyRightButton];
 }
 
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
+- (void) addProgressEvent:(CydiaProgressEvent *)event {
+    [progress_ addEvent:event];
+    [self updateProgress];
 }
 
-- (bool) isCancelling:(size_t)received {
-    return false;
+- (bool) isProgressCancelled {
+    return cancel_ == 2;
 }
 
-- (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) cancel {
+    cancel_ = 2;
+    [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]];
-    }
+- (void) setCancellable:(bool)cancellable {
+    unsigned cancel(cancel_);
 
-    [status_ setText:[words componentsJoinedByString:@" "]];
-}
+    if (!cancellable)
+        cancel_ = 0;
+    else if (cancel_ == 0)
+        cancel_ = 1;
 
-- (void) _setProgressPercent:(NSNumber *)percent {
-    [progress_ setProgress:[percent floatValue]];
+    if (cancel != cancel_)
+        [self updateCancel];
 }
 
-- (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];
-    }
+- (void) setProgressCancellable:(NSNumber *)cancellable {
+    [self setCancellable:[cancellable boolValue]];
 }
 
-- (BOOL) isRunning {
-    return running_;
+- (void) setProgressPercent:(NSNumber *)percent {
+    [progress_ setProgress:[percent floatValue]];
+    [self updateProgress];
 }
 
 @end
@@ -5567,12 +5828,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_];
@@ -5615,7 +5875,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         action:@selector(customButtonClicked)
     ];
 
-    [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/package/#!/%@", UI_, name_]]];
+    [super reloadData];
 }
 
 - (bool) isLoading {
@@ -5989,7 +6249,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;
 }
 
@@ -5999,6 +6258,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* Home Controller {{{ */
 @interface HomeController : CYBrowserController {
 }
+
 @end
 
 @implementation HomeController
@@ -6072,6 +6332,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) queueStatusDidChange;
+
 @end
 
 @implementation ManageController
@@ -6088,8 +6349,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) viewDidLoad {
-    [[self navigationItem] setTitle:UCLocalize("MANAGE")];
-
     [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
         initWithTitle:UCLocalize("SETTINGS")
         style:UIBarButtonItemStylePlain
@@ -6188,14 +6447,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];
@@ -6232,18 +6490,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 {
@@ -6415,52 +6675,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;
 }
@@ -6951,8 +7181,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _trace();
 }
 
-- (void)editButtonClicked {
-    [self setEditing:!editing_];
+- (void) editButtonClicked {
+    [self setEditing:(!editing_)];
 }
 
 @end
@@ -7095,6 +7325,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);
@@ -7262,7 +7494,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void)viewDidAppear:(BOOL)animated {
+- (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
 
     if (!searchloaded_) {
@@ -7334,22 +7566,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 {
@@ -7360,15 +7599,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_;
 
@@ -7394,8 +7668,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")];
@@ -7406,8 +7678,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 {
@@ -7432,7 +7702,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;
@@ -7444,11 +7714,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];
 }
@@ -7616,7 +7887,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;
@@ -7630,12 +7902,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 {{{ */
@@ -7760,24 +8042,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];
 }
 
@@ -7830,7 +8107,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                     message:warning
                     delegate:self
                     cancelButtonTitle:UCLocalize("CANCEL")
-                    otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil
+                    otherButtonTitles:
+                        UCLocalize("ADD_ANYWAY"),
+                    nil
                 ] autorelease];
 
                 [alert setContext:@"warning"];
@@ -7862,9 +8141,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             [alert show];
         }
 
-        [delegate_ setStatusBarShowsProgress:NO];
-        [delegate_ removeProgressHUD:hud_];
+        [delegate_ releaseNetworkActivityIndicator];
 
+        [delegate_ removeProgressHUD:hud_];
         [hud_ autorelease];
         hud_ = nil;
 
@@ -7917,7 +8196,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"]) {
@@ -7943,6 +8222,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:
@@ -8007,7 +8287,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];
@@ -8035,7 +8315,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         message:nil
         delegate:self
         cancelButtonTitle:UCLocalize("CANCEL")
-        otherButtonTitles:UCLocalize("ADD_SOURCE"), nil
+        otherButtonTitles:
+            UCLocalize("ADD_SOURCE"),
+        nil
     ] autorelease];
 
     [alert setContext:@"source"];
@@ -8168,7 +8450,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;
@@ -8260,7 +8542,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.
 }
 
@@ -8298,6 +8580,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UILabel *status_;
     UILabel *caption_;
 }
+
 @end
 
 @implementation StashController
@@ -8368,7 +8651,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @interface Cydia : UIApplication <
     ConfirmationControllerDelegate,
-    ProgressControllerDelegate,
+    DatabaseDelegate,
     CydiaDelegate,
     UINavigationControllerDelegate,
     UITabBarControllerDelegate
@@ -8377,6 +8660,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     UIWindow *window_;
     CYTabBarController *tabbar_;
+    CYEmulatedLoadingController *emulated_;
 
     NSMutableArray *essential_;
     NSMutableArray *broken_;
@@ -8416,7 +8700,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             message:UCLocalize("HALFINSTALLED_PACKAGE_EX")
             delegate:self
             cancelButtonTitle:UCLocalize("FORCIBLY_CLEAR")
-            otherButtonTitles:UCLocalize("TEMPORARY_IGNORE"), nil
+            otherButtonTitles:
+                UCLocalize("TEMPORARY_IGNORE"),
+            nil
         ] autorelease];
 
         [alert setContext:@"fixhalf"];
@@ -8429,7 +8715,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
             message:UCLocalize("ESSENTIAL_UPGRADE_EX")
             delegate:self
             cancelButtonTitle:UCLocalize("TEMPORARY_IGNORE")
-            otherButtonTitles:UCLocalize("UPGRADE_ESSENTIAL"), UCLocalize("COMPLETE_UPGRADE"), nil
+            otherButtonTitles:
+                UCLocalize("UPGRADE_ESSENTIAL"),
+                UCLocalize("COMPLETE_UPGRADE"),
+            nil
         ] autorelease];
 
         [alert setContext:@"upgrade"];
@@ -8480,11 +8769,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))
@@ -8532,14 +8820,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];
@@ -8589,7 +8877,48 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [database_ update];
 }
 
+- (void) complete {
+    @synchronized (self) {
+        [self _reloadDataWithInvocation:nil];
+    }
+}
+
+- (void) presentModalViewController:(UIViewController *)controller {
+    UINavigationController *navigation([[[CYNavigationController alloc] initWithRootViewController:controller] autorelease]);
+    if (IsWildcat_)
+        [navigation setModalPresentationStyle:UIModalPresentationFormSheet];
+    [((UIViewController *) emulated_ ?: tabbar_) 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];
+
+    [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);
 
@@ -8605,28 +8934,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];
 
@@ -8636,6 +8968,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;
 
@@ -8698,37 +9037,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];
 }
@@ -8744,11 +9056,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 {
@@ -8767,7 +9088,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_) {
@@ -8874,7 +9204,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;
@@ -8895,7 +9225,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];
         }
@@ -8930,37 +9260,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];
 
@@ -8980,8 +9305,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];
@@ -8996,7 +9321,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 {
@@ -9017,6 +9342,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];
@@ -9033,13 +9384,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];
 
@@ -9077,26 +9424,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];
 
@@ -9145,10 +9474,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();
@@ -9158,17 +9490,11 @@ _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]];
         return;
     } else {
-        if ([[self showEmulatedLoadingControllerInView:window_] modalViewController] != nil)
-            [[self showEmulatedLoadingControllerInView:window_] dismissModalViewControllerAnimated:YES];
+        if ([emulated_ modalViewController] != nil)
+            [emulated_ dismissModalViewControllerAnimated:YES];
         [window_ setUserInteractionEnabled:NO];
     }
 
@@ -9176,7 +9502,11 @@ _trace();
     PrintTimes();
 
     [window_ addSubview:[tabbar_ view]];
-    [self showEmulatedLoadingControllerInView:nil];
+
+    [[emulated_ view] removeFromSuperview];
+    [emulated_ release];
+    emulated_ = nil;
+
     [window_ setUserInteractionEnabled:YES];
 
     int selectedIndex = 0;
@@ -9200,6 +9530,7 @@ _trace();
             enough = NO;
 
     if (!recently || !items || !enough) {
+        selectedIndex = 0;
         items = [NSMutableArray array];
         [items addObject:[NSArray arrayWithObject:@"cydia://home"]];
         [items addObject:[NSArray arrayWithObject:@"cydia://sections"]];
@@ -9222,7 +9553,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];
         }
@@ -9232,7 +9563,7 @@ _trace();
 
     // (Try to) show the startup URL.
     if (starturl_ != nil) {
-        [self openCydiaURL:starturl_];
+        [self openCydiaURL:starturl_ forExternal:NO];
         [starturl_ release];
         starturl_ = nil;
     }
@@ -9246,6 +9577,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_;
@@ -9321,6 +9668,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];
@@ -9550,10 +9899,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);*/