]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Do not show ProgressController Close if running.
[cydia.git] / MobileCydia.mm
index f526a47f4ff800bea5fb4600cfa1bb7813cc8a28..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];
@@ -563,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 {
@@ -871,6 +888,7 @@ class Pcre {
 
 + (Address *) addressWithString:(NSString *)string;
 - (Address *) initWithString:(NSString *)string;
+
 @end
 
 @implementation Address
@@ -904,7 +922,10 @@ class Pcre {
 }
 
 + (NSArray *) _attributeKeys {
-    return [NSArray arrayWithObjects:@"address", @"name", nil];
+    return [NSArray arrayWithObjects:
+        @"address",
+        @"name",
+    nil];
 }
 
 - (NSArray *) attributeKeys {
@@ -982,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"
@@ -1103,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];
 }
@@ -1113,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;
@@ -1146,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;
     }
 
@@ -1185,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) {
@@ -1203,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) {
@@ -1224,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;
 
@@ -1295,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_;
@@ -1330,7 +1347,7 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 - (NSArray *) packages;
 - (NSArray *) sources;
 - (Source *) sourceWithKey:(NSString *)key;
-- (void) reloadData;
+- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
 
 - (void) configure;
 - (bool) prepare;
@@ -1340,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
@@ -1575,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 {
@@ -1740,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
@@ -2050,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;
 }
@@ -2061,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 {
@@ -2072,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;
@@ -2463,7 +2750,7 @@ struct PackageNameOrdering :
     _end
 
     _profile(Package$visible$isSectionVisible)
-        if (section != nil && !isSectionVisible(section))
+        if (!isSectionVisible(section))
             return false;
     _end
 
@@ -3004,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_);
@@ -3048,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
@@ -3087,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);
@@ -3123,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];
 
@@ -3195,11 +3482,7 @@ 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_;
 }
 
 - (Source *) sourceWithKey:(NSString *)key {
@@ -3211,29 +3494,25 @@ static NSString *Warning_;
 
 - (bool) popErrorWithTitle:(NSString *)title {
     bool fatal(false);
-    std::string message;
 
     while (!_error->empty()) {
         std::string error;
         bool warning(!_error->PopMessage(error));
         if (!warning)
             fatal = true;
+
         for (;;) {
             size_t size(error.size());
             if (size == 0 || error[size - 1] != '\n')
                 break;
             error.resize(size - 1);
         }
+
         lprintf("%c:[%s]\n", warning ? 'W' : 'E', error.c_str());
 
-        if (!message.empty())
-            message += "\n\n";
-        message += error;
+        [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? @"WARNING" : @"ERROR")] forTask:title];
     }
 
-    if (fatal && !message.empty())
-        [delegate_ _setProgressError:[NSString stringWithUTF8String:message.c_str()] withTitle:[NSString stringWithFormat:Colon_, fatal ? Error_ : Warning_, title]];
-
     return fatal;
 }
 
@@ -3241,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();
 
@@ -3277,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());
@@ -3290,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;
         }
 
@@ -3319,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;
     }
 
@@ -3331,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;
         }
 
@@ -3340,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]);
     }
 
     {
@@ -3483,6 +3763,9 @@ static NSString *Warning_;
 }
 
 - (void) perform {
+    bool substrate(RestartSubstrate_);
+    RestartSubstrate_ = false;
+
     NSString *title(UCLocalize("PERFORM_SELECTIONS"));
 
     NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
@@ -3493,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)
@@ -3507,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_);
 
@@ -3570,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 {
@@ -3642,6 +3930,7 @@ static NSString *Warning_;
 }
 
 - (id) initWithDelegate:(IndirectDelegate *)indirect;
+
 @end
 
 @implementation CydiaObject
@@ -3662,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 {
@@ -3677,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;
 }
@@ -3742,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]);
@@ -3815,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];
 }
@@ -3830,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];
@@ -3843,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 {
@@ -3873,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;
@@ -3885,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];
 
@@ -3925,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;
 }
 
@@ -3993,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;
 }
 
@@ -4203,16 +4528,20 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     [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];
@@ -4237,11 +4566,12 @@ 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_ Cydia$webScriptObjectInContext:window] forKey:@"changes"];
-    [window setValue:[issues_ Cydia$webScriptObjectInContext:window] forKey:@"issues"];
-    [window setValue:[sizes_ Cydia$webScriptObjectInContext:window] 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 {
@@ -4288,6 +4618,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                     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]);
 
