+ 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) 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;
+ 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) stopLoading {
+}
+
+@end
+/* }}} */
+
+/* Sections View {{{ */
+@interface SectionsView : RVPage {
+ _transient Database *database_;
+ NSMutableArray *sections_;
+ NSMutableArray *filtered_;
+ UITable *list_;
+ UIView *accessory_;
+ BOOL editing_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+- (void) reloadData;
+- (void) resetView;
+
+@end
+
+@implementation SectionsView
+
+- (void) dealloc {
+ [list_ setDataSource:nil];
+ [list_ setDelegate:nil];
+
+ [sections_ release];
+ [filtered_ 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 = UCLocalize("ALL_PACKAGES");
+ } else {
+ section = [filtered_ objectAtIndex:(row - 1)];
+ name = [section name];
+
+ if (name != nil) {
+ name = [NSString stringWithString:name];
+ title = [[NSBundle mainBundle] localizedStringForKey:Simplify(name) value:nil table:@"Sections"];
+ } else {
+ name = @"";
+ title = UCLocalize("NO_SECTION");
+ }
+ }
+
+ PackageTable *table = [[[FilteredPackageTable alloc]
+ initWithBook:book_
+ database:database_
+ title:title
+ filter:@selector(isVisibleInSection:)
+ 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];
+
+ list_ = [[UITable alloc] initWithFrame:[self bounds]];
+ [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+ [self addSubview:list_];
+
+ UITableColumn *column = [[[UITableColumn alloc]
+ initWithTitle:UCLocalize("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:UIViewAutoresizingFlexibleBoth];
+ } return self;
+}
+
+- (void) reloadData {
+ NSArray *packages = [database_ packages];
+
+ [sections_ removeAllObjects];
+ [filtered_ removeAllObjects];
+
+#if 0
+ typedef __gnu_cxx::hash_map<NSString *, Section *, NSStringMapHash, NSStringMapEqual> SectionMap;
+ SectionMap sections;
+ sections.resize(64);
+#else
+ NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]);
+#endif
+
+ _trace();
+ for (Package *package in packages) {
+ NSString *name([package section]);
+ NSString *key(name == nil ? @"" : name);
+
+#if 0
+ Section **section;
+
+ _profile(SectionsView$reloadData$Section)
+ section = §ions[key];
+ if (*section == nil) {
+ _profile(SectionsView$reloadData$Section$Allocate)
+ *section = [[[Section alloc] initWithName:name localize:YES] autorelease];
+ _end
+ }
+ _end
+
+ [*section addToCount];
+
+ _profile(SectionsView$reloadData$Filter)
+ if (![package valid] || ![package visible])
+ continue;
+ _end
+
+ [*section addToRow];
+#else
+ Section *section;
+
+ _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];
+#endif
+ }
+ _trace();
+
+#if 0
+ for (SectionMap::const_iterator i(sections.begin()), e(sections.end()); i != e; ++i)
+ [sections_ addObject:i->second];
+#else
+ [sections_ addObjectsFromArray:[sections allValues]];
+#endif
+
+ [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];
+ }
+
+ [list_ reloadData];
+ _trace();
+}
+
+- (void) resetView {
+ if (editing_)
+ [self _rightButtonClicked];
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [list_ resetViewAnimated:animated];
+}
+
+- (void) _rightButtonClicked {
+ if ((editing_ = !editing_))
+ [list_ reloadData];
+ else
+ [delegate_ updateData];
+ [book_ reloadTitleForPage:self];
+ [book_ reloadButtonsForPage:self];
+}
+
+- (NSString *) title {
+ return editing_ ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS");
+}
+
+- (NSString *) backButtonTitle {
+ return UCLocalize("SECTIONS");
+}
+
+- (id) rightButtonTitle {
+ return [sections_ count] == 0 ? nil : editing_ ? UCLocalize("DONE") : UCLocalize("EDIT");
+}
+
+- (UINavigationButtonStyle) rightButtonStyle {
+ return editing_ ? UINavigationButtonStyleHighlighted : UINavigationButtonStyleNormal;
+}
+
+- (UIView *) accessoryView {
+ return accessory_;
+}
+
+@end
+/* }}} */
+/* Changes View {{{ */
+@interface ChangesView : RVPage {
+ _transient Database *database_;
+ NSMutableArray *packages_;
+ NSMutableArray *sections_;
+ UITableView *list_;
+ unsigned upgrades_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database delegate:(id)delegate;
+- (void) reloadData;
+
+@end
+
+@implementation ChangesView
+
+- (void) dealloc {
+ [list_ setDelegate:nil];
+ [list_ setDataSource:nil];
+
+ [packages_ release];
+ [sections_ release];
+ [list_ release];
+ [super dealloc];
+}
+
+- (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 *) packageAtIndexPath:(NSIndexPath *)path {
+ Section *section([sections_ objectAtIndex:[path section]]);
+ NSInteger row([path row]);
+ return [packages_ objectAtIndex:([section row] + row)];
+}
+
+- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path {
+ PackageCell *cell([table dequeueReusableCellWithIdentifier:@"Package"]);
+ if (cell == nil)
+ cell = [[[PackageCell alloc] init] autorelease];
+ [cell setPackage:[self packageAtIndexPath:path]];
+ return cell;
+}
+
+- (CGFloat) tableView:(UITableView *)table heightForRowAtIndexPath:(NSIndexPath *)path {
+ return 73;
+ return [PackageCell heightForPackage:[self packageAtIndexPath:path]];
+}
+
+- (NSIndexPath *) tableView:(UITableView *)table willSelectRowAtIndexPath:(NSIndexPath *)path {
+ Package *package([self packageAtIndexPath:path]);
+ PackageView *view([delegate_ packageView]);
+ [view setDelegate:delegate_];
+ [view setPackage:package];
+ [book_ pushPage:view];
+ return path;
+}
+
+- (void) _leftButtonClicked {
+ [(CYBook *)book_ update];
+ [self reloadButtons];
+}
+
+- (void) _rightButtonClicked {
+ [delegate_ distUpgrade];
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database delegate:(id)delegate {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+
+ packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
+ sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+
+ list_ = [[UITableView alloc] initWithFrame:[self bounds] style:UITableViewStylePlain];
+ [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+ [self addSubview:list_];
+
+ //XXX:[list_ setShouldHideHeaderInShortLists:NO];
+ [list_ setDataSource:self];
+ [list_ setDelegate:self];
+ //[list_ setSectionListStyle:1];
+
+ delegate_ = delegate;
+ [self reloadData];
+
+ [self setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+ } return self;
+}
+
+- (void) _reloadPackages:(NSArray *)packages {
+ _trace();
+ for (Package *package in packages)
+ if (
+ [package uninstalled] && [package valid] && [package visible] ||
+ [package upgradableAndEssential:YES]
+ )
+ [packages_ addObject:package];
+
+ _trace();
+ [packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackageChangesRadix) withContext:NULL];
+ _trace();
+}
+
+- (void) reloadData {
+ NSArray *packages = [database_ packages];
+
+ [packages_ removeAllObjects];
+ [sections_ removeAllObjects];
+
+ UIProgressHUD *hud([delegate_ addProgressHUD]);
+ // XXX: localize
+ [hud setText:@"Loading Changes"];
+ NSLog(@"HUD:%@::%@", delegate_, hud);
+ [self yieldToSelector:@selector(_reloadPackages:) withObject:packages];
+ [delegate_ removeProgressHUD:hud];
+
+ Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
+ Section *ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") localize:NO] autorelease];
+ Section *section = nil;
+ NSDate *last = nil;
+
+ upgrades_ = 0;
+ bool unseens = false;
+
+ CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
+
+ for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) {
+ Package *package = [packages_ objectAtIndex:offset];
+
+ BOOL uae = [package upgradableAndEssential:YES];
+
+ if (!uae) {
+ unseens = true;
+ NSDate *seen;
+
+ _profile(ChangesView$reloadData$Remember)
+ seen = [package seen];
+ _end
+
+ if (section == nil || last != seen && (seen == nil || [seen compare:last] != NSOrderedSame)) {
+ last = seen;
+
+ NSString *name;
+ if (seen == nil)
+ name = UCLocalize("UNKNOWN");
+ else {
+ name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) seen);
+ [name autorelease];
+ }
+
+ _profile(ChangesView$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])
+ [ignored addToCount];
+ else {
+ ++upgrades_;
+ [upgradable addToCount];
+ }
+ }
+ _trace();
+
+ CFRelease(formatter);
+
+ if (unseens) {
+ Section *last = [sections_ lastObject];
+ size_t count = [last count];
+ [packages_ removeObjectsInRange:NSMakeRange([packages_ count] - count, count)];
+ [sections_ removeLastObject];
+ }
+
+ if ([ignored count] != 0)
+ [sections_ insertObject:ignored atIndex:0];
+ if (upgrades_ != 0)
+ [sections_ insertObject:upgradable atIndex:0];
+
+ [list_ reloadData];
+ [self reloadButtons];
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [list_ resetViewAnimated:animated];
+}
+
+- (NSString *) leftButtonTitle {
+ return [(CYBook *)book_ updating] ? nil : UCLocalize("REFRESH");
+}
+
+- (id) rightButtonTitle {
+ return upgrades_ == 0 ? nil : [NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", upgrades_]];
+}
+
+- (NSString *) title {
+ return UCLocalize("CHANGES");
+}
+
+@end
+/* }}} */
+/* Search View {{{ */
+@protocol SearchViewDelegate
+- (void) showKeyboard:(BOOL)show;
+@end
+
+@interface SearchView : RVPage {
+ UIView *accessory_;
+ UISearchField *field_;
+ FilteredPackageTable *table_;
+ bool reload_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+- (void) reloadData;
+
+@end
+
+@implementation SearchView
+
+- (void) dealloc {
+ [field_ setDelegate:nil];
+
+ [accessory_ release];
+ [field_ release];
+ [table_ release];
+ [super dealloc];
+}
+
+- (void) _showKeyboard:(BOOL)show {
+ CGSize keysize = [UIKeyboard defaultSize];
+ CGRect keydown = [book_ pageBounds];
+ CGRect keyup = keydown;
+ keyup.size.height -= keysize.height - ButtonBarHeight_;
+
+ float delay = KeyboardTime_ * ButtonBarHeight_ / keysize.height;
+
+ UIFrameAnimation *animation = [[[UIFrameAnimation alloc] initWithTarget:[table_ list]] autorelease];
+ [animation setSignificantRectFields:8];
+
+ if (show) {
+ [animation setStartFrame:keydown];
+ [animation setEndFrame:keyup];
+ } else {
+ [animation setStartFrame:keyup];
+ [animation setEndFrame:keydown];
+ }
+
+ UIAnimator *animator = [UIAnimator sharedAnimator];
+
+ [animator
+ addAnimations:[NSArray arrayWithObjects:animation, nil]
+ withDuration:(KeyboardTime_ - delay)
+ start:!show
+ ];
+
+ if (show)
+ [animator performSelector:@selector(startAnimation:) withObject:animation afterDelay:delay];
+
+ //[delegate_ showKeyboard:show];
+}
+
+- (void) textFieldDidBecomeFirstResponder:(UITextField *)field {
+ [self _showKeyboard:YES];
+ [table_ setObject:[field_ text] forFilter:@selector(isUnfilteredAndSelectedForBy:)];
+ [self reloadData];
+}
+
+- (void) textFieldDidResignFirstResponder:(UITextField *)field {
+ [self _showKeyboard:NO];
+ [table_ setObject:[field_ text] forFilter:@selector(isUnfilteredAndSearchedForBy:)];
+ [self reloadData];
+}
+
+- (void) keyboardInputChanged:(UIFieldEditor *)editor {
+ if (reload_) {
+ NSString *text([field_ text]);
+ [field_ setClearButtonStyle:(text == nil || [text length] == 0 ? 0 : 2)];
+ [table_ setObject:text forFilter:@selector(isUnfilteredAndSelectedForBy:)];
+ [self reloadData];
+ reload_ = false;
+ }
+}
+
+- (void) textFieldClearButtonPressed:(UITextField *)field {
+ reload_ = true;
+}
+
+- (void) keyboardInputShouldDelete:(id)input {
+ reload_ = true;
+}
+
+- (BOOL) keyboardInput:(id)input shouldInsertText:(NSString *)text isMarkedText:(int)marked {
+ if ([text length] != 1 || [text characterAtIndex:0] != '\n') {
+ reload_ = true;
+ return YES;
+ } else {
+ [field_ resignFirstResponder];
+ return NO;
+ }
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ CGRect pageBounds = [book_ pageBounds];
+
+ table_ = [[FilteredPackageTable alloc]
+ initWithBook:book
+ database:database
+ title:nil
+ filter:@selector(isUnfilteredAndSearchedForBy:)
+ with:nil
+ ];
+
+ [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+ [self addSubview:table_];
+
+ [table_ setShouldHideHeaderInShortLists:NO];
+
+ CGRect cnfrect = {{7, 38}, {17, 18}};
+
+ CGRect area;
+
+ area.origin.x = 10;
+ area.origin.y = 1;
+
+ area.size.width = [self bounds].size.width - area.origin.x * 2;
+ area.size.height = [UISearchField defaultHeight];
+
+ field_ = [[UISearchField alloc] initWithFrame:area];
+ [field_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
+
+ UIFont *font = [UIFont systemFontOfSize:16];
+ [field_ setFont:font];
+
+ [field_ setPlaceholder:UCLocalize("SEARCH_EX")];
+ [field_ setDelegate:self];
+
+ [field_ setPaddingTop:5];
+
+ UITextInputTraits *traits([field_ textInputTraits]);
+ [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
+ [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
+ [traits setReturnKeyType:UIReturnKeySearch];
+
+ CGRect accrect = {{0, 6}, {6 + cnfrect.size.width + 6 + area.size.width + 6, area.size.height}};
+
+ accessory_ = [[UIView alloc] initWithFrame:accrect];
+ [accessory_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
+ [accessory_ addSubview:field_];
+
+ [self setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+ } return self;
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [table_ resetViewAnimated:animated];
+}
+
+- (void) _reloadData {
+}
+
+- (void) reloadData {
+ _profile(SearchView$reloadData)
+ [table_ reloadData];
+ _end
+ PrintTimes();
+ [table_ resetCursor];
+}
+
+- (UIView *) accessoryView {
+ return accessory_;
+}
+
+- (NSString *) title {
+ return nil;
+}
+
+- (NSString *) backButtonTitle {
+ return UCLocalize("SEARCH");
+}
+
+- (void) setDelegate:(id)delegate {
+ [table_ setDelegate:delegate];
+ [super setDelegate:delegate];
+}
+
+@end
+/* }}} */
+/* Settings View {{{ */
+@interface SettingsView : RVPage {
+ _transient Database *database_;
+ NSString *name_;
+ Package *package_;
+ UIPreferencesTable *table_;
+ _UISwitchSlider *subscribedSwitch_;
+ _UISwitchSlider *ignoredSwitch_;
+ UIPreferencesControlTableCell *subscribedCell_;
+ UIPreferencesControlTableCell *ignoredCell_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package;
+
+@end
+
+@implementation SettingsView
+
+- (void) dealloc {
+ [table_ setDataSource:nil];
+
+ [name_ release];
+ if (package_ != nil)
+ [package_ release];
+ [table_ release];
+ [subscribedSwitch_ release];
+ [ignoredSwitch_ release];
+ [subscribedCell_ release];
+ [ignoredCell_ release];
+ [super dealloc];
+}
+
+- (int) numberOfGroupsInPreferencesTable:(UIPreferencesTable *)table {
+ if (package_ == nil)
+ return 0;
+
+ return 2;
+}
+
+- (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group {
+ if (package_ == nil)
+ return nil;
+
+ switch (group) {
+ case 0: return nil;
+ case 1: return nil;
+
+ _nodefault
+ }
+
+ return nil;
+}
+
+- (BOOL) preferencesTable:(UIPreferencesTable *)table isLabelGroup:(int)group {
+ if (package_ == nil)
+ return NO;
+
+ switch (group) {
+ case 0: return NO;
+ case 1: return YES;
+
+ _nodefault
+ }
+
+ return NO;
+}
+
+- (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group {
+ if (package_ == nil)
+ return 0;
+
+ switch (group) {
+ case 0: return 1;
+ case 1: return 1;
+
+ _nodefault
+ }
+
+ return 0;
+}
+
+- (void) onSomething:(UIPreferencesControlTableCell *)cell withKey:(NSString *)key {
+ if (package_ == nil)
+ return;
+
+ _UISwitchSlider *slider([cell control]);
+ BOOL value([slider value] != 0);
+ NSMutableDictionary *metadata([package_ metadata]);
+
+ BOOL before;
+ if (NSNumber *number = [metadata objectForKey:key])
+ before = [number boolValue];
+ else
+ before = NO;
+
+ if (value != before) {
+ [metadata setObject:[NSNumber numberWithBool:value] forKey:key];
+ Changed_ = true;
+ [delegate_ updateData];
+ }
+}
+
+- (void) onSubscribed:(UIPreferencesControlTableCell *)cell {
+ [self onSomething:cell withKey:@"IsSubscribed"];
+}
+
+- (void) onIgnored:(UIPreferencesControlTableCell *)cell {
+ [self onSomething:cell withKey:@"IsIgnored"];
+}
+
+- (id) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group {
+ if (package_ == nil)
+ return nil;
+
+ switch (group) {
+ case 0: switch (row) {
+ case 0:
+ return subscribedCell_;
+ case 1:
+ return ignoredCell_;
+ _nodefault
+ } break;
+
+ case 1: switch (row) {
+ case 0: {
+ UIPreferencesControlTableCell *cell([[[UIPreferencesControlTableCell alloc] init] autorelease]);
+ [cell setShowSelection:NO];
+ [cell setTitle:UCLocalize("SHOW_ALL_CHANGES_EX")];
+ return cell;
+ }
+
+ _nodefault
+ } break;
+
+ _nodefault
+ }
+
+ return nil;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package {
+ if ((self = [super initWithBook:book])) {
+ database_ = database;
+ name_ = [package retain];
+
+ table_ = [[UIPreferencesTable alloc] initWithFrame:[self bounds]];
+ [self addSubview:table_];
+
+ subscribedSwitch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(200, 10, 50, 20)];
+ [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventTouchUpInside];
+
+ ignoredSwitch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(200, 10, 50, 20)];
+ [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventTouchUpInside];
+
+ subscribedCell_ = [[UIPreferencesControlTableCell alloc] init];
+ [subscribedCell_ setShowSelection:NO];
+ [subscribedCell_ setTitle:UCLocalize("SHOW_ALL_CHANGES")];
+ [subscribedCell_ setControl:subscribedSwitch_];
+
+ ignoredCell_ = [[UIPreferencesControlTableCell alloc] init];
+ [ignoredCell_ setShowSelection:NO];
+ [ignoredCell_ setTitle:UCLocalize("IGNORE_UPGRADES")];
+ [ignoredCell_ setControl:ignoredSwitch_];
+
+ [table_ setDataSource:self];
+ [self reloadData];
+ } return self;
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [table_ resetViewAnimated:animated];
+}
+
+- (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];
+}
+
+- (NSString *) title {
+ return UCLocalize("SETTINGS");
+}
+
+@end
+/* }}} */
+
+/* Signature View {{{ */
+@interface SignatureView : CydiaBrowserView {
+ _transient Database *database_;
+ NSString *package_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package;
+
+@end
+
+@implementation SignatureView
+
+- (void) dealloc {
+ [package_ release];
+ [super dealloc];
+}
+
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+ // XXX: dude!
+ [super webView:sender didClearWindowObject:window forFrame:frame];
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+ package_ = [package retain];
+ [self reloadData];
+ } return self;
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+}
+
+- (void) reloadData {
+ [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
+}
+
+@end
+/* }}} */
+
+@interface CydiaViewController : UIViewController {
+}
+
+@end
+
+@implementation CydiaViewController
+
+- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
+ return NO; // XXX: return YES;
+}
+
+@end
+
+@interface Cydia : UIApplication <
+ ConfirmationViewDelegate,
+ ProgressViewDelegate,
+ SearchViewDelegate,
+ CydiaDelegate
+> {
+ UIWindow *window_;
+ CydiaViewController *root_;
+
+ UIView *underlay_;
+ UIView *overlay_;
+ CYBook *book_;
+
+ NSArray *items_;
+ UITabBar *toolbar_;
+
+ RVBook *confirm_;
+
+ NSMutableArray *essential_;
+ NSMutableArray *broken_;
+
+ Database *database_;
+ ProgressView *progress_;
+
+ int tag_;
+
+ UIKeyboard *keyboard_;
+ UIProgressHUD *hud_;
+
+ SectionsView *sections_;
+ ChangesView *changes_;
+ ManageView *manage_;
+ SearchView *search_;
+
+#if RecyclePackageViews
+ NSMutableArray *details_;
+#endif
+}
+
+- (RVPage *) _pageForURL:(NSURL *)url withClass:(Class)_class;
+- (void) setPage:(RVPage *)page;
+
+@end
+
+static _finline void _setHomePage(Cydia *self) {
+ [self setPage:[self _pageForURL:[NSURL URLWithString:CydiaURL(@"")] withClass:[HomeView class]]];
+}
+
+@implementation Cydia
+
+- (UIView *) rotatingContentViewForWindow:(UIWindow *)window {
+ return window_;
+}
+
+- (void) _loaded {
+ if ([broken_ count] != 0) {
+ int count = [broken_ count];
+
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:(count == 1 ? UCLocalize("HALFINSTALLED_PACKAGE") : [NSString stringWithFormat:UCLocalize("HALFINSTALLED_PACKAGES"), count])
+ buttons:[NSArray arrayWithObjects:
+ UCLocalize("FORCIBLY_CLEAR"),
+ UCLocalize("TEMPORARY_IGNORE"),
+ nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"fixhalf"
+ ] autorelease];
+
+ [sheet setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
+
+ [sheet setBodyText:UCLocalize("HALFINSTALLED_PACKAGE_EX")];
+ [sheet popupAlertAnimated:YES];
+ } else if (!Ignored_ && [essential_ count] != 0) {
+ int count = [essential_ count];
+
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:(count == 1 ? UCLocalize("ESSENTIAL_UPGRADE") : [NSString stringWithFormat:UCLocalize("ESSENTIAL_UPGRADES"), count])
+ buttons:[NSArray arrayWithObjects:
+ UCLocalize("UPGRADE_ESSENTIAL"),
+ UCLocalize("COMPLETE_UPGRADE"),
+ UCLocalize("TEMPORARY_IGNORE"),
+ nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"upgrade"
+ ] autorelease];
+
+ [sheet setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
+
+ [sheet setBodyText:UCLocalize("ESSENTIAL_UPGRADE_EX")];
+ [sheet popupAlertAnimated:YES];
+ }
+}
+
+- (void) _saveConfig {
+ if (Changed_) {
+ _trace();
+ NSString *error(nil);
+ if (NSData *data = [NSPropertyListSerialization dataFromPropertyList:Metadata_ format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error]) {
+ _trace();
+ NSError *error(nil);
+ if (![data writeToFile:@"/var/lib/cydia/metadata.plist" options:NSAtomicWrite error:&error])
+ NSLog(@"failure to save metadata data: %@", error);
+ _trace();
+ } else {
+ NSLog(@"failure to serialize metadata: %@", error);
+ return;
+ }
+
+ Changed_ = false;
+ }
+}
+
+- (void) _updateData {
+ [self _saveConfig];
+
+ /* XXX: this is just stupid */
+ if (tag_ != 1 && sections_ != nil)
+ [sections_ reloadData];
+ if (tag_ != 2 && changes_ != nil)
+ [changes_ reloadData];
+ if (tag_ != 4 && search_ != nil)
+ [search_ reloadData];
+
+ [book_ reloadData];
+}
+
+- (void) _reloadData {
+ UIView *block();
+
+ static bool loaded(false);
+ UIProgressHUD *hud([self addProgressHUD]);
+ [hud setText:(loaded ? UCLocalize("RELOADING_DATA") : UCLocalize("LOADING_DATA"))];
+
+ [database_ yieldToSelector:@selector(reloadData) withObject:nil];
+ _trace();
+
+ [self removeProgressHUD:hud];
+
+ size_t changes(0);
+
+ [essential_ removeAllObjects];
+ [broken_ removeAllObjects];
+
+ NSArray *packages([database_ packages]);
+ for (Package *package in packages) {
+ if ([package half])
+ [broken_ addObject:package];
+ if ([package upgradableAndEssential:NO]) {
+ if ([package essential])
+ [essential_ addObject:package];
+ ++changes;
+ }
+ }
+
+ if (changes != 0) {
+ NSString *badge([[NSNumber numberWithInt:changes] stringValue]);
+ [[[toolbar_ items] objectAtIndex:2] setBadgeValue:badge];
+ if ([toolbar_ respondsToSelector:@selector(setBadgeAnimated:forButton:)])
+ [[[toolbar_ items] objectAtIndex:2] setAnimatedBadge:YES];
+ if ([self respondsToSelector:@selector(setApplicationBadge:)])
+ [self setApplicationBadge:badge];
+ else
+ [self setApplicationBadgeString:badge];
+ } else {
+ [[[toolbar_ items] objectAtIndex:2] setBadgeValue:nil];
+ if ([toolbar_ respondsToSelector:@selector(setBadgeAnimated:forButton:)])
+ [[[toolbar_ items] objectAtIndex:2] setAnimatedBadge:NO];
+ if ([self respondsToSelector:@selector(removeApplicationBadge)])
+ [self removeApplicationBadge];
+ else // XXX: maybe use setApplicationBadgeString also?
+ [self setApplicationIconBadgeNumber:0];
+ }
+
+ Queuing_ = false;
+ [[[toolbar_ items] objectAtIndex:3] setBadgeValue:nil];
+
+ [self _updateData];
+
+ if (loaded || ManualRefresh) loaded:
+ [self _loaded];
+ else {
+ loaded = true;
+
+ NSDate *update([Metadata_ objectForKey:@"LastUpdate"]);
+
+ if (update != nil) {
+ NSTimeInterval interval([update timeIntervalSinceNow]);
+ if (interval <= 0 && interval > -(15*60))
+ goto loaded;
+ }
+
+ [book_ setUpdate:update];
+ }
+}
+
+- (void) updateData {
+ [database_ setVisible];
+ [self _updateData];
+}
+
+- (void) update_ {
+ [database_ update];
+}
+
+- (void) syncData {
+ FILE *file(fopen("/etc/apt/sources.list.d/cydia.list", "w"));
+ _assert(file != NULL);
+
+ for (NSString *key in [Sources_ allKeys]) {
+ NSDictionary *source([Sources_ objectForKey:key]);
+
+ fprintf(file, "%s %s %s\n",
+ [[source objectForKey:@"Type"] UTF8String],
+ [[source objectForKey:@"URI"] UTF8String],
+ [[source objectForKey:@"Distribution"] UTF8String]
+ );
+ }
+
+ fclose(file);
+
+ [self _saveConfig];
+
+ [progress_
+ detachNewThreadSelector:@selector(update_)
+ toTarget:self
+ withObject:nil
+ title:UCLocalize("UPDATING_SOURCES")
+ ];
+}
+
+- (void) reloadData {
+ @synchronized (self) {
+ if (confirm_ == nil)
+ [self _reloadData];
+ }
+}
+
+- (void) resolve {
+ pkgProblemResolver *resolver = [database_ resolver];
+
+ resolver->InstallProtect();
+ if (!resolver->Resolve(true))
+ _error->Discard();
+}
+
+- (void) popUpBook:(RVBook *)book {
+ [underlay_ popSubview:book];
+}
+
+- (CGRect) popUpBounds {
+ return [underlay_ bounds];
+}
+
+- (bool) perform {
+ if (![database_ prepare])
+ return false;
+
+ confirm_ = [[RVBook alloc] initWithFrame:[self popUpBounds]];
+ [confirm_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+ [confirm_ setDelegate:self];
+
+ ConfirmationView *page([[[ConfirmationView alloc] initWithBook:confirm_ database:database_] autorelease]);
+ [page setDelegate:self];
+
+ [confirm_ setPage:page];
+ [self popUpBook:confirm_];
+
+ return true;
+}
+
+- (void) queue {
+ @synchronized (self) {
+ [self perform];
+ }
+}
+
+- (void) clearPackage:(Package *)package {
+ @synchronized (self) {
+ [package clear];
+ [self resolve];
+ [self perform];
+ }
+}
+
+- (void) installPackages:(NSArray *)packages {
+ @synchronized (self) {
+ for (Package *package in packages)
+ [package install];
+ [self resolve];
+ [self perform];
+ }
+}
+
+- (void) installPackage:(Package *)package {
+ @synchronized (self) {
+ [package install];
+ [self resolve];
+ [self perform];
+ }
+}
+
+- (void) removePackage:(Package *)package {
+ @synchronized (self) {
+ [package remove];
+ [self resolve];
+ [self perform];
+ }
+}
+
+- (void) distUpgrade {
+ @synchronized (self) {
+ if (![database_ upgrade])
+ return;
+ [self perform];
+ }
+}
+
+- (void) cancel {
+ [self slideUp:[[[UIActionSheet alloc]
+ initWithTitle:nil
+ buttons:[NSArray arrayWithObjects:UCLocalize("CONTINUE_QUEUING"), UCLocalize("CANCEL_CLEAR"), nil]
+ defaultButtonIndex:1
+ delegate:self
+ context:@"cancel"
+ ] autorelease]];
+}
+
+- (void) complete {
+ @synchronized (self) {
+ [self _reloadData];
+
+ if (confirm_ != nil) {
+ [confirm_ release];
+ confirm_ = nil;
+ }
+ }
+}
+
+- (void) confirm {
+ [overlay_ removeFromSuperview];
+ reload_ = true;
+
+ [progress_
+ detachNewThreadSelector:@selector(perform)
+ toTarget:database_
+ withObject:nil
+ title:UCLocalize("RUNNING")
+ ];
+}
+
+- (void) progressViewIsComplete:(ProgressView *)progress {
+ if (confirm_ != nil) {
+ [underlay_ addSubview:overlay_];
+ [confirm_ popFromSuperviewAnimated:NO];
+ }
+
+ [self complete];
+}
+
+- (void) setPage:(RVPage *)page {
+ [page resetViewAnimated:NO];
+ [page setDelegate:self];
+ [book_ setPage:page];
+}
+
+- (RVPage *) _pageForURL:(NSURL *)url withClass:(Class)_class {
+ CydiaBrowserView *browser = [[[_class alloc] initWithBook:book_] autorelease];
+ [browser loadURL:url];
+ return browser;
+}
+
+- (SectionsView *) sectionsView {
+ if (sections_ == nil)
+ sections_ = [[SectionsView alloc] initWithBook:book_ database:database_];
+ return sections_;
+}
+
+- (ChangesView *) changesView {
+ if (changes_ == nil)
+ changes_ = [[ChangesView alloc] initWithBook:book_ database:database_ delegate:self];
+ return changes_;
+}
+
+- (ManageView *) manageView {
+ if (manage_ == nil)
+ manage_ = (ManageView *) [[self
+ _pageForURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"manage" ofType:@"html"]]
+ withClass:[ManageView class]
+ ] retain];
+ return manage_;
+}
+
+- (SearchView *) searchView {
+ if (search_ == nil)
+ search_ = [[SearchView alloc] initWithBook:book_ database:database_];
+ return search_;
+}
+
+- (void) tabBar:(UITabBar *)sender didSelectItem:(UITabBarItem *)item {
+ int tag = [item tag];
+ if (tag == tag_) {
+ [book_ resetViewAnimated:YES];
+ return;
+ } else if (tag_ == 1)
+ [[self sectionsView] resetView];
+
+ switch (tag) {
+ case 0: _setHomePage(self); break;
+
+ case 1: [self setPage:[self sectionsView]]; break;
+ case 2: [self setPage:[self changesView]]; break;
+ case 3: [self setPage:[self manageView]]; break;
+ case 4: [self setPage:[self searchView]]; break;
+
+ _nodefault
+ }
+
+ tag_ = tag;
+}
+
+- (void) askForSettings {
+ NSString *parenthetical(UCLocalize("PARENTHETICAL"));
+
+ CYActionSheet *role([[[CYActionSheet alloc]
+ initWithTitle:UCLocalize("WHO_ARE_YOU")
+ buttons:[NSArray arrayWithObjects:
+ [NSString stringWithFormat:parenthetical, UCLocalize("USER"), UCLocalize("USER_EX")],
+ [NSString stringWithFormat:parenthetical, UCLocalize("HACKER"), UCLocalize("HACKER_EX")],
+ [NSString stringWithFormat:parenthetical, UCLocalize("DEVELOPER"), UCLocalize("DEVELOPER_EX")],
+ nil]
+ defaultButtonIndex:-1
+ ] autorelease]);
+
+ [role setBodyText:UCLocalize("ROLE_EX")];
+
+ int button([role yieldToPopupAlertAnimated:YES]);
+
+ switch (button) {
+ case 1: Role_ = @"User"; break;
+ case 2: Role_ = @"Hacker"; break;
+ case 3: Role_ = @"Developer"; break;
+
+ _nodefault
+ }
+
+ Settings_ = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ Role_, @"Role",
+ nil];
+
+ [Metadata_ setObject:Settings_ forKey:@"Settings"];
+
+ Changed_ = true;