X-Git-Url: https://git.saurik.com/cydia.git/blobdiff_plain/066b9ccc3d0530784fc67e93b2e62adb5437f263..900095fdf69cc72c5a0d5b1f75c4451a0bc4440e:/MobileCydia.mm diff --git a/MobileCydia.mm b/MobileCydia.mm index 44b2c1cf..932cb890 100644 --- a/MobileCydia.mm +++ b/MobileCydia.mm @@ -1,5 +1,5 @@ /* Cydia - iPhone UIKit Front-End for Debian APT - * Copyright (C) 2008-2010 Jay Freeman (saurik) + * Copyright (C) 2008-2011 Jay Freeman (saurik) */ /* Modified BSD License {{{ */ @@ -125,6 +125,8 @@ extern "C" { #include "SDURLCache/SDURLCache.h" #include "substrate.h" + +#include "Version.h" /* }}} */ /* Profiler {{{ */ @@ -217,21 +219,14 @@ union SplitHash { static const NSUInteger UIViewAutoresizingFlexibleBoth(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); -void NSLogPoint(const char *fix, const CGPoint &point) { - NSLog(@"%s(%g,%g)", fix, point.x, point.y); -} - -void NSLogRect(const char *fix, const CGRect &rect) { - NSLog(@"%s(%g,%g)+(%g,%g)", fix, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); -} - static _finline NSString *CydiaURL(NSString *path) { - char page[25]; - page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = ':'; - page[5] = '/'; page[6] = '/'; page[7] = 'c'; page[8] = 'y'; page[9] = 'd'; - page[10] = 'i'; page[11] = 'a'; page[12] = '.'; page[13] = 's'; page[14] = 'a'; - page[15] = 'u'; page[16] = 'r'; page[17] = 'i'; page[18] = 'k'; page[19] = '.'; - page[20] = 'c'; page[21] = 'o'; page[22] = 'm'; page[23] = '/'; page[24] = '\0'; + char page[26]; + page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = 's'; + page[5] = ':'; page[6] = '/'; page[7] = '/'; page[8] = 'c'; page[9] = 'y'; + page[10] = 'd'; page[11] = 'i'; page[12] = 'a'; page[13] = '.'; page[14] = 's'; + page[15] = 'a'; page[16] = 'u'; page[17] = 'r'; page[18] = 'i'; page[19] = 'k'; + page[20] = '.'; page[21] = 'c'; page[22] = 'o'; page[23] = 'm'; page[24] = '/'; + page[25] = '\0'; return [[NSString stringWithUTF8String:page] stringByAppendingString:path]; } @@ -296,8 +291,9 @@ static _finline void UpdateExternalStatus(uint64_t newStatus) { NSRunLoop *loop([NSRunLoop currentRunLoop]); NSDate *future([NSDate distantFuture]); + NSString *mode([loop currentMode] ?: NSDefaultRunLoopMode); - while (!stopped && [loop runMode:NSDefaultRunLoopMode beforeDate:future]); + while (!stopped && [loop runMode:mode beforeDate:future]); return [context count] == 0 ? nil : [context objectAtIndex:0]; } @@ -309,18 +305,19 @@ static _finline void UpdateExternalStatus(uint64_t newStatus) { @end /* }}} */ -/* Cydia Action Sheet {{{ */ -@interface CYActionSheet : UIAlertView { +/* Cydia Alert View {{{ */ +@interface CYAlertView : UIAlertView { unsigned button_; } - (int) yieldToPopupAlertAnimated:(BOOL)animated; + @end -@implementation CYActionSheet +@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]; @@ -363,32 +360,6 @@ static const NSStringCompareOptions MatchCompareOptions_ = NSLiteralSearch | NSC static const NSStringCompareOptions LaxCompareOptions_ = NSNumericSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch | NSCaseInsensitiveSearch; static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareNumerically | kCFCompareWidthInsensitive | kCFCompareForcedOrdering; -/* Information Dictionaries {{{ */ -@interface NSMutableArray (Cydia) -- (void) addInfoDictionary:(NSDictionary *)info; -@end - -@implementation NSMutableArray (Cydia) - -- (void) addInfoDictionary:(NSDictionary *)info { - [self addObject:info]; -} - -@end - -@interface NSMutableDictionary (Cydia) -- (void) addInfoDictionary:(NSDictionary *)info; -@end - -@implementation NSMutableDictionary (Cydia) - -- (void) addInfoDictionary:(NSDictionary *)info { - [self setObject:info forKey:[info objectForKey:@"CFBundleIdentifier"]]; -} - -@end -/* }}} */ - #define lprintf(args...) fprintf(stderr, args) #define ForRelease 1 @@ -400,8 +371,8 @@ static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive | #define TrackResize (0 && !ForRelease) #define ManualRefresh (1 && !ForRelease) #define ShowInternals (0 && !ForRelease) -#define IgnoreInstall (0 && !ForRelease) #define AlwaysReload (0 && !ForRelease) +#define TryIndexedCollation (0 && !ForRelease) #if !TraceLogging #undef _trace @@ -597,6 +568,21 @@ void CFArrayInsertionSortValues(CFMutableArrayRef array, CFRange range, CFCompar @end /* }}} */ +@interface NSInvocation (Cydia) ++ (NSInvocation *) invocationWithSelector:(SEL)selector forTarget:(id)target; +@end + +@implementation NSInvocation (Cydia) + ++ (NSInvocation *) invocationWithSelector:(SEL)selector forTarget:(id)target { + NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]]); + [invocation setTarget:target]; + [invocation setSelector:selector]; + return invocation; +} + +@end + @implementation WebScriptObject (NSFastEnumeration) - (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)objects count:(NSUInteger)count { @@ -905,6 +891,7 @@ class Pcre { + (Address *) addressWithString:(NSString *)string; - (Address *) initWithString:(NSString *)string; + @end @implementation Address @@ -938,7 +925,10 @@ class Pcre { } + (NSArray *) _attributeKeys { - return [NSArray arrayWithObjects:@"address", @"name", nil]; + return [NSArray arrayWithObjects: + @"address", + @"name", + nil]; } - (NSArray *) attributeKeys { @@ -1013,7 +1003,10 @@ class CYColor { /* Random Global Variables {{{ */ 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" @@ -1035,7 +1028,6 @@ static UIColor *InstallingColor_; static UIColor *RemovingColor_; static NSString *App_; -static NSString *Home_; static BOOL Advanced_; static BOOL Ignored_; @@ -1050,7 +1042,7 @@ static const char *Machine_ = NULL; static NSString *System_ = nil; static NSString *SerialNumber_ = nil; static NSString *ChipID_ = nil; -static NSString *Token_ = nil; +static _H Token_; static NSString *UniqueID_ = nil; static NSString *PLMN_ = nil; static NSString *Build_ = nil; @@ -1071,7 +1063,9 @@ static _transient NSMutableDictionary *Sources_; static bool Changed_; static time_t now_; -static bool IsWildcat_; +bool IsWildcat_; +static CGFloat ScreenScale_; +static NSString *Idiom_; /* }}} */ /* Display Helpers {{{ */ @@ -1079,23 +1073,6 @@ inline float Interpolate(float begin, float end, float fraction) { return (end - begin) * fraction + begin; } -/* XXX: localize this! */ -NSString *SizeString(double size) { - bool negative = size < 0; - if (negative) - size = -size; - - unsigned power = 0; - while (size > 1024) { - size /= 1024; - ++power; - } - - static const char *powers_[] = {"B", "kB", "MB", "GB"}; - - return [NSString stringWithFormat:@"%s%.1f %s", (negative ? "-" : ""), size, powers_[power]]; -} - static _finline const char *StripVersion_(const char *version) { const char *colon(strchr(version, ':')); return colon == NULL ? version : colon + 1; @@ -1151,7 +1128,7 @@ NSString *GetLastUpdate() { } bool isSectionVisible(NSString *section) { - NSDictionary *metadata([Sections_ objectForKey:section]); + NSDictionary *metadata([Sections_ objectForKey:(section ?: @"")]); NSNumber *hidden(metadata == nil ? nil : [metadata objectForKey:@"Hidden"]); return hidden == nil || ![hidden boolValue]; } @@ -1161,22 +1138,12 @@ bool isSectionVisible(NSString *section) { /* Delegate Prototypes {{{ */ @class Package; @class Source; +@class CydiaProgressEvent; -@interface NSObject (ProgressDelegate) -@end - -@protocol ProgressDelegate -- (void) setProgressError:(NSString *)error withTitle:(NSString *)id; -- (void) setProgressTitle:(NSString *)title; -- (void) setProgressPercent:(float)percent; -- (void) startProgress; -- (void) addProgressOutput:(NSString *)output; -- (bool) isCancelling:(size_t)received; -@end - -@protocol ConfigurationDelegate +@protocol DatabaseDelegate - (void) repairWithSelector:(SEL)selector; - (void) setConfigurationData:(NSString *)data; +- (void) addProgressEventOnMainThread:(CydiaProgressEvent *)event forTask:(NSString *)task; @end @class CYPackageController; @@ -1184,7 +1151,6 @@ bool isSectionVisible(NSString *section) { @protocol CydiaDelegate - (void) retainNetworkActivityIndicator; - (void) releaseNetworkActivityIndicator; -- (void) setPackageController:(CYPackageController *)view; - (void) clearPackage:(Package *)package; - (void) installPackage:(Package *)package; - (void) installPackages:(NSArray *)packages; @@ -1195,30 +1161,77 @@ 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 CydiaApp; /* }}} */ +/* ProgressEvent Interface/Delegate {{{ */ +@interface CydiaProgressEvent : NSObject { + _H message_; + _H type_; + + _H item_; + _H package_; + _H url_; + _H version_; +} + ++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type; ++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package; ++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forItem:(pkgAcquire::ItemDesc &)item; + +- (id) initWithMessage:(NSString *)message ofType:(NSString *)type; + +- (NSString *) message; +- (NSString *) type; + +- (NSArray *) item; +- (NSString *) package; +- (NSString *) url; +- (NSString *) version; + +- (void) setItem:(NSArray *)item; +- (void) setPackage:(NSString *)package; +- (void) setURL:(NSString *)url; +- (void) setVersion:(NSString *)version; + +- (NSString *) compound:(NSString *)value; +- (NSString *) compoundMessage; +- (NSString *) compoundTitle; + +@end + +@protocol ProgressDelegate +- (void) addProgressEvent:(CydiaProgressEvent *)event; +- (void) setProgressPercent:(NSNumber *)percent; +- (void) setProgressStatus:(NSDictionary *)status; +- (void) setProgressCancellable:(NSNumber *)cancellable; +- (bool) isProgressCancelled; +- (void) setTitle:(NSString *)title; +@end +/* }}} */ /* Status Delegation {{{ */ class Status : public pkgAcquireStatus { private: _transient NSObject *delegate_; + bool cancelled_; public: Status() : - delegate_(nil) + delegate_(nil), + cancelled_(false) { } - void setDelegate(id delegate) { + void setDelegate(NSObject *delegate) { delegate_ = delegate; } @@ -1234,8 +1247,9 @@ class Status : } virtual void Fetch(pkgAcquire::ItemDesc &item) { - //NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]); - [delegate_ setProgressTitle:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), [NSString stringWithUTF8String:item.ShortDesc.c_str()]]]; + NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]); + CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), name] ofType:@"STATUS" forItem:item]); + [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES]; } virtual void Done(pkgAcquire::ItemDesc &item) { @@ -1252,80 +1266,50 @@ class Status : if (error.empty()) return; - NSString *description([NSString stringWithUTF8String:item.Description.c_str()]); - NSArray *fields([description componentsSeparatedByString:@" "]); - NSString *source([fields count] == 0 ? nil : [fields objectAtIndex:0]); - - [delegate_ performSelectorOnMainThread:@selector(_setProgressErrorPackage:) - withObject:[NSArray arrayWithObjects: - [NSString stringWithUTF8String:error.c_str()], - source, - nil] - waitUntilDone:YES - ]; + CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:@"ERROR" forItem:item]); + [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES]; } virtual bool Pulse(pkgAcquire *Owner) { bool value = pkgAcquireStatus::Pulse(Owner); - float percent( + double percent( double(CurrentBytes + CurrentItems) / 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 delegate_; - float percent_; + [delegate_ performSelectorOnMainThread:@selector(setProgressStatus:) withObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithDouble:percent], @"Percent", - protected: - virtual void Update() { - /*if (abs(Percent - percent_) > 2) - //NSLog(@"%s:%s:%f", Op.c_str(), SubOp.c_str(), Percent); - percent_ = Percent; - }*/ + [NSNumber numberWithDouble:CurrentBytes], @"Current", + [NSNumber numberWithDouble:TotalBytes], @"Total", + [NSNumber numberWithDouble:CurrentCPS], @"Speed", + nil] waitUntilDone:YES]; - /*[delegate_ setProgressTitle:[NSString stringWithUTF8String:Op.c_str()]]; - [delegate_ setProgressPercent:(Percent / 100)];*/ - } - - public: - Progress() : - delegate_(nil), - percent_(0) - { + 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]; + [delegate_ performSelectorOnMainThread:@selector(setProgressStatus:) withObject:nil waitUntilDone:YES]; } }; /* }}} */ - /* Database Interface {{{ */ typedef std::map< unsigned long, _H > SourceMap; @@ -1344,13 +1328,15 @@ typedef std::map< unsigned long, _H > SourceMap; SPtr manager_; pkgSourceList *list_; - SourceMap sources_; - CFMutableArrayRef deadSources_; + SourceMap sourceMap_; + NSMutableArray *sourceList_; + CFMutableArrayRef packages_; - _transient NSObject *delegate_; + _transient NSObject *delegate_; + _transient NSObject *progress_; + Status status_; - Progress progress_; int cydiafd_; int statusfd_; @@ -1378,7 +1364,8 @@ typedef std::map< unsigned long, _H > SourceMap; - (pkgSourceList &) list; - (NSArray *) packages; - (NSArray *) sources; -- (void) reloadData; +- (Source *) sourceWithKey:(NSString *)key; +- (void) reloadDataWithInvocation:(NSInvocation *)invocation; - (void) configure; - (bool) prepare; @@ -1388,44 +1375,145 @@ typedef std::map< unsigned long, _H > SourceMap; - (void) updateWithStatus:(Status &)status; -- (void) setDelegate:(id)delegate; +- (void) setDelegate:(NSObject *)delegate; + +- (void) setProgressDelegate:(NSObject *)delegate; +- (NSObject *) progressDelegate; + - (Source *) getSource:(pkgCache::PkgFileIterator)file; - (NSString *) mappedSectionForPointer:(const char *)pointer; @end /* }}} */ -/* Delegate Helpers {{{ */ -@implementation NSObject (ProgressDelegate) +/* ProgressEvent Implementation {{{ */ +@implementation CydiaProgressEvent -- (void) _setProgressErrorPackage:(NSArray *)args { - [self performSelector:@selector(setProgressError:forPackage:) - withObject:[args objectAtIndex:0] - withObject:([args count] == 1 ? nil : [args objectAtIndex:1]) - ]; ++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type { + return [[[CydiaProgressEvent alloc] initWithMessage:message ofType:type] autorelease]; } -- (void) _setProgressErrorTitle:(NSArray *)args { - [self performSelector:@selector(setProgressError:withTitle:) - withObject:[args objectAtIndex:0] - withObject:([args count] == 1 ? nil : [args objectAtIndex:1]) - ]; ++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package { + CydiaProgressEvent *event([self eventWithMessage:message ofType:type]); + [event setPackage:package]; + return event; } -- (void) _setProgressError:(NSString *)error withTitle:(NSString *)title { - [self performSelectorOnMainThread:@selector(_setProgressErrorTitle:) - withObject:[NSArray arrayWithObjects:error, title, nil] - waitUntilDone:YES - ]; ++ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forItem:(pkgAcquire::ItemDesc &)item { + CydiaProgressEvent *event([self eventWithMessage:message ofType:type]); + + NSString *description([NSString stringWithUTF8String:item.Description.c_str()]); + NSArray *fields([description componentsSeparatedByString:@" "]); + [event setItem:fields]; + + if ([fields count] > 3) { + [event setPackage:[fields objectAtIndex:2]]; + [event setVersion:[fields objectAtIndex:3]]; + } + + [event setURL:[NSString stringWithUTF8String:item.URI.c_str()]]; + + return event; +} + ++ (NSArray *) _attributeKeys { + return [NSArray arrayWithObjects: + @"item", + @"message", + @"package", + @"type", + @"url", + @"version", + nil]; } -- (void) setProgressError:(NSString *)error forPackage:(NSString *)id { - Package *package = id == nil ? nil : [[Database sharedInstance] packageWithName:id]; +- (NSArray *) attributeKeys { + return [[self class] _attributeKeys]; +} - [self performSelector:@selector(setProgressError:withTitle:) - withObject:error - withObject:(package == nil ? id : [package name]) - ]; ++ (BOOL) isKeyExcludedFromWebScript:(const char *)name { + return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name]; +} + +- (id) initWithMessage:(NSString *)message ofType:(NSString *)type { + if ((self = [super init]) != nil) { + message_ = message; + type_ = type; + } return self; +} + +- (NSString *) message { + return message_; +} + +- (NSString *) type { + return type_; +} + +- (NSArray *) item { + return (id) item_ ?: [NSNull null]; +} + +- (void) setItem:(NSArray *)item { + item_ = item; +} + +- (NSString *) package { + return (id) package_ ?: [NSNull null]; +} + +- (void) setPackage:(NSString *)package { + package_ = package; +} + +- (NSString *) url { + return (id) url_ ?: [NSNull null]; +} + +- (void) setURL:(NSString *)url { + url_ = url; +} + +- (void) setVersion:(NSString *)version { + version_ = version; +} + +- (NSString *) version { + return (id) version_ ?: [NSNull null]; +} + +- (NSString *) compound:(NSString *)value { + if (value != nil) { + NSString *mode(nil); { + NSString *type([self type]); + if ([type isEqualToString:@"ERROR"]) + mode = UCLocalize("ERROR"); + else if ([type isEqualToString:@"WARNING"]) + mode = UCLocalize("WARNING"); + } + + if (mode != nil) + value = [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), mode, value]; + } + + return value; +} + +- (NSString *) compoundMessage { + return [self compound:[self message]]; +} + +- (NSString *) compoundTitle { + NSString *title; + + if (package_ == nil) + title = nil; + else if (Package *package = [[Database sharedInstance] packageWithName:package_]) + title = [package name]; + else + title = package_; + + return [self compound:title]; } @end @@ -1461,7 +1549,7 @@ struct MetaValue : static Cytore::File MetaFile_; // }}} // Cytore Helper Functions {{{ -static PackageValue *PackageFind(const char *name, size_t length) { +static PackageValue *PackageFind(const char *name, size_t length, bool *fail = NULL) { SplitHash nhash = { hashlittle(name, length) }; PackageValue *metadata; @@ -1471,6 +1559,14 @@ static PackageValue *PackageFind(const char *name, size_t length) { *offset = MetaFile_.New(length + 1); metadata = &MetaFile_.Get(*offset); + if (metadata == NULL) { + if (fail != NULL) + *fail = true; + + metadata = new PackageValue(); + memset(metadata, 0, sizeof(*metadata)); + } + memcpy(metadata->name_, name, length + 1); metadata->nhash_ = nhash.u16[1]; } else { @@ -1486,13 +1582,15 @@ static PackageValue *PackageFind(const char *name, size_t length) { } static void PackageImport(const void *key, const void *value, void *context) { + bool &fail(*reinterpret_cast(context)); + char buffer[1024]; if (!CFStringGetCString((CFStringRef) key, buffer, sizeof(buffer), kCFStringEncodingUTF8)) { NSLog(@"failed to import package %@", key); return; } - PackageValue *metadata(PackageFind(buffer, strlen(buffer))); + PackageValue *metadata(PackageFind(buffer, strlen(buffer), &fail)); NSDictionary *package((NSDictionary *) value); if (NSNumber *subscribed = [package objectForKey:@"IsSubscribed"]) @@ -1613,7 +1711,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 { @@ -1778,100 +1888,239 @@ 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]; } -@end -/* }}} */ -/* Package Class {{{ */ -struct ParsedPackage { - CYString tagline_; ++ (BOOL) isKeyExcludedFromWebScript:(const char *)name { + return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name]; +} - CYString icon_; +- (NSString *) operator { + return operator_; +} - CYString depiction_; - CYString homepage_; +- (NSString *) value { + return value_; +} - CYString sponsor_; - CYString author_; +@end +/* }}} */ +/* CydiaClause Class {{{ */ +@interface CydiaClause : NSObject { + NSString *package_; + CydiaOperation *version_; +} - CYString bugs_; - CYString support_; -}; +- (NSString *) package; +- (CydiaOperation *) version; -@interface Package : NSObject { - uint32_t era_ : 26; - uint32_t role_ : 3; - uint32_t essential_ : 1; - uint32_t obsolete_ : 1; - uint32_t ignored_ : 1; +@end - apr_pool_t *pool_; +@implementation CydiaClause - _transient Database *database_; +- (void) dealloc { + [package_ release]; + [version_ release]; + [super dealloc]; +} - pkgCache::VerIterator version_; - pkgCache::PkgIterator iterator_; - pkgCache::VerFileIterator file_; +- (id) initWithIterator:(pkgCache::DepIterator &)dep { + if ((self = [super init]) != nil) { + package_ = [[NSString alloc] initWithUTF8String:dep.TargetPkg().Name()]; - CYString id_; - CYString name_; + if (const char *version = dep.TargetVer()) + version_ = [[CydiaOperation alloc] initWithOperator:dep.CompType() value:version]; + else + version_ = [[NSNull null] retain]; + } return self; +} - CYString latest_; - CYString installed_; ++ (NSArray *) _attributeKeys { + return [NSArray arrayWithObjects: + @"package", + @"version", + nil]; +} - const char *section_; - _transient NSString *section$_; +- (NSArray *) attributeKeys { + return [[self class] _attributeKeys]; +} - Source *source_; ++ (BOOL) isKeyExcludedFromWebScript:(const char *)name { + return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name]; +} - PackageValue *metadata_; - ParsedPackage *parsed_; +- (NSString *) package { + return package_; +} - NSMutableArray *tags_; +- (CydiaOperation *) version { + return version_; } -- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database; -+ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database; +@end +/* }}} */ +/* CydiaRelation Class {{{ */ +@interface CydiaRelation : NSObject { + NSString *relationship_; + NSMutableArray *clauses_; +} -- (pkgCache::PkgIterator) iterator; -- (void) parse; +- (NSString *) relationship; +- (NSArray *) clauses; -- (NSString *) section; -- (NSString *) simpleSection; +@end -- (NSString *) longSection; -- (NSString *) shortSection; +@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 +/* }}} */ +/* Package Class {{{ */ +struct ParsedPackage { + CYString tagline_; + + CYString icon_; + + CYString depiction_; + CYString homepage_; + + CYString sponsor_; + CYString author_; + + CYString bugs_; + CYString support_; +}; + +@interface Package : NSObject { + uint32_t era_ : 26; + uint32_t role_ : 3; + uint32_t essential_ : 1; + uint32_t obsolete_ : 1; + uint32_t ignored_ : 1; + + apr_pool_t *pool_; + + _transient Database *database_; + + pkgCache::VerIterator version_; + pkgCache::PkgIterator iterator_; + pkgCache::VerFileIterator file_; + + CYString id_; + CYString name_; + + CYString latest_; + CYString installed_; + + const char *section_; + _transient NSString *section$_; + + Source *source_; + + PackageValue *metadata_; + ParsedPackage *parsed_; + + NSMutableArray *tags_; +} + +- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database; ++ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database; + +- (pkgCache::PkgIterator) iterator; +- (void) parse; + +- (NSString *) section; +- (NSString *) simpleSection; + +- (NSString *) longSection; +- (NSString *) shortSection; - (NSString *) uri; @@ -2088,8 +2337,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; } @@ -2099,7 +2357,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 { @@ -2110,6 +2394,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; @@ -2501,7 +2807,7 @@ struct PackageNameOrdering : _end _profile(Package$visible$isSectionVisible) - if (section != nil && !isSectionVisible(section)) + if (!isSectionVisible(section)) return false; _end @@ -2735,6 +3041,8 @@ struct PackageNameOrdering : if (range.location != NSNotFound) return YES; + [self parse]; + range = [[self shortDescription] rangeOfString:text options:MatchCompareOptions_]; if (range.location != NSNotFound) return YES; @@ -3040,8 +3348,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_); @@ -3085,28 +3392,36 @@ static NSString *Warning_; lprintf("S:%s\n", data); if (conffile_r(data, size)) { - [delegate_ setConfigurationData:conffile_r[1]]; + // status: /fail : conffile-prompt : '/fail' '/fail.dpkg-new' 1 1 + [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:conffile_r[1] waitUntilDone:YES]; } else if (strncmp(data, "status: ", 8) == 0) { - NSString *string = [NSString stringWithUTF8String:(data + 8)]; - [delegate_ setProgressTitle:string]; + // status: : {unpacked,half-configured,installed} + CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 8)] ofType:@"STATUS"]); + [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES]; + } else if (strncmp(data, "processing: ", 12) == 0) { + // processing: configure: config-test + CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 12)] ofType:@"STATUS"]); + [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES]; } else if (pmstatus_r(data, size)) { std::string type([pmstatus_r[1] UTF8String]); - NSString *id = pmstatus_r[2]; + + NSString *package = pmstatus_r[2]; + if ([package isEqualToString:@"dpkg-exec"]) + package = nil; float percent([pmstatus_r[3] floatValue]); - [delegate_ setProgressPercent:(percent / 100)]; + [progress_ performSelectorOnMainThread:@selector(setProgressPercent:) withObject:[NSNumber numberWithFloat:(percent / 100)] waitUntilDone:YES]; NSString *string = pmstatus_r[4]; - if (type == "pmerror") - [delegate_ performSelectorOnMainThread:@selector(_setProgressErrorPackage:) - withObject:[NSArray arrayWithObjects:string, id, nil] - waitUntilDone:YES - ]; - else if (type == "pmstatus") { - [delegate_ setProgressTitle:string]; + if (type == "pmerror") { + CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:@"ERROR" forPackage:package]); + [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES]; + } else if (type == "pmstatus") { + CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:@"STATUS" forPackage:package]); + [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES]; } else if (type == "pmconffile") - [delegate_ setConfigurationData:string]; + [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:string waitUntilDone:YES]; else lprintf("E:unknown pmstatus\n"); } else @@ -3123,7 +3438,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); @@ -3159,7 +3476,7 @@ static NSString *Warning_; capacity += 1024; packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL); - deadSources_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + sourceList_ = [[NSMutableArray alloc] initWithCapacity:16]; int fds[2]; @@ -3231,104 +3548,37 @@ static NSString *Warning_; } - (NSArray *) sources { - NSMutableArray *sources([NSMutableArray arrayWithCapacity:sources_.size()]); - for (SourceMap::const_iterator i(sources_.begin()); i != sources_.end(); ++i) - [sources addObject:i->second]; - [sources addObjectsFromArray:(NSArray *)deadSources_]; - return sources; + return sourceList_; } -- (NSArray *) issues { - if (cache_->BrokenCount() == 0) - return nil; - - NSMutableArray *issues([NSMutableArray arrayWithCapacity:4]); - - for (Package *package in [self packages]) { - if (![package broken]) - continue; - pkgCache::PkgIterator pkg([package iterator]); - - NSMutableArray *entry([NSMutableArray arrayWithCapacity:4]); - [entry addObject:[package name]]; - [issues addObject:entry]; - - pkgCache::VerIterator ver(cache_[pkg].InstVerIter(cache_)); - if (ver.end()) - continue; - - for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) { - pkgCache::DepIterator start; - pkgCache::DepIterator end; - dep.GlobOr(start, end); // ++dep - - if (!cache_->IsImportantDep(end)) - continue; - if ((cache_[end] & pkgDepCache::DepGInstall) != 0) - continue; - - NSMutableArray *failure([NSMutableArray arrayWithCapacity:4]); - [entry addObject:failure]; - [failure addObject:[NSString stringWithUTF8String:start.DepType()]]; - - NSString *name([NSString stringWithUTF8String:start.TargetPkg().Name()]); - if (Package *package = [self packageWithName:name]) - name = [package name]; - [failure addObject:name]; - - pkgCache::PkgIterator target(start.TargetPkg()); - if (target->ProvidesList != 0) - [failure addObject:@"?"]; - else { - pkgCache::VerIterator ver(cache_[target].InstVerIter(cache_)); - if (!ver.end()) - [failure addObject:[NSString stringWithUTF8String:ver.VerStr()]]; - else if (!cache_[target].CandidateVerIter(cache_).end()) - [failure addObject:@"-"]; - else if (target->ProvidesList == 0) - [failure addObject:@"!"]; - else - [failure addObject:@"%"]; - } - - _forever { - if (start.TargetVer() != 0) - [failure addObject:[NSString stringWithFormat:@"%s %s", start.CompType(), start.TargetVer()]]; - if (start == end) - break; - ++start; - } - } - } - - return issues; +- (Source *) sourceWithKey:(NSString *)key { + for (Source *source in [self sources]) { + if ([[source key] isEqualToString:key]) + return source; + } return nil; } - (bool) popErrorWithTitle:(NSString *)title { bool fatal(false); - std::string message; while (!_error->empty()) { std::string error; bool warning(!_error->PopMessage(error)); if (!warning) fatal = true; + for (;;) { size_t size(error.size()); if (size == 0 || error[size - 1] != '\n') break; error.resize(size - 1); } + lprintf("%c:[%s]\n", warning ? 'W' : 'E', error.c_str()); - if (!message.empty()) - message += "\n\n"; - message += error; + [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? @"WARNING" : @"ERROR")] forTask:title]; } - if (fatal && !message.empty()) - [delegate_ _setProgressError:[NSString stringWithUTF8String:message.c_str()] withTitle:[NSString stringWithFormat:Colon_, fatal ? Error_ : Warning_, title]]; - return fatal; } @@ -3336,14 +3586,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(); @@ -3372,10 +3622,14 @@ static NSString *Warning_; if (chk != -1) close(chk); + if (invocation != nil) + [invocation invoke]; + NSString *title(UCLocalize("DATABASE")); _trace(); - if (!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()); @@ -3384,16 +3638,18 @@ static NSString *Warning_; [delegate_ repairWithSelector:@selector(configure)]; else if (error == "The package lists or status file could not be parsed or opened.") [delegate_ repairWithSelector:@selector(update)]; - // else if (error == "Could not 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)") + // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)") + // else if (error == "Malformed Status line") // else if (error == "The list of sources could not be read.") - else - [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, warning ? Warning_ : Error_, title]]; + else { + [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? @"WARNING" : @"ERROR")] forTask:title]; + return; + } if (warning) goto pop; _error->Discard(); - return; } _trace(); @@ -3412,7 +3668,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; } @@ -3424,7 +3680,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; } @@ -3433,19 +3689,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 *indices = (*source)->GetIndexFiles(); for (std::vector::const_iterator index = indices->begin(); index != indices->end(); ++index) // XXX: this could be more intelligent if (dynamic_cast(*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]); } { @@ -3506,12 +3760,11 @@ static NSString *Warning_; delete resolver_; resolver_ = new pkgProblemResolver(cache_); - for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator) { - if (!cache_[iterator].Keep()) { + for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator) + if (!cache_[iterator].Keep()) cache_->MarkKeep(iterator, false); + else if ((cache_[iterator].iFlags & pkgDepCache::ReInstall) != 0) cache_->SetReInstall(iterator, false); - } - } } } - (void) configure { @@ -3577,6 +3830,9 @@ static NSString *Warning_; } - (void) perform { + bool substrate(RestartSubstrate_); + RestartSubstrate_ = false; + NSString *title(UCLocalize("PERFORM_SELECTIONS")); NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; { @@ -3587,13 +3843,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) @@ -3601,27 +3858,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_); @@ -3664,35 +3913,47 @@ static NSString *Warning_; } - (void) updateWithStatus:(Status &)status { - _transient NSObject *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 *)delegate { delegate_ = delegate; +} + +- (void) setProgressDelegate:(NSObject *)delegate { + progress_ = delegate; status_.setDelegate(delegate); - progress_.setDelegate(delegate); +} + +- (NSObject *) 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 { @@ -3729,23 +3990,6 @@ static NSString *Warning_; @end /* }}} */ -/* Confirmation Controller {{{ */ -bool DepSubstrate(const pkgCache::VerIterator &iterator) { - if (!iterator.end()) - for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) { - if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends) - continue; - pkgCache::PkgIterator package(dep.TargetPkg()); - if (package.end()) - continue; - if (strcmp(package.Name(), "mobilesubstrate") == 0) - return true; - } - - return false; -} -/* }}} */ - /* Web Scripting {{{ */ @interface CydiaObject : NSObject { id indirect_; @@ -3753,6 +3997,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } - (id) initWithDelegate:(IndirectDelegate *)indirect; + @end @implementation CydiaObject @@ -3773,7 +4018,19 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } + (NSArray *) _attributeKeys { - return [NSArray arrayWithObjects:@"device", @"firewire", @"imei", @"mac", @"serial", nil]; + return [NSArray arrayWithObjects: + @"device", + @"ecid", + @"firmware", + @"hostname", + @"idiom", + @"model", + @"plmn", + @"role", + @"serial", + @"token", + @"version", + nil]; } - (NSArray *) attributeKeys { @@ -3784,63 +4041,104 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name]; } +- (NSString *) version { + return @ Cydia_; +} + - (NSString *) device { return [[UIDevice currentDevice] uniqueIdentifier]; } -#if 0 // XXX: implement! -- (NSString *) mac { - if (![indirect_ promptForSensitive:@"Mac Address"]) - return nil; +- (NSString *) firmware { + return [[UIDevice currentDevice] systemVersion]; +} + +- (NSString *) hostname { + return [[UIDevice currentDevice] name]; +} + +- (NSString *) idiom { + return (id) Idiom_ ?: [NSNull null]; +} + +- (NSString *) plmn { + return (id) PLMN_ ?: [NSNull null]; +} + +- (NSString *) ecid { + return (id) ChipID_ ?: [NSNull null]; } - (NSString *) serial { - if (![indirect_ promptForSensitive:@"Serial #"]) - return nil; + return SerialNumber_; } -- (NSString *) firewire { - if (![indirect_ promptForSensitive:@"Firewire GUID"]) - return nil; +- (NSString *) role { + return (id) Role_ ?: [NSNull null]; } -- (NSString *) imei { - if (![indirect_ promptForSensitive:@"IMEI"]) - return nil; +- (NSString *) model { + return [NSString stringWithUTF8String:Machine_]; +} + +- (NSString *) token { + return (id) Token_ ?: [NSNull null]; } -#endif + (NSString *) webScriptNameForSelector:(SEL)selector { - if (selector == @selector(close)) + if (false); + else if (selector == @selector(addTrivialSource:)) + return @"addTrivialSource"; + else if (selector == @selector(close)) return @"close"; + else if (selector == @selector(du:)) + return @"du"; + else if (selector == @selector(stringWithFormat:arguments:)) + return @"format"; + else if (selector == @selector(getAllSources)) + return @"getAllSourcs"; + else if (selector == @selector(getKernelNumber:)) + return @"getKernelNumber"; + else if (selector == @selector(getKernelString:)) + return @"getKernelString"; else if (selector == @selector(getInstalledPackages)) return @"getInstalledPackages"; else if (selector == @selector(getPackageById:)) return @"getPackageById"; else if (selector == @selector(installPackages:)) return @"installPackages"; + else if (selector == @selector(localizedStringForKey:value:table:)) + return @"localize"; + else if (selector == @selector(refreshSources)) + return @"refreshSources"; + else if (selector == @selector(removeButton)) + return @"removeButton"; + else if (selector == @selector(substitutePackageNames:)) + return @"substitutePackageNames"; + 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(setHidesBackButton:)) + return @"setHidesBackButton"; + else if (selector == @selector(setHidesNavigationBar:)) + return @"setHidesNavigationBar"; + else if (selector == @selector(setNavigationBarStyle:)) + return @"setNavigationBarStyle"; + else if (selector == @selector(setNavigationBarTintRed:green:blue:alpha:)) + return @"setNavigationBarTintColor"; 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; } @@ -3853,19 +4151,69 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { return [feature isEqualToString:@"window.open"]; } +- (NSNumber *) getKernelNumber:(NSString *)name { + const char *string([name UTF8String]); + + size_t size; + if (sysctlbyname(string, NULL, &size, NULL, 0) == -1) + return (id) [NSNull null]; + + if (size != sizeof(int)) + return (id) [NSNull null]; + + int value; + if (sysctlbyname(string, &value, &size, NULL, 0) == -1) + return (id) [NSNull null]; + + return [NSNumber numberWithInt:value]; +} + +- (NSString *) getKernelString:(NSString *)name { + const char *string([name UTF8String]); + + size_t size; + if (sysctlbyname(string, NULL, &size, NULL, 0) == -1) + return (id) [NSNull null]; + + char value[size + 1]; + if (sysctlbyname(string, value, &size, NULL, 0) == -1) + return (id) [NSNull null]; + + // XXX: just in case you request something ludicrous + value[size] = '\0'; + + return [NSString stringWithCString:value]; +} + +- (void) addTrivialSource:(NSString *)href { + [delegate_ performSelectorOnMainThread:@selector(addTrivialSource:) withObject:href waitUntilDone:NO]; +} + +- (void) refreshSources { + [delegate_ performSelectorOnMainThread:@selector(syncData) withObject:nil waitUntilDone:NO]; +} + +- (NSArray *) getAllSources { + return [[Database sharedInstance] sources]; +} + - (NSArray *) getInstalledPackages { - NSArray *packages([[Database sharedInstance] packages]); + Database *database([Database sharedInstance]); +@synchronized (database) { + NSArray *packages([database packages]); NSMutableArray *installed([NSMutableArray arrayWithCapacity:1024]); for (Package *package in packages) - if ([package installed] != nil) + if (![package uninstalled]) [installed addObject:package]; return installed; -} +} } - (Package *) getPackageById:(NSString *)id { - Package *package([[Database sharedInstance] packageWithName:id]); - [package parse]; - return package; + if (Package *package = [[Database sharedInstance] packageWithName:id]) { + [package parse]; + return package; + } else + return (Package *) [NSNull null]; } - (NSArray *) statfs:(NSString *)path { @@ -3926,40 +4274,79 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } - (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) setButtonImage:(NSString *)button withStyle:(NSString *)style toFunction:(id)function { - [indirect_ setButtonImage:button withStyle:style toFunction:function]; +- (NSString *) substitutePackageNames:(NSString *)message { + NSMutableArray *words([[message componentsSeparatedByString:@" "] mutableCopy]); + for (size_t i(0), e([words count]); i != e; ++i) { + NSString *word([words objectAtIndex:i]); + if (Package *package = [[Database sharedInstance] packageWithName:word]) + [words replaceObjectAtIndex:i withObject:[package name]]; + } + + return [words componentsJoinedByString:@" "]; } -- (void) setButtonTitle:(NSString *)button withStyle:(NSString *)style toFunction:(id)function { +- (void) removeButton { + [indirect_ removeButton]; +} + +- (void) setButtonImage:(NSString *)button withStyle:(NSString *)style toFunction:(id)function { + [indirect_ setButtonImage:button withStyle:style toFunction:function]; +} + +- (void) setButtonTitle:(NSString *)button withStyle:(NSString *)style toFunction:(id)function { [indirect_ setButtonTitle:button withStyle:style toFunction:function]; } -- (void) setSpecial:(id)function { - [indirect_ setSpecial:function]; +- (void) setHidesBackButton:(NSString *)value { + [indirect_ performSelectorOnMainThread:@selector(setHidesBackButtonByNumber:) withObject:value waitUntilDone:NO]; } -- (void) setToken:(NSString *)token { - if (Token_ != nil) - [Token_ release]; - Token_ = [token retain]; +- (void) setHidesNavigationBar:(NSString *)value { + [indirect_ performSelectorOnMainThread:@selector(setHidesNavigationBarByNumber:) withObject:value waitUntilDone:NO]; +} + +- (void) setNavigationBarStyle:(NSString *)value { + [indirect_ performSelectorOnMainThread:@selector(setNavigationBarStyle:) withObject:value waitUntilDone:NO]; +} + +- (void) setNavigationBarTintRed:(NSNumber *)red green:(NSNumber *)green blue:(NSNumber *)blue alpha:(NSNumber *)alpha { + float opacity(alpha == (id) [WebUndefined undefined] ? 1 : [alpha floatValue]); + UIColor *color([UIColor colorWithRed:[red floatValue] green:[green floatValue] blue:[blue floatValue] alpha:opacity]); + [indirect_ performSelectorOnMainThread:@selector(setNavigationBarTintColor:) withObject:color waitUntilDone:NO]; +} + +- (void) _setToken:(NSString *)token { + Token_ = token; + + if (token == nil) + [Metadata_ removeObjectForKey:@"Token"]; + else + [Metadata_ setObject:Token_ forKey:@"Token"]; - [Metadata_ setObject:Token_ forKey:@"Token"]; Changed_ = true; } +- (void) setToken:(NSString *)token { + [self performSelectorOnMainThread:@selector(_setToken:) withObject:token waitUntilDone:NO]; +} + - (void) setPopupHook:(id)function { [indirect_ setPopupHook:function]; } +- (void) scrollToBottom:(NSNumber *)animated { + [indirect_ performSelectorOnMainThread:@selector(scrollToBottomAnimated:) withObject:animated waitUntilDone:NO]; +} + - (void) setViewportWidth:(float)width { - [indirect_ setViewportWidth:width]; + [indirect_ setViewportWidthOnMainThread:width]; } - (NSString *) stringWithFormat:(NSString *)format arguments:(WebScriptObject *)arguments { @@ -3968,7 +4355,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { id values[count]; for (unsigned i(0); i != count; ++i) values[i] = [arguments objectAtIndex:i]; - return [[[NSString alloc] initWithFormat:format arguments:*(reinterpret_cast(&values))] autorelease]; + return [[[NSString alloc] initWithFormat:format arguments:reinterpret_cast(values)] autorelease]; } - (NSString *) localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)table { @@ -3984,9 +4371,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { /* @ Loading... Indicator {{{ */ @interface CYLoadingIndicator : UIView { - UIActivityIndicatorView *spinner_; - UILabel *label_; - UIView *container_; + _H spinner_; + _H label_; + _H container_; } @property (readonly, nonatomic) UILabel *label; @@ -3996,8 +4383,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { @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]; @@ -4036,51 +4423,67 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [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 : UIViewController { - CYLoadingIndicator *indicator_; - UITabBar *tabbar_; - UINavigationBar *navbar_; +@interface CYEmulatedLoadingController : CYViewController { + _transient Database *database_; + _H indicator_; + _H tabbar_; + _H navbar_; } + @end @implementation CYEmulatedLoadingController -- (CYEmulatedLoadingController *) init { - if ((self = [super init])) { - [[self view] setBackgroundColor:[UIColor pinStripeColor]]; - - indicator_ = [[CYLoadingIndicator alloc] initWithFrame:[[self view] bounds]]; - [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [[self view] addSubview:indicator_]; - [indicator_ release]; - - tabbar_ = [[UITabBar alloc] initWithFrame:CGRectMake(0, 0, 0, 49.0f)]; - [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_]; - [tabbar_ release]; - - navbar_ = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 0, 44.0f)]; - [navbar_ setFrame:CGRectMake(0.0f, 0.0f, [[self view] bounds].size.width, [navbar_ bounds].size.height)]; - [navbar_ setAutoresizingMask:UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth]; - [[self view] addSubview:navbar_]; - [navbar_ release]; +- (id) initWithDatabase:(Database *)database { + if ((self = [super init]) != nil) { + database_ = database; } return self; } -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { - return (IsWildcat_ || orientation == UIInterfaceOrientationPortrait); +// XXX: factor this out somewhere +- (UIColor *) groupTableViewBackgroundColor { + UIDevice *device([UIDevice currentDevice]); + bool iPad([device respondsToSelector:@selector(userInterfaceIdiom)] && [device userInterfaceIdiom] == UIUserInterfaceIdiomPad); + return iPad ? [UIColor colorWithRed:0.821 green:0.834 blue:0.860 alpha:1] : [UIColor groupTableViewBackgroundColor]; +} + +- (void) loadView { + [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]]; + [[self view] setBackgroundColor:[self groupTableViewBackgroundColor]]; + + 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)] 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)] 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_ = nil; + tabbar_ = nil; + navbar_ = nil; } @end @@ -4100,6 +4503,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [super dealloc]; } +- (NSURL *) navigationURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://url/%@", [[[webview_ request] URL] absoluteString]]]; +} + - (void) setHeaders:(NSDictionary *)headers forHost:(NSString *)host { } @@ -4108,18 +4515,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { WebDataSource *source([frame dataSource]); NSURLResponse *response([source response]); + NSURL *url([response URL]); NSString *scheme([url scheme]); - - NSHTTPURLResponse *http; - if (scheme != nil && ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])) - http = (NSHTTPURLResponse *) response; - else - http = nil; - - NSDictionary *headers([http allHeaderFields]); NSString *host([url host]); - [self setHeaders:headers forHost:host]; + + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *http((NSHTTPURLResponse *) response); + NSDictionary *headers([http allHeaderFields]); + [self setHeaders:headers forHost:host]; + } if ( [host isEqualToString:@"cydia.saurik.com"] || @@ -4157,12 +4562,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { WebView *webview([[webview_ _documentView] webView]); - Package *package([[Database sharedInstance] packageWithName:@"cydia"]); - - NSString *application = package == nil ? @"Cydia" : [NSString - stringWithFormat:@"Cydia/%@", - [package installed] - ]; + NSString *application([NSString stringWithFormat:@"Cydia/%@", @ Cydia_]); if (Safari_ != nil) application = [NSString stringWithFormat:@"Safari/%@ %@", Safari_, application]; @@ -4178,7 +4578,58 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { @end /* }}} */ -/* Confirmation {{{ */ +// CydiaScript {{{ +@interface NSObject (CydiaScript) +- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context; +@end + +@implementation NSObject (CydiaScript) + +- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context { + return self; +} + +@end + +@implementation NSArray (CydiaScript) + +- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context { + WebScriptObject *object([context evaluateWebScript:@"[]"]); + for (size_t i(0), e([self count]); i != e; ++i) + [object setWebScriptValueAtIndex:i value:[[self objectAtIndex:i] Cydia$webScriptObjectInContext:context]]; + return object; +} + +@end + +@implementation NSDictionary (CydiaScript) + +- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context { + WebScriptObject *object([context evaluateWebScript:@"({})"]); + for (id i in self) + [object setValue:[[self objectForKey:i] Cydia$webScriptObjectInContext:context] forKey:i]; + return object; +} + +@end +// }}} + +/* Confirmation Controller {{{ */ +bool DepSubstrate(const pkgCache::VerIterator &iterator) { + if (!iterator.end()) + for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) { + if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends) + continue; + pkgCache::PkgIterator package(dep.TargetPkg()); + if (package.end()) + continue; + if (strcmp(package.Name(), "mobilesubstrate") == 0) + return true; + } + + return false; +} + @protocol ConfirmationControllerDelegate - (void) cancelAndClear:(bool)clear; - (void) confirmWithNavigationController:(UINavigationController *)navigation; @@ -4187,10 +4638,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { @interface ConfirmationController : CYBrowserController { _transient Database *database_; + UIAlertView *essential_; - NSArray *changes_; - NSArray *issues_; - NSArray *sizes_; + + NSDictionary *changes_; + NSMutableArray *issues_; + NSDictionary *sizes_; + BOOL substrate_; } @@ -4202,24 +4656,29 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { - (void) dealloc { [changes_ release]; - if (issues_ != nil) - [issues_ release]; + [issues_ release]; [sizes_ release]; + if (essential_ != nil) [essential_ release]; + [super dealloc]; } +- (void) complete { + if (substrate_) + RestartSubstrate_ = true; + [delegate_ confirmWithNavigationController:[self navigationController]]; +} + - (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { NSString *context([alert context]); if ([context isEqualToString:@"remove"]) { - if (button == [alert cancelButtonIndex]) { + if (button == [alert cancelButtonIndex]) [self dismissModalViewControllerAnimated:YES]; - } else if (button == [alert firstOtherButtonIndex]) { - if (substrate_) - Finish_ = 2; - [delegate_ confirmWithNavigationController:[self navigationController]]; + else if (button == [alert firstOtherButtonIndex]) { + [self complete]; } [alert dismissWithClickedButtonIndex:-1 animated:YES]; @@ -4243,49 +4702,140 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { [super webView:view didClearWindowObject:window forFrame:frame]; - [window setValue:changes_ forKey:@"changes"]; - [window setValue:issues_ forKey:@"issues"]; - [window setValue:sizes_ forKey:@"sizes"]; - [window setValue:self forKey:@"queue"]; + + [window setValue:[[NSDictionary dictionaryWithObjectsAndKeys: + changes_, @"changes", + issues_, @"issues", + sizes_, @"sizes", + self, @"queue", + nil] Cydia$webScriptObjectInContext:window] forKey:@"cydiaConfirm"]; } - (id) initWithDatabase:(Database *)database { if ((self = [super init]) != nil) { database_ = database; - [[self navigationItem] setTitle:UCLocalize("CONFIRM")]; - - NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16]; - NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16]; - NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16]; - NSMutableArray *downgrading = [NSMutableArray arrayWithCapacity:16]; - NSMutableArray *removing = [NSMutableArray arrayWithCapacity:16]; + NSMutableArray *installs([NSMutableArray arrayWithCapacity:16]); + NSMutableArray *reinstalls([NSMutableArray arrayWithCapacity:16]); + NSMutableArray *upgrades([NSMutableArray arrayWithCapacity:16]); + NSMutableArray *downgrades([NSMutableArray arrayWithCapacity:16]); + NSMutableArray *removes([NSMutableArray arrayWithCapacity:16]); bool remove(false); + pkgCacheFile &cache([database_ cache]); + NSArray *packages([database_ packages]); pkgDepCache::Policy *policy([database_ policy]); - pkgCacheFile &cache([database_ cache]); - NSArray *packages = [database_ packages]; + issues_ = [[NSMutableArray arrayWithCapacity:4] retain]; + for (Package *package in packages) { - pkgCache::PkgIterator iterator = [package iterator]; + pkgCache::PkgIterator iterator([package iterator]); + NSString *name([package id]); + + if ([package broken]) { + NSMutableArray *reasons([NSMutableArray arrayWithCapacity:4]); + + [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys: + name, @"package", + reasons, @"reasons", + nil]]; + + pkgCache::VerIterator ver(cache[iterator].InstVerIter(cache)); + if (ver.end()) + continue; + + for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) { + pkgCache::DepIterator start; + pkgCache::DepIterator end; + dep.GlobOr(start, end); // ++dep + + if (!cache->IsImportantDep(end)) + continue; + if ((cache[end] & pkgDepCache::DepGInstall) != 0) + continue; + + NSMutableArray *clauses([NSMutableArray arrayWithCapacity:4]); + + [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithUTF8String:start.DepType()], @"relationship", + clauses, @"clauses", + nil]]; + + _forever { + NSString *reason, *installed((NSString *) [WebUndefined undefined]); + + pkgCache::PkgIterator target(start.TargetPkg()); + if (target->ProvidesList != 0) + reason = @"missing"; + else { + pkgCache::VerIterator ver(cache[target].InstVerIter(cache)); + if (!ver.end()) { + reason = @"installed"; + installed = [NSString stringWithUTF8String:ver.VerStr()]; + } else if (!cache[target].CandidateVerIter(cache).end()) + reason = @"uninstalled"; + else if (target->ProvidesList == 0) + reason = @"uninstallable"; + else + reason = @"virtual"; + } + + NSDictionary *version(start.TargetVer() == 0 ? [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithUTF8String:start.CompType()], @"operator", + [NSString stringWithUTF8String:start.TargetVer()], @"value", + nil]); + + [clauses addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithUTF8String:start.TargetPkg().Name()], @"package", + version, @"version", + reason, @"reason", + installed, @"installed", + nil]]; + + // yes, seriously. (wtf?) + if (start == end) + break; + ++start; + } + } + } + pkgDepCache::StateCache &state(cache[iterator]); - NSString *name([package name]); + static Pcre special_r("^(firmware$|gsc\\.|cy\\+)"); if (state.NewInstall()) - [installing addObject:name]; + [installs addObject:name]; else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall) - [reinstalling addObject:name]; + [reinstalls addObject:name]; else if (state.Upgrade()) - [upgrading addObject:name]; + [upgrades addObject:name]; else if (state.Downgrade()) - [downgrading addObject:name]; - else if (state.Delete()) { + [downgrades addObject:name]; + else if (!state.Delete()) + continue; + else if (special_r(name)) + [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNull null], @"package", + [NSArray arrayWithObjects: + [NSDictionary dictionaryWithObjectsAndKeys: + @"Conflicts", @"relationship", + [NSArray arrayWithObjects: + [NSDictionary dictionaryWithObjectsAndKeys: + name, @"package", + [NSNull null], @"version", + @"installed", @"reason", + nil], + nil], @"clauses", + nil], + nil], @"reasons", + nil]]; + else { if ([package essential]) remove = true; - [removing addObject:name]; - } else continue; + [removes addObject:name]; + } substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator)); substrate_ |= DepSubstrate(iterator.CurrentVer()); @@ -4301,7 +4851,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { message:UCLocalize("REMOVING_ESSENTIALS_EX") delegate:self cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")] - otherButtonTitles:[NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")], nil + otherButtonTitles: + [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")], + nil ]; [essential_ setContext:@"remove"]; @@ -4317,28 +4869,23 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [essential_ setContext:@"unable"]; } - changes_ = [[NSArray alloc] initWithObjects: - installing, - reinstalling, - upgrading, - downgrading, - removing, + changes_ = [[NSDictionary alloc] initWithObjectsAndKeys: + installs, @"installs", + reinstalls, @"reinstalls", + upgrades, @"upgrades", + downgrades, @"downgrades", + removes, @"removes", nil]; - issues_ = [database_ issues]; - if (issues_ != nil) - issues_ = [issues_ retain]; - - sizes_ = [[NSArray alloc] initWithObjects: - SizeString([database_ fetcher].FetchNeeded()), - SizeString([database_ fetcher].PartialPresent()), + sizes_ = [[NSDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithInteger:[database_ fetcher].FetchNeeded()], @"downloading", + [NSNumber numberWithInteger:[database_ fetcher].PartialPresent()], @"resuming", nil]; - [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"confirm" ofType:@"html"]]]; + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/confirm/", UI_]]]; [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:UCLocalize("CANCEL") - // OLD: [NSString stringWithFormat:UCLocalize("SLASH_DELIMITED"), UCLocalize("CANCEL"), UCLocalize("QUEUE")] style:UIBarButtonItemStylePlain target:self action:@selector(cancelButtonClicked) @@ -4346,21 +4893,19 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } return self; } +#if !AlwaysReload - (void) applyRightButton { -#if !AlwaysReload && !IgnoreInstall - if (issues_ == nil && ![self isLoading]) + if ([issues_ count] == 0 && ![self isLoading]) [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:UCLocalize("CONFIRM") - style:UIBarButtonItemStylePlain + style:UIBarButtonItemStyleDone target:self action:@selector(confirmButtonClicked) ] autorelease]]; else - [super applyRightButton]; -#else - [[self navigationItem] setRightBarButtonItem:nil]; -#endif + [[self navigationItem] setRightBarButtonItem:nil]; } +#endif - (void) cancelButtonClicked { [self dismissModalViewControllerAnimated:YES]; @@ -4369,16 +4914,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { #if !AlwaysReload - (void) confirmButtonClicked { -#if IgnoreInstall - return; -#endif if (essential_ != nil) [essential_ show]; - else { - if (substrate_) - Finish_ = 2; - [delegate_ confirmWithNavigationController:[self navigationController]]; - } + else + [self complete]; } #endif @@ -4386,196 +4925,204 @@ 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 percent_; -- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object; + float current_; + float total_; + float speed_; + + _H events_; + _H title_; + + _H status_; + _H finish_; +} -- (SEL) selector; -- (id) target; -- (id) object; @end -@implementation ProgressData +@implementation CydiaProgressData + ++ (NSArray *) _attributeKeys { + return [NSArray arrayWithObjects: + @"current", + @"events", + @"finish", + @"percent", + @"running", + @"speed", + @"title", + @"total", + 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) setPercent:(float)value { + percent_ = value; +} + +- (NSNumber *) percent { + return [NSNumber numberWithFloat:percent_]; +} + +- (void) setCurrent:(float)value { + current_ = value; +} + +- (NSNumber *) current { + return [NSNumber numberWithFloat:current_]; +} + +- (void) setTotal:(float)value { + total_ = value; +} + +- (NSNumber *) total { + return [NSNumber numberWithFloat:total_]; +} + +- (void) setSpeed:(float)value { + speed_ = value; +} + +- (NSNumber *) speed { + return [NSNumber numberWithFloat:speed_]; +} + +- (NSArray *) events { + return events_; +} + +- (void) removeAllEvents { + [events_ removeAllObjects]; +} + +- (void) addEvent:(CydiaProgressEvent *)event { + [events_ addObject:event]; +} + +- (void) setTitle:(NSString *)text { + title_ = text; +} + +- (NSString *) title { + return title_; +} + +- (void) setFinish:(NSString *)text { + finish_ = text; +} + +- (NSString *) finish { + return (id) finish_ ?: [NSNull null]; } -- (id) target { - return target_; +- (void) setRunning:(bool)running { + running_ = running; } -- (id) object { - return object_; +- (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 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; - -- (BOOL) isRunning; +- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title; -@end +- (void) setTitle:(NSString *)title; +- (void) setCancellable:(bool)cancellable; -@protocol ProgressControllerDelegate -- (void) progressControllerIsComplete:(ProgressController *)sender; @end @implementation ProgressController - (void) dealloc { - [database_ setDelegate:nil]; - [progress_ release]; - [output_ release]; - [status_ release]; - [close_ release]; - if (title_ != nil) - [title_ release]; + [database_ setProgressDelegate:nil]; + [progress_ setDelegate:nil]; [super dealloc]; } +- (void) updateCancel { + [[self navigationItem] setLeftBarButtonItem:(cancel_ == 1 ? [[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("CANCEL") + style:UIBarButtonItemStylePlain + target:self + action:@selector(cancel) + ] autorelease] : nil)]; +} + - (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]; - } return self; -} + progress_ = [[[CydiaProgressData alloc] init] autorelease]; + [progress_ setDelegate:self]; -- (void) positionViews { - CGRect bounds = [[self view] bounds]; - CGSize prgsize = [UIProgressBar defaultSize]; + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/progress/", UI_]]]; - CGRect prgrect = {{ - (bounds.size.width - prgsize.width) / 2, - bounds.size.height - prgsize.height - 20 - }, prgsize}; + [scroller_ setBackgroundColor:[UIColor blackColor]]; - float closewidth = std::min(bounds.size.width - 20, 300.0f); + [[self navigationItem] setHidesBackButton:YES]; - [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 - 62 - )]; - [close_ setFrame:CGRectMake( - (bounds.size.width - closewidth) / 2, - bounds.size.height - prgsize.height - 50, - closewidth, - 32 + prgsize.height - )]; + [self updateCancel]; + } return self; } -- (void) viewWillAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [[self navigationItem] setHidesBackButton:YES]; - [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack]; - - [self positionViews]; +- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { + [super webView:view didClearWindowObject:window forFrame:frame]; + [window setValue:progress_ forKey:@"cydiaProgress"]; } -- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { - [self positionViews]; +- (void) updateProgress { + [self dispatchEvent:@"CydiaProgressUpdate"]; } -- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { - NSString *context([alert context]); +- (void) viewWillAppear:(BOOL)animated { + if (![self hasLoaded]) + [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack]; - 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); - } + [super viewWillAppear:animated]; } -- (void) closeButtonPushed { - running_ = NO; - +- (void) close { UpdateExternalStatus(0); switch (Finish_) { case 0: - [self dismissModalViewControllerAnimated:YES]; break; case 1: @@ -4607,17 +5154,59 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { reboot2(RB_AUTOBOOT); break; } + + [super close]; +} + +- (void) setTitle:(NSString *)title { + [progress_ setTitle:title]; + [self updateProgress]; +} + +- (UIBarButtonItem *) rightButton { + return [[progress_ running] boolValue] ? nil : [[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("CLOSE") + style:UIBarButtonItemStylePlain + target:self + action:@selector(close) + ] autorelease]; } -- (void) _retachThread { - [[self navigationItem] setTitle:UCLocalize("COMPLETE")]; +- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title { + UpdateExternalStatus(1); + + [progress_ setRunning:true]; + [self setTitle:title]; + // implicit updateProgress + + SHA1SumValue notifyconf; { + FileFd file; + if (!file.Open(NotifyConfig_, FileFd::ReadOnly)) + _error->Discard(); + else { + MMap mmap(file, MMap::ReadOnly); + SHA1Summation sha1; + sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); + notifyconf = sha1.Result(); + } + } - [[self view] addSubview:close_]; - [progress_ removeFromSuperview]; - [status_ removeFromSuperview]; + SHA1SumValue springlist; { + FileFd file; + if (!file.Open(SpringBoard_, FileFd::ReadOnly)) + _error->Discard(); + else { + MMap mmap(file, MMap::ReadOnly); + SHA1Summation sha1; + sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); + springlist = sha1.Result(); + } + } - [database_ popErrorWithTitle:title_]; - [delegate_ progressControllerIsComplete:self]; + if (invocation != nil) { + [invocation yieldToSelector:@selector(invoke)]; + [self setTitle:@"COMPLETE"]; + } if (Finish_ < 4) { FileFd file; @@ -4627,7 +5216,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { MMap mmap(file, MMap::ReadOnly); SHA1Summation sha1; sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); - if (!(notifyconf_ == sha1.Result())) + if (!(notifyconf == sha1.Result())) Finish_ = 4; } } @@ -4640,203 +5229,87 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { MMap mmap(file, MMap::ReadOnly); SHA1Summation sha1; sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); - if (!(springlist_ == sha1.Result())) + if (!(springlist == sha1.Result())) Finish_ = 3; } } + if (Finish_ < 2) { + if (RestartSubstrate_) + Finish_ = 2; + } + + RestartSubstrate_ = false; + 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; + 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; } _trace(); system("su -c /usr/bin/uicache mobile"); _trace(); - UpdateExternalStatus(Finish_ == 0 ? 2 : 0); - - [delegate_ setStatusBarShowsProgress:NO]; -} - -- (void) _detachNewThreadData:(ProgressData *)data { _pooled - [[data target] performSelector:[data selector] withObject:[data object]]; - [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES]; -} - -- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title { - UpdateExternalStatus(1); - - if (title_ != nil) - [title_ release]; - if (title == nil) - title_ = nil; - else - title_ = [title retain]; - - [[self navigationItem] setTitle:title_]; - - [status_ setText:nil]; - [output_ setText:@""]; - [progress_ setProgress:0]; - - [close_ removeFromSuperview]; - [[self view] addSubview:progress_]; - [[self view] addSubview:status_]; - - [delegate_ setStatusBarShowsProgress:YES]; - running_ = YES; - - { - FileFd file; - if (!file.Open(NotifyConfig_, FileFd::ReadOnly)) - _error->Discard(); - else { - MMap mmap(file, MMap::ReadOnly); - SHA1Summation sha1; - sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); - notifyconf_ = sha1.Result(); - } - } - - { - FileFd file; - if (!file.Open(SpringBoard_, FileFd::ReadOnly)) - _error->Discard(); - else { - MMap mmap(file, MMap::ReadOnly); - SHA1Summation sha1; - sha1.Add(reinterpret_cast(mmap.Data()), mmap.Size()); - springlist_ = sha1.Result(); - } - } - - [NSThread - detachNewThreadSelector:@selector(_detachNewThreadData:) - toTarget:self - withObject:[[[ProgressData alloc] - initWithSelector:selector - target:target - object:object - ] autorelease] - ]; -} + UpdateExternalStatus(Finish_ == 0 ? 0 : 2); -- (void) repairWithSelector:(SEL)selector { - [self - detachNewThreadSelector:selector - toTarget:database_ - withObject:nil - title:UCLocalize("REPAIRING") - ]; -} + [progress_ setRunning:false]; + [self updateProgress]; -- (void) setConfigurationData:(NSString *)data { - [self - performSelectorOnMainThread:@selector(_setConfigurationData:) - withObject:data - waitUntilDone:YES - ]; + [self applyRightButton]; } -- (void) setProgressError:(NSString *)error withTitle:(NSString *)title { - CYActionSheet *sheet([[[CYActionSheet alloc] - initWithTitle:title - buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil] - defaultButtonIndex:0 - ] autorelease]); - - [sheet setMessage:error]; - [sheet yieldToPopupAlertAnimated:YES]; - [sheet dismiss]; +- (void) addProgressEvent:(CydiaProgressEvent *)event { + [progress_ addEvent:event]; + [self updateProgress]; } -- (void) setProgressTitle:(NSString *)title { - [self - performSelectorOnMainThread:@selector(_setProgressTitle:) - withObject:title - waitUntilDone:YES - ]; +- (bool) isProgressCancelled { + return cancel_ == 2; } -- (void) setProgressPercent:(float)percent { - [self - performSelectorOnMainThread:@selector(_setProgressPercent:) - withObject:[NSNumber numberWithFloat:percent] - waitUntilDone:YES - ]; +- (void) cancel { + cancel_ = 2; + [self updateCancel]; } -- (void) startProgress { -} +- (void) setCancellable:(bool)cancellable { + unsigned cancel(cancel_); -- (void) addProgressOutput:(NSString *)output { - [self - performSelectorOnMainThread:@selector(_addProgressOutput:) - withObject:output - waitUntilDone:YES - ]; -} + if (!cancellable) + cancel_ = 0; + else if (cancel_ == 0) + cancel_ = 1; -- (bool) isCancelling:(size_t)received { - return false; + if (cancel != cancel_) + [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]; - - 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) setProgressCancellable:(NSNumber *)cancellable { + [self setCancellable:[cancellable boolValue]]; } -- (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) setProgressPercent:(NSNumber *)percent { + [progress_ setPercent:[percent floatValue]]; + [self updateProgress]; } -- (void) _setProgressPercent:(NSNumber *)percent { - [progress_ setProgress:[percent floatValue]]; -} +- (void) setProgressStatus:(NSDictionary *)status { + if (status == nil) { + [progress_ setCurrent:0]; + [progress_ setTotal:0]; + [progress_ setSpeed:0]; + } else { + [progress_ setPercent:[[status objectForKey:@"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]; + [progress_ setCurrent:[[status objectForKey:@"Current"] floatValue]]; + [progress_ setTotal:[[status objectForKey:@"Total"] floatValue]]; + [progress_ setSpeed:[[status objectForKey:@"Speed"] floatValue]]; } -} -- (BOOL) isRunning { - return running_; + [self updateProgress]; } @end @@ -4908,6 +5381,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { @end /* }}} */ + /* Package Cell {{{ */ @interface PackageCell : CYTableViewCell < ContentDelegate @@ -4986,16 +5460,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } return self; } -- (void) _setBackgroundColor { - UIColor *color; - if (NSString *mode = [package_ mode]) { - bool remove([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]); - color = remove ? RemovingColor_ : InstallingColor_; - } else - color = [UIColor whiteColor]; - - [content_ setBackgroundColor:color]; - [self setNeedsDisplay]; +- (NSString *) accessibilityLabel { + return [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), name_, description_]; } - (void) setPackage:(Package *)package { @@ -5044,11 +5510,36 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { if ((badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]) != nil) badge_ = [badge_ retain]; - if ([package installed] != nil) - if ((placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/installed.png", App_]]) != nil) + UIColor *color; + NSString *placard; + + if (NSString *mode = [package_ mode]) { + if ([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]) { + color = RemovingColor_; + //placard = @"removing"; + } else { + color = InstallingColor_; + //placard = @"installing"; + } + + // XXX: the removing/installing placards are not @2x + placard = nil; + } else { + color = [UIColor whiteColor]; + + if ([package installed] != nil) + placard = @"installed"; + else + placard = nil; + } + + [content_ setBackgroundColor:color]; + + if (placard != nil) + if ((placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/%@.png", App_, placard]]) != nil) placard_ = [placard_ retain]; - [self _setBackgroundColor]; + [self setNeedsDisplay]; [content_ setNeedsDisplay]; } @@ -5226,6 +5717,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [switch_ setFrame:CGRectMake(frame.size.width - 102, 9, rect.size.width, rect.size.height)]; } +- (NSString *) accessibilityLabel { + return name_; +} + - (void) drawContentRect:(CGRect)rect { bool highlighted(highlighted_ && !editing_); @@ -5272,12 +5767,12 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { @implementation FileTable - (void) dealloc { - if (package_ != nil) - [package_ release]; - if (name_ != nil) - [name_ release]; + [self releaseSubviews]; + + [package_ release]; + [name_ release]; [files_ release]; - [list_ release]; + [super dealloc]; } @@ -5303,21 +5798,35 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { return cell; } +- (NSURL *) navigationURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/files", [package_ id]]]; +} + +- (void) loadView { + [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]]; + + list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]]; + [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [list_ setRowHeight:24.0f]; + [list_ setDataSource:self]; + [list_ setDelegate:self]; + [[self view] addSubview:list_]; +} + +- (void) viewDidLoad { + [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")]; +} + +- (void) releaseSubviews { + [list_ release]; + list_ = nil; +} + - (id) initWithDatabase:(Database *)database { if ((self = [super init]) != nil) { database_ = database; - [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")]; - files_ = [[NSMutableArray arrayWithCapacity:32] retain]; - - list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]]; - [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [list_ setRowHeight:24.0f]; - [[self view] addSubview:list_]; - - [list_ setDataSource:self]; - [list_ setDelegate:self]; } return self; } @@ -5367,6 +5876,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } - (void) reloadData { + [super reloadData]; + [self setPackage:[database_ packageWithName:name_]]; } @@ -5377,38 +5888,26 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { UIActionSheetDelegate > { _transient Database *database_; - Package *package_; - NSString *name_; + _H package_; + _H name_; bool commercial_; - NSMutableArray *buttons_; - UIBarButtonItem *button_; + _H buttons_; + _H button_; } -- (id) initWithDatabase:(Database *)database; -- (void) setPackage:(Package *)package; +- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name; @end @implementation CYPackageController -- (void) dealloc { - if (package_ != nil) - [package_ release]; - if (name_ != nil) - [name_ release]; - - [buttons_ release]; - - if (button_ != nil) - [button_ release]; - - [super dealloc]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@", (id) name_]]; } -- (void) release { - if ([self retainCount] == 1) - [delegate_ setPackageController:self]; - [super release]; +- (bool) _allowNavigationAction { + // XXX: damn it... I really want this. + return true; } /* XXX: this is not safe at all... localization of /fail/ */ @@ -5439,11 +5938,6 @@ 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:package_ forKey:@"package"]; -} - - (bool) _allowJavaScriptPanel { return commercial_; } @@ -5504,33 +5998,24 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } #endif -- (id) initWithDatabase:(Database *)database { +- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name { if ((self = [super init]) != nil) { database_ = database; - buttons_ = [[NSMutableArray alloc] initWithCapacity:4]; - [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"package" ofType:@"html"]]]; + buttons_ = [NSMutableArray arrayWithCapacity:4]; + name_ = [NSString stringWithString:name]; + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/package/%@", UI_, (id) name_]]]; } return self; } -- (void) setPackage:(Package *)package { - if (package_ != nil) { - [package_ autorelease]; - package_ = nil; - } - - if (name_ != nil) { - [name_ release]; - name_ = nil; - } +- (void) reloadData { + package_ = [database_ packageWithName:name_]; [buttons_ removeAllObjects]; - if (package != nil) { - [package parse]; + if (package_ != nil) { + [(Package *) package_ parse]; - package_ = [package retain]; - name_ = [[package id] retain]; - commercial_ = [package isCommercial]; + commercial_ = [package_ isCommercial]; if ([package_ mode] != nil) [buttons_ addObject:UCLocalize("CLEAR")]; @@ -5545,9 +6030,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [buttons_ addObject:UCLocalize("REMOVE")]; } - if (button_ != nil) - [button_ release]; - NSString *title; switch ([buttons_ count]) { case 0: title = nil; break; @@ -5555,28 +6037,25 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { default: title = UCLocalize("MODIFY"); break; } - button_ = [[UIBarButtonItem alloc] + button_ = [[[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStylePlain target:self action:@selector(customButtonClicked) - ]; + ] autorelease]; - [self reloadURL]; + [super reloadData]; } - (bool) isLoading { return commercial_ ? [super isLoading] : false; } -- (void) reloadData { - [self setPackage:[database_ packageWithName:name_]]; -} - @end /* }}} */ -/* Package Table {{{ */ -@interface PackageTable : UIView < + +/* Package List Controller {{{ */ +@interface PackageListController : CYViewController < UITableViewDataSource, UITableViewDelegate > { @@ -5587,29 +6066,16 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { UITableView *list_; NSMutableArray *index_; NSMutableDictionary *indices_; - // XXX: this target_ seems to be delegate_. :( - _transient id target_; - SEL action_; - // XXX: why do we even have this delegate_? - _transient id delegate_; + NSString *title_; } -- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action; - +- (id) initWithDatabase:(Database *)database title:(NSString *)title; - (void) setDelegate:(id)delegate; - -- (void) reloadData; - (void) resetCursor; -- (UITableView *) list; - -- (void) setShouldHideHeaderInShortLists:(BOOL)hide; - -- (void) deselectWithAnimation:(BOOL)animated; - @end -@implementation PackageTable +@implementation PackageListController - (void) dealloc { [packages_ release]; @@ -5617,13 +6083,97 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [list_ release]; [index_ release]; [indices_ release]; + [title_ release]; [super dealloc]; } +- (void) deselectWithAnimation:(BOOL)animated { + [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; +} + +- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve { + CGRect base = [[self view] bounds]; + base.size.height -= bounds.size.height; + base.origin = [list_ frame].origin; + + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationBeginsFromCurrentState:YES]; + [UIView setAnimationCurve:curve]; + [UIView setAnimationDuration:duration]; + [list_ setFrame:base]; + [UIView commitAnimations]; +} + +- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration { + [self resizeForKeyboardBounds:bounds duration:duration curve:UIViewAnimationCurveLinear]; +} + +- (void) resizeForKeyboardBounds:(CGRect)bounds { + [self resizeForKeyboardBounds:bounds duration:0]; +} + +- (void) keyboardWillShow:(NSNotification *)notification { + CGRect bounds; + CGPoint center; + NSTimeInterval duration; + UIViewAnimationCurve curve; + [[[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey] getValue:&bounds]; + [[[notification userInfo] objectForKey:UIKeyboardCenterEndUserInfoKey] getValue:¢er]; + [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve]; + [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration]; + + CGRect kbframe = CGRectMake(round(center.x - bounds.size.width / 2.0), round(center.y - bounds.size.height / 2.0), bounds.size.width, bounds.size.height); + UIViewController *base = self; + while ([base parentViewController] != nil) + base = [base parentViewController]; + CGRect viewframe = [[base view] convertRect:[list_ frame] fromView:[list_ superview]]; + CGRect intersection = CGRectIntersection(viewframe, kbframe); + + [self resizeForKeyboardBounds:intersection duration:duration curve:curve]; +} + +- (void) keyboardWillHide:(NSNotification *)notification { + NSTimeInterval duration; + UIViewAnimationCurve curve; + [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve]; + [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration]; + + [self resizeForKeyboardBounds:CGRectZero duration:duration curve:curve]; +} + +- (void) viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self resizeForKeyboardBounds:CGRectZero]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; +} + +- (void) viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [self resizeForKeyboardBounds:CGRectZero]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; +} + +- (void) viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [self deselectWithAnimation:animated]; +} + +- (void) didSelectPackage:(Package *)package { + CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id]] autorelease]); + [view setDelegate:delegate_]; + [[self navigationController] pushViewController:view animated:YES]; +} + +#if TryIndexedCollation + (BOOL) hasIndexedCollation { return NO; // XXX: objc_getClass("UILocalizedIndexedCollation") != nil; } +#endif - (NSInteger) numberOfSectionsInTableView:(UITableView *)list { NSInteger count([sections_ count]); @@ -5661,15 +6211,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { return cell; } -- (void) deselectWithAnimation:(BOOL)animated { - [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; -} - -- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path { +- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path { Package *package([self packageAtIndexPath:path]); package = [database_ packageWithName:[package id]]; - [target_ performSelector:action_ withObject:package]; - return path; + [self didSelectPackage:package]; } - (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView { @@ -5678,32 +6223,37 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } - (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { +#if TryIndexedCollation if ([[self class] hasIndexedCollation]) { return [[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionForSectionIndexTitleAtIndex:index]; } +#endif return index; } -- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action { - if ((self = [super initWithFrame:frame]) != nil) { +- (id) initWithDatabase:(Database *)database title:(NSString *)title { + if ((self = [super init]) != nil) { database_ = database; + title_ = [title copy]; + [[self navigationItem] setTitle:title_]; - target_ = target; - action_ = action; +#if TryIndexedCollation + if ([[self class] hasIndexedCollation]) + index_ = [[[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionIndexTitles] retain] + else +#endif + index_ = [[NSMutableArray alloc] initWithCapacity:32]; - index_ = [[self class] hasIndexedCollation] - ? [[[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionIndexTitles] retain] - : [[NSMutableArray alloc] initWithCapacity:32]; indices_ = [[NSMutableDictionary alloc] initWithCapacity:32]; packages_ = [[NSMutableArray arrayWithCapacity:16] retain]; sections_ = [[NSMutableArray arrayWithCapacity:16] retain]; - list_ = [[UITableView alloc] initWithFrame:[self bounds] style:UITableViewStylePlain]; + list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain]; [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; [list_ setRowHeight:73]; - [self addSubview:list_]; + [[self view] addSubview:list_]; [list_ setDataSource:self]; [list_ setDelegate:self]; @@ -5719,6 +6269,8 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } - (void) reloadData { + [super reloadData]; + era_ = [database_ era]; NSArray *packages = [database_ packages]; @@ -5735,6 +6287,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { Section *section = nil; +#if TryIndexedCollation if ([[self class] hasIndexedCollation]) { id collation = [objc_getClass("UILocalizedIndexedCollation") currentCollation]; NSArray *titles = [collation sectionIndexTitles]; @@ -5765,7 +6318,9 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [section addToCount]; } _end - } else { + } else +#endif + { [index_ removeAllObjects]; _profile(PackageTable$reloadData$Section) @@ -5805,18 +6360,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [list_ scrollRectToVisible:CGRectMake(0, 0, 0, 0) animated:NO]; } -- (UITableView *) list { - return list_; -} - -- (void) setShouldHideHeaderInShortLists:(BOOL)hide { - //XXX:[list_ setShouldHideHeaderInShortLists:hide]; -} - @end /* }}} */ -/* Filtered Package Table {{{ */ -@interface FilteredPackageTable : PackageTable { +/* Filtered Package List Controller {{{ */ +@interface FilteredPackageListController : PackageListController { SEL filter_; IMP imp_; id object_; @@ -5825,11 +6372,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { - (void) setObject:(id)object; - (void) setObject:(id)object forFilter:(SEL)filter; -- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action filter:(SEL)filter with:(id)object; +- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object; @end -@implementation FilteredPackageTable +@implementation FilteredPackageListController - (void) dealloc { if (object_ != nil) @@ -5867,1492 +6414,1739 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { _end } -- (id) initWithFrame:(CGRect)frame database:(Database *)database target:(id)target action:(SEL)action filter:(SEL)filter with:(id)object { - if ((self = [super initWithFrame:frame database:database target:target action:action]) != nil) { +- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object { + if ((self = [super initWithDatabase:database title:title]) != nil) { [self setFilter:filter]; - object_ = [object retain]; - [self reloadData]; + [self setObject:object]; } return self; } @end /* }}} */ -/* Filtered Package Controller {{{ */ -@interface FilteredPackageController : CYViewController { - _transient Database *database_; - FilteredPackageTable *packages_; - NSString *title_; +/* Home Controller {{{ */ +@interface HomeController : CYBrowserController { } -- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object; - @end -@implementation FilteredPackageController - -- (void) dealloc { - [packages_ release]; - [title_ release]; +@implementation HomeController - [super dealloc]; +- (id) init { + if ((self = [super init]) != nil) { + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/home/", UI_]]]; + } return self; } -- (void) viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [packages_ deselectWithAnimation:animated]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://home"]; } -- (void) didSelectPackage:(Package *)package { - CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]); - [view setPackage:package]; - [view setDelegate:delegate_]; - [[self navigationController] pushViewController:view animated:YES]; -} +- (void) _setMoreHeaders:(NSMutableURLRequest *)request { + [super _setMoreHeaders:request]; -- (NSString *) title { return title_; } + if (ChipID_ != nil) + [request setValue:ChipID_ forHTTPHeaderField:@"X-Chip-ID"]; + if (UniqueID_ != nil) + [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"]; + if (PLMN_ != nil) + [request setValue:PLMN_ forHTTPHeaderField:@"X-Carrier-ID"]; +} -- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object { - if ((self = [super init]) != nil) { - database_ = database; - title_ = [title copy]; - [[self navigationItem] setTitle:title_]; +- (void) aboutButtonClicked { + UIAlertView *alert([[[UIAlertView alloc] init] autorelease]); - packages_ = [[FilteredPackageTable alloc] - initWithFrame:[[self view] bounds] - database:database - target:self - action:@selector(didSelectPackage:) - filter:filter - with:object - ]; + [alert setTitle:UCLocalize("ABOUT_CYDIA")]; + [alert addButtonWithTitle:UCLocalize("CLOSE")]; + [alert setCancelButtonIndex:0]; - [packages_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [[self view] addSubview:packages_]; - } return self; -} + [alert setMessage: + @"Copyright (C) 2008-2011\n" + "Jay Freeman (saurik)\n" + "saurik@saurik.com\n" + "http://www.saurik.com/" + ]; -- (void) reloadData { - [packages_ reloadData]; + [alert show]; } -- (void) setDelegate:(id)delegate { - [super setDelegate:delegate]; - [packages_ setDelegate:delegate]; +- (void) viewDidLoad { + [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("ABOUT") + style:UIBarButtonItemStylePlain + target:self + action:@selector(aboutButtonClicked) + ] autorelease]]; } @end - /* }}} */ - -/* Source Cell {{{ */ -@interface SourceCell : CYTableViewCell < - ContentDelegate -> { - UIImage *icon_; - NSString *origin_; - NSString *label_; +/* Manage Controller {{{ */ +@interface ManageController : CYBrowserController { } -- (void) setSource:(Source *)source; +- (void) queueStatusDidChange; @end -@implementation SourceCell - -- (void) clearSource { - [icon_ release]; - [origin_ release]; - [label_ release]; +@implementation ManageController - icon_ = nil; - origin_ = nil; - label_ = nil; +- (id) init { + if ((self = [super init]) != nil) { + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/manage/", UI_]]]; + } return self; } -- (void) setSource:(Source *)source { - [self clearSource]; - - if (icon_ == nil) - icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]]; - if (icon_ == nil) - icon_ = [UIImage applicationImageNamed:@"unknown.png"]; - icon_ = [icon_ retain]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://manage"]; +} - origin_ = [[source name] retain]; - label_ = [[source uri] retain]; +- (void) viewDidLoad { + [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("SETTINGS") + style:UIBarButtonItemStylePlain + target:self + action:@selector(settingsButtonClicked) + ] autorelease]]; - [content_ setNeedsDisplay]; + [self queueStatusDidChange]; } -- (void) dealloc { - [self clearSource]; - [super dealloc]; +- (void) settingsButtonClicked { + [delegate_ showSettings]; } -- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { - if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) { - UIView *content([self contentView]); - CGRect bounds([content bounds]); - - content_ = [[ContentView alloc] initWithFrame:bounds]; - [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [content_ setBackgroundColor:[UIColor whiteColor]]; - [content addSubview:content_]; - - [content_ setDelegate:self]; - [content_ setOpaque:YES]; - } return self; +#if !AlwaysReload +- (void) queueButtonClicked { + [delegate_ queue]; } -- (void) drawContentRect:(CGRect)rect { - bool highlighted(highlighted_); - float width(rect.size.width); - - if (icon_ != nil) - [icon_ drawInRect:CGRectMake(10, 10, 30, 30)]; +- (void) applyLoadingTitle { + // Disable "Loading" title. +} - if (highlighted) - UISetColor(White_); +- (void) applyRightButton { + // Disable right button. +} +#endif - if (!highlighted) - UISetColor(Black_); - [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - 80) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation]; +- (void) queueStatusDidChange { +#if !AlwaysReload + if (!IsWildcat_ && Queuing_) { + [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("QUEUE") + style:UIBarButtonItemStyleDone + target:self + action:@selector(queueButtonClicked) + ] autorelease]]; + } else { + [[self navigationItem] setRightBarButtonItem:nil]; + } +#endif +} - if (!highlighted) - UISetColor(Blue_); - [label_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation]; +- (bool) isLoading { + // Never show as loading. + return false; } @end /* }}} */ -/* Source Table {{{ */ -@interface SourcesController : CYViewController < - UITableViewDataSource, - UITableViewDelegate -> { - _transient Database *database_; - UITableView *list_; - NSMutableArray *sources_; - int offset_; - - NSString *href_; - UIProgressHUD *hud_; - NSError *error_; - - //NSURLConnection *installer_; - NSURLConnection *trivial_; - NSURLConnection *trivial_bz2_; - NSURLConnection *trivial_gz_; - //NSURLConnection *automatic_; - BOOL cydia_; +/* Refresh Bar {{{ */ +@interface RefreshBar : UINavigationBar { + UIProgressIndicator *indicator_; + UITextLabel *prompt_; + UIProgressBar *progress_; + UINavigationButton *cancel_; } -- (id) initWithDatabase:(Database *)database; - -- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated; - @end -@implementation SourcesController +@implementation RefreshBar -- (void) _releaseConnection:(NSURLConnection *)connection { - if (connection != nil) { - [connection cancel]; - //[connection setDelegate:nil]; - [connection release]; - } +- (void) dealloc { + [indicator_ release]; + [prompt_ release]; + [progress_ release]; + [cancel_ release]; + [super dealloc]; } -- (void) dealloc { - if (href_ != nil) - [href_ release]; - if (hud_ != nil) - [hud_ release]; - if (error_ != nil) - [error_ release]; +- (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]; - //[self _releaseConnection:installer_]; - [self _releaseConnection:trivial_]; - [self _releaseConnection:trivial_gz_]; - [self _releaseConnection:trivial_bz2_]; - //[self _releaseConnection:automatic_]; + CGSize prgsize = {75, 100}; + CGRect prgrect = {{ + [self frame].size.width - prgsize.width - 10, + ([self frame].size.height - prgsize.height) / 2 + } , prgsize}; + [progress_ setFrame:prgrect]; - [sources_ release]; - [list_ release]; - [super dealloc]; -} + CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]); + unsigned indoffset = ([self frame].size.height - indsize.height) / 2; + CGRect indrect = {{indoffset, indoffset}, indsize}; + [indicator_ setFrame:indrect]; -- (void) viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; + 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]; } -- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { - return offset_ == 0 ? 1 : 2; +- (void) setFrame:(CGRect)frame { + [super setFrame:frame]; + [self positionViews]; } -- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - switch (section + (offset_ == 0 ? 1 : 0)) { - case 0: return UCLocalize("ENTERED_BY_USER"); - case 1: return UCLocalize("INSTALLED_BY_PACKAGE"); +- (id) initWithFrame:(CGRect)frame delegate:(id)delegate { + if ((self = [super initWithFrame:frame]) != nil) { + [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; - _nodefault - } -} + [self setBarStyle:UIBarStyleBlack]; -- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - int count = [sources_ count]; - switch (section) { - case 0: return (offset_ == 0 ? count : offset_); - case 1: return count - offset_; + UIBarStyle barstyle([self _barStyle:NO]); + bool ugly(barstyle == UIBarStyleDefault); - _nodefault - } -} + UIProgressIndicatorStyle style = ugly ? + UIProgressIndicatorStyleMediumBrown : + UIProgressIndicatorStyleMediumWhite; -- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath { - unsigned idx = 0; - switch (indexPath.section) { - case 0: idx = indexPath.row; break; - case 1: idx = indexPath.row + offset_; break; + indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectZero]; + [indicator_ setStyle:style]; + [indicator_ startAnimation]; + [self addSubview:indicator_]; - _nodefault - } - return [sources_ objectAtIndex:idx]; -} + prompt_ = [[UITextLabel alloc] initWithFrame:CGRectZero]; + [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]]; + [prompt_ setBackgroundColor:[UIColor clearColor]]; + [prompt_ setFont:[UIFont systemFontOfSize:15]]; + [self addSubview:prompt_]; -- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *cellIdentifier = @"SourceCell"; + progress_ = [[UIProgressBar alloc] initWithFrame:CGRectZero]; + [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin]; + [progress_ setStyle:0]; + [self addSubview:progress_]; - SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; - if(cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease]; - [cell setSource:[self sourceAtIndexPath:indexPath]]; + cancel_ = [[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted]; + [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside]; + [cancel_ setBarStyle:barstyle]; - return cell; + [self positionViews]; + } return self; } -- (UITableViewCellAccessoryType) tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath { - return UITableViewCellAccessoryDisclosureIndicator; +- (void) setCancellable:(bool)cancellable { + if (cancellable) + [self addSubview:cancel_]; + else + [cancel_ removeFromSuperview]; } -- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - Source *source = [self sourceAtIndexPath:indexPath]; - - FilteredPackageController *packages = [[[FilteredPackageController alloc] - initWithDatabase:database_ - title:[source label] - filter:@selector(isVisibleInSource:) - with:source - ] autorelease]; - - [packages setDelegate:delegate_]; +- (void) start { + [prompt_ setText:UCLocalize("UPDATING_DATABASE")]; + [progress_ setProgress:0]; +} - [[self navigationController] pushViewController:packages animated:YES]; +- (void) stop { + [self setCancellable:NO]; } -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { - Source *source = [self sourceAtIndexPath:indexPath]; - return [source record] != nil; +- (void) setPrompt:(NSString *)prompt { + [prompt_ setText:prompt]; } -- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - Source *source = [self sourceAtIndexPath:indexPath]; - [Sources_ removeObjectForKey:[source key]]; - [delegate_ syncData]; +- (void) setProgress:(float)progress { + [progress_ setProgress:progress]; } -- (void) complete { - [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys: - @"deb", @"Type", - href_, @"URI", - @"./", @"Distribution", - nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]]; +@end +/* }}} */ - [delegate_ syncData]; +@class CYNavigationController; + +/* Cydia Tab Bar Controller {{{ */ +@interface CYTabBarController : UITabBarController < + UITabBarControllerDelegate, + ProgressDelegate +> { + _transient Database *database_; + RefreshBar *refreshbar_; + + bool dropped_; + bool updating_; + // XXX: ok, "updatedelegate_"?... + _transient NSObject *updatedelegate_; + + id root_; + UIViewController *remembered_; + _transient UIViewController *transient_; } -- (NSString *) getWarning { - NSString *href(href_); - NSRange colon([href rangeOfString:@"://"]); - if (colon.location != NSNotFound) - href = [href substringFromIndex:(colon.location + 3)]; - href = [href stringByAddingPercentEscapes]; - href = [CydiaURL(@"api/repotag/") stringByAppendingString:href]; - href = [href stringByCachingURLWithCurrentCDN]; +- (NSArray *) navigationURLCollection; +- (void) dropBar:(BOOL)animated; +- (void) beginUpdate; +- (void) raiseBar:(BOOL)animated; +- (BOOL) updating; - NSURL *url([NSURL URLWithString:href]); +@end - NSStringEncoding encoding; - NSError *error(nil); +@implementation CYTabBarController - if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error]) - return [warning length] == 0 ? nil : warning; - return nil; +- (void) setUnselectedViewController:(UIViewController *)transient { + NSMutableArray *controllers = [[self viewControllers] mutableCopy]; + if (transient != nil) { + if (transient_ == nil) + remembered_ = [[controllers objectAtIndex:0] retain]; + transient_ = transient; + [transient_ setTabBarItem:[remembered_ tabBarItem]]; + [controllers replaceObjectAtIndex:0 withObject:transient_]; + [self setSelectedIndex:0]; + [self setViewControllers:controllers]; + [self concealTabBarSelection]; + } else if (remembered_ != nil) { + [remembered_ setTabBarItem:[transient_ tabBarItem]]; + transient_ = transient; + [controllers replaceObjectAtIndex:0 withObject:remembered_]; + [remembered_ release]; + remembered_ = nil; + [self setViewControllers:controllers]; + [self revealTabBarSelection]; + } } -- (void) _endConnection:(NSURLConnection *)connection { - // XXX: the memory management in this method is horribly awkward +- (UIViewController *) unselectedViewController { + return transient_; +} - NSURLConnection **field = NULL; - if (connection == trivial_) - field = &trivial_; - else if (connection == trivial_bz2_) - field = &trivial_bz2_; - else if (connection == trivial_gz_) - field = &trivial_gz_; - _assert(field != NULL); - [connection release]; - *field = nil; +- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController { + if ([self unselectedViewController]) + [self setUnselectedViewController:nil]; +} - if ( - trivial_ == nil && - trivial_bz2_ == nil && - trivial_gz_ == nil - ) { - bool defer(false); +- (NSArray *) navigationURLCollection { + NSMutableArray *items([NSMutableArray array]); - if (cydia_) { - if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) { - defer = true; + // XXX: Should this deal with transient view controllers? + for (id navigation in [self viewControllers]) { + NSArray *stack = [navigation performSelector:@selector(navigationURLCollection)]; + if (stack != nil) + [items addObject:stack]; + } - UIAlertView *alert = [[[UIAlertView alloc] - initWithTitle:UCLocalize("SOURCE_WARNING") - message:warning - delegate:self - cancelButtonTitle:UCLocalize("CANCEL") - otherButtonTitles:UCLocalize("ADD_ANYWAY"), nil - ] autorelease]; + return items; +} - [alert setContext:@"warning"]; - [alert setNumberOfRows:1]; - [alert show]; - } else - [self complete]; - } else if (error_ != nil) { - UIAlertView *alert = [[[UIAlertView alloc] - initWithTitle:UCLocalize("VERIFICATION_ERROR") - message:[error_ localizedDescription] - delegate:self - cancelButtonTitle:UCLocalize("OK") - otherButtonTitles:nil - ] autorelease]; +- (void) reloadData { + for (CYViewController *controller in [self viewControllers]) + [controller reloadData]; - [alert setContext:@"urlerror"]; - [alert show]; - } else { - UIAlertView *alert = [[[UIAlertView alloc] - initWithTitle:UCLocalize("NOT_REPOSITORY") - message:UCLocalize("NOT_REPOSITORY_EX") - delegate:self - cancelButtonTitle:UCLocalize("OK") - otherButtonTitles:nil - ] autorelease]; + [(CYNavigationController *)[self unselectedViewController] reloadData]; +} - [alert setContext:@"trivial"]; - [alert show]; - } +- (void) dealloc { + [refreshbar_ release]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; - [delegate_ setStatusBarShowsProgress:NO]; - [delegate_ removeProgressHUD:hud_]; + [super dealloc]; +} - [hud_ autorelease]; - hud_ = nil; +- (id) initWithDatabase:(Database *)database { + if ((self = [super init]) != nil) { + database_ = database; + [self setDelegate:self]; - if (!defer) { - [href_ release]; - href_ = nil; - } + [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; - if (error_ != nil) { - [error_ release]; - error_ = nil; - } - } + refreshbar_ = [[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self]; + } return self; } -- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response { - switch ([response statusCode]) { - case 200: - cydia_ = YES; - } +- (void) setUpdate:(NSDate *)date { + [self beginUpdate]; } -- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { - lprintf("connection:\"%s\" didFailWithError:\"%s\"", [href_ UTF8String], [[error localizedDescription] UTF8String]); - if (error_ != nil) - error_ = [error retain]; - [self _endConnection:connection]; -} +- (void) beginUpdate { + [refreshbar_ start]; + [self dropBar:YES]; -- (void) connectionDidFinishLoading:(NSURLConnection *)connection { - [self _endConnection:connection]; + [updatedelegate_ retainNetworkActivityIndicator]; + updating_ = true; + + [NSThread + detachNewThreadSelector:@selector(performUpdate) + toTarget:self + withObject:nil + ]; } -- (NSString *) title { return UCLocalize("SOURCES"); } +- (void) performUpdate { _pooled + Status status; + status.setDelegate(self); + [database_ updateWithStatus:status]; -- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method { - NSMutableURLRequest *request = [NSMutableURLRequest - requestWithURL:[NSURL URLWithString:href] - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:120.0 + [self + performSelectorOnMainThread:@selector(completeUpdate) + withObject:nil + waitUntilDone:NO ]; +} - [request setHTTPMethod:method]; +- (void) stopUpdateWithSelector:(SEL)selector { + updating_ = false; + [updatedelegate_ releaseNetworkActivityIndicator]; - if (Machine_ != NULL) - [request setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"]; - if (UniqueID_ != nil) - [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"]; - if (Role_ != nil) - [request setValue:Role_ forHTTPHeaderField:@"X-Role"]; + [self raiseBar:YES]; + [refreshbar_ stop]; - return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease]; + [updatedelegate_ performSelector:selector withObject:nil afterDelay:0]; } -- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { - NSString *context([alert context]); +- (void) completeUpdate { + if (!updating_) + return; + [self stopUpdateWithSelector:@selector(reloadData)]; +} - if ([context isEqualToString:@"source"]) { - switch (button) { - case 1: { - NSString *href = [[alert textField] text]; - - //installer_ = [[self _requestHRef:href method:@"GET"] retain]; - - if (![href hasSuffix:@"/"]) - href_ = [href stringByAppendingString:@"/"]; - else - href_ = href; - href_ = [href_ retain]; - - trivial_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages"] method:@"HEAD"] retain]; - 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]; +- (void) cancelUpdate { + [self stopUpdateWithSelector:@selector(updateData)]; +} - cydia_ = false; +- (void) cancelPressed { + [self cancelUpdate]; +} - // XXX: this is stupid - hud_ = [[delegate_ addProgressHUD] retain]; - [hud_ setText:UCLocalize("VERIFYING_URL")]; - } break; +- (BOOL) updating { + return updating_; +} - case 0: - break; +- (void) addProgressEvent:(CydiaProgressEvent *)event { + [refreshbar_ setPrompt:[event compoundMessage]]; +} - _nodefault - } +- (bool) isProgressCancelled { + return !updating_; +} - [alert dismissWithClickedButtonIndex:-1 animated:YES]; - } else if ([context isEqualToString:@"trivial"]) - [alert dismissWithClickedButtonIndex:-1 animated:YES]; - else if ([context isEqualToString:@"urlerror"]) - [alert dismissWithClickedButtonIndex:-1 animated:YES]; - else if ([context isEqualToString:@"warning"]) { - switch (button) { - case 1: - [self complete]; - break; +- (void) setProgressCancellable:(NSNumber *)cancellable { + [refreshbar_ setCancellable:(updating_ && [cancellable boolValue])]; +} - case 0: - break; +- (void) setProgressPercent:(NSNumber *)percent { + [refreshbar_ setProgress:[percent floatValue]]; +} - _nodefault - } +- (void) setProgressStatus:(NSDictionary *)status { + if (status != nil) + [self setProgressPercent:[status objectForKey:@"Percent"]]; +} - [href_ release]; - href_ = nil; +- (void) setUpdateDelegate:(id)delegate { + updatedelegate_ = delegate; +} - [alert dismissWithClickedButtonIndex:-1 animated:YES]; +- (CGFloat) statusBarHeight { + if (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) { + return [[UIApplication sharedApplication] statusBarFrame].size.height; + } else { + return [[UIApplication sharedApplication] statusBarFrame].size.width; } } -- (id) initWithDatabase:(Database *)database { - if ((self = [super init]) != nil) { - [[self navigationItem] setTitle:UCLocalize("SOURCES")]; - [self updateButtonsForEditingStatus:NO animated:NO]; +- (UIView *) transitionView { + if ([self respondsToSelector:@selector(_transitionView)]) + return [self _transitionView]; + else + return MSHookIvar(self, "_viewControllerTransitionView"); +} - database_ = database; - sources_ = [[NSMutableArray arrayWithCapacity:16] retain]; +- (void) dropBar:(BOOL)animated { + if (dropped_) + return; + dropped_ = true; - list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain]; - [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [list_ setRowHeight:56]; - [[self view] addSubview:list_]; + UIView *transition([self transitionView]); + [[self view] addSubview:refreshbar_]; - [list_ setDataSource:self]; - [list_ setDelegate:self]; + CGRect barframe([refreshbar_ frame]); - [self reloadData]; - } return self; -} + if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_0) // XXX: _UIApplicationLinkedOnOrAfter(4) + barframe.origin.y = [self statusBarHeight]; + else + barframe.origin.y = 0; -- (void) reloadData { - pkgSourceList list; - if (!list.ReadMainList()) - return; + [refreshbar_ setFrame:barframe]; - [sources_ removeAllObjects]; - [sources_ addObjectsFromArray:[database_ sources]]; - _trace(); - [sources_ sortUsingSelector:@selector(compareByNameAndType:)]; - _trace(); + if (animated) + [UIView beginAnimations:nil context:NULL]; - int count([sources_ count]); - offset_ = 0; - for (int i = 0; i != count; i++) { - if ([[sources_ objectAtIndex:i] record] == nil) - break; - offset_++; - } + CGRect viewframe = [transition frame]; + viewframe.origin.y += barframe.size.height; + viewframe.size.height -= barframe.size.height; + [transition setFrame:viewframe]; - [list_ setEditing:NO]; - [self updateButtonsForEditingStatus:NO animated:NO]; - [list_ reloadData]; + 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]; + + // XXX: fix Apple's layout bug + [[root_ selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation]; } -- (void) showAddSourcePrompt { - UIAlertView *alert = [[[UIAlertView alloc] - initWithTitle:UCLocalize("ENTER_APT_URL") - message:nil - delegate:self - cancelButtonTitle:UCLocalize("CANCEL") - otherButtonTitles:UCLocalize("ADD_SOURCE"), nil - ] autorelease]; +- (void) raiseBar:(BOOL)animated { + if (!dropped_) + return; + dropped_ = false; - [alert setContext:@"source"]; - [alert setTransform:CGAffineTransformTranslate([alert transform], 0.0, 100.0)]; + UIView *transition([self transitionView]); + [refreshbar_ removeFromSuperview]; - [alert setNumberOfRows:1]; - [alert addTextFieldWithValue:@"http://" label:@""]; + CGRect barframe([refreshbar_ frame]); - UITextInputTraits *traits = [[alert textField] textInputTraits]; - [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone]; - [traits setAutocorrectionType:UITextAutocorrectionTypeNo]; - [traits setKeyboardType:UIKeyboardTypeURL]; - // XXX: UIReturnKeyDone - [traits setReturnKeyType:UIReturnKeyNext]; + if (animated) + [UIView beginAnimations:nil context:NULL]; - [alert show]; + CGRect viewframe = [transition frame]; + viewframe.origin.y -= barframe.size.height; + viewframe.size.height += barframe.size.height; + [transition setFrame:viewframe]; + + if (animated) + [UIView commitAnimations]; + + // XXX: fix Apple's layout bug + // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation]; } -- (void) addButtonClicked { - [self showAddSourcePrompt]; +#if 0 +- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration { + // XXX: fix Apple's layout bug + // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation]; } +#endif -- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated { - [[self navigationItem] setLeftBarButtonItem:(editing ? [[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("ADD") - style:UIBarButtonItemStylePlain - target:self - action:@selector(addButtonClicked) - ] autorelease] : [[self navigationItem] backBarButtonItem]) animated:animated]; +- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + bool dropped(dropped_); - [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT")) - style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain) - target:self - action:@selector(editButtonClicked) - ] autorelease] animated:animated]; + if (dropped) + [self raiseBar:NO]; - if (IsWildcat_ && !editing) - [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("SETTINGS") - style:UIBarButtonItemStylePlain - target:self - action:@selector(settingsButtonClicked) - ] autorelease]]; -} + [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; -- (void) settingsButtonClicked { - [delegate_ showSettings]; -} + if (dropped) + [self dropBar:NO]; -- (void) editButtonClicked { - [list_ setEditing:![list_ isEditing] animated:YES]; + // XXX: fix Apple's layout bug + // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation]; +} - [self updateButtonsForEditingStatus:[list_ isEditing] animated:YES]; +- (void) statusBarFrameChanged:(NSNotification *)notification { + if (dropped_) { + [self raiseBar:NO]; + [self dropBar:NO]; + } } @end /* }}} */ - -/* Installed Controller {{{ */ -@interface InstalledController : FilteredPackageController { - BOOL expert_; +/* Cydia Navigation Controller {{{ */ +@interface CYNavigationController : UINavigationController { + _transient Database *database_; + _transient id delegate_; } +- (NSArray *) navigationURLCollection; - (id) initWithDatabase:(Database *)database; - -- (void) updateRoleButton; -- (void) queueStatusDidChange; +- (void) reloadData; @end -@implementation InstalledController -- (void) dealloc { - [super dealloc]; +@implementation CYNavigationController + +- (NSArray *) navigationURLCollection { + NSMutableArray *stack([NSMutableArray array]); + + for (CYViewController *controller in [self viewControllers]) { + NSString *url = [[controller navigationURL] absoluteString]; + if (url != nil) + [stack addObject:url]; + } + + return stack; +} + +- (void) reloadData { + for (CYViewController *page in [self viewControllers]) { + // Only reload controllers that have already loaded. + // This prevents a page from accidentally loading too + // early if it hasn't been shown on the screen yet. + if ([page hasLoaded]) + [page reloadData]; + } } -- (NSString *) title { return UCLocalize("INSTALLED"); } +- (void) setDelegate:(id)delegate { + delegate_ = delegate; +} - (id) initWithDatabase:(Database *)database { - if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) { - [self updateRoleButton]; - [self queueStatusDidChange]; + if ((self = [super init]) != nil) { + database_ = database; } return self; } -#if !AlwaysReload -- (void) queueButtonClicked { - [delegate_ queue]; +@end +/* }}} */ + +/* Cydia:// Protocol {{{ */ +@interface CydiaURLProtocol : NSURLProtocol { } -#endif -- (void) queueStatusDidChange { -#if !AlwaysReload - if (IsWildcat_) { - if (Queuing_) { - [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("QUEUE") - style:UIBarButtonItemStyleDone - target:self - action:@selector(queueButtonClicked) - ] autorelease]]; - } else { - [[self navigationItem] setLeftBarButtonItem:nil]; - } - } -#endif +@end + +@implementation CydiaURLProtocol + ++ (BOOL) canInitWithRequest:(NSURLRequest *)request { + NSURL *url([request URL]); + if (url == nil) + return NO; + + NSString *scheme([[url scheme] lowercaseString]); + if (scheme != nil && [scheme isEqualToString:@"cydia"]) + return YES; + if ([[url absoluteString] hasPrefix:@"about:cydia-"]) + return YES; + + return NO; } -- (void) reloadData { - [packages_ reloadData]; ++ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request { + return request; } -- (void) updateRoleButton { - if (Role_ != nil && ![Role_ isEqualToString:@"Developer"]) - [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE")) - style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain) - target:self - action:@selector(roleButtonClicked) - ] autorelease]]; +- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request { + id client([self client]); + if (icon == nil) + [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]]; + else { + NSData *data(UIImagePNGRepresentation(icon)); + + NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]); + [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [client URLProtocol:self didLoadData:data]; + [client URLProtocolDidFinishLoading:self]; + } } -- (void) roleButtonClicked { - [packages_ setObject:[NSNumber numberWithBool:expert_]]; - [packages_ reloadData]; - expert_ = !expert_; +- (void) startLoading { + id client([self client]); + NSURLRequest *request([self request]); - [self updateRoleButton]; + NSURL *url([request URL]); + NSString *href([url absoluteString]); + NSString *scheme([[url scheme] lowercaseString]); + + NSString *path; + + if ([scheme isEqualToString:@"cydia"]) + path = [href substringFromIndex:8]; + else if ([scheme isEqualToString:@"about"]) + path = [href substringFromIndex:12]; + else _assert(false); + + NSRange slash([path rangeOfString:@"/"]); + + NSString *command; + if (slash.location == NSNotFound) { + command = path; + path = nil; + } else { + command = [path substringToIndex:slash.location]; + path = [path substringFromIndex:(slash.location + 1)]; + } + + Database *database([Database sharedInstance]); + + if ([command isEqualToString:@"package-icon"]) { + if (path == nil) + goto fail; + path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + Package *package([database packageWithName:path]); + if (package == nil) + goto fail; + UIImage *icon([package icon]); + [self _returnPNGWithImage:icon forRequest:request]; + } else if ([command isEqualToString:@"source-icon"]) { + if (path == nil) + goto fail; + path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *source(Simplify(path)); + UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sources/%@.png", App_, source]]); + if (icon == nil) + icon = [UIImage applicationImageNamed:@"unknown.png"]; + [self _returnPNGWithImage:icon forRequest:request]; + } else if ([command isEqualToString:@"uikit-image"]) { + if (path == nil) + goto fail; + path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + UIImage *icon(_UIImageWithName(path)); + [self _returnPNGWithImage:icon forRequest:request]; + } else if ([command isEqualToString:@"section-icon"]) { + if (path == nil) + goto fail; + path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *section(Simplify(path)); + UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]]); + if (icon == nil) + icon = [UIImage applicationImageNamed:@"unknown.png"]; + [self _returnPNGWithImage:icon forRequest:request]; + } else fail: { + [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]]; + } } -- (void) setDelegate:(id)delegate { - [super setDelegate:delegate]; - [packages_ setDelegate:delegate]; +- (void) stopLoading { } @end /* }}} */ + /* Section Controller {{{ */ -@interface CYSectionController : FilteredPackageController { +@interface SectionController : FilteredPackageListController { + _H section_; } - (id) initWithDatabase:(Database *)database section:(NSString *)section; @end -@implementation CYSectionController +@implementation SectionController -- (void) dealloc { - [super dealloc]; +- (NSURL *) navigationURL { + NSString *name = section_; + if (name == nil) + name = @"all"; + + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sections/%@", name]]; } - (id) initWithDatabase:(Database *)database section:(NSString *)name { NSString *title; - - if (name == nil) { + if (name == nil) title = UCLocalize("ALL_PACKAGES"); - } else if (![name isEqual:@""]) { + else if (![name isEqual:@""]) title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"]; - } else { + else title = UCLocalize("NO_SECTION"); - } if ((self = [super initWithDatabase:database title:title filter:@selector(isVisibleInSection:) with:name]) != nil) { + section_ = name; } return self; } -- (void) reloadData { - [packages_ reloadData]; +@end +/* }}} */ +/* Sections Controller {{{ */ +@interface SectionsController : CYViewController < + UITableViewDataSource, + UITableViewDelegate +> { + _transient Database *database_; + NSMutableArray *sections_; + NSMutableArray *filtered_; + UITableView *list_; + BOOL editing_; } -- (void) setDelegate:(id)delegate { - [super setDelegate:delegate]; - [packages_ setDelegate:delegate]; -} +- (id) initWithDatabase:(Database *)database; +- (void) editButtonClicked; @end -/* }}} */ -/* Home Controller {{{ */ -@interface HomeController : CYBrowserController { -} -@end +@implementation SectionsController -@implementation HomeController +- (void) dealloc { + [self releaseSubviews]; + [sections_ release]; + [filtered_ release]; -+ (BOOL)shouldHideNavigationBar { - return NO; + [super dealloc]; } -- (void) _setMoreHeaders:(NSMutableURLRequest *)request { - [super _setMoreHeaders:request]; - - if (ChipID_ != nil) - [request setValue:ChipID_ forHTTPHeaderField:@"X-Chip-ID"]; - if (UniqueID_ != nil) - [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"]; - if (PLMN_ != nil) - [request setValue:PLMN_ forHTTPHeaderField:@"X-Carrier-ID"]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://sections"]; } -- (void) aboutButtonClicked { - UIAlertView *alert([[[UIAlertView alloc] init] autorelease]); +- (void) updateNavigationItem { + [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")]; + if ([sections_ count] == 0) { + [[self navigationItem] setRightBarButtonItem:nil]; + } else { + [[self navigationItem] setRightBarButtonItem:[[UIBarButtonItem alloc] + initWithBarButtonSystemItem:(editing_ ? UIBarButtonSystemItemDone : UIBarButtonSystemItemEdit) + target:self + action:@selector(editButtonClicked) + ] animated:([[self navigationItem] rightBarButtonItem] != nil)]; + } +} - [alert setTitle:UCLocalize("ABOUT_CYDIA")]; - [alert addButtonWithTitle:UCLocalize("CLOSE")]; - [alert setCancelButtonIndex:0]; +- (BOOL) isEditing { + return editing_; +} - [alert setMessage: - @"Copyright (C) 2008-2010\n" - "Jay Freeman (saurik)\n" - "saurik@saurik.com\n" - "http://www.saurik.com/" - ]; +- (void) setEditing:(BOOL)editing { + if ((editing_ = editing)) + [list_ reloadData]; + else + [delegate_ updateData]; - [alert show]; + [self updateNavigationItem]; } -- (void) viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - - if ([[self class] shouldHideNavigationBar]) - [[self navigationController] setNavigationBarHidden:YES animated:animated]; +- (void) viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; } - (void) viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - - if ([[self class] shouldHideNavigationBar]) - [[self navigationController] setNavigationBarHidden:NO animated:animated]; + if (editing_) [self setEditing:NO]; } -- (id) init { - if ((self = [super init]) != nil) { - [self loadURL:[NSURL URLWithString:CydiaURL(@"")]]; - - [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("ABOUT") - style:UIBarButtonItemStylePlain - target:self - action:@selector(aboutButtonClicked) - ] autorelease]]; - } return self; +- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath { + Section *section = (editing_ ? [sections_ objectAtIndex:[indexPath row]] : ([indexPath row] == 0 ? nil : [filtered_ objectAtIndex:([indexPath row] - 1)])); + return section; } -@end -/* }}} */ -/* Manage Controller {{{ */ -@interface ManageController : CYBrowserController { +- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return editing_ ? [sections_ count] : [filtered_ count] + 1; } -- (void) queueStatusDidChange; -@end - -@implementation ManageController +/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 45.0f; +}*/ -- (id) init { - if ((self = [super init]) != nil) { - [[self navigationItem] setTitle:UCLocalize("MANAGE")]; +- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *reuseIdentifier = @"SectionCell"; - [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"manage" ofType:@"html"]]]; + SectionCell *cell = (SectionCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; + if (cell == nil) + cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease]; - [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("SETTINGS") - style:UIBarButtonItemStylePlain - target:self - action:@selector(settingsButtonClicked) - ] autorelease]]; + [cell setSection:[self sectionAtIndexPath:indexPath] editing:editing_]; - [self queueStatusDidChange]; - } return self; + return cell; } -- (void) settingsButtonClicked { - [delegate_ showSettings]; -} +- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (editing_) + return; -#if !AlwaysReload -- (void) queueButtonClicked { - [delegate_ queue]; + Section *section = [self sectionAtIndexPath:indexPath]; + + SectionController *controller = [[[SectionController alloc] + initWithDatabase:database_ + section:[section name] + ] autorelease]; + [controller setDelegate:delegate_]; + + [[self navigationController] pushViewController:controller animated:YES]; } -- (void) applyLoadingTitle { - // No "Loading" title. +- (void) loadView { + [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]]; + + list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]]; + [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [list_ setRowHeight:45.0f]; + [list_ setDataSource:self]; + [list_ setDelegate:self]; + [[self view] addSubview:list_]; } -- (void) applyRightButton { - // No right button. +- (void) viewDidLoad { + [[self navigationItem] setTitle:UCLocalize("SECTIONS")]; } -#endif -- (void) queueStatusDidChange { -#if !AlwaysReload - if (!IsWildcat_ && Queuing_) { - [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("QUEUE") - style:UIBarButtonItemStyleDone - target:self - action:@selector(queueButtonClicked) - ] autorelease]]; - } else { - [[self navigationItem] setRightBarButtonItem:nil]; - } -#endif +- (void) releaseSubviews { + [list_ release]; + list_ = nil; } -- (bool) isLoading { - return false; +- (id) initWithDatabase:(Database *)database { + if ((self = [super init]) != nil) { + database_ = database; + + sections_ = [[NSMutableArray arrayWithCapacity:16] retain]; + filtered_ = [[NSMutableArray arrayWithCapacity:16] retain]; + } return self; } -@end -/* }}} */ +- (void) reloadData { + [super reloadData]; -/* Refresh Bar {{{ */ -@interface RefreshBar : UINavigationBar { - UIProgressIndicator *indicator_; - UITextLabel *prompt_; - UIProgressBar *progress_; - UINavigationButton *cancel_; -} + NSArray *packages = [database_ packages]; -@end + [sections_ removeAllObjects]; + [filtered_ removeAllObjects]; -@implementation RefreshBar + NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]); -- (void) dealloc { - [indicator_ release]; - [prompt_ release]; - [progress_ release]; - [cancel_ release]; - [super dealloc]; -} + _trace(); + for (Package *package in packages) { + NSString *name([package section]); + NSString *key(name == nil ? @"" : name); -- (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]; + Section *section; - CGSize prgsize = {75, 100}; - CGRect prgrect = {{ - [self frame].size.width - prgsize.width - 10, - ([self frame].size.height - prgsize.height) / 2 - } , prgsize}; - [progress_ setFrame:prgrect]; + _profile(SectionsView$reloadData$Section) + section = [sections objectForKey:key]; + if (section == nil) { + _profile(SectionsView$reloadData$Section$Allocate) + section = [[[Section alloc] initWithName:key localize:YES] autorelease]; + [sections setObject:section forKey:key]; + _end + } + _end - CGSize indsize([UIProgressIndicator defaultSizeForStyle:[indicator_ activityIndicatorViewStyle]]); - unsigned indoffset = ([self frame].size.height - indsize.height) / 2; - CGRect indrect = {{indoffset, indoffset}, indsize}; - [indicator_ setFrame:indrect]; + [section addToCount]; - 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]; -} + _profile(SectionsView$reloadData$Filter) + if (![package valid] || ![package visible]) + continue; + _end -- (void)setFrame:(CGRect)frame { - [super setFrame:frame]; + [section addToRow]; + } + _trace(); - [self positionViews]; + [sections_ addObjectsFromArray:[sections allValues]]; + + [sections_ sortUsingSelector:@selector(compareByLocalized:)]; + + for (Section *section in sections_) { + size_t count([section row]); + if (count == 0) + continue; + + section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease]; + [section setCount:count]; + [filtered_ addObject:section]; + } + + [self updateNavigationItem]; + [list_ reloadData]; + _trace(); } -- (id) initWithFrame:(CGRect)frame delegate:(id)delegate { - if ((self = [super initWithFrame:frame])) { - [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; +- (void) editButtonClicked { + [self setEditing:(!editing_)]; +} - [self setBarStyle:UIBarStyleBlack]; +@end +/* }}} */ - UIBarStyle barstyle([self _barStyle:NO]); - bool ugly(barstyle == UIBarStyleDefault); +/* Changes Controller {{{ */ +@interface ChangesController : CYViewController < + UITableViewDataSource, + UITableViewDelegate +> { + _transient Database *database_; + unsigned era_; + CFMutableArrayRef packages_; + NSMutableArray *sections_; + UITableView *list_; + unsigned upgrades_; + BOOL hasSentFirstLoad_; +} - UIProgressIndicatorStyle style = ugly ? - UIProgressIndicatorStyleMediumBrown : - UIProgressIndicatorStyleMediumWhite; +- (id) initWithDatabase:(Database *)database; - indicator_ = [[UIProgressIndicator alloc] initWithFrame:CGRectZero]; - [indicator_ setStyle:style]; - [indicator_ startAnimation]; - [self addSubview:indicator_]; +@end - prompt_ = [[UITextLabel alloc] initWithFrame:CGRectZero]; - [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]]; - [prompt_ setBackgroundColor:[UIColor clearColor]]; - [prompt_ setFont:[UIFont systemFontOfSize:15]]; - [self addSubview:prompt_]; +@implementation ChangesController - progress_ = [[UIProgressBar alloc] initWithFrame:CGRectZero]; - [progress_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin]; - [progress_ setStyle:0]; - [self addSubview:progress_]; +- (void) dealloc { + [self releaseSubviews]; + CFRelease(packages_); + [sections_ release]; - cancel_ = [[UINavigationButton alloc] initWithTitle:UCLocalize("CANCEL") style:UINavigationButtonStyleHighlighted]; - [cancel_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; - [cancel_ addTarget:delegate action:@selector(cancelPressed) forControlEvents:UIControlEventTouchUpInside]; - [cancel_ setBarStyle:barstyle]; + [super dealloc]; +} - [self positionViews]; - } return self; +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://changes"]; } -- (void) cancel { - [cancel_ removeFromSuperview]; +- (void) viewWillAppear:(BOOL)animated { + // Loads after it appears, so don't load beforehand. + loaded_ = YES; + [super viewWillAppear:animated]; } -- (void) start { - [prompt_ setText:UCLocalize("UPDATING_DATABASE")]; - [progress_ setProgress:0]; - [self addSubview:cancel_]; +- (void) viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + if (!hasSentFirstLoad_) { + hasSentFirstLoad_ = YES; + [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0]; + } else { + [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; + } } -- (void) stop { - [cancel_ removeFromSuperview]; +- (NSInteger) numberOfSectionsInTableView:(UITableView *)list { + NSInteger count([sections_ count]); + return count == 0 ? 1 : count; } -- (void) setPrompt:(NSString *)prompt { - [prompt_ setText:prompt]; +- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section { + if ([sections_ count] == 0) + return nil; + return [[sections_ objectAtIndex:section] name]; } -- (void) setProgress:(float)progress { - [progress_ setProgress:progress]; +- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section { + if ([sections_ count] == 0) + return 0; + return [[sections_ objectAtIndex:section] count]; } -@end -/* }}} */ +- (Package *) packageAtIndex:(NSUInteger)index { + return (Package *) CFArrayGetValueAtIndex(packages_, index); +} -@class CYNavigationController; +- (Package *) packageAtIndexPath:(NSIndexPath *)path { +@synchronized (database_) { + if ([database_ era] != era_) + return nil; -/* Cydia Tab Bar Controller {{{ */ -@interface CYTabBarController : UITabBarController < - ProgressDelegate -> { - _transient Database *database_; - RefreshBar *refreshbar_; + NSUInteger sectionIndex([path section]); + if (sectionIndex >= [sections_ count]) + return nil; + Section *section([sections_ objectAtIndex:sectionIndex]); + NSInteger row([path row]); + return [[[self packageAtIndex:([section row] + row)] retain] autorelease]; +} } - bool dropped_; - bool updating_; - // XXX: ok, "updatedelegate_"?... - _transient NSObject *updatedelegate_; +- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path { + PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]); + if (cell == nil) + cell = [[[PackageCell alloc] init] autorelease]; + [cell setPackage:[self packageAtIndexPath:path]]; + return cell; +} - id root_; +- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path { + Package *package([self packageAtIndexPath:path]); + CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id]] autorelease]); + [view setDelegate:delegate_]; + [[self navigationController] pushViewController:view animated:YES]; + return path; } -- (void) dropBar:(BOOL)animated; -- (void) beginUpdate; -- (void) raiseBar:(BOOL)animated; -- (BOOL) updating; +- (void) refreshButtonClicked { + [delegate_ beginUpdate]; + [[self navigationItem] setLeftBarButtonItem:nil animated:YES]; +} -@end +- (void) upgradeButtonClicked { + [delegate_ distUpgrade]; +} -@implementation CYTabBarController +- (void) loadView { + [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]]; -- (void) reloadData { - size_t count([[self viewControllers] count]); - for (size_t i(0); i != count; ++i) { - CYNavigationController *page([[self viewControllers] objectAtIndex:(count - i - 1)]); - [page reloadData]; - } + list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain]; + [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [list_ setRowHeight:73]; + [list_ setDataSource:self]; + [list_ setDelegate:self]; + [[self view] addSubview:list_]; +} + +- (void) viewDidLoad { + [[self navigationItem] setTitle:UCLocalize("CHANGES")]; +} + +- (void) releaseSubviews { + [list_ release]; + list_ = nil; } - (id) initWithDatabase:(Database *)database { if ((self = [super init]) != nil) { database_ = database; - [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; - - refreshbar_ = [[RefreshBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, [UINavigationBar defaultSize].height) delegate:self]; + packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + sections_ = [[NSMutableArray arrayWithCapacity:16] retain]; } return self; } -- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { - return IsWildcat_ || orientation == UIInterfaceOrientationPortrait; -} +// 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); -- (void) setUpdate:(NSDate *)date { - [self beginUpdate]; + _trace(); + _profile(ChangesController$_reloadPackages$Filter) + for (Package *package in packages) + if ([package upgradableAndEssential:YES] || [package visible]) + CFArrayAppendValue(packages_, package); + _end + _trace(); + _profile(ChangesController$_reloadPackages$radixSort) + [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast(&PackageChangesRadix) withContext:NULL]; + _end + _trace(); } -- (void) beginUpdate { - [refreshbar_ start]; - [self dropBar:YES]; +- (void) reloadData { +@synchronized (database_) { + era_ = [database_ era]; + NSArray *packages = [database_ packages]; - [updatedelegate_ retainNetworkActivityIndicator]; - updating_ = true; + [sections_ removeAllObjects]; - [NSThread - detachNewThreadSelector:@selector(performUpdate) - toTarget:self - withObject:nil - ]; -} +#if 1 + UIProgressHUD *hud([delegate_ addProgressHUD]); + [hud setText:UCLocalize("LOADING")]; + //NSLog(@"HUD:%@::%@", delegate_, hud); + [self yieldToSelector:@selector(_reloadPackages:) withObject:packages]; + [delegate_ removeProgressHUD:hud]; +#else + [self _reloadPackages:packages]; +#endif -- (void) performUpdate { _pooled - Status status; - status.setDelegate(self); - [database_ updateWithStatus:status]; + Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease]; + Section *ignored = nil; + Section *section = nil; + time_t last = 0; - [self - performSelectorOnMainThread:@selector(completeUpdate) - withObject:nil - waitUntilDone:NO - ]; -} + upgrades_ = 0; + bool unseens = false; -- (void) stopUpdateWithSelector:(SEL)selector { - updating_ = false; - [updatedelegate_ releaseNetworkActivityIndicator]; + CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle)); - [self raiseBar:YES]; - [refreshbar_ stop]; + for (size_t offset = 0, count = CFArrayGetCount(packages_); offset != count; ++offset) { + Package *package = [self packageAtIndex:offset]; - [updatedelegate_ performSelector:selector withObject:nil afterDelay:0]; + BOOL uae = [package upgradableAndEssential:YES]; + + if (!uae) { + unseens = true; + time_t seen([package seen]); + + if (section == nil || last != seen) { + last = seen; + + NSString *name; + name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]); + [name autorelease]; + + _profile(ChangesController$reloadData$Allocate) + name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name]; + section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease]; + [sections_ addObject:section]; + _end + } + + [section addToCount]; + } else if ([package ignored]) { + if (ignored == nil) { + ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease]; + } + [ignored addToCount]; + } else { + ++upgrades_; + [upgradable addToCount]; + } + } + _trace(); + + CFRelease(formatter); + + if (unseens) { + Section *last = [sections_ lastObject]; + size_t count = [last count]; + CFArrayReplaceValues(packages_, CFRangeMake(CFArrayGetCount(packages_) - count, count), NULL, 0); + [sections_ removeLastObject]; + } + + if ([ignored count] != 0) + [sections_ insertObject:ignored atIndex:0]; + if (upgrades_ != 0) + [sections_ insertObject:upgradable atIndex:0]; + + [list_ reloadData]; + + if (upgrades_ > 0) + [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", upgrades_]] + style:UIBarButtonItemStylePlain + target:self + action:@selector(upgradeButtonClicked) + ] autorelease]]; + + if (![delegate_ updating]) + [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("REFRESH") + style:UIBarButtonItemStylePlain + target:self + action:@selector(refreshButtonClicked) + ] autorelease]]; + + PrintTimes(); +} } + +@end +/* }}} */ +/* Search Controller {{{ */ +@interface SearchController : FilteredPackageListController < + UISearchBarDelegate +> { + _H search_; + BOOL searchloaded_; } -- (void) completeUpdate { - if (!updating_) - return; - [self stopUpdateWithSelector:@selector(reloadData)]; +- (id) initWithDatabase:(Database *)database; +- (void) setSearchTerm:(NSString *)term; +- (void) reloadData; + +@end + +@implementation SearchController + +- (void) dealloc { + [search_ setDelegate:nil]; + [super dealloc]; } -- (void) cancelUpdate { - [self stopUpdateWithSelector:@selector(updateData)]; +- (NSURL *) navigationURL { + if ([search_ text] == nil || [[search_ text] isEqualToString:@""]) + return [NSURL URLWithString:@"cydia://search"]; + else + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://search/%@", [search_ text]]]; } -- (void) cancelPressed { - [self cancelUpdate]; +- (void) setSearchTerm:(NSString *)searchTerm { + [search_ setText:searchTerm]; + [self reloadData]; } -- (BOOL) updating { - return updating_; +- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar { + [self setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)]; + [search_ resignFirstResponder]; + [self reloadData]; } -- (void) setProgressError:(NSString *)error withTitle:(NSString *)title { - [refreshbar_ setPrompt:[NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), UCLocalize("ERROR"), error]]; +- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text { + [self setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)]; + [self reloadData]; } -- (void) startProgress { +- (id) initWithDatabase:(Database *)database { + if ((self = [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil])) { + search_ = [[[UISearchBar alloc] init] autorelease]; + [search_ setDelegate:self]; + } return self; } -- (void) setProgressTitle:(NSString *)title { - [self - performSelectorOnMainThread:@selector(_setProgressTitle:) - withObject:title - waitUntilDone:YES - ]; +- (void) viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + if (!searchloaded_) { + searchloaded_ = YES; + [search_ setFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)]; + [search_ layoutSubviews]; + [search_ setPlaceholder:UCLocalize("SEARCH_EX")]; + + UITextField *textField; + if ([search_ respondsToSelector:@selector(searchField)]) + textField = [search_ searchField]; + else + textField = MSHookIvar(search_, "_searchField"); + + [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin]; + [textField setEnablesReturnKeyAutomatically:NO]; + [[self navigationItem] setTitleView:textField]; + } } -- (bool) isCancelling:(size_t)received { - return !updating_; +- (void) reloadData { + [self setObject:[search_ text]]; + [super reloadData]; + [self resetCursor]; } -- (void) setProgressPercent:(float)percent { - [self - performSelectorOnMainThread:@selector(_setProgressPercent:) - withObject:[NSNumber numberWithFloat:percent] - waitUntilDone:YES - ]; +- (void) didSelectPackage:(Package *)package { + [search_ resignFirstResponder]; + [super didSelectPackage:package]; } -- (void) addProgressOutput:(NSString *)output { - [self - performSelectorOnMainThread:@selector(_addProgressOutput:) - withObject:output - waitUntilDone:YES - ]; +@end +/* }}} */ +/* Package Settings Controller {{{ */ +@interface PackageSettingsController : CYViewController < + UITableViewDataSource, + UITableViewDelegate +> { + _transient Database *database_; + NSString *name_; + Package *package_; + UITableView *table_; + UISwitch *subscribedSwitch_; + UISwitch *ignoredSwitch_; + UITableViewCell *subscribedCell_; + UITableViewCell *ignoredCell_; } -- (void) _setProgressTitle:(NSString *)title { - [refreshbar_ setPrompt:title]; +- (id) initWithDatabase:(Database *)database package:(NSString *)package; + +@end + +@implementation PackageSettingsController + +- (void) dealloc { + [self releaseSubviews]; + [name_ release]; + [package_ release]; + + [super dealloc]; } -- (void) _setProgressPercent:(NSNumber *)percent { - [refreshbar_ setProgress:[percent floatValue]]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/settings", [package_ id]]]; } -- (void) _addProgressOutput:(NSString *)output { +- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { + if (package_ == nil) + return 0; + + if ([package_ installed] == nil) + return 1; + else + return 2; } -- (void) setUpdateDelegate:(id)delegate { - updatedelegate_ = delegate; +- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + if (package_ == nil) + return 0; + + // both sections contain just one item right now. + return 1; } -- (CGFloat) statusBarHeight { - if (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) { - return [[UIApplication sharedApplication] statusBarFrame].size.height; - } else { - return [[UIApplication sharedApplication] statusBarFrame].size.width; - } +- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return nil; } -- (UIView *) transitionView { - if ([self respondsToSelector:@selector(_transitionView)]) - return [self _transitionView]; +- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { + if (section == 0) + return UCLocalize("SHOW_ALL_CHANGES_EX"); else - return MSHookIvar(self, "_viewControllerTransitionView"); + return UCLocalize("IGNORE_UPGRADES_EX"); } -- (void) dropBar:(BOOL)animated { - if (dropped_) - return; - dropped_ = true; +- (void) onSubscribed:(id)control { + bool value([control isOn]); + if (package_ == nil) + return; + if ([package_ setSubscribed:value]) + [delegate_ updateData]; +} - UIView *transition([self transitionView]); - [[self view] addSubview:refreshbar_]; +- (void) _updateIgnored { + const char *package([name_ UTF8String]); + bool on([ignoredSwitch_ isOn]); - CGRect barframe([refreshbar_ frame]); + pid_t pid(ExecFork()); + if (pid == 0) { + FILE *dpkg(popen("dpkg --set-selections", "w")); + fwrite(package, strlen(package), 1, dpkg); - if (false) // XXX: _UIApplicationLinkedOnOrAfter(4) - barframe.origin.y = [self statusBarHeight]; - else - barframe.origin.y = 0; + if (on) + fwrite(" hold\n", 6, 1, dpkg); + else + fwrite(" install\n", 9, 1, dpkg); - [refreshbar_ setFrame:barframe]; + pclose(dpkg); - if (animated) - [UIView beginAnimations:nil context:NULL]; + exit(0); + _assert(false); + } - CGRect viewframe = [transition frame]; - viewframe.origin.y += barframe.size.height; - viewframe.size.height -= barframe.size.height; - [transition setFrame:viewframe]; + _forever { + int status; + int result(waitpid(pid, &status, 0)); - if (animated) - [UIView commitAnimations]; + if (result != -1) { + _assert(result == pid); + break; + } + } +} - // Ensure bar has the proper width for our view, it might have changed - barframe.size.width = viewframe.size.width; - [refreshbar_ setFrame:barframe]; +- (void) onIgnored:(id)control { + NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(_updateIgnored)]]); + [invocation setTarget:self]; + [invocation setSelector:@selector(_updateIgnored)]; - // XXX: fix Apple's layout bug - [[root_ selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation]; + [delegate_ reloadDataWithInvocation:invocation]; } -- (void) raiseBar:(BOOL)animated { - if (!dropped_) - return; - dropped_ = false; +- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if (package_ == nil) + return nil; - UIView *transition([self transitionView]); - [refreshbar_ removeFromSuperview]; + switch ([indexPath section]) { + case 0: return subscribedCell_; + case 1: return ignoredCell_; - CGRect barframe([refreshbar_ frame]); + _nodefault + } - if (animated) - [UIView beginAnimations:nil context:NULL]; + return nil; +} - CGRect viewframe = [transition frame]; - viewframe.origin.y -= barframe.size.height; - viewframe.size.height += barframe.size.height; - [transition setFrame:viewframe]; +- (void) loadView { + [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]]; - if (animated) - [UIView commitAnimations]; + table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped]; + [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [table_ setDataSource:self]; + [table_ setDelegate:self]; + [[self view] addSubview:table_]; - // XXX: fix Apple's layout bug - // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation]; + subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)]; + [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged]; + + ignoredSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)]; + [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; + [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged]; + + subscribedCell_ = [[UITableViewCell alloc] init]; + [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")]; + [subscribedCell_ setAccessoryView:subscribedSwitch_]; + [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone]; + + ignoredCell_ = [[UITableViewCell alloc] init]; + [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")]; + [ignoredCell_ setAccessoryView:ignoredSwitch_]; + [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone]; } -#if 0 -- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration { - // XXX: fix Apple's layout bug - // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation]; +- (void) viewDidLoad { + [[self navigationItem] setTitle:UCLocalize("SETTINGS")]; } -#endif -- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { - bool dropped(dropped_); +- (void) releaseSubviews { + [ignoredCell_ release]; + ignoredCell_ = nil; - if (dropped) - [self raiseBar:NO]; + [subscribedCell_ release]; + subscribedCell_ = nil; - [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; + [table_ release]; + table_ = nil; - if (dropped) - [self dropBar:NO]; + [ignoredSwitch_ release]; + ignoredSwitch_ = nil; - // XXX: fix Apple's layout bug - // SRK [[self selectedViewController] _updateLayoutForStatusBarAndInterfaceOrientation]; + [subscribedSwitch_ release]; + subscribedSwitch_ = nil; } -- (void) statusBarFrameChanged:(NSNotification *)notification { - if (dropped_) { - [self raiseBar:NO]; - [self dropBar:NO]; - } +- (id) initWithDatabase:(Database *)database package:(NSString *)package { + if ((self = [super init]) != nil) { + database_ = database; + name_ = [package retain]; + } return self; } -- (void) dealloc { - [refreshbar_ release]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [super dealloc]; +- (void) reloadData { + [super reloadData]; + + if (package_ != nil) + [package_ autorelease]; + package_ = [database_ packageWithName:name_]; + + if (package_ != nil) { + 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]; } @end /* }}} */ -/* Cydia Navigation Controller {{{ */ -@interface CYNavigationController : UINavigationController { - _transient Database *database_; - _transient id delegate_; +/* Installed Controller {{{ */ +@interface InstalledController : FilteredPackageListController { + BOOL expert_; } - (id) initWithDatabase:(Database *)database; -- (void) reloadData; + +- (void) updateRoleButton; +- (void) queueStatusDidChange; @end +@implementation InstalledController -@implementation CYNavigationController +- (void) dealloc { + [super dealloc]; +} -- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { - // Inherit autorotation settings for modal parents. - if ([self parentViewController] && [[self parentViewController] modalViewController] == self) { - return [[self parentViewController] shouldAutorotateToInterfaceOrientation:orientation]; - } else if ([self parentViewController]) { - return [[self parentViewController] shouldAutorotateToInterfaceOrientation:orientation]; - } else { - return [super shouldAutorotateToInterfaceOrientation:orientation]; - } +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://installed"]; } -- (void) dealloc { - [super dealloc]; +- (id) initWithDatabase:(Database *)database { + if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED") filter:@selector(isInstalledAndUnfiltered:) with:[NSNumber numberWithBool:YES]]) != nil) { + [self updateRoleButton]; + [self queueStatusDidChange]; + } return self; } -- (void) reloadData { - size_t count([[self viewControllers] count]); - for (size_t i(0); i != count; ++i) { - CYViewController *page([[self viewControllers] objectAtIndex:(count - i - 1)]); - [page reloadData]; +#if !AlwaysReload +- (void) queueButtonClicked { + [delegate_ queue]; +} +#endif + +- (void) queueStatusDidChange { +#if !AlwaysReload + if (IsWildcat_) { + if (Queuing_) { + [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("QUEUE") + style:UIBarButtonItemStyleDone + target:self + action:@selector(queueButtonClicked) + ] autorelease]]; + } else { + [[self navigationItem] setLeftBarButtonItem:nil]; + } } +#endif } -- (void) setDelegate:(id)delegate { - delegate_ = delegate; +- (void) updateRoleButton { + if (Role_ != nil && ![Role_ isEqualToString:@"Developer"]) + [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:(expert_ ? UCLocalize("EXPERT") : UCLocalize("SIMPLE")) + style:(expert_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain) + target:self + action:@selector(roleButtonClicked) + ] autorelease]]; } -- (id) initWithDatabase:(Database *)database { - if ((self = [super init]) != nil) { - database_ = database; - } return self; +- (void) roleButtonClicked { + [self setObject:[NSNumber numberWithBool:expert_]]; + [self reloadData]; + expert_ = !expert_; + + [self updateRoleButton]; } @end /* }}} */ -/* Cydia:// Protocol {{{ */ -@interface CydiaURLProtocol : NSURLProtocol { + +/* Source Cell {{{ */ +@interface SourceCell : CYTableViewCell < + ContentDelegate +> { + UIImage *icon_; + NSString *origin_; + NSString *label_; } +- (void) setSource:(Source *)source; + @end -@implementation CydiaURLProtocol +@implementation SourceCell -+ (BOOL) canInitWithRequest:(NSURLRequest *)request { - NSURL *url([request URL]); - if (url == nil) - return NO; - NSString *scheme([[url scheme] lowercaseString]); - if (scheme == nil || ![scheme isEqualToString:@"cydia"]) - return NO; - return YES; -} +- (void) clearSource { + [icon_ release]; + [origin_ release]; + [label_ release]; -+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request { - return request; + icon_ = nil; + origin_ = nil; + label_ = nil; } -- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request { - id client([self client]); - if (icon == nil) - [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]]; - else { - NSData *data(UIImagePNGRepresentation(icon)); +- (void) setSource:(Source *)source { + [self clearSource]; - NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]); - [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; - [client URLProtocol:self didLoadData:data]; - [client URLProtocolDidFinishLoading:self]; - } -} + if (icon_ == nil) + icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]]; + if (icon_ == nil) + icon_ = [UIImage applicationImageNamed:@"unknown.png"]; + icon_ = [icon_ retain]; -- (void) startLoading { - id client([self client]); - NSURLRequest *request([self request]); + origin_ = [[source name] retain]; + label_ = [[source uri] retain]; - NSURL *url([request URL]); - NSString *href([url absoluteString]); + [content_ setNeedsDisplay]; +} - NSString *path([href substringFromIndex:8]); - NSRange slash([path rangeOfString:@"/"]); +- (void) dealloc { + [self clearSource]; + [super dealloc]; +} - NSString *command; - if (slash.location == NSNotFound) { - command = path; - path = nil; - } else { - command = [path substringToIndex:slash.location]; - path = [path substringFromIndex:(slash.location + 1)]; - } +- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { + if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) { + UIView *content([self contentView]); + CGRect bounds([content bounds]); - Database *database([Database sharedInstance]); + content_ = [[ContentView alloc] initWithFrame:bounds]; + [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [content_ setBackgroundColor:[UIColor whiteColor]]; + [content addSubview:content_]; - if ([command isEqualToString:@"package-icon"]) { - if (path == nil) - goto fail; - path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - Package *package([database packageWithName:path]); - if (package == nil) - goto fail; - UIImage *icon([package icon]); - [self _returnPNGWithImage:icon forRequest:request]; - } else if ([command isEqualToString:@"source-icon"]) { - if (path == nil) - goto fail; - path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - NSString *source(Simplify(path)); - UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sources/%@.png", App_, source]]); - if (icon == nil) - icon = [UIImage applicationImageNamed:@"unknown.png"]; - [self _returnPNGWithImage:icon forRequest:request]; - } else if ([command isEqualToString:@"uikit-image"]) { - if (path == nil) - goto fail; - path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - UIImage *icon(_UIImageWithName(path)); - [self _returnPNGWithImage:icon forRequest:request]; - } else if ([command isEqualToString:@"section-icon"]) { - if (path == nil) - goto fail; - path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - NSString *section(Simplify(path)); - UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]]); - if (icon == nil) - icon = [UIImage applicationImageNamed:@"unknown.png"]; - [self _returnPNGWithImage:icon forRequest:request]; - } else fail: { - [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]]; - } + [content_ setDelegate:self]; + [content_ setOpaque:YES]; + } return self; } -- (void) stopLoading { +- (NSString *) accessibilityLabel { + return label_; +} + +- (void) drawContentRect:(CGRect)rect { + bool highlighted(highlighted_); + float width(rect.size.width); + + if (icon_ != nil) + [icon_ drawInRect:CGRectMake(10, 10, 30, 30)]; + + if (highlighted) + UISetColor(White_); + + if (!highlighted) + UISetColor(Black_); + [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - 80) withFont:Font18Bold_ lineBreakMode:UILineBreakModeTailTruncation]; + + if (!highlighted) + UISetColor(Blue_); + [label_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:UILineBreakModeTailTruncation]; } @end /* }}} */ - -/* Section Controller {{{ */ -@interface SectionController : FilteredPackageController { +/* Source Controller {{{ */ +@interface SourceController : FilteredPackageListController { + _transient Source *source_; + NSString *key_; } -- (id) initWithDatabase:(Database *)database section:(NSString *)section; +- (id) initWithDatabase:(Database *)database source:(Source *)source; @end -@implementation SectionController +@implementation SourceController -- (void) dealloc { - [super dealloc]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sources/%@", [source_ name]]]; } -- (id) initWithDatabase:(Database *)database section:(NSString *)name { - NSString *title; - - if (name == nil) { - title = UCLocalize("ALL_PACKAGES"); - } else if (![name isEqual:@""]) { - title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"]; - } else { - title = UCLocalize("NO_SECTION"); - } - - if ((self = [super initWithDatabase:database title:title filter:@selector(isVisibleInSection:) with:name]) != nil) { +- (id) initWithDatabase:(Database *)database source:(Source *)source { + if ((self = [super initWithDatabase:database title:[source label] filter:@selector(isVisibleInSource:) with:source]) != nil) { + source_ = source; + key_ = [[source key] retain]; } return self; } - (void) reloadData { - [packages_ reloadData]; -} + source_ = [database_ sourceWithKey:key_]; + [key_ release]; + key_ = [[source_ key] retain]; + [self setObject:source_]; + [[self navigationItem] setTitle:[source_ label]]; -- (void) setDelegate:(id)delegate { - [super setDelegate:delegate]; - [packages_ setDelegate:delegate]; + [super reloadData]; } @end /* }}} */ -/* Sections Controller {{{ */ -@interface SectionsController : CYViewController < +/* Sources Controller {{{ */ +@interface SourcesController : CYViewController < UITableViewDataSource, UITableViewDelegate > { _transient Database *database_; - NSMutableArray *sections_; - NSMutableArray *filtered_; UITableView *list_; - UIView *accessory_; - BOOL editing_; + NSMutableArray *sources_; + int offset_; + + NSString *href_; + UIProgressHUD *hud_; + NSError *error_; + + //NSURLConnection *installer_; + NSURLConnection *trivial_; + NSURLConnection *trivial_bz2_; + NSURLConnection *trivial_gz_; + //NSURLConnection *automatic_; + + BOOL cydia_; } - (id) initWithDatabase:(Database *)database; -- (void) reloadData; -- (void) resetView; - -- (void) editButtonClicked; +- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated; @end -@implementation SectionsController +@implementation SourcesController + +- (void) _releaseConnection:(NSURLConnection *)connection { + if (connection != nil) { + [connection cancel]; + //[connection setDelegate:nil]; + [connection release]; + } +} - (void) dealloc { - [list_ setDataSource:nil]; - [list_ setDelegate:nil]; + [self releaseSubviews]; - [sections_ release]; - [filtered_ release]; - [list_ release]; - [accessory_ release]; + [href_ release]; + [hud_ release]; + [error_ release]; + + //[self _releaseConnection:installer_]; + [self _releaseConnection:trivial_]; + [self _releaseConnection:trivial_gz_]; + [self _releaseConnection:trivial_bz2_]; + //[self _releaseConnection:automatic_]; + + [sources_ release]; [super dealloc]; } -- (void) setEditing:(BOOL)editing { - if ((editing_ = editing)) - [list_ reloadData]; - else - [delegate_ updateData]; - - [[self navigationItem] setTitle:editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")]; - [[[self navigationItem] rightBarButtonItem] setTitle:[sections_ count] == 0 ? nil : editing_ ? UCLocalize("DONE") : UCLocalize("EDIT")]; - [[[self navigationItem] rightBarButtonItem] setStyle:editing_ ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain]; +- (NSURL *) navigationURL { + return [NSURL URLWithString:@"cydia://sources"]; } - (void) viewDidAppear:(BOOL)animated { @@ -7360,645 +8154,401 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; } -- (void) viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - if (editing_) [self setEditing:NO]; +- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { + return offset_ == 0 ? 1 : 2; } -- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath { - Section *section = (editing_ ? [sections_ objectAtIndex:[indexPath row]] : ([indexPath row] == 0 ? nil : [filtered_ objectAtIndex:([indexPath row] - 1)])); - return section; +- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + switch (section + (offset_ == 0 ? 1 : 0)) { + case 0: return UCLocalize("ENTERED_BY_USER"); + case 1: return UCLocalize("INSTALLED_BY_PACKAGE"); + + _nodefault + } } - (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return editing_ ? [sections_ count] : [filtered_ count] + 1; + int count = [sources_ count]; + switch (section) { + case 0: return (offset_ == 0 ? count : offset_); + case 1: return count - offset_; + + _nodefault + } } -/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - return 45.0f; -}*/ +- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath { + unsigned idx = 0; + switch (indexPath.section) { + case 0: idx = indexPath.row; break; + case 1: idx = indexPath.row + offset_; break; -- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *reuseIdentifier = @"SectionCell"; + _nodefault + } + return [sources_ objectAtIndex:idx]; +} - SectionCell *cell = (SectionCell *) [tableView dequeueReusableCellWithIdentifier:reuseIdentifier]; - if (cell == nil) - cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease]; +- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *cellIdentifier = @"SourceCell"; - [cell setSection:[self sectionAtIndexPath:indexPath] editing:editing_]; + SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if(cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease]; + [cell setSource:[self sourceAtIndexPath:indexPath]]; + [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; return cell; } - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if (editing_) - return; - - Section *section = [self sectionAtIndexPath:indexPath]; + Source *source = [self sourceAtIndexPath:indexPath]; - SectionController *controller = [[[SectionController alloc] + SourceController *controller = [[[SourceController alloc] initWithDatabase:database_ - section:[section name] + source:source ] autorelease]; + [controller setDelegate:delegate_]; [[self navigationController] pushViewController:controller animated:YES]; } -- (id) initWithDatabase:(Database *)database { - if ((self = [super init]) != nil) { - database_ = database; +- (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 { + Source *source = [self sourceAtIndexPath:indexPath]; + [Sources_ removeObjectForKey:[source key]]; + [delegate_ syncData]; +} - [[self navigationItem] setTitle:UCLocalize("SECTIONS")]; +- (void) complete { + [delegate_ addTrivialSource:href_]; + [delegate_ syncData]; +} - sections_ = [[NSMutableArray arrayWithCapacity:16] retain]; - filtered_ = [[NSMutableArray arrayWithCapacity:16] retain]; +- (NSString *) getWarning { + NSString *href(href_); + NSRange colon([href rangeOfString:@"://"]); + if (colon.location != NSNotFound) + href = [href substringFromIndex:(colon.location + 3)]; + href = [href stringByAddingPercentEscapes]; + href = [CydiaURL(@"api/repotag/") stringByAppendingString:href]; + href = [href stringByCachingURLWithCurrentCDN]; - list_ = [[UITableView alloc] initWithFrame:[[self view] bounds]]; - [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [list_ setRowHeight:45.0f]; - [[self view] addSubview:list_]; + NSURL *url([NSURL URLWithString:href]); - [list_ setDataSource:self]; - [list_ setDelegate:self]; + NSStringEncoding encoding; + NSError *error(nil); - [self reloadData]; - } return self; + if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error]) + return [warning length] == 0 ? nil : warning; + return nil; } -- (void) reloadData { - NSArray *packages = [database_ packages]; +- (void) _endConnection:(NSURLConnection *)connection { + // XXX: the memory management in this method is horribly awkward - [sections_ removeAllObjects]; - [filtered_ removeAllObjects]; + NSURLConnection **field = NULL; + if (connection == trivial_) + field = &trivial_; + else if (connection == trivial_bz2_) + field = &trivial_bz2_; + else if (connection == trivial_gz_) + field = &trivial_gz_; + _assert(field != NULL); + [connection release]; + *field = nil; - NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]); + if ( + trivial_ == nil && + trivial_bz2_ == nil && + trivial_gz_ == nil + ) { + bool defer(false); - _trace(); - for (Package *package in packages) { - NSString *name([package section]); - NSString *key(name == nil ? @"" : name); + if (cydia_) { + if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) { + defer = true; - Section *section; + UIAlertView *alert = [[[UIAlertView alloc] + initWithTitle:UCLocalize("SOURCE_WARNING") + message:warning + delegate:self + cancelButtonTitle:UCLocalize("CANCEL") + otherButtonTitles: + UCLocalize("ADD_ANYWAY"), + nil + ] autorelease]; - _profile(SectionsView$reloadData$Section) - section = [sections objectForKey:key]; - if (section == nil) { - _profile(SectionsView$reloadData$Section$Allocate) - section = [[[Section alloc] initWithName:name localize:YES] autorelease]; - [sections setObject:section forKey:key]; - _end - } - _end - - [section addToCount]; - - _profile(SectionsView$reloadData$Filter) - if (![package valid] || ![package visible]) - continue; - _end - - [section addToRow]; - } - _trace(); - - [sections_ addObjectsFromArray:[sections allValues]]; - - [sections_ sortUsingSelector:@selector(compareByLocalized:)]; - - for (Section *section in sections_) { - size_t count([section row]); - if (count == 0) - continue; - - section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease]; - [section setCount:count]; - [filtered_ addObject:section]; - } - - [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:([sections_ count] == 0 ? nil : UCLocalize("EDIT")) - style:UIBarButtonItemStylePlain - target:self - action:@selector(editButtonClicked) - ] autorelease] animated:([[self navigationItem] rightBarButtonItem] != nil)]; - - [list_ reloadData]; - _trace(); -} - -- (void) resetView { - if (editing_) - [self editButtonClicked]; -} - -- (void)editButtonClicked { - [self setEditing:!editing_]; -} - -- (UIView *) accessoryView { - return accessory_; -} - -@end -/* }}} */ - -/* Changes Controller {{{ */ -@interface ChangesController : CYViewController < - UITableViewDataSource, - UITableViewDelegate -> { - _transient Database *database_; - unsigned era_; - CFMutableArrayRef packages_; - NSMutableArray *sections_; - UITableView *list_; - unsigned upgrades_; - BOOL hasSentFirstLoad_; -} - -- (id) initWithDatabase:(Database *)database delegate:(id)delegate; -- (void) reloadData; - -@end - -@implementation ChangesController - -- (void) dealloc { - [list_ setDelegate:nil]; - [list_ setDataSource:nil]; - - CFRelease(packages_); - - [sections_ release]; - [list_ release]; - [super dealloc]; -} - -- (void) viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - if (!hasSentFirstLoad_) { - hasSentFirstLoad_ = YES; - [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.0]; - } else { - [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated]; - } -} - -- (NSInteger) numberOfSectionsInTableView:(UITableView *)list { - NSInteger count([sections_ count]); - return count == 0 ? 1 : count; -} - -- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section { - if ([sections_ count] == 0) - return nil; - return [[sections_ objectAtIndex:section] name]; -} - -- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section { - if ([sections_ count] == 0) - return 0; - return [[sections_ objectAtIndex:section] count]; -} - -- (Package *) packageAtIndex:(NSUInteger)index { - return (Package *) CFArrayGetValueAtIndex(packages_, index); -} - -- (Package *) packageAtIndexPath:(NSIndexPath *)path { -@synchronized (database_) { - if ([database_ era] != era_) - return nil; - - NSUInteger sectionIndex([path section]); - if (sectionIndex >= [sections_ count]) - return nil; - Section *section([sections_ objectAtIndex:sectionIndex]); - NSInteger row([path row]); - return [[[self packageAtIndex:([section row] + row)] retain] autorelease]; -} } - -- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path { - PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]); - if (cell == nil) - cell = [[[PackageCell alloc] init] autorelease]; - [cell setPackage:[self packageAtIndexPath:path]]; - return cell; -} - -- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path { - Package *package([self packageAtIndexPath:path]); - CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_] autorelease]); - [view setDelegate:delegate_]; - [view setPackage:package]; - [[self navigationController] pushViewController:view animated:YES]; - return path; -} - -- (void) refreshButtonClicked { - [delegate_ beginUpdate]; - [[self navigationItem] setLeftBarButtonItem:nil animated:YES]; -} - -- (void) upgradeButtonClicked { - [delegate_ distUpgrade]; -} - -- (NSString *) title { return UCLocalize("CHANGES"); } - -- (id) initWithDatabase:(Database *)database delegate:(id)delegate { - if ((self = [super init]) != nil) { - database_ = database; - [[self navigationItem] setTitle:UCLocalize("CHANGES")]; - - packages_ = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); - - sections_ = [[NSMutableArray arrayWithCapacity:16] retain]; - - list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain]; - [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [list_ setRowHeight:73]; - [[self view] addSubview:list_]; - - [list_ setDataSource:self]; - [list_ setDelegate:self]; - - delegate_ = delegate; - } return self; -} - -- (void) _reloadPackages:(NSArray *)packages { - CFRelease(packages_); - packages_ = CFArrayCreateMutable(kCFAllocatorDefault, [packages count], NULL); - - _trace(); - _profile(ChangesController$_reloadPackages$Filter) - for (Package *package in packages) - if ([package upgradableAndEssential:YES] || [package visible]) - CFArrayAppendValue(packages_, package); - _end - _trace(); - _profile(ChangesController$_reloadPackages$radixSort) - [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast(&PackageChangesRadix) withContext:NULL]; - _end - _trace(); -} - -- (void) reloadData { -@synchronized (database_) { - era_ = [database_ era]; - NSArray *packages = [database_ packages]; - - [sections_ removeAllObjects]; - -#if 1 - UIProgressHUD *hud([delegate_ addProgressHUD]); - [hud setText:UCLocalize("LOADING")]; - //NSLog(@"HUD:%@::%@", delegate_, hud); - [self yieldToSelector:@selector(_reloadPackages:) withObject:packages]; - [delegate_ removeProgressHUD:hud]; -#else - [self _reloadPackages:packages]; -#endif - - Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease]; - Section *ignored = nil; - Section *section = nil; - time_t last = 0; - - upgrades_ = 0; - bool unseens = false; - - CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle)); - - for (size_t offset = 0, count = CFArrayGetCount(packages_); offset != count; ++offset) { - Package *package = [self packageAtIndex:offset]; - - BOOL uae = [package upgradableAndEssential:YES]; - - if (!uae) { - unseens = true; - time_t seen([package seen]); - - if (section == nil || last != seen) { - last = seen; - - NSString *name; - name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]); - [name autorelease]; - - _profile(ChangesController$reloadData$Allocate) - name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name]; - section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease]; - [sections_ addObject:section]; - _end - } + [alert setContext:@"warning"]; + [alert setNumberOfRows:1]; + [alert show]; + } else + [self complete]; + } else if (error_ != nil) { + UIAlertView *alert = [[[UIAlertView alloc] + initWithTitle:UCLocalize("VERIFICATION_ERROR") + message:[error_ localizedDescription] + delegate:self + cancelButtonTitle:UCLocalize("OK") + otherButtonTitles:nil + ] autorelease]; - [section addToCount]; - } else if ([package ignored]) { - if (ignored == nil) { - ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease]; - } - [ignored addToCount]; + [alert setContext:@"urlerror"]; + [alert show]; } else { - ++upgrades_; - [upgradable addToCount]; - } - } - _trace(); - - CFRelease(formatter); - - if (unseens) { - Section *last = [sections_ lastObject]; - size_t count = [last count]; - CFArrayReplaceValues(packages_, CFRangeMake(CFArrayGetCount(packages_) - count, count), NULL, 0); - [sections_ removeLastObject]; - } - - if ([ignored count] != 0) - [sections_ insertObject:ignored atIndex:0]; - if (upgrades_ != 0) - [sections_ insertObject:upgradable atIndex:0]; - - [list_ reloadData]; - - if (upgrades_ > 0) - [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", upgrades_]] - style:UIBarButtonItemStylePlain - target:self - action:@selector(upgradeButtonClicked) - ] autorelease]]; - - if (![delegate_ updating]) - [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] - initWithTitle:UCLocalize("REFRESH") - style:UIBarButtonItemStylePlain - target:self - action:@selector(refreshButtonClicked) - ] autorelease]]; - - PrintTimes(); -} } + UIAlertView *alert = [[[UIAlertView alloc] + initWithTitle:UCLocalize("NOT_REPOSITORY") + message:UCLocalize("NOT_REPOSITORY_EX") + delegate:self + cancelButtonTitle:UCLocalize("OK") + otherButtonTitles:nil + ] autorelease]; -@end -/* }}} */ -/* Search Controller {{{ */ -@interface SearchController : FilteredPackageController < - UISearchBarDelegate -> { - UISearchBar *search_; -} + [alert setContext:@"trivial"]; + [alert show]; + } -- (id) initWithDatabase:(Database *)database; -- (void) setSearchTerm:(NSString *)term; -- (void) reloadData; + [delegate_ releaseNetworkActivityIndicator]; -@end + [delegate_ removeProgressHUD:hud_]; + [hud_ autorelease]; + hud_ = nil; -@implementation SearchController + if (!defer) { + [href_ release]; + href_ = nil; + } -- (void) dealloc { - [search_ release]; - [super dealloc]; + if (error_ != nil) { + [error_ release]; + error_ = nil; + } + } } -- (void) setSearchTerm:(NSString *)searchTerm { - [search_ setText:searchTerm]; +- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response { + switch ([response statusCode]) { + case 200: + cydia_ = YES; + } } -- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar { - [packages_ setObject:[search_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)]; - [search_ resignFirstResponder]; - [self reloadData]; +- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + lprintf("connection:\"%s\" didFailWithError:\"%s\"", [href_ UTF8String], [[error localizedDescription] UTF8String]); + if (error_ != nil) + error_ = [error retain]; + [self _endConnection:connection]; } -- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text { - [packages_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)]; - [self reloadData]; +- (void) connectionDidFinishLoading:(NSURLConnection *)connection { + [self _endConnection:connection]; } -- (id) initWithDatabase:(Database *)database { - return [super initWithDatabase:database title:UCLocalize("SEARCH") filter:@selector(isUnfilteredAndSearchedForBy:) with:nil]; -} +- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method { + NSMutableURLRequest *request = [NSMutableURLRequest + requestWithURL:[NSURL URLWithString:href] + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:120.0 + ]; -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - if (!search_) { - search_ = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)]; - [search_ layoutSubviews]; - [search_ setPlaceholder:UCLocalize("SEARCH_EX")]; + [request setHTTPMethod:method]; - UITextField *textField; - if ([search_ respondsToSelector:@selector(searchField)]) - textField = [search_ searchField]; - else - textField = MSHookIvar(search_, "_searchField"); + if (Machine_ != NULL) + [request setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"]; + if (UniqueID_ != nil) + [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"]; + if (Role_ != nil) + [request setValue:Role_ forHTTPHeaderField:@"X-Role"]; - [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin]; - [search_ setDelegate:self]; - [textField setEnablesReturnKeyAutomatically:NO]; - [[self navigationItem] setTitleView:textField]; - } + return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease]; } -- (void) _reloadData { -} +- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { + NSString *context([alert context]); -- (void) reloadData { - _profile(SearchController$reloadData) - [packages_ reloadData]; - _end - PrintTimes(); - [packages_ resetCursor]; -} + if ([context isEqualToString:@"source"]) { + switch (button) { + case 1: { + NSString *href = [[alert textField] text]; -- (void) didSelectPackage:(Package *)package { - [search_ resignFirstResponder]; - [super didSelectPackage:package]; -} + //installer_ = [[self _requestHRef:href method:@"GET"] retain]; -@end -/* }}} */ -/* Settings Controller {{{ */ -@interface PackageSettingsController : CYViewController < - UITableViewDataSource, - UITableViewDelegate -> { - _transient Database *database_; - NSString *name_; - Package *package_; - UITableView *table_; - UISwitch *subscribedSwitch_; - UISwitch *ignoredSwitch_; - UITableViewCell *subscribedCell_; - UITableViewCell *ignoredCell_; -} + if (![href hasSuffix:@"/"]) + href_ = [href stringByAppendingString:@"/"]; + else + href_ = href; + href_ = [href_ retain]; -- (id) initWithDatabase:(Database *)database package:(NSString *)package; + trivial_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages"] method:@"HEAD"] retain]; + 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]; -@end + cydia_ = false; -@implementation PackageSettingsController + // XXX: this is stupid + hud_ = [[delegate_ addProgressHUD] retain]; + [hud_ setText:UCLocalize("VERIFYING_URL")]; + [delegate_ retainNetworkActivityIndicator]; + } break; -- (void) dealloc { - [name_ release]; - if (package_ != nil) - [package_ release]; - [table_ release]; - [subscribedSwitch_ release]; - [ignoredSwitch_ release]; - [subscribedCell_ release]; - [ignoredCell_ release]; + case 0: + break; - [super dealloc]; -} + _nodefault + } -- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { - if (package_ == nil) - return 0; + [alert dismissWithClickedButtonIndex:-1 animated:YES]; + } else if ([context isEqualToString:@"trivial"]) + [alert dismissWithClickedButtonIndex:-1 animated:YES]; + else if ([context isEqualToString:@"urlerror"]) + [alert dismissWithClickedButtonIndex:-1 animated:YES]; + else if ([context isEqualToString:@"warning"]) { + switch (button) { + case 1: + [self complete]; + break; - return 1; -} + case 0: + break; -- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (package_ == nil) - return 0; + _nodefault + } + + [href_ release]; + href_ = nil; - return 2; + [alert dismissWithClickedButtonIndex:-1 animated:YES]; + } } -- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - return UCLocalize("CHANGE_PACKAGE_SETTINGS"); +- (void) loadView { + [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]]; + + list_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain]; + [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [list_ setRowHeight:56]; + [list_ setDataSource:self]; + [list_ setDelegate:self]; + [[self view] addSubview:list_]; } -- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { - return UCLocalize("SHOW_ALL_CHANGES_EX"); +- (void) viewDidLoad { + [[self navigationItem] setTitle:UCLocalize("SOURCES")]; + [self updateButtonsForEditingStatus:NO animated:NO]; } -- (void) onSubscribed:(id)control { - bool value([control isOn]); - if (package_ == nil) - return; - if ([package_ setSubscribed:value]) - [delegate_ updateData]; +- (void) releaseSubviews { + [list_ release]; + list_ = nil; } -- (void) onIgnored:(id)control { - // TODO: set Held state - possibly call out to dpkg, etc. +- (id) initWithDatabase:(Database *)database { + if ((self = [super init]) != nil) { + database_ = database; + sources_ = [[NSMutableArray arrayWithCapacity:16] retain]; + } return self; } -- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if (package_ == nil) - return nil; +- (void) reloadData { + [super reloadData]; - switch ([indexPath row]) { - case 0: return subscribedCell_; - case 1: return ignoredCell_; + pkgSourceList list; + if ([database_ popErrorWithTitle:UCLocalize("SOURCES") forOperation:list.ReadMainList()]) + return; - _nodefault + [sources_ removeAllObjects]; + [sources_ addObjectsFromArray:[database_ sources]]; + _trace(); + [sources_ sortUsingSelector:@selector(compareByNameAndType:)]; + _trace(); + + int count([sources_ count]); + offset_ = 0; + for (int i = 0; i != count; i++) { + if ([[sources_ objectAtIndex:i] record] == nil) + break; + offset_++; } - return nil; + [list_ setEditing:NO]; + [self updateButtonsForEditingStatus:NO animated:NO]; + [list_ reloadData]; } -- (NSString *) title { return UCLocalize("SETTINGS"); } +- (void) showAddSourcePrompt { + UIAlertView *alert = [[[UIAlertView alloc] + initWithTitle:UCLocalize("ENTER_APT_URL") + message:nil + delegate:self + cancelButtonTitle:UCLocalize("CANCEL") + otherButtonTitles: + UCLocalize("ADD_SOURCE"), + nil + ] autorelease]; -- (id) initWithDatabase:(Database *)database package:(NSString *)package { - if ((self = [super init])) { - database_ = database; - name_ = [package retain]; + [alert setContext:@"source"]; + [alert setTransform:CGAffineTransformTranslate([alert transform], 0.0, 100.0)]; - [[self navigationItem] setTitle:UCLocalize("SETTINGS")]; - - table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped]; - [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [[self view] addSubview:table_]; - - subscribedSwitch_ = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)]; - [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin]; - [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged]; - - 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")]; - [subscribedCell_ setAccessoryView:subscribedSwitch_]; - [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone]; - - ignoredCell_ = [[UITableViewCell alloc] init]; - [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")]; - [ignoredCell_ setAccessoryView:ignoredSwitch_]; - [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone]; - // FIXME: Ignored state is not saved. - [ignoredCell_ setUserInteractionEnabled:NO]; - - [table_ setDataSource:self]; - [table_ setDelegate:self]; - [self reloadData]; - } return self; -} + [alert setNumberOfRows:1]; + [alert addTextFieldWithValue:@"http://" label:@""]; -- (void) reloadData { - if (package_ != nil) - [package_ autorelease]; - package_ = [database_ packageWithName:name_]; - if (package_ != nil) { - [package_ retain]; - [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO]; - [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO]; - } + UITextInputTraits *traits = [[alert textField] textInputTraits]; + [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone]; + [traits setAutocorrectionType:UITextAutocorrectionTypeNo]; + [traits setKeyboardType:UIKeyboardTypeURL]; + // XXX: UIReturnKeyDone + [traits setReturnKeyType:UIReturnKeyNext]; - [table_ reloadData]; + [alert show]; } -@end -/* }}} */ -/* Signature Controller {{{ */ -@interface SignatureController : CYBrowserController { - _transient Database *database_; - NSString *package_; +- (void) addButtonClicked { + [self showAddSourcePrompt]; } -- (id) initWithDatabase:(Database *)database package:(NSString *)package; - -@end +- (void) updateButtonsForEditingStatus:(BOOL)editing animated:(BOOL)animated { + [[self navigationItem] setLeftBarButtonItem:(editing ? [[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("ADD") + style:UIBarButtonItemStylePlain + target:self + action:@selector(addButtonClicked) + ] autorelease] : [[self navigationItem] backBarButtonItem]) animated:animated]; -@implementation SignatureController + [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT")) + style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain) + target:self + action:@selector(editButtonClicked) + ] autorelease] animated:animated]; -- (void) dealloc { - [package_ release]; - [super dealloc]; + if (IsWildcat_ && !editing) + [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc] + initWithTitle:UCLocalize("SETTINGS") + style:UIBarButtonItemStylePlain + target:self + action:@selector(settingsButtonClicked) + ] autorelease]]; } -- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame { - // XXX: dude! - [super webView:view didClearWindowObject:window forFrame:frame]; +- (void) settingsButtonClicked { + [delegate_ showSettings]; } -- (id) initWithDatabase:(Database *)database package:(NSString *)package { - if ((self = [super init]) != nil) { - database_ = database; - package_ = [package retain]; - [self reloadData]; - } return self; -} +- (void) editButtonClicked { + [list_ setEditing:![list_ isEditing] animated:YES]; -- (void) reloadData { - [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]]; + [self updateButtonsForEditingStatus:[list_ isEditing] animated:YES]; } @end /* }}} */ -/* Role Controller {{{ */ -@interface CYSettingsController : CYViewController < +/* Settings Controller {{{ */ +@interface SettingsController : CYViewController < UITableViewDataSource, UITableViewDelegate > { @@ -8015,49 +8565,64 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { @end -@implementation CYSettingsController +@implementation SettingsController + - (void) dealloc { - [table_ release]; - [segment_ release]; - [container_ release]; + [self releaseSubviews]; [super dealloc]; } -- (id) initWithDatabase:(Database *)database delegate:(id)delegate { - if ((self = [super init])) { - database_ = database; - roledelegate_ = delegate; +- (void) loadView { + [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]]; - [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")]; + table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped]; + [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; + [table_ setDelegate:self]; + [table_ setDataSource:self]; + [[self view] addSubview:table_]; - NSArray *items = [NSArray arrayWithObjects: - UCLocalize("USER"), - UCLocalize("HACKER"), - UCLocalize("DEVELOPER"), - nil]; - segment_ = [[UISegmentedControl alloc] initWithItems:items]; - container_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, 44.0f)]; - [container_ addSubview:segment_]; - - int index = -1; - if ([Role_ isEqualToString:@"User"]) index = 0; - if ([Role_ isEqualToString:@"Hacker"]) index = 1; - if ([Role_ isEqualToString:@"Developer"]) index = 2; - if (index != -1) { - [segment_ setSelectedSegmentIndex:index]; - [self showDoneButton]; - } + NSArray *items = [NSArray arrayWithObjects: + UCLocalize("USER"), + UCLocalize("HACKER"), + UCLocalize("DEVELOPER"), + nil]; + segment_ = [[UISegmentedControl alloc] initWithItems:items]; + container_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[self view] frame].size.width, 44.0f)]; + [container_ addSubview:segment_]; +} - [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged]; - [self resizeSegmentedControl]; +- (void) viewDidLoad { + [[self navigationItem] setTitle:UCLocalize("WHO_ARE_YOU")]; - table_ = [[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped]; - [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth]; - [table_ setDelegate:self]; - [table_ setDataSource:self]; - [[self view] addSubview:table_]; - [table_ reloadData]; + int index = -1; + if ([Role_ isEqualToString:@"User"]) index = 0; + if ([Role_ isEqualToString:@"Hacker"]) index = 1; + if ([Role_ isEqualToString:@"Developer"]) index = 2; + if (index != -1) { + [segment_ setSelectedSegmentIndex:index]; + [self showDoneButton]; + } + + [segment_ addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged]; + [self resizeSegmentedControl]; +} + +- (void) releaseSubviews { + [table_ release]; + table_ = nil; + + [segment_ release]; + segment_ = nil; + + [container_ release]; + container_ = nil; +} + +- (id) initWithDatabase:(Database *)database delegate:(id)delegate { + if ((self = [super init]) != nil) { + database_ = database; + roledelegate_ = delegate; } return self; } @@ -8147,7 +8712,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. } @@ -8172,72 +8737,91 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { return section == 3 ? container_ : nil; } +- (void) reloadData { + [super reloadData]; + [table_ reloadData]; +} + @end /* }}} */ /* Stash Controller {{{ */ -@interface CYStashController : CYViewController { - // XXX: just delete these things - _transient UIActivityIndicatorView *spinner_; - _transient UILabel *status_; - _transient UILabel *caption_; +@interface StashController : CYViewController { + UIActivityIndicatorView *spinner_; + UILabel *status_; + UILabel *caption_; } + @end -@implementation CYStashController -- (id) init { - if ((self = [super init])) { - [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]]; +@implementation StashController - spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease]; - CGRect spinrect = [spinner_ frame]; - spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2); - spinrect.origin.y = [[self view] frame].size.height - 80.0f; - [spinner_ setFrame:spinrect]; - [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin]; - [[self view] addSubview:spinner_]; - [spinner_ startAnimating]; +- (void) dealloc { + [self releaseSubviews]; - CGRect captrect; - captrect.size.width = [[self view] frame].size.width; - captrect.size.height = 40.0f; - captrect.origin.x = 0; - captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2); - caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease]; - [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")]; - [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin]; - [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]]; - [caption_ setTextColor:[UIColor whiteColor]]; - [caption_ setBackgroundColor:[UIColor clearColor]]; - [caption_ setShadowColor:[UIColor blackColor]]; - [caption_ setTextAlignment:UITextAlignmentCenter]; - [[self view] addSubview:caption_]; - - CGRect statusrect; - statusrect.size.width = [[self view] frame].size.width; - statusrect.size.height = 30.0f; - statusrect.origin.x = 0; - statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height; - status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease]; - [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin]; - [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")]; - [status_ setFont:[UIFont systemFontOfSize:16.0f]]; - [status_ setTextColor:[UIColor whiteColor]]; - [status_ setBackgroundColor:[UIColor clearColor]]; - [status_ setShadowColor:[UIColor blackColor]]; - [status_ setTextAlignment:UITextAlignmentCenter]; - [[self view] addSubview:status_]; - } return self; + [super dealloc]; +} + +- (void) loadView { + [self setView:[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]]; + [[self view] setBackgroundColor:[UIColor viewFlipsideBackgroundColor]]; + + spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease]; + CGRect spinrect = [spinner_ frame]; + spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2); + spinrect.origin.y = [[self view] frame].size.height - 80.0f; + [spinner_ setFrame:spinrect]; + [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin]; + [[self view] addSubview:spinner_]; + [spinner_ startAnimating]; + + CGRect captrect; + captrect.size.width = [[self view] frame].size.width; + captrect.size.height = 40.0f; + captrect.origin.x = 0; + captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2); + caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease]; + [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")]; + [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin]; + [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]]; + [caption_ setTextColor:[UIColor whiteColor]]; + [caption_ setBackgroundColor:[UIColor clearColor]]; + [caption_ setShadowColor:[UIColor blackColor]]; + [caption_ setTextAlignment:UITextAlignmentCenter]; + [[self view] addSubview:caption_]; + + CGRect statusrect; + statusrect.size.width = [[self view] frame].size.width; + statusrect.size.height = 30.0f; + statusrect.origin.x = 0; + statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height; + status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease]; + [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin]; + [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")]; + [status_ setFont:[UIFont systemFontOfSize:16.0f]]; + [status_ setTextColor:[UIColor whiteColor]]; + [status_ setBackgroundColor:[UIColor clearColor]]; + [status_ setShadowColor:[UIColor blackColor]]; + [status_ setTextAlignment:UITextAlignmentCenter]; + [[self view] addSubview:status_]; } -- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { - return IsWildcat_ || orientation == UIInterfaceOrientationPortrait; +- (void) releaseSubviews { + [spinner_ release]; + spinner_ = nil; + + [status_ release]; + status_ = nil; + + [caption_ release]; + caption_ = nil; } + @end /* }}} */ @interface Cydia : UIApplication < ConfirmationControllerDelegate, - ProgressControllerDelegate, + DatabaseDelegate, CydiaDelegate, UINavigationControllerDelegate, UITabBarControllerDelegate @@ -8246,6 +8830,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { UIWindow *window_; CYTabBarController *tabbar_; + CYEmulatedLoadingController *emulated_; NSMutableArray *essential_; NSMutableArray *broken_; @@ -8257,12 +8842,11 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { unsigned locked_; unsigned activity_; - CYStashController *stash_; + StashController *stash_; bool loaded_; } -- (void) setPage:(CYViewController *)page; - (void) loadData; @end @@ -8286,10 +8870,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { message:UCLocalize("HALFINSTALLED_PACKAGE_EX") delegate:self cancelButtonTitle:UCLocalize("FORCIBLY_CLEAR") - otherButtonTitles:UCLocalize("TEMPORARY_IGNORE"), nil + otherButtonTitles: + UCLocalize("TEMPORARY_IGNORE"), + nil ] autorelease]; [alert setContext:@"fixhalf"]; + [alert setNumberOfRows:2]; [alert show]; } else if (!Ignored_ && [essential_ count] != 0) { int count = [essential_ count]; @@ -8299,7 +8886,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { message:UCLocalize("ESSENTIAL_UPGRADE_EX") delegate:self cancelButtonTitle:UCLocalize("TEMPORARY_IGNORE") - otherButtonTitles:UCLocalize("UPGRADE_ESSENTIAL"), UCLocalize("COMPLETE_UPGRADE"), nil + otherButtonTitles: + UCLocalize("UPGRADE_ESSENTIAL"), + UCLocalize("COMPLETE_UPGRADE"), + nil ] autorelease]; [alert setContext:@"upgrade"]; @@ -8350,11 +8940,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)) @@ -8368,8 +8957,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { if (recently || loaded_ || ManualRefresh) { [self performSelectorOnMainThread:@selector(_loaded) withObject:nil waitUntilDone:NO]; - // If we are cancelling due to ManualRefresh or a recent refresh - // we need to make sure it knows it's already loaded. + // If we are cancelling, we need to make sure it knows it's already loaded. loaded_ = true; return; } else { @@ -8403,14 +8991,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]; @@ -8460,7 +9048,70 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [database_ update]; } +- (void) complete { + @synchronized (self) { + [self _reloadDataWithInvocation:nil]; + } +} + +- (void) disemulate { + if (emulated_ == nil) + return; + + [window_ addSubview:[tabbar_ view]]; + [[emulated_ view] removeFromSuperview]; + [emulated_ release]; + emulated_ = nil; + [window_ setUserInteractionEnabled:YES]; +} + +- (void) presentModalViewController:(UIViewController *)controller force:(BOOL)force { + UINavigationController *navigation([[[CYNavigationController alloc] initWithRootViewController:controller] autorelease]); + if (IsWildcat_) + [navigation setModalPresentationStyle:UIModalPresentationFormSheet]; + + UIViewController *parent; + if (emulated_ == nil) + parent = tabbar_; + else if (!force) + parent = emulated_; + else { + [self disemulate]; + parent = tabbar_; + } + + [parent presentModalViewController:navigation animated:YES]; +} + +- (ProgressController *) invokeNewProgress:(NSInvocation *)invocation forController:(UINavigationController *)navigation withTitle:(NSString *)title { + ProgressController *progress([[[ProgressController alloc] initWithDatabase:database_ delegate:self] autorelease]); + + if (navigation != nil) + [navigation pushViewController:progress animated:YES]; + else + [self presentModalViewController:progress force:YES]; + + [progress invoke:invocation withTitle:title]; + return progress; +} + +- (void) detachNewProgressSelector:(SEL)selector toTarget:(id)target forController:(UINavigationController *)navigation title:(NSString *)title { + [self invokeNewProgress:[NSInvocation invocationWithSelector:selector forTarget:target] forController:navigation withTitle:title]; +} + +- (void) repairWithInvocation:(NSInvocation *)invocation { + _trace(); + [self invokeNewProgress:invocation forController:nil withTitle:UCLocalize("REPAIRING")]; + _trace(); +} + +- (void) repairWithSelector:(SEL)selector { + [self performSelectorOnMainThread:@selector(repairWithInvocation:) withObject:[NSInvocation invocationWithSelector:selector forTarget:database_] waitUntilDone:YES]; +} + - (void) syncData { + [self _saveConfig]; + FILE *file(fopen("/etc/apt/sources.list.d/cydia.list", "w")); _assert(file != NULL); @@ -8476,28 +9127,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]; @@ -8507,6 +9161,13 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } - (bool) perform { + // XXX: this is a really crappy way of doing this. + // like, seriously: this state machine is still broken, and cancelling this here doesn't really /fix/ that. + // for one, the user can still /start/ a reloading data event while they have a queue, which is stupid + // for two, this just means there is a race condition between the refresh completing and the confirmation controller appearing. + if ([tabbar_ updating]) + [tabbar_ cancelUpdate]; + if (![database_ prepare]) return false; @@ -8569,88 +9230,16 @@ 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]; } -- (void) setPage:(CYViewController *)page { - [page setDelegate:self]; - - CYNavigationController *navController = (CYNavigationController *) [tabbar_ selectedViewController]; - [navController setViewControllers:[NSArray arrayWithObject:page]]; - for (CYNavigationController *page in [tabbar_ viewControllers]) - if (page != navController) - [page setViewControllers:nil]; -} - -- (void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController { - CYNavigationController *controller = (CYNavigationController *) viewController; - - if ([[controller viewControllers] count] == 0) { - int index = [tabbar_ selectedIndex]; - CYViewController *root = nil; - - if (index == 0) - root = [[[HomeController alloc] init] autorelease]; - else if (index == 1) - root = [[[SectionsController alloc] initWithDatabase:database_] autorelease]; - else if (index == 2) - root = [[[ChangesController alloc] initWithDatabase:database_ delegate:self] autorelease]; - - if (IsWildcat_) { - if (index == 3) - root = [[[InstalledController alloc] initWithDatabase:database_] autorelease]; - else if (index == 4) - root = [[[SourcesController alloc] initWithDatabase:database_] autorelease]; - else if (index == 5) - root = [[[SearchController alloc] initWithDatabase:database_] autorelease]; - } else { - if (index == 3) - root = [[[ManageController alloc] init] autorelease]; - else if (index == 4) - root = [[[SearchController alloc] initWithDatabase:database_] autorelease]; - } - - [root setDelegate:self]; - - if (root != nil) - [controller setViewControllers:[NSArray arrayWithObject:root]]; - } -} - - (void) showSettings { - CYSettingsController *role = [[[CYSettingsController alloc] initWithDatabase:database_ delegate:self] autorelease]; + SettingsController *role = [[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease]; CYNavigationController *nav = [[[CYNavigationController alloc] initWithRootViewController:role] autorelease]; if (IsWildcat_) [nav setModalPresentationStyle:UIModalPresentationFormSheet]; @@ -8660,17 +9249,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]; -} -- (void) setPackageController:(CYPackageController *)view { - WebThreadLock(); - [view setPackage:nil]; - WebThreadUnlock(); +#if TraceLogging + NSLog(@"releaseNetworkActivityIndicator->%d", activity_); +#endif + } - (void) cancelAndClear:(bool)clear { @@ -8689,8 +9281,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { - (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button { NSString *context([alert context]); - if ([context isEqualToString:@"fixhalf"]) { - if (button == [alert firstOtherButtonIndex]) { + 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_) { [broken remove]; @@ -8705,7 +9306,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [self resolve]; [self perform]; } - } else if (button == [alert cancelButtonIndex]) { + } else if (button == [alert firstOtherButtonIndex]) { [broken_ removeAllObjects]; [self _loaded]; } @@ -8793,27 +9394,17 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { } - (CYViewController *) pageForPackage:(NSString *)name { - if (Package *package = [database_ packageWithName:name]) { - CYPackageController *view = [[[CYPackageController alloc] initWithDatabase:database_] autorelease]; - [view setPackage:package]; - return view; - } else { - NSURL *url([NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"unknown" ofType:@"html"]]); - url = [NSURL URLWithString:[[url absoluteString] stringByAppendingString:[NSString stringWithFormat:@"?%@", name]]]; - CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease]; - [browser loadURL:url]; - return browser; - } + return [[[CYPackageController alloc] initWithDatabase:database_ forPackage:name] autorelease]; } -- (CYViewController *) pageForURL:(NSURL *)url { +- (CYViewController *) pageForURL:(NSURL *)url forExternal:(BOOL)external { NSString *scheme([[url scheme] lowercaseString]); if ([[url absoluteString] length] <= [scheme length] + 3) return nil; NSString *path([[url absoluteString] substringFromIndex:[scheme length] + 3]); NSArray *components([path pathComponents]); - if ([scheme isEqualToString:@"apptapp"] && [components count] && [[components objectAtIndex:0] isEqualToString:@"package"]) + if ([scheme isEqualToString:@"apptapp"] && [components count] > 0 && [[components objectAtIndex:0] isEqualToString:@"package"]) return [self pageForPackage:[components objectAtIndex:1]]; if ([components count] < 1 || ![scheme isEqualToString:@"cydia"]) @@ -8821,110 +9412,100 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { NSString *base([components objectAtIndex:0]); - if ([components count] == 1) { - if ([base isEqualToString:@"storage"]) { - CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease]; - [browser loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"storage" ofType:@"html"]]]; - return browser; + CYViewController *controller = nil; + + if ([base isEqualToString:@"url"]) { + // This kind of URL can contain slashes in the argument, so we can't parse them below. + NSString *destination = [[url absoluteString] substringFromIndex:([scheme length] + [@"://" length] + [base length] + [@"/" length])]; + controller = [[[CYBrowserController alloc] initWithURL:[NSURL URLWithString:destination]] autorelease]; + } else if (!external && [components count] == 1) { + if ([base isEqualToString:@"manage"]) { + controller = [[[ManageController alloc] init] autorelease]; } if ([base isEqualToString:@"sources"]) { - SourcesController *source = [[[SourcesController alloc] initWithDatabase:database_] autorelease]; - return source; + controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease]; } if ([base isEqualToString:@"home"]) { - HomeController *home = [[[HomeController alloc] init] autorelease]; - return home; + controller = [[[HomeController alloc] init] autorelease]; } if ([base isEqualToString:@"sections"]) { - SectionsController *sections = [[[SectionsController alloc] initWithDatabase:database_] autorelease]; - return sections; + controller = [[[SectionsController alloc] initWithDatabase:database_] autorelease]; } if ([base isEqualToString:@"search"]) { - SearchController *search = [[[SearchController alloc] initWithDatabase:database_] autorelease]; - return search; + controller = [[[SearchController alloc] initWithDatabase:database_] autorelease]; } if ([base isEqualToString:@"changes"]) { - ChangesController *changes = [[[ChangesController alloc] initWithDatabase:database_ delegate:self] autorelease]; - return changes; + controller = [[[ChangesController alloc] initWithDatabase:database_] autorelease]; } if ([base isEqualToString:@"installed"]) { - InstalledController *installed = [[[InstalledController alloc] initWithDatabase:database_] autorelease]; - return installed; + controller = [[[InstalledController alloc] initWithDatabase:database_] autorelease]; } } else if ([components count] == 2) { NSString *argument = [components objectAtIndex:1]; if ([base isEqualToString:@"package"]) { - CYViewController *package = [self pageForPackage:argument]; - return package; + controller = [self pageForPackage:argument]; } - if ([base isEqualToString:@"search"]) { - SearchController *search = [[[SearchController alloc] initWithDatabase:database_] autorelease]; - [search setSearchTerm:argument]; - return search; + if (!external && [base isEqualToString:@"search"]) { + controller = [[[SearchController alloc] initWithDatabase:database_] autorelease]; + [(SearchController *)controller setSearchTerm:argument]; } - if ([base isEqualToString:@"sections"]) { + if (!external && [base isEqualToString:@"sections"]) { if ([argument isEqualToString:@"all"]) argument = nil; - SectionController *section = [[[SectionController alloc] initWithDatabase:database_ section:argument] autorelease]; - [section setDelegate:self]; - return section; + controller = [[[SectionController alloc] initWithDatabase:database_ section:argument] autorelease]; } - if ([base isEqualToString:@"sources"]) { + if (!external && [base isEqualToString:@"sources"]) { if ([argument isEqualToString:@"add"]) { - SourcesController *source = [[[SourcesController alloc] initWithDatabase:database_] autorelease]; - [source showAddSourcePrompt]; - return source; + controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease]; + [(SourcesController *)controller showAddSourcePrompt]; } else { - // XXX: Create page of the source specfified. + Source *source = [database_ sourceWithKey:argument]; + controller = [[[SourceController alloc] initWithDatabase:database_ source:source] autorelease]; } } - if ([base isEqualToString:@"url"]) { - CYBrowserController *browser = [[[CYBrowserController alloc] init] autorelease]; - [browser loadURL:[NSURL URLWithString:argument]]; - return browser; - } - - if ([base isEqualToString:@"launch"]) { + if (!external && [base isEqualToString:@"launch"]) { [self launchApplicationWithIdentifier:argument suspended:NO]; + return nil; } - } else if ([components count] == 3) { + } else if (!external && [components count] == 3) { NSString *arg1 = [components objectAtIndex:1]; NSString *arg2 = [components objectAtIndex:2]; if ([base isEqualToString:@"package"]) { if ([arg2 isEqualToString:@"settings"]) { - return [[[PackageSettingsController alloc] initWithDatabase:database_ package:arg1] autorelease]; - } else if ([arg2 isEqualToString:@"signature"]) { - return [[[SignatureController alloc] initWithDatabase:database_ package:arg1] autorelease]; + controller = [[[PackageSettingsController alloc] initWithDatabase:database_ package:arg1] autorelease]; } else if ([arg2 isEqualToString:@"files"]) { if (Package *package = [database_ packageWithName:arg1]) { - FileTable *files = [[[FileTable alloc] initWithDatabase:database_] autorelease]; - [files setPackage:package]; - return files; + controller = [[[FileTable alloc] initWithDatabase:database_] autorelease]; + [(FileTable *)controller setPackage:package]; } } } } - return nil; + [controller setDelegate:self]; + return controller; } -- (BOOL) openCydiaURL:(NSURL *)url { - CYViewController *page([self pageForURL:url]); +- (BOOL) openCydiaURL:(NSURL *)url forExternal:(BOOL)external { + CYViewController *page([self pageForURL:url forExternal:external]); - if (page != nil) - [self setPage:page]; + if (page != nil) { + CYNavigationController *nav = [[[CYNavigationController alloc] init] autorelease]; + [nav setViewControllers:[NSArray arrayWithObject:page]]; + [tabbar_ setUnselectedViewController:nav]; + } return page != nil; } @@ -8933,7 +9514,7 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [super applicationOpenURL:url]; if (!loaded_) starturl_ = [url retain]; - else [self openCydiaURL:url]; + else [self openCydiaURL:url forExternal:YES]; } - (void) applicationWillResignActive:(UIApplication *)application { @@ -8945,9 +9526,45 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [super applicationWillResignActive:application]; } +- (void) applicationWillTerminate:(UIApplication *)application { + Changed_ = true; + [Metadata_ setObject:[tabbar_ navigationURLCollection] forKey:@"InterfaceState"]; + [Metadata_ setObject:[NSDate date] forKey:@"LastClosed"]; + [Metadata_ setObject:[NSNumber numberWithInt:[tabbar_ selectedIndex]] forKey:@"InterfaceIndex"]; + + [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 setNumberOfRows:2]; + [alert show]; +} + - (void) addStashController { ++locked_; - stash_ = [[CYStashController alloc] init]; + stash_ = [[StashController alloc] init]; [window_ addSubview:[stash_ view]]; } @@ -8961,13 +9578,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]; @@ -8979,7 +9592,6 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { - (void) setupViewControllers { tabbar_ = [[CYTabBarController alloc] initWithDatabase:database_]; - [tabbar_ setDelegate:self]; NSMutableArray *items([NSMutableArray arrayWithObjects: [[[UITabBarItem alloc] initWithTitle:@"Cydia" image:[UIImage applicationImageNamed:@"home.png"] tag:0] autorelease], @@ -9004,22 +9616,10 @@ bool DepSubstrate(const pkgCache::VerIterator &iterator) { [tabbar_ setViewControllers:controllers]; [tabbar_ setUpdateDelegate:self]; - [window_ addSubview:[tabbar_ view]]; -} - -- (void)showEmulatedLoadingControllerInView:(UIView *)view { - static CYEmulatedLoadingController *fake = [[CYEmulatedLoadingController alloc] init]; - if (view != nil) { - [view addSubview:[fake view]]; - } else { - [[fake view] removeFromSuperview]; - } } - (void) applicationDidFinishLaunching:(id)unused { _trace(); - CydiaApp = self; - if ([self respondsToSelector:@selector(setApplicationSupportsShakeToEdit:)]) [self setApplicationSupportsShakeToEdit:NO]; @@ -9068,9 +9668,13 @@ _trace(); } database_ = [Database sharedInstance]; + [database_ setDelegate:self]; [window_ setUserInteractionEnabled:NO]; - [self showEmulatedLoadingControllerInView:window_]; + [self setupViewControllers]; + + emulated_ = [[CYEmulatedLoadingController alloc] initWithDatabase:database_]; + [window_ addSubview:[emulated_ view]]; [self performSelector:@selector(loadData) withObject:nil afterDelay:0]; _trace(); @@ -9079,24 +9683,75 @@ _trace(); - (void) loadData { _trace(); if (Role_ == nil) { - [self showSettings]; + [window_ setUserInteractionEnabled:YES]; + [self presentModalViewController:[[[SettingsController alloc] initWithDatabase:database_ delegate:self] autorelease] force:NO]; return; + } else { + if ([emulated_ modalViewController] != nil) + [emulated_ dismissModalViewControllerAnimated:YES]; + [window_ setUserInteractionEnabled:NO]; } [self reloadData]; PrintTimes(); - [self setupViewControllers]; - [self showEmulatedLoadingControllerInView:nil]; - [window_ setUserInteractionEnabled:YES]; + [self disemulate]; + + int selectedIndex = 0; + NSMutableArray *items = nil; - // Show the home page. - CYNavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:0]; - [navigation setViewControllers:[NSArray arrayWithObject:[[[HomeController alloc] init] autorelease]]]; + bool recently = false; + NSDate *closed([Metadata_ objectForKey:@"LastClosed"]); + if (closed != nil) { + NSTimeInterval interval([closed timeIntervalSinceNow]); + // XXX: Is 15 minutes the optimal time here? + if (interval <= 0 && interval > -(15*60)) + recently = true; + } + + items = [[Metadata_ objectForKey:@"InterfaceState"] mutableCopy]; + selectedIndex = [[Metadata_ objectForKey:@"InterfaceIndex"] intValue]; + + BOOL enough = YES; + for (NSArray *entry in items) + if ([entry count] <= 0) + enough = NO; + + if (!recently || !items || !enough) { + selectedIndex = 0; + items = [NSMutableArray array]; + [items addObject:[NSArray arrayWithObject:@"cydia://home"]]; + [items addObject:[NSArray arrayWithObject:@"cydia://sections"]]; + [items addObject:[NSArray arrayWithObject:@"cydia://changes"]]; + if (!IsWildcat_) { + [items addObject:[NSArray arrayWithObject:@"cydia://manage"]]; + } else { + [items addObject:[NSArray arrayWithObject:@"cydia://installed"]]; + [items addObject:[NSArray arrayWithObject:@"cydia://sources"]]; + } + [items addObject:[NSArray arrayWithObject:@"cydia://search"]]; + } + + [tabbar_ setSelectedIndex:selectedIndex]; + for (unsigned int tab = 0; tab < [[tabbar_ viewControllers] count]; tab++) { + NSArray *stack = [items objectAtIndex:tab]; + CYNavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:tab]; + NSMutableArray *current = [NSMutableArray array]; + + for (unsigned int nav = 0; nav < [stack count]; nav++) { + NSString *addr = [stack objectAtIndex:nav]; + NSURL *url = [NSURL URLWithString:addr]; + CYViewController *page = [self pageForURL:url forExternal:NO]; + if (page != nil) + [current addObject:page]; + } + + [navigation setViewControllers:current]; + } // (Try to) show the startup URL. if (starturl_ != nil) { - [self openCydiaURL:starturl_]; + [self openCydiaURL:starturl_ forExternal:NO]; [starturl_ release]; starturl_ = nil; } @@ -9110,6 +9765,22 @@ _trace(); } } +- (void) addProgressEvent:(CydiaProgressEvent *)event forTask:(NSString *)task { + id 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_; @@ -9155,15 +9826,65 @@ MSHook(void, UIHardware$_playSystemSound$, Class self, SEL _cmd, int sound) { } } +Class $UIApplication; + +MSHook(void, UIApplication$_updateApplicationAccessibility, UIApplication *self, SEL _cmd) { + static BOOL initialized = NO; + static BOOL started = NO; + + NSDictionary *dict([[[NSDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.apple.Accessibility.plist"] autorelease]); + BOOL enabled = [[dict objectForKey:@"VoiceOverTouchEnabled"] boolValue] || [[dict objectForKey:@"VoiceOverTouchEnabledByiTunes"] boolValue]; + + if ([self respondsToSelector:@selector(_accessibilityBundlePrincipalClass)]) { + id bundle = [self performSelector:@selector(_accessibilityBundlePrincipalClass)]; + if (![bundle respondsToSelector:@selector(_accessibilityStopServer)]) return; + if (![bundle respondsToSelector:@selector(_accessibilityStartServer)]) return; + + if (initialized && !enabled) { + initialized = NO; + [bundle performSelector:@selector(_accessibilityStopServer)]; + } else if (enabled) { + initialized = YES; + if (!started) { + started = YES; + [bundle performSelector:@selector(_accessibilityStartServer)]; + } + } + } +} + 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]; } else IsWildcat_ = false; + UIScreen *screen([UIScreen mainScreen]); + if ([screen respondsToSelector:@selector(scale)]) + ScreenScale_ = [screen scale]; + else + ScreenScale_ = 1; + + UIDevice *device([UIDevice currentDevice]); + if (![device respondsToSelector:@selector(userInterfaceIdiom)]) + Idiom_ = @"iphone"; + else { + UIUserInterfaceIdiom idiom([device userInterfaceIdiom]); + if (idiom == UIUserInterfaceIdiomPhone) + Idiom_ = @"iphone"; + else if (idiom == UIUserInterfaceIdiomPad) + Idiom_ = @"ipad"; + else + NSLog(@"unknown UIUserInterfaceIdiom!"); + } + + UI_ = CydiaURL([NSString stringWithFormat:@"ui/ios~%@", Idiom_]); + PackageName = reinterpret_cast(method_getImplementation(class_getInstanceMethod([Package class], @selector(cyname)))); /* Library Hacks {{{ */ @@ -9182,6 +9903,13 @@ int main(int argc, char *argv[]) { _pooled _UIHardware$_playSystemSound$ = reinterpret_cast(method_getImplementation(UIHardware$_playSystemSound$)); method_setImplementation(UIHardware$_playSystemSound$, reinterpret_cast(&$UIHardware$_playSystemSound$)); } + + $UIApplication = objc_getClass("UIApplication"); + Method UIApplication$_updateApplicationAccessibility(class_getInstanceMethod($UIApplication, @selector(_updateApplicationAccessibility))); + if (UIApplication$_updateApplicationAccessibility != NULL) { + _UIApplication$_updateApplicationAccessibility = reinterpret_cast(method_getImplementation(UIApplication$_updateApplicationAccessibility)); + method_setImplementation(UIApplication$_updateApplicationAccessibility, reinterpret_cast(&$UIApplication$_updateApplicationAccessibility)); + } /* }}} */ /* Set Locale {{{ */ Locale_ = CFLocaleCopyCurrent(); @@ -9196,9 +9924,9 @@ int main(int argc, char *argv[]) { _pooled else { lang = [[Languages_ objectAtIndex:0] UTF8String]; setenv("LANG", lang, true); + std::setlocale(LC_ALL, lang); } - //std::setlocale(LC_ALL, lang); NSLog(@"Setting Language: %s", lang); /* }}} */ @@ -9229,7 +9957,6 @@ int main(int argc, char *argv[]) { _pooled /* }}} */ App_ = [[NSBundle mainBundle] bundlePath]; - Home_ = NSHomeDirectory(); Advanced_ = YES; setuid(0); @@ -9355,19 +10082,31 @@ int main(int argc, char *argv[]) { _pooled _trace(); if (Packages_ != nil) { - CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, NULL); + bool fail(false); + CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, &fail); _trace(); - [Metadata_ removeObjectForKey:@"Packages"]; - Packages_ = nil; - Changed_ = true; + + if (!fail) { + [Metadata_ removeObjectForKey:@"Packages"]; + Packages_ = nil; + Changed_ = true; + } } 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) { \ + void *handle(dlopen("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", RTLD_LAZY | RTLD_GLOBAL)); \ + if (handle == NULL) \ + NSLog(@"%s", dlerror()); \ + } + + 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);*/