]> git.saurik.com Git - cydia.git/blobdiff - MobileCydia.mm
Port build environment to Xcode 5.0.2 (iOS 7 SDK).
[cydia.git] / MobileCydia.mm
index 30997aa8f8ce1ec181688daa21c8e87c069efd2f..79163ba9506a496e611d2dbfe5a681fc6007dd1f 100644 (file)
@@ -38,7 +38,6 @@
 #include <CoreFoundation/CFInternal.h>
 #endif
 
-#include <CoreFoundation/CFPriv.h>
 #include <CoreFoundation/CFUniChar.h>
 
 #include <SystemConfiguration/SystemConfiguration.h>
@@ -55,6 +54,7 @@
 
 #include <algorithm>
 #include <iomanip>
+#include <set>
 #include <sstream>
 #include <string>
 
@@ -863,6 +863,16 @@ static NSString *CYHex(NSData *data, bool reverse = false) {
 
 @class CYPackageController;
 
+@protocol SourceDelegate
+- (void) setFetch:(NSNumber *)fetch;
+@end
+
+@protocol FetchDelegate
+- (bool) isSourceCancelled;
+- (void) startSourceFetch:(NSString *)uri;
+- (void) stopSourceFetch:(NSString *)uri;
+@end
+
 @protocol CydiaDelegate
 - (void) returnToCydia;
 - (void) saveState;
@@ -874,6 +884,7 @@ static NSString *CYHex(NSData *data, bool reverse = false) {
 - (void) removePackage:(Package *)package;
 - (void) beginUpdate;
 - (BOOL) updating;
+- (bool) requestUpdate;
 - (void) distUpgrade;
 - (void) loadData;
 - (void) updateData;
@@ -888,29 +899,19 @@ static NSString *CYHex(NSData *data, bool reverse = false) {
 @end
 /* }}} */
 
-/* Status Delegation {{{ */
-class Status :
+/* CancelStatus {{{ */
+class CancelStatus :
     public pkgAcquireStatus
 {
   private:
-    _transient NSObject<ProgressDelegate> *delegate_;
     bool cancelled_;
 
   public:
-    Status() :
-        delegate_(nil),
+    CancelStatus() :
         cancelled_(false)
     {
     }
 
-    void setDelegate(NSObject<ProgressDelegate> *delegate) {
-        delegate_ = delegate;
-    }
-
-    NSObject<ProgressDelegate> *getDelegate() const {
-        return delegate_;
-    }
-
     virtual bool MediaChange(std::string media, std::string drive) {
         return false;
     }
@@ -919,6 +920,39 @@ class Status :
         Done(item);
     }
 
+    virtual bool Pulse_(pkgAcquire *Owner) = 0;
+
+    virtual bool Pulse(pkgAcquire *Owner) {
+        if (pkgAcquireStatus::Pulse(Owner) && Pulse_(Owner))
+            return true;
+        else {
+            cancelled_ = true;
+            return false;
+        }
+    }
+
+    _finline bool WasCancelled() const {
+        return cancelled_;
+    }
+};
+/* }}} */
+/* DelegateStatus {{{ */
+class CydiaStatus :
+    public CancelStatus
+{
+  private:
+    _transient NSObject<ProgressDelegate> *delegate_;
+
+  public:
+    CydiaStatus() :
+        delegate_(nil)
+    {
+    }
+
+    void setDelegate(NSObject<ProgressDelegate> *delegate) {
+        delegate_ = delegate;
+    }
+
     virtual void Fetch(pkgAcquire::ItemDesc &item) {
         NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
         CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), name] ofType:kCydiaProgressEventTypeStatus forItem:item]);
@@ -946,9 +980,7 @@ class Status :
         [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
     }
 