@@ -4312,8 +4649,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                             [NSString stringWithUTF8String:start.TargetVer()], @"value",
                         nil]);
 
-                        [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys:
-                            [NSString stringWithUTF8String:start.DepType()], @"relation",
+                        [clauses addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                             [NSString stringWithUTF8String:start.TargetPkg().Name()], @"package",
                             version, @"version",
                             reason, @"reason",
@@ -4347,10 +4683,14 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                     [NSNull null], @"package",
                     [NSArray arrayWithObjects:
                         [NSDictionary dictionaryWithObjectsAndKeys:
-                            @"Conflicts", @"relation",
-                            name, @"package",
-                            [NSNull null], @"version",
-                            @"installed", @"reason",
+                            @"Conflicts", @"relationship",
+                            [NSArray arrayWithObjects:
+                                [NSDictionary dictionaryWithObjectsAndKeys:
+                                    name, @"package",
+                                    [NSNull null], @"version",
+                                    @"installed", @"reason",
+                                nil],
+                            nil], @"clauses",
                         nil],
                     nil], @"reasons",
                 nil]];
@@ -4439,11 +4779,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 - (void) confirmButtonClicked {
     if (essential_ != nil)
         [essential_ show];
-    else {
-        if (substrate_)
-            Finish_ = 2;
-        [delegate_ confirmWithNavigationController:[self navigationController]];
-    }
+    else
+        [self complete];
 }
 #endif
 
@@ -4451,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_;
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
+    _H<NSMutableArray> events_;
+    _H<NSString> title_;
+
+    _H<NSString> status_;
+    _H<NSString> finish_;
+}
 
-- (SEL) selector;
-- (id) target;
-- (id) object;
 @end
 
-@implementation ProgressData
+@implementation CydiaProgressData
+
++ (NSArray *) _attributeKeys {
+    return [NSArray arrayWithObjects:
+        @"events",
+        @"finish",
+        @"progress",
+        @"running",
+        @"title",
+    nil];
+}
+
+- (NSArray *) attributeKeys {
+    return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+    return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
 
-- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
+- (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];
 }
 
