+ default:
+ _assert(false);
+ return -1;
+ }
+}
+
+- (int) numberOfRowsInTable:(UITable *)table {
+ return [sources_ count];
+}
+
+- (float) table:(UITable *)table heightForRow:(int)row {
+ Source *source = [sources_ objectAtIndex:row];
+ return [source description] == nil ? 56 : 73;
+}
+
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col {
+ Source *source = [sources_ objectAtIndex:row];
+ // XXX: weird warning, stupid selectors ;P
+ return [[[SourceCell alloc] initWithSource:(id)source] autorelease];
+}
+
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
+ return YES;
+}
+
+- (BOOL) table:(UITable *)table canSelectRow:(int)row {
+ return YES;
+}
+
+- (void) tableRowSelected:(NSNotification*)notification {
+ UITable *table([list_ table]);
+ int row([table selectedRow]);
+ if (row == INT_MAX)
+ return;
+
+ Source *source = [sources_ objectAtIndex:row];
+
+ PackageTable *packages = [[[FilteredPackageTable alloc]
+ initWithBook:book_
+ database:database_
+ title:[source label]
+ filter:@selector(isVisibleInSource:)
+ with:source
+ ] autorelease];
+
+ [packages setDelegate:delegate_];
+
+ [book_ pushPage:packages];
+}
+
+- (BOOL) table:(UITable *)table canDeleteRow:(int)row {
+ Source *source = [sources_ objectAtIndex:row];
+ return [source record] != nil;
+}
+
+- (void) table:(UITable *)table willSwipeToDeleteRow:(int)row {
+ [[list_ table] setDeleteConfirmationRow:row];
+}
+
+- (void) table:(UITable *)table deleteRow:(int)row {
+ Source *source = [sources_ objectAtIndex:row];
+ [Sources_ removeObjectForKey:[source key]];
+ [delegate_ syncData];
+}
+
+- (void) complete {
+ [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
+ @"deb", @"Type",
+ href_, @"URI",
+ @"./", @"Distribution",
+ nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
+
+ [delegate_ syncData];
+}
+
+- (NSString *) getWarning {
+ NSString *href(href_);
+ NSRange colon([href rangeOfString:@"://"]);
+ if (colon.location != NSNotFound)
+ href = [href substringFromIndex:(colon.location + 3)];
+ href = [href stringByAddingPercentEscapes];
+ href = [@"http://cydia.saurik.com/api/repotag/" stringByAppendingString:href];
+ href = [href stringByCachingURLWithCurrentCDN];
+
+ NSURL *url([NSURL URLWithString:href]);
+
+ NSStringEncoding encoding;
+ NSError *error(nil);
+
+ if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error])
+ return [warning length] == 0 ? nil : warning;
+ return nil;
+}
+
+- (void) _endConnection:(NSURLConnection *)connection {
+ NSURLConnection **field = NULL;
+ if (connection == trivial_bz2_)
+ field = &trivial_bz2_;
+ else if (connection == trivial_gz_)
+ field = &trivial_gz_;
+ _assert(field != NULL);
+ [connection release];
+ *field = nil;
+
+ if (
+ trivial_bz2_ == nil &&
+ trivial_gz_ == nil
+ ) {
+ bool defer(false);
+
+ if (trivial_) {
+ if (NSString *warning = [self yieldToSelector:@selector(getWarning)]) {
+ defer = true;
+
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:CYLocalize("SOURCE_WARNING")
+ buttons:[NSArray arrayWithObjects:CYLocalize("ADD_ANYWAY"), CYLocalize("CANCEL"), nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"warning"
+ ] autorelease];
+
+ [sheet setNumberOfRows:1];
+
+ [sheet setBodyText:warning];
+ [sheet popupAlertAnimated:YES];
+ } else
+ [self complete];
+ } else if (error_ != nil) {
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:CYLocalize("VERIFICATION_ERROR")
+ buttons:[NSArray arrayWithObjects:CYLocalize("OK"), nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"urlerror"
+ ] autorelease];
+
+ [sheet setBodyText:[error_ localizedDescription]];
+ [sheet popupAlertAnimated:YES];
+ } else {
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:CYLocalize("NOT_REPOSITORY")
+ buttons:[NSArray arrayWithObjects:CYLocalize("OK"), nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"trivial"
+ ] autorelease];
+
+ [sheet setBodyText:CYLocalize("NOT_REPOSITORY_EX")];
+ [sheet popupAlertAnimated:YES];
+ }
+
+ [delegate_ setStatusBarShowsProgress:NO];
+ [delegate_ removeProgressHUD:hud_];
+
+ [hud_ autorelease];
+ hud_ = nil;
+
+ if (!defer) {
+ [href_ release];
+ href_ = nil;
+ }
+
+ if (error_ != nil) {
+ [error_ release];
+ error_ = nil;
+ }
+ }
+}
+
+- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
+ switch ([response statusCode]) {
+ case 200:
+ trivial_ = YES;
+ }
+}
+
+- (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) connectionDidFinishLoading:(NSURLConnection *)connection {
+ [self _endConnection:connection];
+}
+
+- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
+ NSMutableURLRequest *request = [NSMutableURLRequest
+ requestWithURL:[NSURL URLWithString:href]
+ cachePolicy:NSURLRequestUseProtocolCachePolicy
+ timeoutInterval:20.0
+ ];
+
+ [request setHTTPMethod:method];
+
+ 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"];
+
+ return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
+}
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ NSString *context([sheet context]);
+
+ if ([context isEqualToString:@"source"]) {
+ switch (button) {
+ case 1: {
+ NSString *href = [[sheet textField] text];
+
+ //installer_ = [[self _requestHRef:href method:@"GET"] retain];
+
+ if (![href hasSuffix:@"/"])
+ href_ = [href stringByAppendingString:@"/"];
+ else
+ href_ = href;
+ href_ = [href_ 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];
+
+ trivial_ = false;
+
+ hud_ = [[delegate_ addProgressHUD] retain];
+ [hud_ setText:CYLocalize("VERIFYING_URL")];
+ } break;
+
+ case 2:
+ break;
+
+ default:
+ _assert(false);
+ }
+
+ [sheet dismiss];
+ } else if ([context isEqualToString:@"trivial"])
+ [sheet dismiss];
+ else if ([context isEqualToString:@"urlerror"])
+ [sheet dismiss];
+ else if ([context isEqualToString:@"warning"]) {
+ switch (button) {
+ case 1:
+ [self complete];
+ break;
+
+ case 2:
+ break;
+
+ default:
+ _assert(false);
+ }
+
+ [href_ release];
+ href_ = nil;
+
+ [sheet dismiss];
+ }
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+ sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
+
+ //list_ = [[UITable alloc] initWithFrame:[self bounds]];
+ list_ = [[UISectionList alloc] initWithFrame:[self bounds] showSectionIndex:NO];
+ [list_ setShouldHideHeaderInShortLists:NO];
+
+ [self addSubview:list_];
+ [list_ setDataSource:self];
+
+ UITableColumn *column = [[UITableColumn alloc]
+ initWithTitle:CYLocalize("NAME")
+ identifier:@"name"
+ width:[self frame].size.width
+ ];
+
+ UITable *table = [list_ table];
+ [table setSeparatorStyle:1];
+ [table addTableColumn:column];
+ [table setDelegate:self];
+
+ [self reloadData];
+
+ [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
+ [list_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
+ } return self;
+}
+
+- (void) reloadData {
+ pkgSourceList list;
+ _assert(list.ReadMainList());
+
+ [sources_ removeAllObjects];
+ [sources_ addObjectsFromArray:[database_ sources]];
+ _trace();
+ [sources_ sortUsingSelector:@selector(compareByNameAndType:)];
+ _trace();
+
+ int count = [sources_ count];
+ for (offset_ = 0; offset_ != count; ++offset_) {
+ Source *source = [sources_ objectAtIndex:offset_];
+ if ([source record] == nil)
+ break;
+ }
+
+ [list_ reloadData];
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [list_ resetViewAnimated:animated];
+}
+
+- (void) _leftButtonClicked {
+ /*[book_ pushPage:[[[AddSourceView alloc]
+ initWithBook:book_
+ database:database_
+ ] autorelease]];*/
+
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:CYLocalize("ENTER_APT_URL")
+ buttons:[NSArray arrayWithObjects:CYLocalize("ADD_SOURCE"), CYLocalize("CANCEL"), nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"source"
+ ] autorelease];
+
+ [sheet setNumberOfRows:1];
+
+ [sheet addTextFieldWithValue:@"http://" label:@""];
+
+ UITextInputTraits *traits = [[sheet textField] textInputTraits];
+ [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
+ [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
+ [traits setKeyboardType:UIKeyboardTypeURL];
+ // XXX: UIReturnKeyDone
+ [traits setReturnKeyType:UIReturnKeyNext];
+
+ [sheet popupAlertAnimated:YES];
+}
+
+- (void) _rightButtonClicked {
+ UITable *table = [list_ table];
+ BOOL editing = [table isRowDeletionEnabled];
+ [table enableRowDeletion:!editing animated:YES];
+ [book_ reloadButtonsForPage:self];
+}
+
+- (NSString *) title {
+ return CYLocalize("SOURCES");
+}
+
+- (NSString *) leftButtonTitle {
+ return [[list_ table] isRowDeletionEnabled] ? CYLocalize("ADD") : nil;
+}
+
+- (id) rightButtonTitle {
+ return [[list_ table] isRowDeletionEnabled] ? CYLocalize("DONE") : CYLocalize("EDIT");
+}
+
+- (UINavigationButtonStyle) rightButtonStyle {
+ return [[list_ table] isRowDeletionEnabled] ? UINavigationButtonStyleHighlighted : UINavigationButtonStyleNormal;
+}
+
+@end
+/* }}} */
+
+/* Installed View {{{ */
+@interface InstalledView : RVPage {
+ _transient Database *database_;
+ FilteredPackageTable *packages_;
+ BOOL expert_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+
+@end
+
+@implementation InstalledView
+
+- (void) dealloc {
+ [packages_ release];
+ [super dealloc];
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+
+ packages_ = [[FilteredPackageTable alloc]
+ initWithBook:book
+ database:database
+ title:nil
+ filter:@selector(isInstalledAndVisible:)
+ with:[NSNumber numberWithBool:YES]
+ ];
+
+ [self addSubview:packages_];
+
+ [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
+ [packages_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
+ } return self;
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [packages_ resetViewAnimated:animated];
+}
+
+- (void) reloadData {
+ [packages_ reloadData];
+}
+
+- (void) _rightButtonClicked {
+ [packages_ setObject:[NSNumber numberWithBool:expert_]];
+ [packages_ reloadData];
+ expert_ = !expert_;
+ [book_ reloadButtonsForPage:self];
+}
+
+- (NSString *) title {
+ return CYLocalize("INSTALLED");
+}
+
+- (NSString *) backButtonTitle {
+ return CYLocalize("PACKAGES");
+}
+
+- (id) rightButtonTitle {
+ return Role_ != nil && [Role_ isEqualToString:@"Developer"] ? nil : expert_ ? CYLocalize("EXPERT") : CYLocalize("SIMPLE");
+}
+
+- (UINavigationButtonStyle) rightButtonStyle {
+ return expert_ ? UINavigationButtonStyleHighlighted : UINavigationButtonStyleNormal;
+}
+
+- (void) setDelegate:(id)delegate {
+ [super setDelegate:delegate];
+ [packages_ setDelegate:delegate];
+}
+
+@end
+/* }}} */
+
+/* Home View {{{ */
+@interface HomeView : BrowserView {
+}
+
+@end
+
+@implementation HomeView
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ NSString *context([sheet context]);
+
+ if ([context isEqualToString:@"about"])
+ [sheet dismiss];
+ else
+ [super alertSheet:sheet buttonClicked:button];
+}
+
+- (void) _leftButtonClicked {
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:CYLocalize("ABOUT_CYDIA")
+ buttons:[NSArray arrayWithObjects:CYLocalize("CLOSE"), nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"about"
+ ] autorelease];
+
+ [sheet setBodyText:
+ @"Copyright (C) 2008-2009\n"
+ "Jay Freeman (saurik)\n"
+ "saurik@saurik.com\n"
+ "http://www.saurik.com/\n"
+ "\n"
+ "The Okori Group\n"
+ "http://www.theokorigroup.com/\n"
+ "\n"
+ "College of Creative Studies,\n"
+ "University of California,\n"
+ "Santa Barbara\n"
+ "http://www.ccs.ucsb.edu/"
+ ];
+
+ [sheet popupAlertAnimated:YES];
+}
+
+- (NSString *) leftButtonTitle {
+ return CYLocalize("ABOUT");
+}
+
+@end
+/* }}} */
+/* Manage View {{{ */
+@interface ManageView : BrowserView {
+}
+
+@end
+
+@implementation ManageView
+
+- (NSString *) title {
+ return CYLocalize("MANAGE");
+}
+
+- (void) _leftButtonClicked {
+ [delegate_ askForSettings];
+}
+
+- (NSString *) leftButtonTitle {
+ return CYLocalize("SETTINGS");
+}
+
+#if !AlwaysReload
+- (id) _rightButtonTitle {
+ return Queuing_ ? CYLocalize("QUEUE") : nil;
+}
+
+- (UINavigationButtonStyle) rightButtonStyle {
+ return Queuing_ ? UINavigationButtonStyleHighlighted : UINavigationButtonStyleNormal;
+}
+
+- (void) _rightButtonClicked {
+ [delegate_ queue];
+}
+#endif
+
+- (bool) isLoading {
+ return false;
+}
+
+@end
+/* }}} */
+
+#include <BrowserView.m>
+
+/* Cydia Book {{{ */
+@interface CYBook : RVBook <
+ ProgressDelegate
+> {
+ _transient Database *database_;
+ UINavigationBar *overlay_;
+ UINavigationBar *underlay_;
+ UIProgressIndicator *indicator_;
+ UITextLabel *prompt_;
+ UIProgressBar *progress_;
+ UINavigationButton *cancel_;
+ bool updating_;
+}
+
+- (id) initWithFrame:(CGRect)frame database:(Database *)database;
+- (void) update;
+- (BOOL) updating;
+
+@end
+
+@implementation CYBook
+
+- (void) dealloc {
+ [overlay_ release];
+ [indicator_ release];
+ [prompt_ release];
+ [progress_ release];
+ [cancel_ release];
+ [super dealloc];
+}
+
+- (NSString *) getTitleForPage:(RVPage *)page {
+ return [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:CYLocalize("UPDATING_DATABASE")];
+ [progress_ setProgress:0];
+
+ 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;
+
+ overlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
+ [self addSubview:overlay_];
+
+ 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_];
+
+ [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:CYLocalize("CANCEL") style:UINavigationButtonStyleHighlighted];
+ [cancel_ addTarget:self action:@selector(_onCancel) forControlEvents:UIControlEventTouchUpInside];
+
+ CGRect frame = [cancel_ frame];
+ 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:CYLocalize("ERROR_MESSAGE"), error]];
+}
+
+- (void) setProgressTitle:(NSString *)title {
+ [self
+ performSelectorOnMainThread:@selector(_setProgressTitle:)
+ withObject:title
+ waitUntilDone:YES
+ ];
+}