-    virtual bool Pulse(pkgAcquire *Owner) {
-        bool value = pkgAcquireStatus::Pulse(Owner);
-
+    virtual bool Pulse_(pkgAcquire *Owner) {
         double percent(
             double(CurrentBytes + CurrentItems) /
             double(TotalBytes + TotalItems)
@@ -962,16 +994,7 @@ class Status :
             [NSNumber numberWithDouble:CurrentCPS], @"Speed",
         nil] waitUntilDone:YES];
 
-        if (value && ![delegate_ isProgressCancelled])
-            return true;
-        else {
-            cancelled_ = true;
-            return false;
-        }
-    }
-
-    _finline bool WasCancelled() const {
-        return cancelled_;
+        return ![delegate_ isProgressCancelled];
     }
 
     virtual void Start() {
@@ -1012,7 +1035,7 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
     _transient NSObject<DatabaseDelegate> *delegate_;
     _transient NSObject<ProgressDelegate> *progress_;
 
-    Status status_;
+    CydiaStatus status_;
 
     int cydiafd_;
     int statusfd_;
@@ -1049,7 +1072,7 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 - (bool) upgrade;
 - (void) update;
 
-- (void) updateWithStatus:(Status &)status;
+- (void) updateWithStatus:(CancelStatus &)status;
 
 - (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate;
 
@@ -1057,11 +1080,61 @@ typedef std::map< unsigned long, _H<Source> > SourceMap;
 - (NSObject<ProgressDelegate> *) progressDelegate;
 
 - (Source *) getSource:(pkgCache::PkgFileIterator)file;
+- (void) setFetch:(bool)fetch forURI:(const char *)uri;
+- (void) resetFetch;
 
 - (NSString *) mappedSectionForPointer:(const char *)pointer;
 
 @end
 /* }}} */
+/* SourceStatus {{{ */
+class SourceStatus :
+    public CancelStatus
+{
+  private:
+    _transient NSObject<FetchDelegate> *delegate_;
+    _transient Database *database_;
+
+  public:
+    SourceStatus(NSObject<FetchDelegate> *delegate, Database *database) :
+        delegate_(delegate),
+        database_(database)
+    {
+    }
+
+    void Set(bool fetch, pkgAcquire::ItemDesc &desc) {
+        desc.Owner->ID = 0;
+        [database_ setFetch:fetch forURI:desc.Owner->DescURI().c_str()];
+    }
+
+    virtual void Fetch(pkgAcquire::ItemDesc &desc) {
+        Set(true, desc);
+    }
+
+    virtual void Done(pkgAcquire::ItemDesc &desc) {
+        Set(false, desc);
+    }
+
+    virtual void Fail(pkgAcquire::ItemDesc &desc) {
+        Set(false, desc);
+    }
+
+    virtual bool Pulse_(pkgAcquire *Owner) {
+        for (pkgAcquire::ItemCIterator item = Owner->ItemsBegin(); item != Owner->ItemsEnd(); ++item)
+            if ((*item)->ID != 0);
+            else if ((*item)->Status == pkgAcquire::Item::StatIdle) {
+                (*item)->ID = 1;
+                [database_ setFetch:true forURI:(*item)->DescURI().c_str()];
+            } else (*item)->ID = 0;
+        return ![delegate_ isSourceCancelled];
+    }
+
+    virtual void Stop() {
+        pkgAcquireStatus::Stop();
+        [database_ resetFetch];
+    }
+};
+/* }}} */
 /* ProgressEvent Implementation {{{ */
 @implementation CydiaProgressEvent
 
@@ -1326,6 +1399,10 @@ static void PackageImport(const void *key, const void *value, void *context) {
 
     _H<NSMutableDictionary> record_;
     BOOL trusted_;
+
+    std::set<std::string> fetches_;
+    std::set<std::string> files_;
+    _transient NSObject<SourceDelegate> *delegate_;
 }
 
 - (Source *) initWithMetaIndex:(metaIndex *)index forDatabase:(Database *)database inPool:(apr_pool_t *)pool;
@@ -1355,6 +1432,9 @@ static void PackageImport(const void *key, const void *value, void *context) {
 - (NSString *) defaultIcon;
 - (NSURL *) iconURL;
 
+- (void) setFetch:(bool)fetch forURI:(const char *)uri;
+- (void) resetFetch;
+
 @end
 
 @implementation Source
@@ -1417,7 +1497,19 @@ static void PackageImport(const void *key, const void *value, void *context) {
 
     debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index));
     if (dindex != NULL) {
-        base_.set(pool, dindex->MetaIndexURI(""));
+        std::string file(dindex->MetaIndexURI(""));
+        files_.insert(file + "Release.gpg");
+        files_.insert(file + "Release");
+        base_.set(pool, file);
+
+        std::vector<IndexTarget *> *targets(dindex->ComputeIndexTargets());
+        for (std::vector<IndexTarget *>::const_iterator target(targets->begin()); target != targets->end(); ++target) {
+            std::string file((*target)->URI);
+            files_.insert(file);
+            files_.insert(file + ".gz");
+            files_.insert(file + ".bz2");
+            files_.insert(file + "Index");
+        } delete targets;
 
         FileFd fd;
         if (!fd.Open(dindex->MetaIndexFile("Release"), FileFd::ReadOnly))
@@ -1653,6 +1745,31 @@ static void PackageImport(const void *key, const void *value, void *context) {
     return defaultIcon_;
 }
 
+- (void) setDelegate:(NSObject<SourceDelegate> *)delegate {
+    delegate_ = delegate;
+}
+
+- (bool) fetch {
+    return !fetches_.empty();
+}
+
+- (void) setFetch:(bool)fetch forURI:(const char *)uri {
+    if (!fetch) {
+        if (fetches_.erase(uri) == 0)
+            return;
+    } else if (files_.find(uri) == files_.end())
+        return;
+    else if (!fetches_.insert(uri).second)
+        return;
+
+    [delegate_ performSelectorOnMainThread:@selector(setFetch:) withObject:[NSNumber numberWithBool:[self fetch]] waitUntilDone:NO];
+}
+
+- (void) resetFetch {
+    fetches_.clear();
+    [delegate_ performSelectorOnMainThread:@selector(setFetch:) withObject:[NSNumber numberWithBool:NO] waitUntilDone:NO];
+}
+
 @end
 /* }}} */
 /* CydiaOperation Class {{{ */
@@ -3055,7 +3172,7 @@ struct PackageNameOrdering :
 }
 
 - (bool) isInstalledAndUnfiltered:(NSNumber *)number {
-    return ![self uninstalled] && (![number boolValue] && role_ != 7 || [self unfiltered]);
+    return ![self uninstalled] && role_ <= ([number boolValue] ? 1 : 3);
 }
 
 - (bool) isVisibleInSection:(NSString *)name source:(Source *)source {
@@ -3809,7 +3926,7 @@ class CydiaLogCleaner :
     [self updateWithStatus:status_];
 }
 
-- (void) updateWithStatus:(Status &)status {
+- (void) updateWithStatus:(CancelStatus &)status {
     NSString *title(UCLocalize("REFRESHING_DATA"));
 
     pkgSourceList list;
@@ -3853,6 +3970,16 @@ class CydiaLogCleaner :
     return i == sourceMap_.end() ? nil : i->second;
 }
 
+- (void) setFetch:(bool)fetch forURI:(const char *)uri {
+    for (Source *source in (id) sourceList_)
+        [source setFetch:fetch forURI:uri];
+}
+
+- (void) resetFetch {
+    for (Source *source in (id) sourceList_)
+        [source resetFetch];
+}
+
 - (NSString *) mappedSectionForPointer:(const char *)section {
     _H<NSString> *mapped;
 
@@ -4896,7 +5023,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
                                 reason = @"virtual";
                         }
 
-                        NSDictionary *version(start.TargetVer() == 0 ? [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys:
+                        NSDictionary *version(start.TargetVer() == 0 ? (NSDictionary *) [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSString stringWithUTF8String:start.CompType()], @"operator",
                             [NSString stringWithUTF8String:start.TargetVer()], @"value",
                         nil]);
@@ -5765,7 +5892,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) {
         section_ = [section localized];
 
         name_  = section_ == nil || [section_ length] == 0 ? UCLocalize("NO_SECTION") : (NSString *) section_;
-        count_ = [NSString stringWithFormat:@"%d", [section count]];
+        count_ = [NSString stringWithFormat:@"%zd", [section count]];
 
         if (editing_)
             [switch_ setOn:(isSectionVisible(basic_) ? 1 : 0) animated:NO];
@@ -6683,100 +6810,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 @end
 /* }}} */
 
-/* Refresh Bar {{{ */
-@interface RefreshBar : UINavigationBar {
-    _H<UIProgressIndicator> indicator_;
-    _H<UITextLabel> prompt_;
-    _H<UINavigationButton> cancel_;
-}
-
-@end
-
-@implementation RefreshBar
-
-- (void) positionViews {
-    CGRect frame = [cancel_ frame];
-    frame.size = [cancel_ sizeThatFits:frame.size];
-    frame.origin.x = [self frame].size.width - frame.size.width - 5;
-    frame.origin.y = ([self frame].size.height - frame.size.height) / 2;
-    [cancel_ setFrame:frame];
-
-    CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]);
-    unsigned indoffset = ([self frame].size.height - indsize.height) / 2;
-    CGRect indrect = {{indoffset, indoffset}, indsize};
-    [indicator_ setFrame:indrect];
-
-    CGSize prmsize = {215, indsize.height + 4};
-    CGRect prmrect = {{
-        indoffset * 2 + indsize.width,
-        unsigned([self frame].size.height - prmsize.height) / 2 - 1
-    }, prmsize};
-    [prompt_ setFrame:prmrect];
-}
-
-- (void) setFrame:(CGRect)frame {
-    [super setFrame:frame];
-    [self positionViews];
-}
-
-- (id) initWithFrame:(CGRect)frame delegate:(id)delegate {
-    if ((self = [super initWithFrame:frame]) != nil) {
-        [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
-
-        [self setBarStyle:UIBarStyleBlack];
-
-        UIBarStyle barstyle([self _barStyle:NO]);
-        bool ugly(barstyle == UIBarStyleDefault);
-
-        UIProgressIndicatorStyle style = ugly ?
-            UIProgressIndicatorStyleMediumBrown :
-            UIProgressIndicatorStyleMediumWhite;
-
-        indicator_ = [[[UIProgressIndicator alloc] initWithFrame:CGRectZero] autorelease];
-        [(UIProgressIndicator *) indicator_ setStyle:style];
-        [indicator_ startAnimation];
-        [self addSubview:indicator_];
-
-        prompt_ = [[[UITextLabel alloc] initWithFrame:CGRectZero] autorelease];
-        [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
-        [prompt_ setBackgroundColor:[UIColor clearColor]];
-        [prompt_ setFont:[UIFont systemFontOfSize:15]];
-        [self addSubview:prompt_];
-
-        cancel_ = [[[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted] autorelease];
-        [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
-        [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside];
-        [cancel_ setBarStyle:barstyle];
-
-        [self positionViews];
-    } return self;
-}
-
-- (void) setCancellable:(bool)cancellable {
-    if (cancellable)
-        [self addSubview:cancel_];
-    else
-        [cancel_ removeFromSuperview];
-}
-
-- (void) start {
-    [prompt_ setText:UCLocalize("UPDATING_DATABASE")];
-}
-
-- (void) stop {
-    [self setCancellable:NO];
-}
-
-- (void) setPrompt:(NSString *)prompt {
-    [prompt_ setText:prompt];
-}
-
-- (void) setProgress:(float)progress {
-}
-
-@end
-/* }}} */
-
 /* Cydia Navigation Controller Interface {{{ */
 @interface UINavigationController (Cydia)
 
@@ -6789,21 +6822,19 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 /* Cydia Tab Bar Controller {{{ */
 @interface CydiaTabBarController : CyteTabBarController <
     UITabBarControllerDelegate,
-    ProgressDelegate
+    FetchDelegate
 > {
     _transient Database *database_;
-    _H<RefreshBar, 1> refreshbar_;
 
-    bool dropped_;
+    _H<UIActivityIndicatorView> indicator_;
+
     bool updating_;
     // XXX: ok, "updatedelegate_"?...
     _transient NSObject<CydiaDelegate> *updatedelegate_;
 }
 
 - (NSArray *) navigationURLCollection;
-- (void) dropBar:(BOOL)animated;
 - (void) beginUpdate;
-- (void) raiseBar:(BOOL)animated;
 - (BOOL) updating;
 
 @end
@@ -6823,21 +6854,15 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     return items;
 }
 
-- (void) dealloc {
-    [[NSNotificationCenter defaultCenter] removeObserver:self];
-
-    [super dealloc];
-}
-
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super init]) != nil) {
         database_ = database;
         [self setDelegate:self];
 
-        [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
-        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
+        indicator_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteTiny] autorelease];
+        [indicator_ setOrigin:CGPointMake(kCFCoreFoundationVersionNumber >= 800 ? 2 : 4, 2)];
 
-        refreshbar_ = [[[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self] autorelease];
+        [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
     } return self;
 }
 
@@ -6846,8 +6871,17 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 }
 
 - (void) beginUpdate {
-    [(RefreshBar *) refreshbar_ start];
-    [self dropBar:YES];
+    if (updating_)
+        return;
+
+    UIViewController *controller([[self viewControllers] objectAtIndex:1]);
+    UITabBarItem *item([controller tabBarItem]);
+
+    [item setBadgeValue:@""];
+    UIView *badge(MSHookIvar<UIView *>([item view], "_badge"));
+
+    [indicator_ startAnimating];
+    [badge addSubview:indicator_];
 
     [updatedelegate_ retainNetworkActivityIndicator];
     updating_ = true;
@@ -6862,8 +6896,7 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
 - (void) performUpdate {
     NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
 
-    Status status;
-    status.setDelegate(self);
+    SourceStatus status(self, database_);
     [database_ updateWithStatus:status];
 
     [self
@@ -6879,8 +6912,11 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     updating_ = false;
     [updatedelegate_ releaseNetworkActivityIndicator];
 
-    [self raiseBar:YES];
-    [refreshbar_ stop];
+    UIViewController *controller([[self viewControllers] objectAtIndex:1]);
+    [[controller tabBarItem] setBadgeValue:nil];
+
+    [indicator_ removeFromSuperview];
+    [indicator_ stopAnimating];
 
     [updatedelegate_ performSelector:selector withObject:nil afterDelay:0];
 }
@@ -6903,25 +6939,14 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
     return updating_;
 }
 
-- (void) addProgressEvent:(CydiaProgressEvent *)event {
-    [refreshbar_ setPrompt:[event compoundMessage]];
-}
-
-- (bool) isProgressCancelled {
+- (bool) isSourceCancelled {
     return !updating_;
 }
 
-- (void) setProgressCancellable:(NSNumber *)cancellable {
-    [refreshbar_ setCancellable:(updating_ && [cancellable boolValue])];
+- (void) startSourceFetch:(NSString *)uri {
 }
 
-- (void) setProgressPercent:(NSNumber *)percent {
-    [refreshbar_ setProgress:[percent floatValue]];
-}
-
-- (void) setProgressStatus:(NSDictionary *)status {
-    if (status != nil)
-        [self setProgressPercent:[status objectForKey:@"Percent"]];
+- (void) stopSourceFetch:(NSString *)uri {
 }
 
 - (void) setUpdateDelegate:(id)delegate {
@@ -6937,93 +6962,6 @@ static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachabi
         return [[[self _transitionView] superview] superview];
 }
 
-- (void) dropBar:(BOOL)animated {
-    if (dropped_)
-        return;
-    dropped_ = true;
-
-    UIView *transition([self transitionView]);
-    [[self view] addSubview:refreshbar_];
-
-    CGRect barframe([refreshbar_ frame]);
-
-    if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) // XXX: _UIApplicationLinkedOnOrAfter(4)
-        barframe.origin.y = 0;
-    else if (kCFCoreFoundationVersionNumber < 800)
-        barframe.origin.y = CYStatusBarHeight();
-    else
-        barframe.origin.y = 0; //-barframe.size.height + CYStatusBarHeight();
-
-    [refreshbar_ setFrame:barframe];
-
-    CGRect viewframe = [transition frame];
-
-if (kCFCoreFoundationVersionNumber < 800) {
-    if (animated)
-        [UIView beginAnimations:nil context:NULL];
-
-    float adjust(barframe.size.height);
-    if (kCFCoreFoundationVersionNumber >= 800)
-        adjust -= CYStatusBarHeight();
-    viewframe.origin.y += adjust;
-    viewframe.size.height -= adjust;
-    [transition setFrame:viewframe];
-
-    if (animated)
-        [UIView commitAnimations];
-}
-
-    // Ensure bar has the proper width for our view, it might have changed
-    barframe.size.width = viewframe.size.width;
-    [refreshbar_ setFrame:barframe];
-}
-
-- (void) raiseBar:(BOOL)animated {
-    if (!dropped_)
-        return;
-    dropped_ = false;
-
-    UIView *transition([self transitionView]);
-    [refreshbar_ removeFromSuperview];
-
-    CGRect barframe([refreshbar_ frame]);
-
-if (kCFCoreFoundationVersionNumber < 800) {
-    if (animated)
-        [UIView beginAnimations:nil context:NULL];
-
-    CGRect viewframe = [transition frame];
-    float adjust(barframe.size.height);
-    if (kCFCoreFoundationVersionNumber >= 800)
-        adjust -= CYStatusBarHeight();
-    viewframe.origin.y -= adjust;
-    viewframe.size.height += adjust;
-    [transition setFrame:viewframe];
-
-    if (animated)
-        [UIView commitAnimations];
-}
-}
-
-- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
-    bool dropped(dropped_);
-
-    if (dropped)
-        [self raiseBar:NO];
-
-    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
-
-    if (dropped)
-        [self dropBar:NO];
-}
-
-- (void) statusBarFrameChanged:(NSNotification *)notification {
-    if (dropped_) {
-        [self raiseBar:NO];
-        [self dropBar:NO];
-    }
-}
-
 @end
 /* }}} */
 
@@ -7518,22 +7456,30 @@ if (kCFCoreFoundationVersionNumber < 800) {
         [alert dismissWithClickedButtonIndex:-1 animated:YES];
 }
 
+- (void) setLeftBarButtonItem {
+    if ([delegate_ updating])
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("CANCEL")
+            style:UIBarButtonItemStyleDone
+            target:self
+            action:@selector(cancelButtonClicked)
+        ] autorelease] animated:YES];
+    else
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("REFRESH")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(refreshButtonClicked)
+        ] autorelease] animated:YES];
+}
+
 - (void) refreshButtonClicked {
-    if (IsReachable("cydia.saurik.com")) {
-        [delegate_ beginUpdate];
-        [[self navigationItem] setLeftBarButtonItem:nil animated:YES];
-    } else {
-        UIAlertView *alert = [[[UIAlertView alloc]
-            initWithTitle:[NSString stringWithFormat:Colon_, Error_, UCLocalize("REFRESH")]
-            message:@"Host Unreachable" // XXX: Localize
-            delegate:self
-            cancelButtonTitle:UCLocalize("OK")
-            otherButtonTitles:nil
-        ] autorelease];
+    if ([delegate_ requestUpdate])
+        [self setLeftBarButtonItem];
+}
 
-        [alert setContext:@"norefresh"];
-        [alert show];
-    }
+- (void) cancelButtonClicked {
+    [delegate_ cancelUpdate];
 }
 
 - (void) upgradeButtonClicked {
@@ -7598,6 +7544,8 @@ if (kCFCoreFoundationVersionNumber < 800) {
 } }
 
 - (void) _reloadData {
+    [self setLeftBarButtonItem];
+
     NSMutableArray *packages;
 
   reload:
@@ -7687,13 +7635,6 @@ if (kCFCoreFoundationVersionNumber < 800) {
         action:@selector(upgradeButtonClicked)
     ] autorelease]) animated:YES];
 