-- (id) target {
-    return target_;
+- (void) addEvent:(CydiaProgressEvent *)event {
+    [events_ addObject:event];
 }
 
-- (id) object {
-    return object_;
+- (void) setTitle:(NSString *)text {
+    title_ = text;
+}
+
+- (NSString *) title {
+    return title_;
+}
+
+- (void) setFinish:(NSString *)text {
+    finish_ = text;
+}
+
+- (NSString *) finish {
+    return (id) finish_ ?: [NSNull null];
+}
+
+- (void) setRunning:(bool)running {
+    running_ = running;
+}
+
+- (NSNumber *) running {
+    return running_ ? (NSNumber *) kCFBooleanTrue : (NSNumber *) kCFBooleanFalse;
 }
 
 @end
 /* }}} */
 /* Progress Controller {{{ */
-@interface ProgressController : CYViewController <
-    ConfigurationDelegate,
+@interface ProgressController : CYBrowserController <
     ProgressDelegate
 > {
     _transient Database *database_;
-    UIProgressBar *progress_;
-    UITextView *output_;
-    UITextLabel *status_;
-    UIPushButton *close_;
-    BOOL running_;
-    SHA1SumValue springlist_;
-    SHA1SumValue notifyconf_;
-    NSString *title_;
+    _H<CydiaProgressData> progress_;
+    unsigned cancel_;
 }
 
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate;
 
-- (void) _retachThread;
-- (void) _detachNewThreadData:(ProgressData *)data;
-- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title;
+- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title;
 
-- (BOOL) isRunning;
+- (void) setTitle:(NSString *)title;
+- (void) setCancellable:(bool)cancellable;
 
 @end
 
-@protocol ProgressControllerDelegate
-- (void) progressControllerIsComplete:(ProgressController *)sender;
-@end
-
 @implementation ProgressController
 
 - (void) dealloc {
-    [database_ setDelegate:nil];
-    [progress_ release];
-    [output_ release];
-    [status_ release];
-    [close_ release];
-    if (title_ != nil)
-        [title_ release];
+    [database_ setProgressDelegate:nil];
+    [progress_ setDelegate:nil];
     [super dealloc];
 }
 
 - (id) initWithDatabase:(Database *)database delegate:(id)delegate {
     if ((self = [super init]) != nil) {
         database_ = database;
-        [database_ setDelegate:self];
         delegate_ = delegate;
 
-        [[self view] setBackgroundColor:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]];
-
-        progress_ = [[UIProgressBar alloc] init];
-        [progress_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [progress_ setStyle:0];
+        [database_ setProgressDelegate:self];
 
-        status_ = [[UITextLabel alloc] init];
-        [status_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [status_ setColor:[UIColor whiteColor]];
-        [status_ setBackgroundColor:[UIColor clearColor]];
-        [status_ setCentersHorizontally:YES];
-        //[status_ setFont:font];
-
-        output_ = [[UITextView alloc] init];
-        [output_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        //[output_ setTextFont:@"Courier New"];
-        [output_ setFont:[[output_ font] fontWithSize:12]];
-        [output_ setTextColor:[UIColor whiteColor]];
-        [output_ setBackgroundColor:[UIColor clearColor]];
-        [output_ setMarginTop:0];
-        [output_ setAllowsRubberBanding:YES];
-        [output_ setEditable:NO];
-        [[self view] addSubview:output_];
-
-        close_ = [[UIPushButton alloc] init];
-        [close_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
-        [close_ setAutosizesToFit:NO];
-        [close_ setDrawsShadow:YES];
-        [close_ setStretchBackground:YES];
-        [close_ setEnabled:YES];
-        [close_ setTitleFont:[UIFont boldSystemFontOfSize:22]];
-        [close_ addTarget:self action:@selector(closeButtonPushed) forEvents:UIControlEventTouchUpInside];
-        [close_ setBackground:[UIImage applicationImageNamed:@"green-up.png"] forState:0];
-        [close_ setBackground:[UIImage applicationImageNamed:@"green-dn.png"] forState:1];
+        progress_ = [[[CydiaProgressData alloc] init] autorelease];
+        [progress_ setDelegate:self];
     } return self;
 }
 
-- (void) positionViews {
-    CGRect bounds = [[self view] bounds];
-    CGSize prgsize = [UIProgressBar defaultSize];
-
-    CGRect prgrect = {{
-        (bounds.size.width - prgsize.width) / 2,
-        bounds.size.height - prgsize.height - 20
-    }, prgsize};
+- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+    [super webView:view didClearWindowObject:window forFrame:frame];
+    [window setValue:progress_ forKey:@"cydiaProgress"];
+}
 
-    float closewidth = std::min(bounds.size.width - 20, 300.0f);
+- (void) updateProgress {
+    [self dispatchEvent:@"CydiaProgressUpdate"];
+}
 
-    [progress_ setFrame:prgrect];
-    [status_ setFrame:CGRectMake(
-        10,
-        bounds.size.height - prgsize.height - 50,
-        bounds.size.width - 20,
-        24
-    )];
-    [output_ setFrame:CGRectMake(
-        10,
-        20,
-        bounds.size.width - 20,
-        bounds.size.height - 96
-    )];
-    [close_ setFrame:CGRectMake(
-        (bounds.size.width - closewidth) / 2,
-        bounds.size.height - prgsize.height - 50,
-        closewidth,
-        32 + prgsize.height
-    )];
+- (void) updateCancel {
+    [[self navigationItem] setLeftBarButtonItem:(cancel_ == 1 ? [[[UIBarButtonItem alloc]
+        initWithTitle:UCLocalize("CANCEL")
+        style:UIBarButtonItemStylePlain
+        target:self
+        action:@selector(cancel)
+    ] autorelease] : nil)];
 }
 
 - (void) viewWillAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    [[self navigationItem] setHidesBackButton:YES];
-    [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack];
+    if (![self hasLoaded]) {
+        [scroller_ setBackgroundColor:[UIColor blackColor]];
+        [self loadURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/progress/", UI_]]];
+    }
 
-    [self positionViews];
-}
+    [super viewDidAppear:animated];
 
-- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
-    [self positionViews];
-}
+    [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack];
 
-- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
-    NSString *context([alert context]);
+    [[self navigationItem] setHidesBackButton:YES];
 
-    if ([context isEqualToString:@"conffile"]) {
-        FILE *input = [database_ input];
-        if (button == [alert cancelButtonIndex])
-            fprintf(input, "N\n");
-        else if (button == [alert firstOtherButtonIndex])
-            fprintf(input, "Y\n");
-        fflush(input);
-    }
+    [self updateCancel];
 }
 
-- (void) closeButtonPushed {
-    running_ = NO;
-
+- (void) close {
     UpdateExternalStatus(0);
 
     switch (Finish_) {
         case 0:
-            [self dismissModalViewControllerAnimated:YES];
         break;
 
         case 1:
@@ -4671,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();
@@ -4691,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();
@@ -4704,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;
-        }
-    }
-
-    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;
-    }
-
-    _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_];
+            springlist = sha1.Result();
+        }
+    }
 
