*/
/* #include Directives {{{ */
+#import "UICaboodle.h"
+
#include <objc/objc.h>
#include <objc/runtime.h>
#import "BrowserView.h"
#import "ResetView.h"
-#import "UICaboodle.h"
/* }}} */
//#define _finline __attribute__((force_inline))
#define _finline inline
+struct timeval _ltv;
+bool _itv;
+
+#define _limit(count) do { \
+ static size_t _count(0); \
+ if (++_count == count) \
+ exit(0); \
+} while (false)
+
/* Objective-C Handle<> {{{ */
template <typename Type_>
class _H {
#define ForSaurik 1
#define RecycleWebViews 0
+/* Radix Sort {{{ */
+@interface NSMutableArray (Radix)
+- (void) radixUsingSelector:(SEL)selector withObject:(id)object;
+@end
+
+@implementation NSMutableArray (Radix)
+
+- (void) radixUsingSelector:(SEL)selector withObject:(id)object {
+ NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:"L12@0:4@8"]]);
+ [invocation setSelector:selector];
+ [invocation setArgument:&object atIndex:2];
+
+ size_t count([self count]);
+
+ struct RadixItem {
+ size_t index;
+ uint32_t key;
+ } *swap(new RadixItem[count * 2]), *lhs(swap), *rhs(swap + count);
+
+ for (size_t i(0); i != count; ++i) {
+ RadixItem &item(lhs[i]);
+ item.index = i;
+
+ id object([self objectAtIndex:i]);
+ [invocation setTarget:object];
+
+ [invocation invoke];
+ [invocation getReturnValue:&item.key];
+ }
+
+ static const size_t bits = 11;
+ static const size_t slots = 1 << bits;
+ static const size_t passes = (32 + (bits - 1)) / bits;
+
+ size_t *hist(new size_t[slots]);
+
+ for (size_t pass(0); pass != passes; ++pass) {
+ memset(hist, 0, sizeof(size_t) * slots);
+
+ for (size_t i(0); i != count; ++i) {
+ uint32_t key(lhs[i].key);
+ key >>= pass * bits;
+ key &= _not(uint32_t) >> 32 - bits;
+ ++hist[key];
+ }
+
+ size_t offset(0);
+ for (size_t i(0); i != slots; ++i) {
+ size_t local(offset);
+ offset += hist[i];
+ hist[i] = local;
+ }
+
+ for (size_t i(0); i != count; ++i) {
+ uint32_t key(lhs[i].key);
+ key >>= pass * bits;
+ key &= _not(uint32_t) >> 32 - bits;
+ rhs[hist[key]++] = lhs[i];
+ }
+
+ RadixItem *tmp(lhs);
+ lhs = rhs;
+ rhs = tmp;
+ }
+
+ delete [] hist;
+
+ NSMutableArray *values([NSMutableArray arrayWithCapacity:count]);
+ for (size_t i(0); i != count; ++i)
+ [values addObject:[self objectAtIndex:lhs[i].index]];
+ [self setArray:values];
+
+ delete [] swap;
+}
+
+@end
+/* }}} */
+
typedef enum {
kUIControlEventMouseDown = 1 << 0,
kUIControlEventMouseMovedInside = 1 << 2, // mouse moved inside control target
Source *source_;
bool cached_;
+ NSString *section_;
+
NSString *latest_;
NSString *installed_;
- (NSString *) description;
- (NSString *) index;
+- (NSMutableDictionary *) metadata;
- (NSDate *) seen;
+- (BOOL) subscribed;
+- (BOOL) ignored;
- (NSString *) latest;
- (NSString *) installed;
- (NSComparisonResult) compareByName:(Package *)package;
- (NSComparisonResult) compareBySection:(Package *)package;
-- (NSComparisonResult) compareBySectionAndName:(Package *)package;
-- (NSComparisonResult) compareForChanges:(Package *)package;
+
+- (uint32_t) compareForChanges;
- (void) install;
- (void) remove;
if (source_ != nil)
[source_ release];
+ if (section_ != nil)
+ [section_ release];
+
[latest_ release];
if (installed_ != nil)
[installed_ release];
NSMutableDictionary *metadata = [Packages_ objectForKey:key];
if (metadata == nil) {
- metadata = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ metadata = [[NSMutableDictionary dictionaryWithObjectsAndKeys:
now_, @"FirstSeen",
- nil];
+ nil] mutableCopy];
if (solid != nil)
[metadata setObject:solid forKey:@"LastVersion"];
}
- (NSString *) section {
+ if (section_ != nil)
+ return section_;
+
const char *section = iterator_.Section();
if (section == NULL)
return nil;
goto lookup;
}
- return [name stringByReplacingCharacter:'_' withCharacter:' '];
+ section_ = [[name stringByReplacingCharacter:'_' withCharacter:' '] retain];
+ return section_;
}
- (Address *) maintainer {
return [index length] != 0 && isalpha([index characterAtIndex:0]) ? index : @"123";
}
+- (NSMutableDictionary *) metadata {
+ return [Packages_ objectForKey:[id_ lowercaseString]];
+}
+
- (NSDate *) seen {
- NSDictionary *metadata([Packages_ objectForKey:[id_ lowercaseString]]);
- bool subscribed;
- if (NSNumber *isSubscribed = [metadata objectForKey:@"IsSubscribed"])
- subscribed = [isSubscribed boolValue];
- else
- subscribed = false;
- if (subscribed)
+ NSDictionary *metadata([self metadata]);
+ if ([self subscribed])
if (NSDate *last = [metadata objectForKey:@"LastSeen"])
return last;
return [metadata objectForKey:@"FirstSeen"];
}
+- (BOOL) subscribed {
+ NSDictionary *metadata([self metadata]);
+ if (NSNumber *subscribed = [metadata objectForKey:@"IsSubscribed"])
+ return [subscribed boolValue];
+ else
+ return false;
+}
+
+- (BOOL) ignored {
+ NSDictionary *metadata([self metadata]);
+ if (NSNumber *ignored = [metadata objectForKey:@"IsIgnored"])
+ return [ignored boolValue];
+ else
+ return false;
+}
+
- (NSString *) latest {
return latest_;
}
return NSOrderedSame;
}
-- (NSComparisonResult) compareBySectionAndName:(Package *)package {
- NSString *lhs = [self section];
- NSString *rhs = [package section];
-
- if (lhs == NULL && rhs != NULL)
- return NSOrderedAscending;
- else if (lhs != NULL && rhs == NULL)
- return NSOrderedDescending;
- else if (lhs != NULL && rhs != NULL) {
- NSComparisonResult result = [lhs compare:rhs];
- if (result != NSOrderedSame)
- return result;
- }
+- (uint32_t) compareForChanges {
+ union {
+ uint32_t key;
- return [self compareByName:package];
-}
+ struct {
+ uint32_t timestamp : 30;
+ uint32_t ignored : 1;
+ uint32_t upgradable : 1;
+ } bits;
+ } value;
-- (NSComparisonResult) compareForChanges:(Package *)package {
- BOOL lhs = [self upgradableAndEssential:YES];
- BOOL rhs = [package upgradableAndEssential:YES];
+ value.bits.upgradable = [self upgradableAndEssential:YES] ? 1 : 0;
- if (lhs != rhs)
- return lhs ? NSOrderedAscending : NSOrderedDescending;
- else if (!lhs) {
- switch ([[self seen] compare:[package seen]]) {
- case NSOrderedAscending:
- return NSOrderedDescending;
- case NSOrderedSame:
- break;
- case NSOrderedDescending:
- return NSOrderedAscending;
- default:
- _assert(false);
- }
+ if ([self upgradableAndEssential:YES]) {
+ value.bits.timestamp = 0;
+ value.bits.ignored = [self ignored] ? 0 : 1;
+ value.bits.upgradable = 1;
+ } else {
+ value.bits.timestamp = static_cast<uint32_t>([[self seen] timeIntervalSince1970]) >> 2;
+ value.bits.ignored = 0;
+ value.bits.upgradable = 0;
}
- return [self compareByName:package];
+ return _not(uint32_t) - value.key;
}
- (void) install {
count_ = [[NSString stringWithFormat:@"%d", [section count]] retain];
if (editing_)
- [switch_ setValue:isSectionVisible(section_) animated:NO];
+ [switch_ setValue:(isSectionVisible(section_) ? 1 : 0) animated:NO];
}
}
[super webView:sender didClearWindowObject:window forFrame:frame];
}
-#if 0
- (void) _rightButtonClicked {
/*[super _rightButtonClicked];
return;*/
] autorelease]];
}
}
-#endif
- (NSString *) _rightButtonTitle {
int count = [buttons_ count];
[sources_ removeAllObjects];
[sources_ addObjectsFromArray:[database_ sources]];
+ _trace();
[sources_ sortUsingSelector:@selector(compareByNameAndType:)];
+ _trace();
int count = [sources_ count];
for (offset_ = 0; offset_ != count; ++offset_) {
[listener use];
}
+- (void) webView:(WebView *)webView decidePolicyForMIMEType:(NSString *)type request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
+ if ([WebView canShowMIMEType:type])
+ [listener use];
+ else {
+ // XXX: handle more mime types!
+ [listener ignore];
+ if (frame == [webView mainFrame])
+ [UIApp openURL:[request URL]];
+ }
+}
+
- (void) webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)action request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
NSURL *url([request URL]);
int store(_not(int));
if (NSURL *itms = [url itmsURL:&store]) {
+ NSLog(@"itms#%@#%u#%@", url, store, itms);
if (
store == 1 && [capability containsObject:@"com.apple.MobileStore"] ||
store == 2 && [capability containsObject:@"com.apple.AppStore"]
@end
/* }}} */
+/* Cydia Book {{{ */
@interface CYBook : RVBook <
ProgressDelegate
> {
@end
-/* Install View {{{ */
-@interface InstallView : RVPage {
- _transient Database *database_;
- NSMutableArray *sections_;
- NSMutableArray *filtered_;
- UITransitionView *transition_;
- UITable *list_;
- UIView *accessory_;
- BOOL editing_;
-}
-
-- (id) initWithBook:(RVBook *)book database:(Database *)database;
-- (void) reloadData;
-- (void) resetView;
-
-@end
-
-@implementation InstallView
+@implementation CYBook
- (void) dealloc {
- [list_ setDataSource:nil];
- [list_ setDelegate:nil];
-
- [sections_ release];
- [filtered_ release];
- [transition_ release];
- [list_ release];
- [accessory_ release];
+ [overlay_ release];
+ [indicator_ release];
+ [prompt_ release];
+ [progress_ release];
+ [cancel_ release];
[super dealloc];
}
-- (int) numberOfRowsInTable:(UITable *)table {
- return editing_ ? [sections_ count] : [filtered_ count] + 1;
-}
-
-- (float) table:(UITable *)table heightForRow:(int)row {
- return 45;
-}
-
-- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
- if (reusing == nil)
- reusing = [[[SectionCell alloc] init] autorelease];
- [(SectionCell *)reusing setSection:(editing_ ?
- [sections_ objectAtIndex:row] :
- (row == 0 ? nil : [filtered_ objectAtIndex:(row - 1)])
- ) editing:editing_];
- return reusing;
+- (NSString *) getTitleForPage:(RVPage *)page {
+ return Simplify([super getTitleForPage:page]);
}
-- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
- return !editing_;
+- (BOOL) updating {
+ return updating_;
}
-- (BOOL) table:(UITable *)table canSelectRow:(int)row {
- return !editing_;
-}
+- (void) update {
+ [UIView beginAnimations:nil context:NULL];
-- (void) tableRowSelected:(NSNotification *)notification {
- int row = [[notification object] selectedRow];
- if (row == INT_MAX)
- return;
+ CGRect ovrframe = [overlay_ frame];
+ ovrframe.origin.y = 0;
+ [overlay_ setFrame:ovrframe];
- Section *section;
- NSString *name;
- NSString *title;
+ CGRect barframe = [navbar_ frame];
+ barframe.origin.y += ovrframe.size.height;
+ [navbar_ setFrame:barframe];
- if (row == 0) {
- section = nil;
- name = nil;
- title = @"All Packages";
- } else {
- section = [filtered_ objectAtIndex:(row - 1)];
- name = [section name];
+ CGRect trnframe = [transition_ frame];
+ trnframe.origin.y += ovrframe.size.height;
+ trnframe.size.height -= ovrframe.size.height;
+ [transition_ setFrame:trnframe];
- if (name != nil)
- title = name;
- else {
- name = @"";
- title = @"(No Section)";
- }
- }
+ [UIView endAnimations];
- PackageTable *table = [[[PackageTable alloc]
- initWithBook:book_
- database:database_
- title:title
- filter:@selector(isVisiblyUninstalledInSection:)
- with:name
- ] autorelease];
+ [indicator_ startAnimation];
+ [prompt_ setText:@"Updating Database"];
+ [progress_ setProgress:0];
- [table setDelegate:delegate_];
+ received_ = 0;
+ last_ = [NSDate timeIntervalSinceReferenceDate];
+ updating_ = true;
+ [overlay_ addSubview:cancel_];
- [book_ pushPage:table];
+ [NSThread
+ detachNewThreadSelector:@selector(_update)
+ toTarget:self
+ withObject:nil
+ ];
}
-- (id) initWithBook:(RVBook *)book database:(Database *)database {
- if ((self = [super initWithBook:book]) != nil) {
- database_ = database;
+- (void) _update_ {
+ updating_ = false;
- sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
- filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
+ [indicator_ stopAnimation];
- transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
- [self addSubview:transition_];
+ [UIView beginAnimations:nil context:NULL];
- list_ = [[UITable alloc] initWithFrame:[transition_ bounds]];
- [transition_ transition:0 toView:list_];
+ CGRect ovrframe = [overlay_ frame];
+ ovrframe.origin.y = -ovrframe.size.height;
+ [overlay_ setFrame:ovrframe];
- UITableColumn *column = [[[UITableColumn alloc]
- initWithTitle:@"Name"
- identifier:@"name"
- width:[self frame].size.width
- ] autorelease];
+ CGRect barframe = [navbar_ frame];
+ barframe.origin.y -= ovrframe.size.height;
+ [navbar_ setFrame:barframe];
- [list_ setDataSource:self];
- [list_ setSeparatorStyle:1];
- [list_ addTableColumn:column];
- [list_ setDelegate:self];
- [list_ setReusesTableCells:YES];
+ CGRect trnframe = [transition_ frame];
+ trnframe.origin.y -= ovrframe.size.height;
+ trnframe.size.height += ovrframe.size.height;
+ [transition_ setFrame:trnframe];
- [self reloadData];
+ [UIView commitAnimations];
- [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
- [list_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
- } return self;
+ [delegate_ performSelector:@selector(reloadData) withObject:nil afterDelay:0];
}
-- (void) reloadData {
- NSArray *packages = [database_ packages];
+- (id) initWithFrame:(CGRect)frame database:(Database *)database {
+ if ((self = [super initWithFrame:frame]) != nil) {
+ database_ = database;
- [sections_ removeAllObjects];
- [filtered_ removeAllObjects];
+ CGRect ovrrect = [navbar_ bounds];
+ ovrrect.size.height = [UINavigationBar defaultSize].height;
+ ovrrect.origin.y = -ovrrect.size.height;
- NSMutableArray *filtered = [NSMutableArray arrayWithCapacity:[packages count]];
- NSMutableDictionary *sections = [NSMutableDictionary dictionaryWithCapacity:32];
+ overlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
+ [self addSubview:overlay_];
- for (size_t i(0); i != [packages count]; ++i) {
- Package *package([packages objectAtIndex:i]);
- NSString *name([package section]);
+ ovrrect.origin.y = frame.size.height;
+ underlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
+ [underlay_ setTintColor:[UIColor colorWithRed:0.23 green:0.23 blue:0.23 alpha:1]];
+ [self addSubview:underlay_];
- if (name != nil) {
- Section *section([sections objectForKey:name]);
- if (section == nil) {
- section = [[[Section alloc] initWithName:name] autorelease];
- [sections setObject:section forKey:name];
- }
- }
+ [overlay_ setBarStyle:1];
+ [underlay_ setBarStyle:1];
+
+ int barstyle = [overlay_ _barStyle:NO];
+ bool ugly = barstyle == 0;
+
+ UIProgressIndicatorStyle style = ugly ?
+ UIProgressIndicatorStyleMediumBrown :
+ UIProgressIndicatorStyleMediumWhite;
+
+ CGSize indsize = [UIProgressIndicator defaultSizeForStyle:style];
+ unsigned indoffset = (ovrrect.size.height - indsize.height) / 2;
+ CGRect indrect = {{indoffset, indoffset}, indsize};
+
+ indicator_ = [[UIProgressIndicator alloc] initWithFrame:indrect];
+ [indicator_ setStyle:style];
+ [overlay_ addSubview:indicator_];
+
+ CGSize prmsize = {215, indsize.height + 4};
+
+ CGRect prmrect = {{
+ indoffset * 2 + indsize.width,
+#ifdef __OBJC2__
+ -1 +
+#endif
+ unsigned(ovrrect.size.height - prmsize.height) / 2
+ }, prmsize};
+
+ UIFont *font = [UIFont systemFontOfSize:15];
+
+ prompt_ = [[UITextLabel alloc] initWithFrame:prmrect];
+
+ [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
+ [prompt_ setBackgroundColor:[UIColor clearColor]];
+ [prompt_ setFont:font];
+
+ [overlay_ addSubview:prompt_];
+
+ CGSize prgsize = {75, 100};
+
+ CGRect prgrect = {{
+ ovrrect.size.width - prgsize.width - 10,
+ (ovrrect.size.height - prgsize.height) / 2
+ } , prgsize};
+
+ progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
+ [progress_ setStyle:0];
+ [overlay_ addSubview:progress_];
+
+ cancel_ = [[UINavigationButton alloc] initWithTitle:@"Cancel" style:UINavigationButtonStyleHighlighted];
+ [cancel_ addTarget:self action:@selector(_onCancel) forControlEvents:UIControlEventTouchUpInside];
+
+ CGRect frame = [cancel_ frame];
+ frame.size.width = 65;
+ frame.origin.x = ovrrect.size.width - frame.size.width - 5;
+ frame.origin.y = (ovrrect.size.height - frame.size.height) / 2;
+ [cancel_ setFrame:frame];
+
+ [cancel_ setBarStyle:barstyle];
+ } return self;
+}
+
+- (void) _onCancel {
+ updating_ = false;
+ [cancel_ removeFromSuperview];
+}
+
+- (void) _update { _pooled
+ Status status;
+ status.setDelegate(self);
+
+ [database_ updateWithStatus:status];
+
+ [self
+ performSelectorOnMainThread:@selector(_update_)
+ withObject:nil
+ waitUntilDone:NO
+ ];
+}
+
+- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
+ [prompt_ setText:[NSString stringWithFormat:@"Error: %@", error]];
+}
+
+- (void) setProgressTitle:(NSString *)title {
+ [self
+ performSelectorOnMainThread:@selector(_setProgressTitle:)
+ withObject:title
+ waitUntilDone:YES
+ ];
+}
+
+- (void) setProgressPercent:(float)percent {
+ [self
+ performSelectorOnMainThread:@selector(_setProgressPercent:)
+ withObject:[NSNumber numberWithFloat:percent]
+ waitUntilDone:YES
+ ];
+}
+
+- (void) startProgress {
+}
+
+- (void) addProgressOutput:(NSString *)output {
+ [self
+ performSelectorOnMainThread:@selector(_addProgressOutput:)
+ withObject:output
+ waitUntilDone:YES
+ ];
+}
+
+- (bool) isCancelling:(size_t)received {
+ NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
+ if (received_ != received) {
+ received_ = received;
+ last_ = now;
+ } else if (now - last_ > 15)
+ return true;
+ return !updating_;
+}
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ [sheet dismiss];
+}
+
+- (void) _setProgressTitle:(NSString *)title {
+ [prompt_ setText:title];
+}
+
+- (void) _setProgressPercent:(NSNumber *)percent {
+ [progress_ setProgress:[percent floatValue]];
+}
+
+- (void) _addProgressOutput:(NSString *)output {
+}
+
+@end
+/* }}} */
+/* Cydia:// Protocol {{{ */
+@interface CydiaURLProtocol : NSURLProtocol {
+}
+
+@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 NO;
+ return YES;
+}
+
++ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
+ return request;
+}
+
+- (void) startLoading {
+ id<NSURLProtocolClient> client([self client]);
+ NSURLRequest *request([self request]);
+
+ NSURL *url([request URL]);
+ NSString *href([url absoluteString]);
+
+ NSString *path([href substringFromIndex:8]);
+ 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;
+ Package *package([database packageWithName:path]);
+ if (package == nil)
+ goto fail;
+
+ NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
+
+ UIImage *icon([package icon]);
+ NSData *data(UIImagePNGRepresentation(icon));
+
+ [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+ [client URLProtocol:self didLoadData:data];
+ [client URLProtocolDidFinishLoading:self];
+ } else fail: {
+ [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
+ }
+}
+
+- (void) stopLoading {
+}
+
+@end
+/* }}} */
+
+/* Install View {{{ */
+@interface InstallView : RVPage {
+ _transient Database *database_;
+ NSMutableArray *sections_;
+ NSMutableArray *filtered_;
+ UITransitionView *transition_;
+ UITable *list_;
+ UIView *accessory_;
+ BOOL editing_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+- (void) reloadData;
+- (void) resetView;
+
+@end
+
+@implementation InstallView
+
+- (void) dealloc {
+ [list_ setDataSource:nil];
+ [list_ setDelegate:nil];
+
+ [sections_ release];
+ [filtered_ release];
+ [transition_ release];
+ [list_ release];
+ [accessory_ release];
+ [super dealloc];
+}
+
+- (int) numberOfRowsInTable:(UITable *)table {
+ return editing_ ? [sections_ count] : [filtered_ count] + 1;
+}
+
+- (float) table:(UITable *)table heightForRow:(int)row {
+ return 45;
+}
+
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
+ if (reusing == nil)
+ reusing = [[[SectionCell alloc] init] autorelease];
+ [(SectionCell *)reusing setSection:(editing_ ?
+ [sections_ objectAtIndex:row] :
+ (row == 0 ? nil : [filtered_ objectAtIndex:(row - 1)])
+ ) editing:editing_];
+ return reusing;
+}
+
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
+ return !editing_;
+}
+
+- (BOOL) table:(UITable *)table canSelectRow:(int)row {
+ return !editing_;
+}
+
+- (void) tableRowSelected:(NSNotification *)notification {
+ int row = [[notification object] selectedRow];
+ if (row == INT_MAX)
+ return;
+
+ Section *section;
+ NSString *name;
+ NSString *title;
+
+ if (row == 0) {
+ section = nil;
+ name = nil;
+ title = @"All Packages";
+ } else {
+ section = [filtered_ objectAtIndex:(row - 1)];
+ name = [section name];
+
+ if (name != nil)
+ title = name;
+ else {
+ name = @"";
+ title = @"(No Section)";
+ }
+ }
+
+ PackageTable *table = [[[PackageTable alloc]
+ initWithBook:book_
+ database:database_
+ title:title
+ filter:@selector(isVisiblyUninstalledInSection:)
+ with:name
+ ] autorelease];
+
+ [table setDelegate:delegate_];
+
+ [book_ pushPage:table];
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+
+ sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+ filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
+
+ transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
+ [self addSubview:transition_];
+
+ list_ = [[UITable alloc] initWithFrame:[transition_ bounds]];
+ [transition_ transition:0 toView:list_];
+
+ UITableColumn *column = [[[UITableColumn alloc]
+ initWithTitle:@"Name"
+ identifier:@"name"
+ width:[self frame].size.width
+ ] autorelease];
+
+ [list_ setDataSource:self];
+ [list_ setSeparatorStyle:1];
+ [list_ addTableColumn:column];
+ [list_ setDelegate:self];
+ [list_ setReusesTableCells:YES];
+
+ [self reloadData];
+
+ [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
+ [list_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
+ } return self;
+}
+
+- (void) reloadData {
+ NSArray *packages = [database_ packages];
+
+ [sections_ removeAllObjects];
+ [filtered_ removeAllObjects];
+
+ NSMutableArray *filtered = [NSMutableArray arrayWithCapacity:[packages count]];
+ NSMutableDictionary *sections = [NSMutableDictionary dictionaryWithCapacity:32];
+
+ _trace();
+ for (size_t i(0); i != [packages count]; ++i) {
+ Package *package([packages objectAtIndex:i]);
+ NSString *name([package section]);
+
+ if (name != nil) {
+ Section *section([sections objectForKey:name]);
+ if (section == nil) {
+ section = [[[Section alloc] initWithName:name] autorelease];
+ [sections setObject:section forKey:name];
+ }
+ }
if ([package valid] && [package installed] == nil && [package visible])
[filtered addObject:package];
}
+ _trace();
[sections_ addObjectsFromArray:[sections allValues]];
[sections_ sortUsingSelector:@selector(compareByName:)];
+ _trace();
[filtered sortUsingSelector:@selector(compareBySection:)];
+ _trace();
Section *section = nil;
for (size_t offset = 0, count = [filtered count]; offset != count; ++offset) {
[section addToCount];
}
+ _trace();
[list_ reloadData];
+ _trace();
}
- (void) resetView {
- (void) _rightButtonClicked {
if ((editing_ = !editing_))
[list_ reloadData];
- else {
+ else
[delegate_ updateData];
- }
-
[book_ reloadTitleForPage:self];
[book_ reloadButtonsForPage:self];
}
[packages_ removeAllObjects];
[sections_ removeAllObjects];
+ _trace();
for (size_t i(0); i != [packages count]; ++i) {
Package *package([packages objectAtIndex:i]);
[packages_ addObject:package];
}
- [packages_ sortUsingSelector:@selector(compareForChanges:)];
+ _trace();
+ [packages_ radixUsingSelector:@selector(compareForChanges) withObject:nil];
+ _trace();
Section *upgradable = [[[Section alloc] initWithName:@"Available Upgrades"] autorelease];
+ Section *ignored = [[[Section alloc] initWithName:@"Ignored Upgrades"] autorelease];
Section *section = nil;
+ NSDate *last = nil;
upgrades_ = 0;
bool unseens = false;
CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
+ _trace();
for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) {
Package *package = [packages_ objectAtIndex:offset];
- if ([package upgradableAndEssential:YES]) {
- ++upgrades_;
- [upgradable addToCount];
- } else {
+ if (![package upgradableAndEssential:YES]) {
unseens = true;
NSDate *seen = [package seen];
- NSString *name;
+ if (section == nil || last != seen && (seen == nil || [seen compare:last] != NSOrderedSame)) {
+ last = seen;
- if (seen == nil)
- name = [@"n/a ?" retain];
- else {
- name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) seen);
- }
-
- if (section == nil || ![[section name] isEqualToString:name]) {
+ NSString *name(seen == nil ? [@"n/a ?" retain] : (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) seen));
section = [[[Section alloc] initWithName:name row:offset] autorelease];
[sections_ addObject:section];
+ [name release];
}
- [name release];
[section addToCount];
+ } else if ([package ignored])
+ [ignored addToCount];
+ else {
+ ++upgrades_;
+ [upgradable addToCount];
}
}
+ _trace();
CFRelease(formatter);
[sections_ removeLastObject];
}
+ if ([ignored count] != 0)
+ [sections_ insertObject:ignored atIndex:0];
if (upgrades_ != 0)
[sections_ insertObject:upgradable atIndex:0];
[self flipPage];
}
-- (void) resetViewAnimated:(BOOL)animated {
- if (flipped_)
- [self flipPage];
- [table_ resetViewAnimated:animated];
-}
-
-- (void) _reloadData {
-}
-
-- (void) reloadData {
- if (flipped_)
- [self flipPage];
- [table_ setObject:[field_ text]];
- [table_ reloadData];
- [table_ resetCursor];
-}
-
-- (UIView *) accessoryView {
- return accessory_;
-}
-
-- (NSString *) title {
- return nil;
-}
-
-- (NSString *) backButtonTitle {
- return @"Search";
-}
-
-- (void) setDelegate:(id)delegate {
- [table_ setDelegate:delegate];
- [super setDelegate:delegate];
-}
-
-@end
-/* }}} */
-
-@implementation CYBook
-
-- (void) dealloc {
- [overlay_ release];
- [indicator_ release];
- [prompt_ release];
- [progress_ release];
- [cancel_ release];
- [super dealloc];
-}
-
-- (NSString *) getTitleForPage:(RVPage *)page {
- return Simplify([super getTitleForPage:page]);
-}
-
-- (BOOL) updating {
- return updating_;
-}
-
-- (void) update {
- [UIView beginAnimations:nil context:NULL];
-
- CGRect ovrframe = [overlay_ frame];
- ovrframe.origin.y = 0;
- [overlay_ setFrame:ovrframe];
-
- CGRect barframe = [navbar_ frame];
- barframe.origin.y += ovrframe.size.height;
- [navbar_ setFrame:barframe];
-
- CGRect trnframe = [transition_ frame];
- trnframe.origin.y += ovrframe.size.height;
- trnframe.size.height -= ovrframe.size.height;
- [transition_ setFrame:trnframe];
-
- [UIView endAnimations];
-
- [indicator_ startAnimation];
- [prompt_ setText:@"Updating Database"];
- [progress_ setProgress:0];
-
- received_ = 0;
- last_ = [NSDate timeIntervalSinceReferenceDate];
- updating_ = true;
- [overlay_ addSubview:cancel_];
-
- [NSThread
- detachNewThreadSelector:@selector(_update)
- toTarget:self
- withObject:nil
- ];
-}
-
-- (void) _update_ {
- updating_ = false;
-
- [indicator_ stopAnimation];
-
- [UIView beginAnimations:nil context:NULL];
-
- CGRect ovrframe = [overlay_ frame];
- ovrframe.origin.y = -ovrframe.size.height;
- [overlay_ setFrame:ovrframe];
-
- CGRect barframe = [navbar_ frame];
- barframe.origin.y -= ovrframe.size.height;
- [navbar_ setFrame:barframe];
-
- CGRect trnframe = [transition_ frame];
- trnframe.origin.y -= ovrframe.size.height;
- trnframe.size.height += ovrframe.size.height;
- [transition_ setFrame:trnframe];
-
- [UIView commitAnimations];
-
- [delegate_ performSelector:@selector(reloadData) withObject:nil afterDelay:0];
-}
-
-- (id) initWithFrame:(CGRect)frame database:(Database *)database {
- if ((self = [super initWithFrame:frame]) != nil) {
- database_ = database;
-
- CGRect ovrrect = [navbar_ bounds];
- ovrrect.size.height = [UINavigationBar defaultSize].height;
- ovrrect.origin.y = -ovrrect.size.height;
+- (void) resetViewAnimated:(BOOL)animated {
+ if (flipped_)
+ [self flipPage];
+ [table_ resetViewAnimated:animated];
+}
- overlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
- [self addSubview:overlay_];
+- (void) _reloadData {
+}
- ovrrect.origin.y = frame.size.height;
- underlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
- [underlay_ setTintColor:[UIColor colorWithRed:0.23 green:0.23 blue:0.23 alpha:1]];
- [self addSubview:underlay_];
+- (void) reloadData {
+ if (flipped_)
+ [self flipPage];
+ [table_ setObject:[field_ text]];
+ [table_ reloadData];
+ [table_ resetCursor];
+}
- [overlay_ setBarStyle:1];
- [underlay_ setBarStyle:1];
+- (UIView *) accessoryView {
+ return accessory_;
+}
- int barstyle = [overlay_ _barStyle:NO];
- bool ugly = barstyle == 0;
+- (NSString *) title {
+ return nil;
+}
- UIProgressIndicatorStyle style = ugly ?
- UIProgressIndicatorStyleMediumBrown :
- UIProgressIndicatorStyleMediumWhite;
+- (NSString *) backButtonTitle {
+ return @"Search";
+}
- CGSize indsize = [UIProgressIndicator defaultSizeForStyle:style];
- unsigned indoffset = (ovrrect.size.height - indsize.height) / 2;
- CGRect indrect = {{indoffset, indoffset}, indsize};
+- (void) setDelegate:(id)delegate {
+ [table_ setDelegate:delegate];
+ [super setDelegate:delegate];
+}
- indicator_ = [[UIProgressIndicator alloc] initWithFrame:indrect];
- [indicator_ setStyle:style];
- [overlay_ addSubview:indicator_];
+@end
+/* }}} */
- CGSize prmsize = {215, indsize.height + 4};
+@interface SettingsView : RVPage {
+ _transient Database *database_;
+ NSString *name_;
+ Package *package_;
+ UIPreferencesTable *table_;
+ _UISwitchSlider *subscribedSwitch_;
+ _UISwitchSlider *ignoredSwitch_;
+ UIPreferencesControlTableCell *subscribedCell_;
+ UIPreferencesControlTableCell *ignoredCell_;
+}
- CGRect prmrect = {{
- indoffset * 2 + indsize.width,
-#ifdef __OBJC2__
- -1 +
-#endif
- unsigned(ovrrect.size.height - prmsize.height) / 2
- }, prmsize};
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package;
- UIFont *font = [UIFont systemFontOfSize:15];
+@end
- prompt_ = [[UITextLabel alloc] initWithFrame:prmrect];
+@implementation SettingsView
- [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
- [prompt_ setBackgroundColor:[UIColor clearColor]];
- [prompt_ setFont:font];
+- (void) dealloc {
+ [table_ setDataSource:nil];
- [overlay_ addSubview:prompt_];
+ [name_ release];
+ if (package_ != nil)
+ [package_ release];
+ [table_ release];
+ [subscribedSwitch_ release];
+ [ignoredSwitch_ release];
+ [subscribedCell_ release];
+ [ignoredCell_ release];
+ [super dealloc];
+}
- CGSize prgsize = {75, 100};
+- (int) numberOfGroupsInPreferencesTable:(UIPreferencesTable *)table {
+ if (package_ == nil)
+ return 0;
- CGRect prgrect = {{
- ovrrect.size.width - prgsize.width - 10,
- (ovrrect.size.height - prgsize.height) / 2
- } , prgsize};
+ return 2;
+}
- progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
- [progress_ setStyle:0];
- [overlay_ addSubview:progress_];
+- (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group {
+ if (package_ == nil)
+ return nil;
- cancel_ = [[UINavigationButton alloc] initWithTitle:@"Cancel" style:UINavigationButtonStyleHighlighted];
- [cancel_ addTarget:self action:@selector(_onCancel) forControlEvents:UIControlEventTouchUpInside];
+ switch (group) {
+ case 0: return nil;
+ case 1: return nil;
- CGRect frame = [cancel_ frame];
- frame.size.width = 65;
- frame.origin.x = ovrrect.size.width - frame.size.width - 5;
- frame.origin.y = (ovrrect.size.height - frame.size.height) / 2;
- [cancel_ setFrame:frame];
+ default: _assert(false);
+ }
- [cancel_ setBarStyle:barstyle];
- } return self;
+ return nil;
}
-- (void) _onCancel {
- updating_ = false;
- [cancel_ removeFromSuperview];
-}
+- (BOOL) preferencesTable:(UIPreferencesTable *)table isLabelGroup:(int)group {
+ if (package_ == nil)
+ return NO;
-- (void) _update { _pooled
- Status status;
- status.setDelegate(self);
+ switch (group) {
+ case 0: return NO;
+ case 1: return YES;
- [database_ updateWithStatus:status];
+ default: _assert(false);
+ }
- [self
- performSelectorOnMainThread:@selector(_update_)
- withObject:nil
- waitUntilDone:NO
- ];
+ return NO;
}
-- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
- [prompt_ setText:[NSString stringWithFormat:@"Error: %@", error]];
-}
+- (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group {
+ if (package_ == nil)
+ return 0;
-- (void) setProgressTitle:(NSString *)title {
- [self
- performSelectorOnMainThread:@selector(_setProgressTitle:)
- withObject:title
- waitUntilDone:YES
- ];
-}
+ switch (group) {
+ case 0: return 1;
+ case 1: return 1;
-- (void) setProgressPercent:(float)percent {
- [self
- performSelectorOnMainThread:@selector(_setProgressPercent:)
- withObject:[NSNumber numberWithFloat:percent]
- waitUntilDone:YES
- ];
-}
+ default: _assert(false);
+ }
-- (void) startProgress {
+ return 0;
}
-- (void) addProgressOutput:(NSString *)output {
- [self
- performSelectorOnMainThread:@selector(_addProgressOutput:)
- withObject:output
- waitUntilDone:YES
- ];
-}
+- (void) onSomething:(UIPreferencesControlTableCell *)cell withKey:(NSString *)key {
+ if (package_ == nil)
+ return;
-- (bool) isCancelling:(size_t)received {
- NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
- if (received_ != received) {
- received_ = received;
- last_ = now;
- } else if (now - last_ > 15)
- return true;
- return !updating_;
-}
+ _UISwitchSlider *slider([cell control]);
+ // XXX: this is just weird
+ BOOL value([slider value] != 0);
+ NSMutableDictionary *metadata([package_ metadata]);
-- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
- [sheet dismiss];
-}
+ BOOL before;
+ if (NSNumber *number = [metadata objectForKey:key])
+ before = [number boolValue];
+ else
+ before = NO;
+ NSLog(@"%@:%@ %@:%@ : %u:%u", cell, slider, name_, key, value, before);
-- (void) _setProgressTitle:(NSString *)title {
- [prompt_ setText:title];
+ if (value != before) {
+ [metadata setObject:[NSNumber numberWithBool:value] forKey:key];
+ Changed_ = true;
+ //[delegate_ performSelector:@selector(updateData) withObject:nil afterDelay:0];
+ [delegate_ updateData];
+ }
}
-- (void) _setProgressPercent:(NSNumber *)percent {
- [progress_ setProgress:[percent floatValue]];
+- (void) onSubscribed:(UIPreferencesControlTableCell *)cell {
+ [self onSomething:cell withKey:@"IsSubscribed"];
}
-- (void) _addProgressOutput:(NSString *)output {
+- (void) onIgnored:(UIPreferencesControlTableCell *)cell {
+ [self onSomething:cell withKey:@"IsIgnored"];
}
-@end
-
-@interface CydiaURLProtocol : NSURLProtocol {
-}
+- (id) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group {
+ if (package_ == nil)
+ return nil;
-@end
+ switch (group) {
+ case 0: switch (row) {
+ case 0:
+ return subscribedCell_;
+ case 1:
+ return ignoredCell_;
+ default: _assert(false);
+ } break;
+
+ case 1: switch (row) {
+ case 0: {
+ UIPreferencesControlTableCell *cell([[[UIPreferencesControlTableCell alloc] init] autorelease]);
+ [cell setTitle:@"Changes only shows upgrades to installed packages. This minimizes spam from packagers. Activate this to see upgrades to this package even when it is not installed."];
+ return cell;
+ }
-@implementation CydiaURLProtocol
+ default: _assert(false);
+ } break;
-+ (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;
-}
+ default: _assert(false);
+ }
-+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
- return request;
+ return nil;
}
-- (void) startLoading {
- id<NSURLProtocolClient> client([self client]);
- NSURLRequest *request([self request]);
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package {
+ if ((self = [super initWithBook:book])) {
+ database_ = database;
+ name_ = [package retain];
- NSURL *url([request URL]);
- NSString *href([url absoluteString]);
+ table_ = [[UIPreferencesTable alloc] initWithFrame:[self bounds]];
+ [self addSubview:table_];
- NSString *path([href substringFromIndex:8]);
- NSRange slash([path rangeOfString:@"/"]);
+ subscribedSwitch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(200, 10, 50, 20)];
+ [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:kUIControlEventMouseUpInside];
- NSString *command;
- if (slash.location == NSNotFound) {
- command = path;
- path = nil;
- } else {
- command = [path substringToIndex:slash.location];
- path = [path substringFromIndex:(slash.location + 1)];
- }
+ ignoredSwitch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(200, 10, 50, 20)];
+ [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:kUIControlEventMouseUpInside];
- Database *database([Database sharedInstance]);
+ subscribedCell_ = [[UIPreferencesControlTableCell alloc] init];
+ [subscribedCell_ setTitle:@"Show All Changes"];
+ [subscribedCell_ setControl:subscribedSwitch_];
- if ([command isEqualToString:@"package-icon"]) {
- if (path == nil)
- goto fail;
- Package *package([database packageWithName:path]);
- if (package == nil)
- goto fail;
+ ignoredCell_ = [[UIPreferencesControlTableCell alloc] init];
+ [ignoredCell_ setTitle:@"Ignore Upgrades"];
+ [ignoredCell_ setControl:ignoredSwitch_];
- NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
+ [table_ setDataSource:self];
+ [self reloadData];
+ } return self;
+}
- UIImage *icon([package icon]);
- NSData *data(UIImagePNGRepresentation(icon));
+- (void) resetViewAnimated:(BOOL)animated {
+ [table_ resetViewAnimated:animated];
+}
- [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
- [client URLProtocol:self didLoadData:data];
- [client URLProtocolDidFinishLoading:self];
- } else fail: {
- [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
+- (void) reloadData {
+ if (package_ != nil)
+ [package_ autorelease];
+ package_ = [database_ packageWithName:name_];
+ if (package_ != nil) {
+ [package_ retain];
+ [subscribedSwitch_ setValue:([package_ subscribed] ? 1 : 0) animated:NO];
+ [ignoredSwitch_ setValue:([package_ ignored] ? 1 : 0) animated:NO];
}
+
+ [table_ reloadData];
}
-- (void) stopLoading {
+- (NSString *) title {
+ return @"Settings";
}
@end
+/* Signature View {{{ */
@interface SignatureView : BrowserView {
_transient Database *database_;
NSString *package_;
} return self;
}
+- (void) resetViewAnimated:(BOOL)animated {
+}
+
- (void) reloadData {
[self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
}
@end
+/* }}} */
@interface Cydia : UIApplication <
ConfirmationViewDelegate,
[overlay_ addSubview:hud];
[hud show:YES];*/
+ _trace();
[database_ reloadData];
+ _trace();
size_t changes(0);
- (void) _saveConfig {
if (Changed_) {
+ _trace();
_assert([Metadata_ writeToFile:@"/var/lib/cydia/metadata.plist" atomically:YES] == YES);
+ _trace();
Changed_ = false;
}
}
[self _saveConfig];
/* XXX: this is just stupid */
- if (tag_ != 2)
+ if (tag_ != 2 && install_ != nil)
[install_ reloadData];
- if (tag_ != 3)
+ if (tag_ != 3 && changes_ != nil)
[changes_ reloadData];
- if (tag_ != 5)
+ if (tag_ != 5 && search_ != nil)
[search_ reloadData];
[book_ reloadData];
[[UIKeyboardImpl sharedInstance] setSoundsEnabled:(Sounds_Keyboard_ ? YES : NO)];
[overlay_ addSubview:keyboard_];
+ if (!bootstrap_)
+ [underlay_ addSubview:overlay_];
+
+ [self reloadData];
+
install_ = [[InstallView alloc] initWithBook:book_ database:database_];
changes_ = [[ChangesView alloc] initWithBook:book_ database:database_];
search_ = [[SearchView alloc] initWithBook:book_ database:database_];
withClass:[ManageView class]
] retain];
- if (!bootstrap_)
- [underlay_ addSubview:overlay_];
-
- [self reloadData];
-
if (bootstrap_)
[self bootstrap];
else
return [self _pageForURL:[NSURL URLWithString:[href substringFromIndex:12]] withClass:[BrowserView class]];
else if ([href hasPrefix:@"cydia://launch/"])
[self launchApplicationWithIdentifier:[href substringFromIndex:15] suspended:NO];
+ else if ([href hasPrefix:@"cydia://package-settings/"])
+ return [[[SettingsView alloc] initWithBook:book_ database:database_ package:[href substringFromIndex:25]] autorelease];
else if ([href hasPrefix:@"cydia://package-signature/"])
return [[[SignatureView alloc] initWithBook:book_ database:database_ package:[href substringFromIndex:26]] autorelease];
else if ([href hasPrefix:@"cydia://package/"])