-    [[self navigationItem] setLeftBarButtonItem:([delegate_ updating] ? nil : [[[UIBarButtonItem alloc]
-        initWithTitle:UCLocalize("REFRESH")
-        style:UIBarButtonItemStylePlain
-        target:self
-        action:@selector(refreshButtonClicked)
-    ] autorelease]) animated:YES];
-
     PrintTimes();
 } }
 
@@ -8020,12 +7961,9 @@ if (kCFCoreFoundationVersionNumber < 800) {
 
 /* Installed Controller {{{ */
 @interface InstalledController : FilteredPackageListController {
-    BOOL expert_;
 }
 
 - (id) initWithDatabase:(Database *)database;
-
-- (void) updateRoleButton;
 - (void) queueStatusDidChange;
 
 @end
@@ -8042,7 +7980,13 @@ if (kCFCoreFoundationVersionNumber < 800) {
 
 - (id) initWithDatabase:(Database *)database {
     if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) {
-        [self updateRoleButton];
+        UISegmentedControl *segmented([[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:UCLocalize("SIMPLE"), UCLocalize("EXPERT"), nil]] autorelease]);
+        [segmented setSelectedSegmentIndex:0];
+        [segmented setSegmentedControlStyle:UISegmentedControlStyleBar];
+        [[self navigationItem] setTitleView:segmented];
+
+        [segmented addTarget:self action:@selector(modeChanged:) forEvents:UIControlEventValueChanged];
+
         [self queueStatusDidChange];
     } return self;
 }