-    [delegate_ setStatusBarShowsProgress:YES];
-    running_ = YES;
+    if (invocation != nil) {
+        [invocation yieldToSelector:@selector(invoke)];
+        [self setTitle:@"COMPLETE"];
+    }
 
-    {
+    if (Finish_ < 4) {
         FileFd file;
         if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
             _error->Discard();
@@ -4762,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();
@@ -4774,134 +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]
-    ];
-}
+    if (Finish_ < 2) {
+        if (RestartSubstrate_)
+            Finish_ = 2;
+    }
 
-- (void) repairWithSelector:(SEL)selector {
-    [self
-        detachNewThreadSelector:selector
-        toTarget:database_
-        withObject:nil
-        title:UCLocalize("REPAIRING")
-    ];
-}
+    RestartSubstrate_ = false;
 
-- (void) setConfigurationData:(NSString *)data {
-    [self
-        performSelectorOnMainThread:@selector(_setConfigurationData:)
-        withObject:data
-        waitUntilDone:YES
-    ];
-}
+    switch (Finish_) {
+        case 0: [progress_ setFinish:UCLocalize("RETURN_TO_CYDIA")]; break; /* XXX: Maybe UCLocalize("DONE")? */
+        case 1: [progress_ setFinish:UCLocalize("CLOSE_CYDIA")]; break;
+        case 2: [progress_ setFinish:UCLocalize("RESTART_SPRINGBOARD")]; break;
+        case 3: [progress_ setFinish:UCLocalize("RELOAD_SPRINGBOARD")]; break;
+        case 4: [progress_ setFinish:UCLocalize("REBOOT_DEVICE")]; break;
+    }
 
-- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
-    CYAlertView *sheet([[[CYAlertView alloc]
-        initWithTitle:title
-        buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
-        defaultButtonIndex:0
-    ] autorelease]);
+    _trace();
+    system("su -c /usr/bin/uicache mobile");
+    _trace();
 
-    [sheet setMessage:error];
-    [sheet yieldToPopupAlertAnimated:YES];
-    [sheet dismiss];
-}
+    UpdateExternalStatus(Finish_ == 0 ? 0 : 2);
 
-- (void) setProgressTitle:(NSString *)title {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressTitle:)
-        withObject:title
-        waitUntilDone:YES
-    ];
-}
+    [progress_ setRunning:false];
+    [self updateProgress];
 
-- (void) setProgressPercent:(float)percent {
-    [self
-        performSelectorOnMainThread:@selector(_setProgressPercent:)
-        withObject:[NSNumber numberWithFloat:percent]
-        waitUntilDone:YES
-    ];
+    [self applyRightButton];
 }
 
-- (void) startProgress {
+- (void) addProgressEvent:(CydiaProgressEvent *)event {
+    [progress_ addEvent:event];
+    [self updateProgress];
 }
 
-- (void) addProgressOutput:(NSString *)output {
-    [self
-        performSelectorOnMainThread:@selector(_addProgressOutput:)
-        withObject:output
-        waitUntilDone:YES
-    ];
+- (bool) isProgressCancelled {
+    return cancel_ == 2;
 }
 
-- (bool) isCancelling:(size_t)received {
-    return false;
+- (void) cancel {
+    cancel_ = 2;
+    [self updateCancel];
 }
 
-- (void) _setConfigurationData:(NSString *)data {
-    static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
-
-    if (!conffile_r(data)) {
-        lprintf("E:invalid conffile\n");
-        return;
-    }
-
-    NSString *ofile = conffile_r[1];
-    //NSString *nfile = conffile_r[2];
+- (void) setCancellable:(bool)cancellable {
+    unsigned cancel(cancel_);
 
-    UIAlertView *alert = [[[UIAlertView alloc]
-        initWithTitle:UCLocalize("CONFIGURATION_UPGRADE")
-        message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
-        delegate:self
-        cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
-        otherButtonTitles:
-            UCLocalize("ACCEPT_NEW_COPY"),
-            // XXX: UCLocalize("SEE_WHAT_CHANGED"),
-        nil
-    ] autorelease];
+    if (!cancellable)
+        cancel_ = 0;
+    else if (cancel_ == 0)
+        cancel_ = 1;
 
-    [alert setContext:@"conffile"];
-    [alert show];
+    if (cancel != cancel_)
+        [self updateCancel];
 }
 
-- (void) _setProgressTitle:(NSString *)title {
-    NSMutableArray *words([[title componentsSeparatedByString:@" "] mutableCopy]);
-    for (size_t i(0), e([words count]); i != e; ++i) {
-        NSString *word([words objectAtIndex:i]);
-        if (Package *package = [database_ packageWithName:word])
-            [words replaceObjectAtIndex:i withObject:[package name]];
-    }
-
-    [status_ setText:[words componentsJoinedByString:@" "]];
+- (void) setProgressCancellable:(NSNumber *)cancellable {
+    [self setCancellable:[cancellable boolValue]];
 }
 
-- (void) _setProgressPercent:(NSNumber *)percent {
+- (void) setProgressPercent:(NSNumber *)percent {
     [progress_ setProgress:[percent floatValue]];
-}
-
-- (void) _addProgressOutput:(NSString *)output {
-    [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]];
-    CGSize size = [output_ contentSize];
-    CGPoint offset = [output_ contentOffset];
-    if (size.height - offset.y < [output_ frame].size.height + 20.f) {
-        CGRect rect = {{0, size.height-1}, {size.width, 1}};
-        [output_ scrollRectToVisible:rect animated:YES];
-    }
-}
-
-- (BOOL) isRunning {
-    return running_;
+    [self updateProgress];
 }
 
 @end
@@ -5604,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_];
@@ -5652,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 {
@@ -6026,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;
 }
 
@@ -6036,6 +6258,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 /* Home Controller {{{ */
 @interface HomeController : CYBrowserController {
 }
+
 @end
 
 @implementation HomeController
@@ -6109,6 +6332,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 }
 
 - (void) queueStatusDidChange;
+
 @end
 
 @implementation ManageController
@@ -6223,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];
@@ -6267,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 {
@@ -6450,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;
 }
@@ -6986,8 +7181,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     _trace();
 }
 
-- (void)editButtonClicked {
-    [self setEditing:!editing_];
+- (void) editButtonClicked {
+    [self setEditing:(!editing_)];
 }
 
 @end
@@ -7130,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);
@@ -7297,7 +7494,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     } return self;
 }
 
-- (void)viewDidAppear:(BOOL)animated {
+- (void) viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
 
     if (!searchloaded_) {
@@ -7369,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 {
@@ -7395,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_;
 
@@ -7429,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")];
@@ -7441,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 {
@@ -7467,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;
@@ -7479,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];
 }
@@ -7806,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];
 }
 
@@ -7910,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;
 
@@ -7965,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"]) {
@@ -7991,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:
@@ -8055,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];
@@ -8218,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;
@@ -8310,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.
 }
 
@@ -8348,6 +8580,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
     UILabel *status_;
     UILabel *caption_;
 }
+
 @end
 
 @implementation StashController
@@ -8418,7 +8651,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
 @interface Cydia : UIApplication <
     ConfirmationControllerDelegate,
-    ProgressControllerDelegate,
+    DatabaseDelegate,
     CydiaDelegate,
     UINavigationControllerDelegate,
     UITabBarControllerDelegate
@@ -8427,6 +8660,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
 
     UIWindow *window_;
     CYTabBarController *tabbar_;
+    CYEmulatedLoadingController *emulated_;
 
     NSMutableArray *essential_;
     NSMutableArray *broken_;
@@ -8535,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))
@@ -8587,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];
@@ -8644,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);
 
@@ -8660,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];
 
@@ -8760,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];
 }
@@ -8806,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 {
@@ -8829,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_) {
@@ -9074,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];
@@ -9090,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];
 
@@ -9134,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];
 
@@ -9202,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();
@@ -9215,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];
     }
 
@@ -9233,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;
@@ -9304,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_;
@@ -9379,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];
@@ -9608,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);*/