@@ -8068,21 +8012,10 @@ if (kCFCoreFoundationVersionNumber < 800) {
 #endif
 }
 
-- (void) updateRoleButton {
-    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-        initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE"))
-        style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
-        target:self
-        action:@selector(roleButtonClicked)
-    ] autorelease]];
-}
-
-- (void) roleButtonClicked {
-    [self setObject:[NSNumber numberWithBool:expert_]];
+- (void) modeChanged:(UISegmentedControl *)segmented {
+    bool simple([segmented selectedSegmentIndex] == 0);
+    [self setObject:[NSNumber numberWithBool:simple]];
     [self reloadData];
-    expert_ = !expert_;
-
-    [self updateRoleButton];
 }
 
 @end
@@ -8090,15 +8023,19 @@ if (kCFCoreFoundationVersionNumber < 800) {
 
 /* Source Cell {{{ */
 @interface SourceCell : CyteTableViewCell <
-    CyteTableViewCellDelegate
+    CyteTableViewCellDelegate,
+    SourceDelegate
 > {
+    _H<Source, 1> source_;
     _H<NSURL> url_;
     _H<UIImage> icon_;
     _H<NSString> origin_;
     _H<NSString> label_;
+    _H<UIActivityIndicatorView> indicator_;
 }
 
 - (void) setSource:(Source *)source;
+- (void) setFetch:(NSNumber *)fetch;
 
 @end
 
@@ -8131,6 +8068,11 @@ if (kCFCoreFoundationVersionNumber < 800) {
 }
 
 - (void) setSource:(Source *)source {
+    source_ = source;
+    [source_ setDelegate:self];
+
+    [self setFetch:[NSNumber numberWithBool:[source_ fetch]]];
+
     icon_ = [UIImage applicationImageNamed:@"unknown.png"];
 
     origin_ = [source name];
@@ -8143,6 +8085,9 @@ if (kCFCoreFoundationVersionNumber < 800) {
 }
 
 - (void) setAllSource {
+    source_ = nil;
+    [indicator_ stopAnimating];
+
     icon_ = [UIImage applicationImageNamed:@"folder.png"];
     origin_ = UCLocalize("ALL_SOURCES");
     label_ = UCLocalize("ALL_SOURCES_EX");
@@ -8162,10 +8107,29 @@ if (kCFCoreFoundationVersionNumber < 800) {
         [content_ setDelegate:self];
         [content_ setOpaque:YES];
 
+        indicator_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGraySmall] autorelease];
+        [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin];// | UIViewAutoresizingFlexibleBottomMargin];
+        [content addSubview:indicator_];
+
         [[content_ layer] setContentsGravity:kCAGravityTopLeft];
     } return self;
 }
 
+- (void) layoutSubviews {
+    [super layoutSubviews];
+
+    UIView *content([self contentView]);
+    CGRect bounds([content bounds]);
+
+    CGRect frame([indicator_ frame]);
+    frame.origin.x = bounds.size.width - frame.size.width;
+    frame.origin.y = (bounds.size.height - frame.size.height) / 2;
+
+    if (kCFCoreFoundationVersionNumber < 800)
+        frame.origin.x -= 8;
+    [indicator_ setFrame:frame];
+}
+
 - (NSString *) accessibilityLabel {
     return origin_;
 }
@@ -8201,6 +8165,13 @@ if (kCFCoreFoundationVersionNumber < 800) {
     [label_ drawAtPoint:CGPointMake(52, 29) forWidth:(width - 61) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation];
 }
 
+- (void) setFetch:(NSNumber *)fetch {
+    if ([fetch boolValue])
+        [indicator_ startAnimating];
+    else
+        [indicator_ stopAnimating];
+}
+
 @end
 /* }}} */
 /* Sources Controller {{{ */
@@ -8219,10 +8190,8 @@ if (kCFCoreFoundationVersionNumber < 800) {
     _H<UIProgressHUD> hud_;
     _H<NSError> error_;
 
-    //NSURLConnection *installer_;
     NSURLConnection *trivial_bz2_;
     NSURLConnection *trivial_gz_;
-    //NSURLConnection *automatic_;
 
     BOOL cydia_;
 }
@@ -8243,10 +8212,8 @@ if (kCFCoreFoundationVersionNumber < 800) {
 }
 
 - (void) dealloc {
-    //[self _releaseConnection:installer_];
     [self _releaseConnection:trivial_gz_];
     [self _releaseConnection:trivial_bz2_];
-    //[self _releaseConnection:automatic_];
 
     [super dealloc];
 }
@@ -8337,6 +8304,10 @@ if (kCFCoreFoundationVersionNumber < 800) {
     }
 }
 
+- (void) tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
+    [self updateButtonsForEditingStatusAnimated:YES];
+}
+
 - (void) complete {
     [delegate_ addTrivialSource:href_];
     href_ = nil;
@@ -8489,7 +8460,21 @@ if (kCFCoreFoundationVersionNumber < 800) {
             case 1: {
                 NSString *href = [[alert textField] text];
 
-                //installer_ = [[self _requestHRef:href method:@"GET"] retain];
+                static Pcre href_r("^http(s?)://[^# ]*$");
+                if (!href_r(href)) {
+                    UIAlertView *alert = [[[UIAlertView alloc]
+                        initWithTitle:Error_
+                        message:UCLocalize("INVALID_URL")
+                        delegate:self
+                        cancelButtonTitle:UCLocalize("OK")
+                        otherButtonTitles:nil
+                    ] autorelease];
+
+                    [alert setContext:@"badurl"];
+                    [alert show];
+
+                    break;
+                }
 
                 if (![href hasSuffix:@"/"])
                     href_ = [href stringByAppendingString:@"/"];
@@ -8498,7 +8483,6 @@ if (kCFCoreFoundationVersionNumber < 800) {
 
                 trivial_bz2_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.bz2"] method:@"HEAD"] retain];
                 trivial_gz_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.gz"] method:@"HEAD"] retain];
-                //trivial_bz2_ = [[self _requestHRef:[href stringByAppendingString:@"dists/Release"] method:@"HEAD"] retain];
 
                 cydia_ = false;
 
@@ -8535,6 +8519,39 @@ if (kCFCoreFoundationVersionNumber < 800) {
     }
 }
 
+- (void) updateButtonsForEditingStatusAnimated:(BOOL)animated {
+    BOOL editing([list_ isEditing]);
+
+    if (editing)
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("ADD")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(addButtonClicked)
+        ] autorelease] animated:animated];
+    else if ([delegate_ updating])
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("CANCEL")
+            style:UIBarButtonItemStyleDone
+            target:self
+            action:@selector(cancelButtonClicked)
+        ] autorelease] animated:animated];
+    else
+        [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
+            initWithTitle:UCLocalize("REFRESH")
+            style:UIBarButtonItemStylePlain
+            target:self
+            action:@selector(refreshButtonClicked)
+        ] autorelease] animated:animated];
+
+    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
+        initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
+        style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
+        target:self
+        action:@selector(editButtonClicked)
+    ] autorelease] animated:animated];
+}
+
 - (void) loadView {
     list_ = [[[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain] autorelease];
     [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
@@ -8574,6 +8591,7 @@ if (kCFCoreFoundationVersionNumber < 800) {
 
 - (void) reloadData {
     [super reloadData];
+    [self updateButtonsForEditingStatusAnimated:YES];
 
 @synchronized (database_) {
     era_ = [database_ era];
@@ -8625,22 +8643,13 @@ if (kCFCoreFoundationVersionNumber < 800) {
     [self showAddSourcePrompt];
 }
 
-- (void) updateButtonsForEditingStatusAnimated:(BOOL)animated { 
-    BOOL editing([list_ isEditing]);
-
-    [[self navigationItem] setLeftBarButtonItem:(editing ? [[[UIBarButtonItem alloc]
-        initWithTitle:UCLocalize("ADD")
-        style:UIBarButtonItemStylePlain
-        target:self
-        action:@selector(addButtonClicked)
-    ] autorelease] : [[self navigationItem] backBarButtonItem]) animated:animated];
+- (void) refreshButtonClicked {
+    if ([delegate_ requestUpdate])
+        [self updateButtonsForEditingStatusAnimated:YES];
+}
 
-    [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
-        initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
-        style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
-        target:self
-        action:@selector(editButtonClicked)
-    ] autorelease] animated:animated];
+- (void) cancelButtonClicked {
+    [delegate_ cancelUpdate];
 }
 
 - (void) editButtonClicked {
@@ -8821,6 +8830,30 @@ if (kCFCoreFoundationVersionNumber < 800) {
     [tabbar_ beginUpdate];
 }
 
+- (void) cancelUpdate {
+    [tabbar_ cancelUpdate];
+}
+
+- (bool) requestUpdate {
+    if (IsReachable("cydia.saurik.com")) {
+        [self beginUpdate];
+        return true;
+    } else {
+        UIAlertView *alert = [[[UIAlertView alloc]
+            initWithTitle:[NSString stringWithFormat:Colon_, Error_, UCLocalize("REFRESH")]
+            message:@"Host Unreachable" // XXX: Localize
+            delegate:self
+            cancelButtonTitle:UCLocalize("OK")
+            otherButtonTitles:nil
+        ] autorelease];
+
+        [alert setContext:@"norefresh"];
+        [alert show];
+
+        return false;
+    }
+}
+
 - (BOOL) updating {
     return [tabbar_ updating];
 }
@@ -10202,6 +10235,16 @@ int main(int argc, char *argv[]) {
 
         Changed_ = true;
     }
+
+    _H<NSMutableArray> broken([NSMutableArray array]);
+    for (NSString *key in (id) Sources_)
+        if ([key rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"# "]].location != NSNotFound)
+            [broken addObject:key];
+    if ([broken count] != 0) {
+        for (NSString *key in (id) broken)
+            [Sources_ removeObjectForKey:key];
+        Changed_ = true;
+    } broken = nil;
     /* }}} */
 
     CydiaWriteSources();