+- (BOOL) valid {
+ return !version_.end();
+}
+
+- (BOOL) upgradableAndEssential:(BOOL)essential {
+ pkgCache::VerIterator current = iterator_.CurrentVer();
+
+ if (current.end())
+ return essential && [self essential];
+ else {
+ pkgCache::VerIterator candidate = [database_ policy]->GetCandidateVer(iterator_);
+ return !candidate.end() && candidate != current;
+ }
+}
+
+- (BOOL) essential {
+ return (iterator_->Flags & pkgCache::Flag::Essential) == 0 ? NO : YES;
+}
+
+- (BOOL) broken {
+ return [database_ cache][iterator_].InstBroken();
+}
+
+- (BOOL) unfiltered {
+ NSString *section = [self section];
+ return section == nil || isSectionVisible(section);
+}
+
+- (BOOL) visible {
+ return [self hasSupportingRole] && [self unfiltered];
+}
+
+- (BOOL) half {
+ unsigned char current = iterator_->CurrentState;
+ return current == pkgCache::State::HalfConfigured || current == pkgCache::State::HalfInstalled;
+}
+
+- (BOOL) halfConfigured {
+ return iterator_->CurrentState == pkgCache::State::HalfConfigured;
+}
+
+- (BOOL) halfInstalled {
+ return iterator_->CurrentState == pkgCache::State::HalfInstalled;
+}
+
+- (BOOL) hasMode {
+ pkgDepCache::StateCache &state([database_ cache][iterator_]);
+ return state.Mode != pkgDepCache::ModeKeep;
+}
+
+- (NSString *) mode {
+ pkgDepCache::StateCache &state([database_ cache][iterator_]);
+
+ switch (state.Mode) {
+ case pkgDepCache::ModeDelete:
+ if ((state.iFlags & pkgDepCache::Purge) != 0)
+ return @"Purge";
+ else
+ return @"Remove";
+ case pkgDepCache::ModeKeep:
+ if ((state.iFlags & pkgDepCache::AutoKept) != 0)
+ return nil;
+ else
+ return nil;
+ case pkgDepCache::ModeInstall:
+ if ((state.iFlags & pkgDepCache::ReInstall) != 0)
+ return @"Reinstall";
+ else switch (state.Status) {
+ case -1:
+ return @"Downgrade";
+ case 0:
+ return @"Install";
+ case 1:
+ return @"Upgrade";
+ case 2:
+ return @"New Install";
+ default:
+ _assert(false);
+ }
+ default:
+ _assert(false);
+ }
+}
+
+- (NSString *) id {
+ return id_;
+}
+
+- (NSString *) name {
+ return name_ == nil ? id_ : name_;
+}
+
+- (NSString *) tagline {
+ return tagline_;
+}
+
+- (UIImage *) icon {
+ NSString *section = [self section];
+ if (section != nil)
+ section = Simplify(section);
+
+ UIImage *icon(nil);
+ if (NSString *icon = icon_)
+ icon = [UIImage imageAtPath:[icon_ substringFromIndex:6]];
+ if (icon == nil) if (section != nil)
+ icon = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]];
+ if (icon == nil) if (source_ != nil) if (NSString *icon = [source_ defaultIcon])
+ icon = [UIImage imageAtPath:[icon substringFromIndex:6]];
+ if (icon == nil)
+ icon = [UIImage applicationImageNamed:@"unknown.png"];
+ return icon;
+}
+
+- (NSString *) homepage {
+ return homepage_;
+}
+
+- (NSString *) depiction {
+ return depiction_;
+}
+
+- (Address *) sponsor {
+ return sponsor_;
+}
+
+- (Address *) author {
+ return author_;
+}
+
+- (NSArray *) files {
+ NSString *path = [NSString stringWithFormat:@"/var/lib/dpkg/info/%@.list", id_];
+ NSMutableArray *files = [NSMutableArray arrayWithCapacity:128];
+
+ std::ifstream fin;
+ fin.open([path UTF8String]);
+ if (!fin.is_open())
+ return nil;
+
+ std::string line;
+ while (std::getline(fin, line))
+ [files addObject:[NSString stringWithUTF8String:line.c_str()]];
+
+ return files;
+}
+
+- (NSArray *) relationships {
+ return relationships_;
+}
+
+- (NSArray *) warnings {
+ NSMutableArray *warnings([NSMutableArray arrayWithCapacity:4]);
+ const char *name(iterator_.Name());
+
+ size_t length(strlen(name));
+ if (length < 2) invalid:
+ [warnings addObject:@"illegal package identifier"];
+ else for (size_t i(0); i != length; ++i)
+ if (
+ (name[i] < 'a' || name[i] > 'z') &&
+ (name[i] < '0' || name[i] > '9') &&
+ (i == 0 || name[i] != '+' && name[i] != '-' && name[i] != '.')
+ ) goto invalid;
+
+ if (strcmp(name, "cydia") != 0) {
+ bool cydia = false;
+ bool stash = false;
+
+ if (NSArray *files = [self files])
+ for (NSString *file in files)
+ if (!cydia && [file isEqualToString:@"/Applications/Cydia.app"])
+ cydia = true;
+ else if (!stash && [file isEqualToString:@"/var/stash"])
+ stash = true;
+
+ if (cydia)
+ [warnings addObject:@"files installed into Cydia.app"];
+ if (stash)
+ [warnings addObject:@"files installed to /var/stash"];
+ }
+
+ return [warnings count] == 0 ? nil : warnings;
+}
+
+- (NSArray *) applications {
+ NSString *me([[NSBundle mainBundle] bundleIdentifier]);
+
+ NSMutableArray *applications([NSMutableArray arrayWithCapacity:2]);
+
+ static Pcre application_r("^/Applications/(.*)\\.app/Info.plist$");
+ if (NSArray *files = [self files])
+ for (NSString *file in files)
+ if (application_r(file)) {
+ NSDictionary *info([NSDictionary dictionaryWithContentsOfFile:file]);
+ NSString *id([info objectForKey:@"CFBundleIdentifier"]);
+ if ([id isEqualToString:me])
+ continue;
+
+ NSString *display([info objectForKey:@"CFBundleDisplayName"]);
+ if (display == nil)
+ display = application_r[1];
+
+ NSString *bundle([file stringByDeletingLastPathComponent]);
+ NSString *icon([info objectForKey:@"CFBundleIconFile"]);
+ if (icon == nil || [icon length] == 0)
+ icon = @"icon.png";
+ NSURL *url([NSURL fileURLWithPath:[bundle stringByAppendingPathComponent:icon]]);
+
+ NSMutableArray *application([NSMutableArray arrayWithCapacity:2]);
+ [applications addObject:application];
+
+ [application addObject:id];
+ [application addObject:display];
+ [application addObject:url];
+ }
+
+ return [applications count] == 0 ? nil : applications;
+}
+
+- (Source *) source {
+ if (!cached_) {
+ source_ = file_.end() ? nil : [[database_ getSource:file_.File()] retain];
+ cached_ = true;
+ }
+
+ return source_;
+}
+
+- (NSString *) role {
+ return role_;
+}
+
+- (BOOL) matches:(NSString *)text {
+ if (text == nil)
+ return NO;
+
+ NSRange range;
+
+ range = [[self id] rangeOfString:text options:NSCaseInsensitiveSearch];
+ if (range.location != NSNotFound)
+ return YES;
+
+ range = [[self name] rangeOfString:text options:NSCaseInsensitiveSearch];
+ if (range.location != NSNotFound)
+ return YES;
+
+ range = [[self tagline] rangeOfString:text options:NSCaseInsensitiveSearch];
+ if (range.location != NSNotFound)
+ return YES;
+
+ return NO;
+}
+
+- (bool) hasSupportingRole {
+ if (role_ == nil)
+ return true;
+ if ([role_ isEqualToString:@"enduser"])
+ return true;
+ if ([Role_ isEqualToString:@"User"])
+ return false;
+ if ([role_ isEqualToString:@"hacker"])
+ return true;
+ if ([Role_ isEqualToString:@"Hacker"])
+ return false;
+ if ([role_ isEqualToString:@"developer"])
+ return true;
+ if ([Role_ isEqualToString:@"Developer"])
+ return false;
+ _assert(false);
+}
+
+- (BOOL) hasTag:(NSString *)tag {
+ return tags_ == nil ? NO : [tags_ containsObject:tag];
+}
+
+- (NSString *) primaryPurpose {
+ for (NSString *tag in tags_)
+ if ([tag hasPrefix:@"purpose::"])
+ return [tag substringFromIndex:9];
+ return nil;
+}
+
+- (NSArray *) purposes {
+ NSMutableArray *purposes([NSMutableArray arrayWithCapacity:2]);
+ for (NSString *tag in tags_)
+ if ([tag hasPrefix:@"purpose::"])
+ [purposes addObject:[tag substringFromIndex:9]];
+ return [purposes count] == 0 ? nil : purposes;
+}
+
+- (NSComparisonResult) compareByName:(Package *)package {
+ NSString *lhs = [self name];
+ NSString *rhs = [package name];
+
+ if ([lhs length] != 0 && [rhs length] != 0) {
+ unichar lhc = [lhs characterAtIndex:0];
+ unichar rhc = [rhs characterAtIndex:0];
+
+ if (isalpha(lhc) && !isalpha(rhc))
+ return NSOrderedAscending;
+ else if (!isalpha(lhc) && isalpha(rhc))
+ return NSOrderedDescending;
+ }
+
+ return [lhs compare:rhs options:CompareOptions_];
+}
+
+- (NSComparisonResult) compareBySection:(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 options:CompareOptions_];
+ if (result != NSOrderedSame)
+ return result;
+ }
+
+ return NSOrderedSame;
+}
+
+- (uint32_t) compareForChanges {
+ union {
+ uint32_t key;
+
+ struct {
+ uint32_t timestamp : 30;
+ uint32_t ignored : 1;
+ uint32_t upgradable : 1;
+ } bits;
+ } value;
+
+ value.bits.upgradable = [self upgradableAndEssential:YES] ? 1 : 0;
+
+ 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 _not(uint32_t) - value.key;
+}
+
+- (void) install {
+ pkgProblemResolver *resolver = [database_ resolver];
+ resolver->Clear(iterator_);
+ resolver->Protect(iterator_);
+ pkgCacheFile &cache([database_ cache]);
+ cache->MarkInstall(iterator_, false);
+ pkgDepCache::StateCache &state((*cache)[iterator_]);
+ if (!state.Install())
+ cache->SetReInstall(iterator_, true);
+}
+
+- (void) remove {
+ pkgProblemResolver *resolver = [database_ resolver];
+ resolver->Clear(iterator_);
+ resolver->Protect(iterator_);
+ resolver->Remove(iterator_);
+ [database_ cache]->MarkDelete(iterator_, true);
+}
+
+- (NSNumber *) isUnfilteredAndSearchedForBy:(NSString *)search {
+ return [NSNumber numberWithBool:(
+ [self unfiltered] && [self matches:search]
+ )];
+}
+
+- (NSNumber *) isInstalledAndVisible:(NSNumber *)number {
+ return [NSNumber numberWithBool:(
+ (![number boolValue] || [self visible]) && [self installed] != nil
+ )];
+}
+
+- (NSNumber *) isVisiblyUninstalledInSection:(NSString *)name {
+ NSString *section = [self section];
+
+ return [NSNumber numberWithBool:(
+ [self visible] &&
+ [self installed] == nil && (
+ name == nil ||
+ section == nil && [name length] == 0 ||
+ [name isEqualToString:section]
+ )
+ )];
+}
+
+- (NSNumber *) isVisibleInSource:(Source *)source {
+ return [NSNumber numberWithBool:([self source] == source && [self visible])];
+}
+
+@end
+/* }}} */
+/* Section Class {{{ */
+@interface Section : NSObject {
+ NSString *name_;
+ size_t row_;
+ size_t count_;
+}
+
+- (NSComparisonResult) compareByName:(Section *)section;
+- (Section *) initWithName:(NSString *)name;
+- (Section *) initWithName:(NSString *)name row:(size_t)row;
+- (NSString *) name;
+- (size_t) row;
+- (size_t) count;
+- (void) addToCount;
+
+@end
+
+@implementation Section
+
+- (void) dealloc {
+ [name_ release];
+ [super dealloc];
+}
+
+- (NSComparisonResult) compareByName:(Section *)section {
+ NSString *lhs = [self name];
+ NSString *rhs = [section name];
+
+ if ([lhs length] != 0 && [rhs length] != 0) {
+ unichar lhc = [lhs characterAtIndex:0];
+ unichar rhc = [rhs characterAtIndex:0];
+
+ if (isalpha(lhc) && !isalpha(rhc))
+ return NSOrderedAscending;
+ else if (!isalpha(lhc) && isalpha(rhc))
+ return NSOrderedDescending;
+ }
+
+ return [lhs compare:rhs options:CompareOptions_];
+}
+
+- (Section *) initWithName:(NSString *)name {
+ return [self initWithName:name row:0];
+}
+
+- (Section *) initWithName:(NSString *)name row:(size_t)row {
+ if ((self = [super init]) != nil) {
+ name_ = [name retain];
+ row_ = row;
+ } return self;
+}
+
+- (NSString *) name {
+ return name_;
+}
+
+- (size_t) row {
+ return row_;
+}
+
+- (size_t) count {
+ return count_;
+}
+
+- (void) addToCount {
+ ++count_;
+}
+
+@end
+/* }}} */
+
+static int Finish_;
+static NSArray *Finishes_;
+
+/* Database Implementation {{{ */
+@implementation Database
+
++ (Database *) sharedInstance {
+ static Database *instance;
+ if (instance == nil)
+ instance = [[Database alloc] init];
+ return instance;
+}
+
+- (void) dealloc {
+ _assert(false);
+ [super dealloc];
+}
+
+- (void) _readCydia:(NSNumber *)fd { _pooled
+ __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
+ std::istream is(&ib);
+ std::string line;
+
+ static Pcre finish_r("^finish:([^:]*)$");
+
+ while (std::getline(is, line)) {
+ const char *data(line.c_str());
+ size_t size = line.size();
+ lprintf("C:%s\n", data);
+
+ if (finish_r(data, size)) {
+ NSString *finish = finish_r[1];
+ int index = [Finishes_ indexOfObject:finish];
+ if (index != INT_MAX && index > Finish_)
+ Finish_ = index;
+ }
+ }
+
+ _assert(false);
+}
+
+- (void) _readStatus:(NSNumber *)fd { _pooled
+ __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
+ std::istream is(&ib);
+ std::string line;
+
+ static Pcre conffile_r("^status: [^ ]* : conffile-prompt : (.*?) *$");
+ static Pcre pmstatus_r("^([^:]*):([^:]*):([^:]*):(.*)$");
+
+ while (std::getline(is, line)) {
+ const char *data(line.c_str());
+ size_t size = line.size();
+ lprintf("S:%s\n", data);
+
+ if (conffile_r(data, size)) {
+ [delegate_ setConfigurationData:conffile_r[1]];
+ } else if (strncmp(data, "status: ", 8) == 0) {
+ NSString *string = [NSString stringWithUTF8String:(data + 8)];
+ [delegate_ setProgressTitle:string];
+ } else if (pmstatus_r(data, size)) {
+ std::string type([pmstatus_r[1] UTF8String]);
+ NSString *id = pmstatus_r[2];
+
+ float percent([pmstatus_r[3] floatValue]);
+ [delegate_ setProgressPercent:(percent / 100)];
+
+ NSString *string = pmstatus_r[4];
+
+ if (type == "pmerror")
+ [delegate_ performSelectorOnMainThread:@selector(_setProgressError:)
+ withObject:[NSArray arrayWithObjects:string, id, nil]
+ waitUntilDone:YES
+ ];
+ else if (type == "pmstatus")
+ [delegate_ setProgressTitle:string];
+ else if (type == "pmconffile")
+ [delegate_ setConfigurationData:string];
+ else _assert(false);
+ } else _assert(false);
+ }
+
+ _assert(false);
+}
+
+- (void) _readOutput:(NSNumber *)fd { _pooled
+ __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
+ std::istream is(&ib);
+ std::string line;
+
+ while (std::getline(is, line)) {
+ lprintf("O:%s\n", line.c_str());
+ [delegate_ addProgressOutput:[NSString stringWithUTF8String:line.c_str()]];
+ }
+
+ _assert(false);
+}
+
+- (FILE *) input {
+ return input_;
+}
+
+- (Package *) packageWithName:(NSString *)name {
+ if (static_cast<pkgDepCache *>(cache_) == NULL)
+ return nil;
+ pkgCache::PkgIterator iterator(cache_->FindPkg([name UTF8String]));
+ return iterator.end() ? nil : [Package packageWithIterator:iterator database:self];
+}
+
+- (Database *) init {
+ if ((self = [super init]) != nil) {
+ policy_ = NULL;
+ records_ = NULL;
+ resolver_ = NULL;
+ fetcher_ = NULL;
+ lock_ = NULL;
+
+ sources_ = [[NSMutableDictionary dictionaryWithCapacity:16] retain];
+ packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
+
+ int fds[2];
+
+ _assert(pipe(fds) != -1);
+ cydiafd_ = fds[1];
+
+ _config->Set("APT::Keep-Fds::", cydiafd_);
+ setenv("CYDIA", [[[[NSNumber numberWithInt:cydiafd_] stringValue] stringByAppendingString:@" 1"] UTF8String], _not(int));
+
+ [NSThread
+ detachNewThreadSelector:@selector(_readCydia:)
+ toTarget:self
+ withObject:[[NSNumber numberWithInt:fds[0]] retain]
+ ];
+
+ _assert(pipe(fds) != -1);
+ statusfd_ = fds[1];
+
+ [NSThread
+ detachNewThreadSelector:@selector(_readStatus:)
+ toTarget:self
+ withObject:[[NSNumber numberWithInt:fds[0]] retain]
+ ];
+
+ _assert(pipe(fds) != -1);
+ _assert(dup2(fds[0], 0) != -1);
+ _assert(close(fds[0]) != -1);
+
+ input_ = fdopen(fds[1], "a");
+
+ _assert(pipe(fds) != -1);
+ _assert(dup2(fds[1], 1) != -1);
+ _assert(close(fds[1]) != -1);
+
+ [NSThread
+ detachNewThreadSelector:@selector(_readOutput:)
+ toTarget:self
+ withObject:[[NSNumber numberWithInt:fds[0]] retain]
+ ];
+ } return self;
+}
+
+- (pkgCacheFile &) cache {
+ return cache_;
+}
+
+- (pkgDepCache::Policy *) policy {
+ return policy_;
+}
+
+- (pkgRecords *) records {
+ return records_;
+}
+
+- (pkgProblemResolver *) resolver {
+ return resolver_;
+}
+
+- (pkgAcquire &) fetcher {
+ return *fetcher_;
+}
+
+- (NSArray *) packages {
+ return packages_;
+}
+
+- (NSArray *) sources {
+ return [sources_ allValues];
+}
+
+- (NSArray *) issues {
+ if (cache_->BrokenCount() == 0)
+ return nil;
+
+ NSMutableArray *issues([NSMutableArray arrayWithCapacity:4]);
+
+ for (Package *package in packages_) {
+ if (![package broken])
+ continue;
+ pkgCache::PkgIterator pkg([package iterator]);
+
+ NSMutableArray *entry([NSMutableArray arrayWithCapacity:4]);
+ [entry addObject:[package name]];
+ [issues addObject:entry];
+
+ pkgCache::VerIterator ver(cache_[pkg].InstVerIter(cache_));
+ if (ver.end())
+ continue;
+
+ for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
+ pkgCache::DepIterator start;
+ pkgCache::DepIterator end;
+ dep.GlobOr(start, end); // ++dep
+
+ if (!cache_->IsImportantDep(end))
+ continue;
+ if ((cache_[end] & pkgDepCache::DepGInstall) != 0)
+ continue;
+
+ NSMutableArray *failure([NSMutableArray arrayWithCapacity:4]);
+ [entry addObject:failure];
+ [failure addObject:[NSString stringWithUTF8String:start.DepType()]];
+
+ Package *package([self packageWithName:[NSString stringWithUTF8String:start.TargetPkg().Name()]]);
+ [failure addObject:[package name]];
+
+ pkgCache::PkgIterator target(start.TargetPkg());
+ if (target->ProvidesList != 0)
+ [failure addObject:@"?"];
+ else {
+ pkgCache::VerIterator ver(cache_[target].InstVerIter(cache_));
+ if (!ver.end())
+ [failure addObject:[NSString stringWithUTF8String:ver.VerStr()]];
+ else if (!cache_[target].CandidateVerIter(cache_).end())
+ [failure addObject:@"-"];
+ else if (target->ProvidesList == 0)
+ [failure addObject:@"!"];
+ else
+ [failure addObject:@"%"];
+ }
+
+ _forever {
+ if (start.TargetVer() != 0)
+ [failure addObject:[NSString stringWithFormat:@"%s %s", start.CompType(), start.TargetVer()]];
+ if (start == end)
+ break;
+ ++start;
+ }
+ }
+ }
+
+ return issues;
+}
+
+- (void) reloadData {
+ _error->Discard();
+
+ delete list_;
+ list_ = NULL;
+ manager_ = NULL;
+ delete lock_;
+ lock_ = NULL;
+ delete fetcher_;
+ fetcher_ = NULL;
+ delete resolver_;
+ resolver_ = NULL;
+ delete records_;
+ records_ = NULL;
+ delete policy_;
+ policy_ = NULL;
+
+ cache_.Close();
+
+ if (!cache_.Open(progress_, true)) {
+ std::string error;
+ if (!_error->PopMessage(error))
+ _assert(false);
+ _error->Discard();
+ lprintf("cache_.Open():[%s]\n", error.c_str());
+
+ if (error == "dpkg was interrupted, you must manually run 'dpkg --configure -a' to correct the problem. ")
+ [delegate_ repairWithSelector:@selector(configure)];
+ else if (error == "The package lists or status file could not be parsed or opened.")
+ [delegate_ repairWithSelector:@selector(update)];
+ // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
+ // else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
+ // else if (error == "The list of sources could not be read.")
+ else _assert(false);
+
+ return;
+ }
+
+ now_ = [[NSDate date] retain];
+
+ policy_ = new pkgDepCache::Policy();
+ records_ = new pkgRecords(cache_);
+ resolver_ = new pkgProblemResolver(cache_);
+ fetcher_ = new pkgAcquire(&status_);
+ lock_ = NULL;
+
+ list_ = new pkgSourceList();
+ _assert(list_->ReadMainList());
+
+ _assert(cache_->DelCount() == 0 && cache_->InstCount() == 0);
+ _assert(pkgApplyStatus(cache_));
+
+ if (cache_->BrokenCount() != 0) {
+ _assert(pkgFixBroken(cache_));
+ _assert(cache_->BrokenCount() == 0);
+ _assert(pkgMinimizeUpgrade(cache_));
+ }
+
+ [sources_ removeAllObjects];
+ for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
+ std::vector<pkgIndexFile *> *indices = (*source)->GetIndexFiles();
+ for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
+ [sources_
+ setObject:[[[Source alloc] initWithMetaIndex:*source] autorelease]
+ forKey:[NSNumber numberWithLong:reinterpret_cast<uintptr_t>(*index)]
+ ];
+ }
+
+ [packages_ removeAllObjects];
+ for (pkgCache::PkgIterator iterator = cache_->PkgBegin(); !iterator.end(); ++iterator)
+ if (Package *package = [Package packageWithIterator:iterator database:self])
+ [packages_ addObject:package];
+
+ [packages_ sortUsingSelector:@selector(compareByName:)];
+}
+
+- (void) configure {
+ NSString *dpkg = [NSString stringWithFormat:@"dpkg --configure -a --status-fd %u", statusfd_];
+ system([dpkg UTF8String]);
+}
+
+- (void) clean {
+ if (lock_ != NULL)
+ return;
+
+ FileFd Lock;
+ Lock.Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
+ _assert(!_error->PendingError());
+
+ pkgAcquire fetcher;
+ fetcher.Clean(_config->FindDir("Dir::Cache::Archives"));
+
+ class LogCleaner :
+ public pkgArchiveCleaner
+ {
+ protected:
+ virtual void Erase(const char *File, std::string Pkg, std::string Ver, struct stat &St) {
+ unlink(File);
+ }
+ } cleaner;
+
+ if (!cleaner.Go(_config->FindDir("Dir::Cache::Archives") + "partial/", cache_)) {
+ std::string error;
+ while (_error->PopMessage(error))
+ lprintf("ArchiveCleaner: %s\n", error.c_str());
+ }
+}
+
+- (void) prepare {
+ pkgRecords records(cache_);
+
+ lock_ = new FileFd();
+ lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
+ _assert(!_error->PendingError());
+
+ pkgSourceList list;
+ // XXX: explain this with an error message
+ _assert(list.ReadMainList());
+
+ manager_ = (_system->CreatePM(cache_));
+ _assert(manager_->GetArchives(fetcher_, &list, &records));
+ _assert(!_error->PendingError());
+}
+
+- (void) perform {
+ NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
+ pkgSourceList list;
+ _assert(list.ReadMainList());
+ for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
+ [before addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
+ }
+
+ if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue) {
+ _trace();
+ return;
+ }
+
+ bool failed = false;
+ for (pkgAcquire::ItemIterator item = fetcher_->ItemsBegin(); item != fetcher_->ItemsEnd(); item++) {
+ if ((*item)->Status == pkgAcquire::Item::StatDone && (*item)->Complete)
+ continue;
+
+ std::string uri = (*item)->DescURI();
+ std::string error = (*item)->ErrorText;
+
+ lprintf("pAf:%s:%s\n", uri.c_str(), error.c_str());
+ failed = true;
+
+ [delegate_ performSelectorOnMainThread:@selector(_setProgressError:)
+ withObject:[NSArray arrayWithObjects:[NSString stringWithUTF8String:error.c_str()], nil]
+ waitUntilDone:YES
+ ];
+ }
+
+ if (failed) {
+ _trace();
+ return;
+ }
+
+ _system->UnLock();
+ pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
+
+ if (_error->PendingError()) {
+ _trace();
+ return;
+ }
+
+ if (result == pkgPackageManager::Failed) {
+ _trace();
+ return;
+ }
+
+ if (result != pkgPackageManager::Completed) {
+ _trace();
+ return;
+ }
+
+ NSMutableArray *after = [NSMutableArray arrayWithCapacity:16]; {
+ pkgSourceList list;
+ _assert(list.ReadMainList());
+ for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
+ [after addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
+ }
+
+ if (![before isEqualToArray:after])
+ [self update];
+}
+
+- (void) upgrade {
+ _assert(pkgDistUpgrade(cache_));
+}
+
+- (void) update {
+ [self updateWithStatus:status_];
+}
+
+- (void) updateWithStatus:(Status &)status {
+ pkgSourceList list;
+ _assert(list.ReadMainList());
+
+ FileFd lock;
+ lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
+ _assert(!_error->PendingError());
+
+ pkgAcquire fetcher(&status);
+ _assert(list.GetIndexes(&fetcher));
+
+ if (fetcher.Run(PulseInterval_) != pkgAcquire::Failed) {
+ bool failed = false;
+ for (pkgAcquire::ItemIterator item = fetcher.ItemsBegin(); item != fetcher.ItemsEnd(); item++)
+ if ((*item)->Status != pkgAcquire::Item::StatDone) {
+ (*item)->Finished();
+ failed = true;
+ }
+
+ if (!failed && _config->FindB("APT::Get::List-Cleanup", true) == true) {
+ _assert(fetcher.Clean(_config->FindDir("Dir::State::lists")));
+ _assert(fetcher.Clean(_config->FindDir("Dir::State::lists") + "partial/"));
+ }
+
+ [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"];
+ Changed_ = true;
+ }
+}
+
+- (void) setDelegate:(id)delegate {
+ delegate_ = delegate;
+ status_.setDelegate(delegate);
+ progress_.setDelegate(delegate);
+}
+
+- (Source *) getSource:(const pkgCache::PkgFileIterator &)file {
+ pkgIndexFile *index(NULL);
+ list_->FindIndex(file, index);
+ return [sources_ objectForKey:[NSNumber numberWithLong:reinterpret_cast<uintptr_t>(index)]];
+}
+
+@end
+/* }}} */
+
+/* PopUp Windows {{{ */
+@interface PopUpView : UIView {
+ _transient id delegate_;
+ UITransitionView *transition_;
+ UIView *overlay_;
+}
+
+- (void) cancel;
+- (id) initWithView:(UIView *)view delegate:(id)delegate;
+
+@end
+
+@implementation PopUpView
+
+- (void) dealloc {
+ [transition_ setDelegate:nil];
+ [transition_ release];
+ [overlay_ release];
+ [super dealloc];
+}
+
+- (void) cancel {
+ [transition_ transition:UITransitionPushFromTop toView:nil];
+}
+
+- (void) transitionViewDidComplete:(UITransitionView*)view fromView:(UIView*)from toView:(UIView*)to {
+ if (from != nil && to == nil)
+ [self removeFromSuperview];
+}
+
+- (id) initWithView:(UIView *)view delegate:(id)delegate {
+ if ((self = [super initWithFrame:[view bounds]]) != nil) {
+ delegate_ = delegate;
+
+ transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
+ [self addSubview:transition_];
+
+ overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]];
+
+ [view addSubview:self];
+
+ [transition_ setDelegate:self];
+
+ UIView *blank = [[[UIView alloc] initWithFrame:[transition_ bounds]] autorelease];
+ [transition_ transition:UITransitionNone toView:blank];
+ [transition_ transition:UITransitionPushFromBottom toView:overlay_];
+ } return self;
+}
+
+@end
+/* }}} */
+
+/* Mail Composition {{{ */
+@interface MailToView : PopUpView {
+ MailComposeController *controller_;
+}
+
+- (id) initWithView:(UIView *)view delegate:(id)delegate url:(NSURL *)url;
+
+@end
+
+@implementation MailToView
+
+- (void) dealloc {
+ [controller_ release];
+ [super dealloc];
+}
+
+- (void) mailComposeControllerWillAttemptToSend:(MailComposeController *)controller {
+ NSLog(@"will");
+}
+
+- (void) mailComposeControllerDidAttemptToSend:(MailComposeController *)controller mailDelivery:(id)delivery {
+ NSLog(@"did:%@", delivery);
+// [UIApp setStatusBarShowsProgress:NO];
+if ([controller error]){
+NSArray *buttons = [NSArray arrayWithObjects:@"OK", nil];
+UIActionSheet *mailAlertSheet = [[UIActionSheet alloc] initWithTitle:@"Error" buttons:buttons defaultButtonIndex:0 delegate:self context:self];
+[mailAlertSheet setBodyText:[controller error]];
+[mailAlertSheet popupAlertAnimated:YES];
+}
+}
+
+- (void) showError {
+ NSLog(@"%@", [controller_ error]);
+ NSArray *buttons = [NSArray arrayWithObjects:@"OK", nil];
+ UIActionSheet *mailAlertSheet = [[UIActionSheet alloc] initWithTitle:@"Error" buttons:buttons defaultButtonIndex:0 delegate:self context:self];
+ [mailAlertSheet setBodyText:[controller_ error]];
+ [mailAlertSheet popupAlertAnimated:YES];
+}
+
+- (void) deliverMessage { _pooled
+ setuid(501);
+ setgid(501);
+
+ if (![controller_ deliverMessage])
+ [self performSelectorOnMainThread:@selector(showError) withObject:nil waitUntilDone:NO];
+}
+
+- (void) mailComposeControllerCompositionFinished:(MailComposeController *)controller {
+ if ([controller_ needsDelivery])
+ [NSThread detachNewThreadSelector:@selector(deliverMessage) toTarget:self withObject:nil];
+ else
+ [self cancel];
+}
+
+- (id) initWithView:(UIView *)view delegate:(id)delegate url:(NSURL *)url {
+ if ((self = [super initWithView:view delegate:delegate]) != nil) {
+ controller_ = [[MailComposeController alloc] initForContentSize:[overlay_ bounds].size];
+ [controller_ setDelegate:self];
+ [controller_ initializeUI];
+ [controller_ setupForURL:url];
+
+ UIView *view([controller_ view]);
+ [overlay_ addSubview:view];
+ } return self;
+}
+
+@end
+/* }}} */
+/* Confirmation View {{{ */
+bool DepSubstrate(const pkgCache::VerIterator &iterator) {
+ if (!iterator.end())
+ for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) {
+ if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends)
+ continue;
+ pkgCache::PkgIterator package(dep.TargetPkg());
+ if (package.end())
+ continue;
+ if (strcmp(package.Name(), "mobilesubstrate") == 0)
+ return true;
+ }
+
+ return false;
+}
+
+@protocol ConfirmationViewDelegate
+- (void) cancel;
+- (void) confirm;
+@end
+
+@interface ConfirmationView : BrowserView {
+ _transient Database *database_;
+ UIActionSheet *essential_;
+ NSArray *changes_;
+ NSArray *issues_;
+ NSArray *sizes_;
+ BOOL substrate_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+
+@end
+
+@implementation ConfirmationView
+
+- (void) dealloc {
+ [changes_ release];
+ if (issues_ != nil)
+ [issues_ release];
+ [sizes_ release];
+ if (essential_ != nil)
+ [essential_ release];
+ [super dealloc];
+}
+
+- (void) cancel {
+ [delegate_ cancel];
+ [book_ popFromSuperviewAnimated:YES];
+}
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ NSString *context = [sheet context];
+
+ if ([context isEqualToString:@"remove"])
+ switch (button) {
+ case 1:
+ [self cancel];
+ break;
+ case 2:
+ if (substrate_)
+ Finish_ = 2;
+ [delegate_ confirm];
+ break;
+ default:
+ _assert(false);
+ }
+ else if ([context isEqualToString:@"unable"])
+ [self cancel];
+
+ [sheet dismiss];
+}
+
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+ [window setValue:changes_ forKey:@"changes"];
+ [window setValue:issues_ forKey:@"issues"];
+ [window setValue:sizes_ forKey:@"sizes"];
+ [super webView:sender didClearWindowObject:window forFrame:frame];
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+
+ NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *downgrading = [NSMutableArray arrayWithCapacity:16];
+ NSMutableArray *removing = [NSMutableArray arrayWithCapacity:16];
+
+ bool remove(false);
+
+ pkgDepCache::Policy *policy([database_ policy]);
+
+ pkgCacheFile &cache([database_ cache]);
+ NSArray *packages = [database_ packages];
+ for (size_t i(0), e = [packages count]; i != e; ++i) {
+ Package *package = [packages objectAtIndex:i];
+ pkgCache::PkgIterator iterator = [package iterator];
+ pkgDepCache::StateCache &state(cache[iterator]);
+
+ NSString *name([package name]);
+
+ if (state.NewInstall())
+ [installing addObject:name];
+ else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
+ [reinstalling addObject:name];
+ else if (state.Upgrade())
+ [upgrading addObject:name];
+ else if (state.Downgrade())
+ [downgrading addObject:name];
+ else if (state.Delete()) {
+ if ([package essential])
+ remove = true;
+ [removing addObject:name];
+ } else continue;
+
+ substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
+ substrate_ |= DepSubstrate(iterator.CurrentVer());
+ }
+
+ if (!remove)
+ essential_ = nil;
+ else if (Advanced_ || true) {
+ essential_ = [[UIActionSheet alloc]
+ initWithTitle:@"Removing Essentials"
+ buttons:[NSArray arrayWithObjects:
+ @"Cancel Operation (Safe)",
+ @"Force Removal (Unsafe)",
+ nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"remove"
+ ];
+
+#ifndef __OBJC2__
+ [essential_ setDestructiveButton:[[essential_ buttons] objectAtIndex:0]];
+#endif
+ [essential_ setBodyText:@"This operation involves the removal of one or more packages that are required for the continued operation of either Cydia or iPhoneOS. If you continue, you may not be able to use Cydia to repair any damage."];
+ } else {
+ essential_ = [[UIActionSheet alloc]
+ initWithTitle:@"Unable to Comply"
+ buttons:[NSArray arrayWithObjects:@"Okay", nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"unable"
+ ];
+
+ [essential_ setBodyText:@"This operation requires the removal of one or more packages that are required for the continued operation of either Cydia or iPhoneOS. In order to continue and force this operation you will need to be activate the Advanced mode under to continue and force this operation you will need to be activate the Advanced mode under Settings."];
+ }
+
+ changes_ = [[NSArray alloc] initWithObjects:
+ installing,
+ reinstalling,
+ upgrading,
+ downgrading,
+ removing,
+ nil];
+
+ issues_ = [database_ issues];
+ if (issues_ != nil)
+ issues_ = [issues_ retain];
+
+ sizes_ = [[NSArray alloc] initWithObjects:
+ SizeString([database_ fetcher].FetchNeeded()),
+ SizeString([database_ fetcher].PartialPresent()),
+ SizeString([database_ cache]->UsrSize()),
+ nil];
+
+ [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"confirm" ofType:@"html"]]];
+ } return self;
+}
+
+- (NSString *) backButtonTitle {
+ return @"Confirm";
+}
+
+- (NSString *) leftButtonTitle {
+ return @"Cancel";
+}
+
+- (NSString *) _rightButtonTitle {
+ return issues_ == nil ? @"Confirm" : nil;
+}
+
+- (void) _leftButtonClicked {
+ [self cancel];
+}
+
+- (void) _rightButtonClicked {
+ if (essential_ != nil)
+ [essential_ popupAlertAnimated:YES];
+ else {
+ if (substrate_)
+ Finish_ = 2;
+ [delegate_ confirm];
+ }
+}
+
+@end
+/* }}} */
+
+/* Progress Data {{{ */
+@interface ProgressData : NSObject {
+ SEL selector_;
+ id target_;
+ id object_;
+}
+
+- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
+
+- (SEL) selector;
+- (id) target;
+- (id) object;
+@end
+
+@implementation ProgressData
+
+- (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
+ if ((self = [super init]) != nil) {
+ selector_ = selector;
+ target_ = target;
+ object_ = object;
+ } return self;
+}
+
+- (SEL) selector {
+ return selector_;
+}
+
+- (id) target {
+ return target_;
+}
+
+- (id) object {
+ return object_;
+}
+
+@end
+/* }}} */
+/* Progress View {{{ */
+@interface ProgressView : UIView <
+ ConfigurationDelegate,
+ ProgressDelegate
+> {
+ _transient Database *database_;
+ UIView *view_;
+ UIView *background_;
+ UITransitionView *transition_;
+ UIView *overlay_;
+ UINavigationBar *navbar_;
+ UIProgressBar *progress_;
+ UITextView *output_;
+ UITextLabel *status_;
+ UIPushButton *close_;
+ id delegate_;
+ BOOL running_;
+ SHA1SumValue springlist_;
+ SHA1SumValue sandplate_;
+ size_t received_;
+ NSTimeInterval last_;
+}
+
+- (void) transitionViewDidComplete:(UITransitionView*)view fromView:(UIView*)from toView:(UIView*)to;
+
+- (id) initWithFrame:(struct CGRect)frame database:(Database *)database delegate:(id)delegate;
+- (void) setContentView:(UIView *)view;
+- (void) resetView;
+
+- (void) _retachThread;
+- (void) _detachNewThreadData:(ProgressData *)data;
+- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title;
+
+- (BOOL) isRunning;
+
+@end
+
+@protocol ProgressViewDelegate
+- (void) progressViewIsComplete:(ProgressView *)sender;
+@end
+
+@implementation ProgressView
+
+- (void) dealloc {
+ [transition_ setDelegate:nil];
+ [navbar_ setDelegate:nil];
+
+ [view_ release];
+ if (background_ != nil)
+ [background_ release];
+ [transition_ release];
+ [overlay_ release];
+ [navbar_ release];
+ [progress_ release];
+ [output_ release];
+ [status_ release];
+ [close_ release];
+ [super dealloc];
+}
+
+- (void) transitionViewDidComplete:(UITransitionView*)view fromView:(UIView*)from toView:(UIView*)to {
+ if (bootstrap_ && from == overlay_ && to == view_)
+ exit(0);
+}
+
+- (id) initWithFrame:(struct CGRect)frame database:(Database *)database delegate:(id)delegate {
+ if ((self = [super initWithFrame:frame]) != nil) {
+ database_ = database;
+ delegate_ = delegate;
+
+ transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
+ [transition_ setDelegate:self];
+
+ overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]];
+
+ if (bootstrap_)
+ [overlay_ setBackgroundColor:[UIColor blackColor]];
+ else {
+ background_ = [[UIView alloc] initWithFrame:[self bounds]];
+ [background_ setBackgroundColor:[UIColor blackColor]];
+ [self addSubview:background_];
+ }
+
+ [self addSubview:transition_];
+
+ CGSize navsize = [UINavigationBar defaultSize];
+ CGRect navrect = {{0, 0}, navsize};
+
+ navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
+ [overlay_ addSubview:navbar_];
+
+ [navbar_ setBarStyle:1];
+ [navbar_ setDelegate:self];
+
+ UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:nil] autorelease];
+ [navbar_ pushNavigationItem:navitem];
+
+ CGRect bounds = [overlay_ bounds];
+ CGSize prgsize = [UIProgressBar defaultSize];
+
+ CGRect prgrect = {{
+ (bounds.size.width - prgsize.width) / 2,
+ bounds.size.height - prgsize.height - 20
+ }, prgsize};
+
+ progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
+ [progress_ setStyle:0];
+
+ status_ = [[UITextLabel alloc] initWithFrame:CGRectMake(
+ 10,
+ bounds.size.height - prgsize.height - 50,
+ bounds.size.width - 20,
+ 24
+ )];
+
+ [status_ setColor:[UIColor whiteColor]];
+ [status_ setBackgroundColor:[UIColor clearColor]];
+
+ [status_ setCentersHorizontally:YES];
+ //[status_ setFont:font];
+
+ output_ = [[UITextView alloc] initWithFrame:CGRectMake(
+ 10,
+ navrect.size.height + 20,
+ bounds.size.width - 20,
+ bounds.size.height - navsize.height - 62 - navrect.size.height
+ )];
+
+ //[output_ setTextFont:@"Courier New"];
+ [output_ setTextSize:12];
+
+ [output_ setTextColor:[UIColor whiteColor]];
+ [output_ setBackgroundColor:[UIColor clearColor]];
+
+ [output_ setMarginTop:0];
+ [output_ setAllowsRubberBanding:YES];
+ [output_ setEditable:NO];
+
+ [overlay_ addSubview:output_];
+
+ close_ = [[UIPushButton alloc] initWithFrame:CGRectMake(
+ 10,
+ bounds.size.height - prgsize.height - 50,
+ bounds.size.width - 20,
+ 32 + prgsize.height
+ )];
+
+ [close_ setAutosizesToFit:NO];
+ [close_ setDrawsShadow:YES];
+ [close_ setStretchBackground:YES];
+ [close_ setEnabled:YES];
+
+ UIFont *bold = [UIFont boldSystemFontOfSize:22];
+ [close_ setTitleFont:bold];
+
+ [close_ addTarget:self action:@selector(closeButtonPushed) forEvents:kUIControlEventMouseUpInside];
+ [close_ setBackground:[UIImage applicationImageNamed:@"green-up.png"] forState:0];
+ [close_ setBackground:[UIImage applicationImageNamed:@"green-dn.png"] forState:1];
+ } return self;
+}
+
+- (void) setContentView:(UIView *)view {
+ view_ = [view retain];
+}
+
+- (void) resetView {
+ [transition_ transition:6 toView:view_];
+}
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ NSString *context = [sheet context];
+ if ([context isEqualToString:@"conffile"]) {
+ FILE *input = [database_ input];
+
+ switch (button) {
+ case 1:
+ fprintf(input, "N\n");
+ fflush(input);
+ break;
+ case 2:
+ fprintf(input, "Y\n");
+ fflush(input);
+ break;
+ default:
+ _assert(false);
+ }
+ }
+
+ [sheet dismiss];
+}
+
+- (void) closeButtonPushed {
+ running_ = NO;
+
+ switch (Finish_) {
+ case 0:
+ [self resetView];
+ break;
+
+ case 1:
+ [delegate_ suspendWithAnimation:YES];
+ break;
+
+ case 2:
+ system("launchctl stop com.apple.SpringBoard");
+ break;
+
+ case 3:
+ system("launchctl unload "SpringBoard_"; launchctl load "SpringBoard_);
+ break;
+
+ case 4:
+ system("reboot");
+ break;
+ }
+}
+
+- (void) _retachThread {
+ UINavigationItem *item = [navbar_ topItem];
+ [item setTitle:@"Complete"];
+
+ [overlay_ addSubview:close_];
+ [progress_ removeFromSuperview];
+ [status_ removeFromSuperview];
+
+ [delegate_ progressViewIsComplete:self];
+
+ if (Finish_ < 4) {
+ FileFd file(SandboxTemplate_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ if (!(sandplate_ == sha1.Result()))
+ Finish_ = 4;
+ }
+
+ if (Finish_ < 3) {
+ FileFd file(SpringBoard_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ if (!(springlist_ == sha1.Result()))
+ Finish_ = 3;
+ }
+
+ switch (Finish_) {
+ case 0: [close_ setTitle:@"Return to Cydia"]; break;
+ case 1: [close_ setTitle:@"Close Cydia (Restart)"]; break;
+ case 2: [close_ setTitle:@"Restart SpringBoard"]; break;
+ case 3: [close_ setTitle:@"Reload SpringBoard"]; break;
+ case 4: [close_ setTitle:@"Reboot Device"]; break;
+ }
+
+#define Cache_ "/User/Library/Caches/com.apple.mobile.installation.plist"
+
+ if (NSMutableDictionary *cache = [[NSMutableDictionary alloc] initWithContentsOfFile:@ Cache_]) {
+ [cache autorelease];
+
+ NSFileManager *manager = [NSFileManager defaultManager];
+ NSError *error = nil;
+
+ id system = [cache objectForKey:@"System"];
+ if (system == nil)
+ goto error;
+
+ struct stat info;
+ if (stat(Cache_, &info) == -1)
+ goto error;
+
+ [system removeAllObjects];
+
+ if (NSArray *apps = [manager contentsOfDirectoryAtPath:@"/Applications" error:&error]) {
+ for (NSString *app in apps)
+ if ([app hasSuffix:@".app"]) {
+ NSString *path = [@"/Applications" stringByAppendingPathComponent:app];
+ NSString *plist = [path stringByAppendingPathComponent:@"Info.plist"];
+ if (NSMutableDictionary *info = [[NSMutableDictionary alloc] initWithContentsOfFile:plist]) {
+ [info autorelease];
+ [info setObject:path forKey:@"Path"];
+ [info setObject:@"System" forKey:@"ApplicationType"];
+ [system addInfoDictionary:info];
+ }
+ }
+ } else goto error;
+
+ [cache writeToFile:@Cache_ atomically:YES];
+
+ if (chown(Cache_, info.st_uid, info.st_gid) == -1)
+ goto error;
+ if (chmod(Cache_, info.st_mode) == -1)
+ goto error;
+
+ if (false) error:
+ lprintf("%s\n", error == nil ? strerror(errno) : [[error localizedDescription] UTF8String]);
+ }
+
+ notify_post("com.apple.mobile.application_installed");
+
+ [delegate_ setStatusBarShowsProgress:NO];
+}
+
+- (void) _detachNewThreadData:(ProgressData *)data { _pooled
+ [[data target] performSelector:[data selector] withObject:[data object]];
+ [data release];
+
+ [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES];
+}
+
+- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title {
+ UINavigationItem *item = [navbar_ topItem];
+ [item setTitle:title];
+
+ [status_ setText:nil];
+ [output_ setText:@""];
+ [progress_ setProgress:0];
+
+ received_ = 0;
+ last_ = 0;//[NSDate timeIntervalSinceReferenceDate];
+
+ [close_ removeFromSuperview];
+ [overlay_ addSubview:progress_];
+ [overlay_ addSubview:status_];
+
+ [delegate_ setStatusBarShowsProgress:YES];
+ running_ = YES;
+
+ {
+ FileFd file(SandboxTemplate_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ sandplate_ = sha1.Result();
+ }
+
+ {
+ FileFd file(SpringBoard_, FileFd::ReadOnly);
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ springlist_ = sha1.Result();
+ }
+
+ [transition_ transition:6 toView:overlay_];
+
+ [NSThread
+ detachNewThreadSelector:@selector(_detachNewThreadData:)
+ toTarget:self
+ withObject:[[ProgressData alloc]
+ initWithSelector:selector
+ target:target
+ object:object
+ ]
+ ];
+}
+
+- (void) repairWithSelector:(SEL)selector {
+ [self
+ detachNewThreadSelector:selector
+ toTarget:database_
+ withObject:nil
+ title:@"Repairing"
+ ];
+}
+
+- (void) setConfigurationData:(NSString *)data {
+ [self
+ performSelectorOnMainThread:@selector(_setConfigurationData:)
+ withObject:data
+ waitUntilDone:YES
+ ];
+}
+
+- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
+ Package *package = id == nil ? nil : [database_ packageWithName:id];
+
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:(package == nil ? id : [package name])
+ buttons:[NSArray arrayWithObjects:@"Okay", nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"error"
+ ] autorelease];
+
+ [sheet setBodyText:error];
+ [sheet popupAlertAnimated:YES];
+}
+
+- (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 {
+ last_ = [NSDate timeIntervalSinceReferenceDate];
+}
+
+- (void) addProgressOutput:(NSString *)output {
+ [self
+ performSelectorOnMainThread:@selector(_addProgressOutput:)
+ withObject:output
+ waitUntilDone:YES
+ ];
+}
+
+- (bool) isCancelling:(size_t)received {
+ if (last_ != 0) {
+ NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
+ if (received_ != received) {
+ received_ = received;
+ last_ = now;
+ } else if (now - last_ > 30)
+ return true;
+ }
+
+ return false;
+}
+
+- (void) _setConfigurationData:(NSString *)data {
+ static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
+
+ _assert(conffile_r(data));
+
+ NSString *ofile = conffile_r[1];
+ //NSString *nfile = conffile_r[2];
+
+ UIActionSheet *sheet = [[[UIActionSheet alloc]
+ initWithTitle:@"Configuration Upgrade"
+ buttons:[NSArray arrayWithObjects:
+ @"Keep My Old Copy",
+ @"Accept The New Copy",
+ // XXX: @"See What Changed",
+ nil]
+ defaultButtonIndex:0
+ delegate:self
+ context:@"conffile"
+ ] autorelease];
+
+ [sheet setBodyText:[NSString stringWithFormat:
+ @"The following file has been changed by both the package maintainer and by you (or for you by a script).\n\n%@"
+ , ofile]];
+
+ [sheet popupAlertAnimated:YES];
+}
+
+- (void) _setProgressTitle:(NSString *)title {
+ [status_ setText:title];
+}
+
+- (void) _setProgressPercent:(NSNumber *)percent {
+ [progress_ setProgress:[percent floatValue]];
+}
+
+- (void) _addProgressOutput:(NSString *)output {
+ [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]];
+ CGSize size = [output_ contentSize];
+ CGRect rect = {{0, size.height}, {size.width, 0}};
+ [output_ scrollRectToVisible:rect animated:YES];
+}
+
+- (BOOL) isRunning {
+ return running_;
+}
+
+@end
+/* }}} */
+
+/* Package Cell {{{ */
+@interface PackageCell : UISimpleTableCell {
+ UIImage *icon_;
+ NSString *name_;
+ NSString *description_;
+ NSString *source_;
+ UIImage *badge_;
+#ifdef USE_BADGES
+ UITextLabel *status_;
+#endif
+}
+
+- (PackageCell *) init;
+- (void) setPackage:(Package *)package;
+
++ (int) heightForPackage:(Package *)package;
+
+@end
+
+@implementation PackageCell
+
+- (void) clearPackage {
+ if (icon_ != nil) {
+ [icon_ release];
+ icon_ = nil;
+ }
+
+ if (name_ != nil) {
+ [name_ release];
+ name_ = nil;
+ }
+
+ if (description_ != nil) {
+ [description_ release];
+ description_ = nil;
+ }
+
+ if (source_ != nil) {
+ [source_ release];
+ source_ = nil;
+ }
+
+ if (badge_ != nil) {
+ [badge_ release];
+ badge_ = nil;
+ }
+}
+
+- (void) dealloc {
+ [self clearPackage];
+#ifdef USE_BADGES
+ [status_ release];
+#endif
+ [super dealloc];
+}
+
+- (PackageCell *) init {
+ if ((self = [super init]) != nil) {
+#ifdef USE_BADGES
+ status_ = [[UITextLabel alloc] initWithFrame:CGRectMake(48, 68, 280, 20)];
+ [status_ setBackgroundColor:[UIColor clearColor]];
+ [status_ setFont:small];
+#endif
+ } return self;
+}
+
+- (void) setPackage:(Package *)package {
+ [self clearPackage];
+
+ Source *source = [package source];
+ NSString *section = [package section];
+ if (section != nil)
+ section = Simplify(section);
+
+ icon_ = [[package icon] retain];
+
+ name_ = [[package name] retain];
+ description_ = [[package tagline] retain];
+
+ NSString *label = nil;
+ bool trusted = false;
+
+ if (source != nil) {
+ label = [source label];
+ trusted = [source trusted];
+ } else if ([[package id] isEqualToString:@"firmware"])
+ label = @"Apple";
+ else
+ label = @"Unknown/Local";
+
+ NSString *from = [NSString stringWithFormat:@"from %@", label];
+
+ if (section != nil && ![section isEqualToString:label])
+ from = [from stringByAppendingString:[NSString stringWithFormat:@" (%@)", section]];
+
+ source_ = [from retain];
+
+ if (NSString *purpose = [package primaryPurpose])
+ if ((badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]) != nil)
+ badge_ = [badge_ retain];
+
+#ifdef USE_BADGES
+ if (NSString *mode = [package mode]) {
+ [badge_ setImage:[UIImage applicationImageNamed:
+ [mode isEqualToString:@"Remove"] || [mode isEqualToString:@"Purge"] ? @"removing.png" : @"installing.png"
+ ]];
+
+ [status_ setText:[NSString stringWithFormat:@"Queued for %@", mode]];
+ [status_ setColor:[UIColor colorWithCGColor:Blueish_]];
+ } else if ([package half]) {
+ [badge_ setImage:[UIImage applicationImageNamed:@"damaged.png"]];
+ [status_ setText:@"Package Damaged"];
+ [status_ setColor:[UIColor redColor]];
+ } else {
+ [badge_ setImage:nil];
+ [status_ setText:nil];
+ }
+#endif
+}
+
+- (void) drawContentInRect:(CGRect)rect selected:(BOOL)selected {
+ if (icon_ != nil) {
+ CGRect rect;
+ rect.size = [icon_ size];
+
+ rect.size.width /= 2;
+ rect.size.height /= 2;
+
+ rect.origin.x = 25 - rect.size.width / 2;
+ rect.origin.y = 25 - rect.size.height / 2;
+
+ [icon_ drawInRect:rect];
+ }
+
+ if (badge_ != nil) {
+ CGSize size = [badge_ size];
+
+ [badge_ drawAtPoint:CGPointMake(
+ 36 - size.width / 2,
+ 36 - size.height / 2
+ )];
+ }
+
+ if (selected)
+ UISetColor(White_);
+
+ if (!selected)
+ UISetColor(Black_);
+ [name_ drawAtPoint:CGPointMake(48, 8) forWidth:240 withFont:Font18Bold_ ellipsis:2];
+ [source_ drawAtPoint:CGPointMake(58, 29) forWidth:225 withFont:Font12_ ellipsis:2];
+
+ if (!selected)
+ UISetColor(Gray_);
+ [description_ drawAtPoint:CGPointMake(12, 46) forWidth:280 withFont:Font14_ ellipsis:2];
+
+ [super drawContentInRect:rect selected:selected];
+}
+
++ (int) heightForPackage:(Package *)package {
+ NSString *tagline([package tagline]);
+ int height = tagline == nil || [tagline length] == 0 ? -17 : 0;
+#ifdef USE_BADGES
+ if ([package hasMode] || [package half])
+ return height + 96;
+ else
+#endif
+ return height + 73;
+}
+
+@end
+/* }}} */
+/* Section Cell {{{ */
+@interface SectionCell : UISimpleTableCell {
+ NSString *section_;
+ NSString *name_;
+ NSString *count_;
+ UIImage *icon_;
+ _UISwitchSlider *switch_;
+ BOOL editing_;
+}
+
+- (id) init;
+- (void) setSection:(Section *)section editing:(BOOL)editing;
+
+@end
+
+@implementation SectionCell
+
+- (void) clearSection {
+ if (section_ != nil) {
+ [section_ release];
+ section_ = nil;
+ }
+
+ if (name_ != nil) {
+ [name_ release];
+ name_ = nil;
+ }
+
+ if (count_ != nil) {
+ [count_ release];
+ count_ = nil;
+ }
+}
+
+- (void) dealloc {
+ [self clearSection];
+ [icon_ release];
+ [switch_ release];
+ [super dealloc];
+}
+
+- (id) init {
+ if ((self = [super init]) != nil) {
+ icon_ = [[UIImage applicationImageNamed:@"folder.png"] retain];
+
+ switch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(218, 9, 60, 25)];
+ [switch_ addTarget:self action:@selector(onSwitch:) forEvents:kUIControlEventMouseUpInside];
+ } return self;
+}
+
+- (void) onSwitch:(id)sender {
+ NSMutableDictionary *metadata = [Sections_ objectForKey:section_];
+ if (metadata == nil) {
+ metadata = [NSMutableDictionary dictionaryWithCapacity:2];
+ [Sections_ setObject:metadata forKey:section_];
+ }
+
+ Changed_ = true;
+ [metadata setObject:[NSNumber numberWithBool:([switch_ value] == 0)] forKey:@"Hidden"];
+}
+
+- (void) setSection:(Section *)section editing:(BOOL)editing {
+ if (editing != editing_) {
+ if (editing_)
+ [switch_ removeFromSuperview];
+ else
+ [self addSubview:switch_];
+ editing_ = editing;
+ }
+
+ [self clearSection];
+
+ if (section == nil) {
+ name_ = [@"All Packages" retain];
+ count_ = nil;
+ } else {
+ section_ = [section name];
+ if (section_ != nil)
+ section_ = [section_ retain];
+ name_ = [(section_ == nil ? @"(No Section)" : section_) retain];
+ count_ = [[NSString stringWithFormat:@"%d", [section count]] retain];
+
+ if (editing_)
+ [switch_ setValue:(isSectionVisible(section_) ? 1 : 0) animated:NO];
+ }
+}
+
+- (void) drawContentInRect:(CGRect)rect selected:(BOOL)selected {
+ [icon_ drawInRect:CGRectMake(8, 7, 32, 32)];
+
+ if (selected)
+ UISetColor(White_);
+
+ if (!selected)
+ UISetColor(Black_);
+ [name_ drawAtPoint:CGPointMake(48, 9) forWidth:(editing_ ? 164 : 250) withFont:Font22Bold_ ellipsis:2];
+
+ CGSize size = [count_ sizeWithFont:Font14_];
+
+ UISetColor(White_);
+ if (count_ != nil)
+ [count_ drawAtPoint:CGPointMake(13 + (29 - size.width) / 2, 16) withFont:Font12Bold_];
+
+ [super drawContentInRect:rect selected:selected];
+}
+
+@end
+/* }}} */
+
+/* File Table {{{ */
+@interface FileTable : RVPage {
+ _transient Database *database_;
+ Package *package_;
+ NSString *name_;
+ NSMutableArray *files_;
+ UITable *list_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+- (void) setPackage:(Package *)package;
+
+@end
+
+@implementation FileTable
+
+- (void) dealloc {
+ if (package_ != nil)
+ [package_ release];
+ if (name_ != nil)
+ [name_ release];
+ [files_ release];
+ [list_ release];
+ [super dealloc];
+}
+
+- (int) numberOfRowsInTable:(UITable *)table {
+ return files_ == nil ? 0 : [files_ count];
+}
+
+- (float) table:(UITable *)table heightForRow:(int)row {
+ return 24;
+}
+
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
+ if (reusing == nil) {
+ reusing = [[[UIImageAndTextTableCell alloc] init] autorelease];
+ UIFont *font = [UIFont systemFontOfSize:16];
+ [[(UIImageAndTextTableCell *)reusing titleTextLabel] setFont:font];
+ }
+ [(UIImageAndTextTableCell *)reusing setTitle:[files_ objectAtIndex:row]];
+ return reusing;
+}
+
+- (BOOL) table:(UITable *)table canSelectRow:(int)row {
+ return NO;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+
+ files_ = [[NSMutableArray arrayWithCapacity:32] retain];
+
+ list_ = [[UITable alloc] initWithFrame:[self bounds]];
+ [self addSubview: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];
+ } return self;
+}
+
+- (void) setPackage:(Package *)package {
+ if (package_ != nil) {
+ [package_ autorelease];
+ package_ = nil;
+ }
+
+ if (name_ != nil) {
+ [name_ release];
+ name_ = nil;
+ }
+
+ [files_ removeAllObjects];
+
+ if (package != nil) {
+ package_ = [package retain];
+ name_ = [[package id] retain];
+
+ if (NSArray *files = [package files])
+ [files_ addObjectsFromArray:files];
+
+ if ([files_ count] != 0) {
+ if ([[files_ objectAtIndex:0] isEqualToString:@"/."])
+ [files_ removeObjectAtIndex:0];
+ [files_ sortUsingSelector:@selector(compareByPath:)];
+
+ NSMutableArray *stack = [NSMutableArray arrayWithCapacity:8];
+ [stack addObject:@"/"];
+
+ for (int i(0), e([files_ count]); i != e; ++i) {
+ NSString *file = [files_ objectAtIndex:i];
+ while (![file hasPrefix:[stack lastObject]])
+ [stack removeLastObject];
+ NSString *directory = [stack lastObject];
+ [stack addObject:[file stringByAppendingString:@"/"]];
+ [files_ replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"%*s%@",
+ ([stack count] - 2) * 3, "",
+ [file substringFromIndex:[directory length]]
+ ]];
+ }
+ }
+ }
+
+ [list_ reloadData];
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [list_ resetViewAnimated:animated];
+}
+
+- (void) reloadData {
+ [self setPackage:[database_ packageWithName:name_]];
+ [self reloadButtons];
+}
+
+- (NSString *) title {
+ return @"Installed Files";
+}
+
+- (NSString *) backButtonTitle {
+ return @"Files";
+}
+
+@end
+/* }}} */
+/* Package View {{{ */
+@interface PackageView : BrowserView {
+ _transient Database *database_;
+ Package *package_;
+ NSString *name_;
+ NSMutableArray *buttons_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+- (void) setPackage:(Package *)package;
+
+@end
+
+@implementation PackageView
+
+- (void) dealloc {
+ if (package_ != nil)
+ [package_ release];
+ if (name_ != nil)
+ [name_ release];
+ [buttons_ release];
+ [super dealloc];
+}
+
+- (void) _clickButtonWithName:(NSString *)name {
+ if ([name isEqualToString:@"Install"])
+ [delegate_ installPackage:package_];
+ else if ([name isEqualToString:@"Reinstall"])
+ [delegate_ installPackage:package_];
+ else if ([name isEqualToString:@"Remove"])
+ [delegate_ removePackage:package_];
+ else if ([name isEqualToString:@"Upgrade"])
+ [delegate_ installPackage:package_];
+ else _assert(false);
+}
+
+- (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
+ int count = [buttons_ count];
+ _assert(count != 0);
+ _assert(button <= count + 1);
+
+ if (count != button - 1)
+ [self _clickButtonWithName:[buttons_ objectAtIndex:(button - 1)]];
+
+ [sheet dismiss];
+}
+
+- (void) webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
+ [[frame windowObject] evaluateWebScript:@"document.base.target = '_top'"];
+ return [super webView:sender didFinishLoadForFrame:frame];
+}
+
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+ [window setValue:package_ forKey:@"package"];
+ [super webView:sender didClearWindowObject:window forFrame:frame];
+}
+
+- (void) _rightButtonClicked {
+ /*[super _rightButtonClicked];
+ return;*/
+
+ int count = [buttons_ count];
+ _assert(count != 0);
+
+ if (count == 1)
+ [self _clickButtonWithName:[buttons_ objectAtIndex:0]];
+ else {
+ NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:(count + 1)];
+ [buttons addObjectsFromArray:buttons_];
+ [buttons addObject:@"Cancel"];
+
+ [delegate_ slideUp:[[[UIActionSheet alloc]
+ initWithTitle:nil
+ buttons:buttons
+ defaultButtonIndex:2
+ delegate:self
+ context:@"manage"
+ ] autorelease]];
+ }
+}
+
+- (NSString *) _rightButtonTitle {
+ int count = [buttons_ count];
+ return count == 0 ? nil : count != 1 ? @"Modify" : [buttons_ objectAtIndex:0];
+}
+
+- (NSString *) backButtonTitle {
+ return @"Details";
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+ buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
+ } return self;
+}
+
+- (void) setPackage:(Package *)package {
+ if (package_ != nil) {
+ [package_ autorelease];
+ package_ = nil;
+ }
+
+ if (name_ != nil) {
+ [name_ release];
+ name_ = nil;
+ }
+
+ [buttons_ removeAllObjects];
+
+ if (package != nil) {
+ package_ = [package retain];
+ name_ = [[package id] retain];
+
+ [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"package" ofType:@"html"]]];
+
+ if ([package_ source] == nil);
+ else if ([package_ upgradableAndEssential:NO])
+ [buttons_ addObject:@"Upgrade"];
+ else if ([package_ installed] == nil)
+ [buttons_ addObject:@"Install"];
+ else
+ [buttons_ addObject:@"Reinstall"];
+ if ([package_ installed] != nil)
+ [buttons_ addObject:@"Remove"];
+ }
+}
+
+- (void) reloadData {
+ [self setPackage:[database_ packageWithName:name_]];
+ [self reloadButtons];
+}
+
+@end
+/* }}} */
+/* Package Table {{{ */
+@interface PackageTable : RVPage {
+ _transient Database *database_;
+ NSString *title_;
+ SEL filter_;
+ id object_;
+ NSMutableArray *packages_;
+ NSMutableArray *sections_;
+ UISectionList *list_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
+
+- (void) setDelegate:(id)delegate;
+- (void) setObject:(id)object;
+
+- (void) reloadData;
+- (void) resetCursor;
+
+- (UISectionList *) list;
+
+- (void) setShouldHideHeaderInShortLists:(BOOL)hide;
+
+@end
+
+@implementation PackageTable
+
+- (void) dealloc {
+ [list_ setDataSource:nil];
+
+ [title_ release];
+ if (object_ != nil)
+ [object_ release];
+ [packages_ release];
+ [sections_ release];
+ [list_ release];
+ [super dealloc];
+}
+
+- (int) numberOfSectionsInSectionList:(UISectionList *)list {
+ return [sections_ count];
+}
+
+- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section {
+ return [[sections_ objectAtIndex:section] name];
+}
+
+- (int) sectionList:(UISectionList *)list rowForSection:(int)section {
+ return [[sections_ objectAtIndex:section] row];
+}
+
+- (int) numberOfRowsInTable:(UITable *)table {
+ return [packages_ count];
+}
+
+- (float) table:(UITable *)table heightForRow:(int)row {
+ return [PackageCell heightForPackage:[packages_ objectAtIndex:row]];
+}
+
+- (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
+ if (reusing == nil)
+ reusing = [[[PackageCell alloc] init] autorelease];
+ [(PackageCell *)reusing setPackage:[packages_ objectAtIndex:row]];
+ return reusing;
+}
+
+- (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
+ return NO;
+}
+
+- (void) tableRowSelected:(NSNotification *)notification {
+ int row = [[notification object] selectedRow];
+ if (row == INT_MAX)
+ return;
+
+ Package *package = [packages_ objectAtIndex:row];
+ PackageView *view = [[[PackageView alloc] initWithBook:book_ database:database_] autorelease];
+ [view setDelegate:delegate_];
+ [view setPackage:package];
+ [book_ pushPage:view];
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+ title_ = [title retain];
+ filter_ = filter;
+ object_ = object == nil ? nil : [object retain];
+
+ packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
+ sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
+
+ list_ = [[UISectionList alloc] initWithFrame:[self bounds] showSectionIndex:YES];
+ [list_ setDataSource:self];
+
+ UITableColumn *column = [[[UITableColumn alloc]
+ initWithTitle:@"Name"
+ identifier:@"name"
+ width:[self frame].size.width
+ ] autorelease];
+
+ UITable *table = [list_ table];
+ [table setSeparatorStyle:1];
+ [table addTableColumn:column];
+ [table setDelegate:self];
+ [table setReusesTableCells:YES];
+
+ [self addSubview:list_];
+ [self reloadData];
+
+ [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
+ [list_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
+ } return self;
+}
+
+- (void) setDelegate:(id)delegate {
+ delegate_ = delegate;
+}
+
+- (void) setObject:(id)object {
+ if (object_ != nil)
+ [object_ release];
+ if (object == nil)
+ object_ = nil;
+ else
+ object_ = [object retain];
+}
+
+- (void) reloadData {
+ NSArray *packages = [database_ packages];
+
+ [packages_ removeAllObjects];
+ [sections_ removeAllObjects];
+
+ for (size_t i(0); i != [packages count]; ++i) {
+ Package *package([packages objectAtIndex:i]);
+ if ([package valid] && [[package performSelector:filter_ withObject:object_] boolValue])
+ [packages_ addObject:package];
+ }
+
+ Section *section = nil;
+
+ for (size_t offset(0); offset != [packages_ count]; ++offset) {
+ Package *package = [packages_ objectAtIndex:offset];
+ NSString *name = [package index];
+
+ if (section == nil || ![[section name] isEqualToString:name]) {
+ section = [[[Section alloc] initWithName:name row:offset] autorelease];
+ [sections_ addObject:section];
+ }
+
+ [section addToCount];
+ }
+
+ [list_ reloadData];
+}
+
+- (NSString *) title {
+ return title_;
+}
+
+- (void) resetViewAnimated:(BOOL)animated {
+ [list_ resetViewAnimated:animated];
+}
+
+- (void) resetCursor {
+ [[list_ table] scrollPointVisibleAtTopLeft:CGPointMake(0, 0) animated:NO];
+}
+
+- (UISectionList *) list {
+ return list_;
+}
+
+- (void) setShouldHideHeaderInShortLists:(BOOL)hide {
+ [list_ setShouldHideHeaderInShortLists:hide];
+}
+
+@end
+/* }}} */
+
+/* Add Source View {{{ */
+@interface AddSourceView : RVPage {
+ _transient Database *database_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+
+@end
+
+@implementation AddSourceView
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database {
+ if ((self = [super initWithBook:book]) != nil) {
+ database_ = database;
+ } return self;
+}
+
+@end
+/* }}} */
+/* Source Cell {{{ */
+@interface SourceCell : UITableCell {
+ UIImage *icon_;
+ NSString *origin_;
+ NSString *description_;
+ NSString *label_;
+}
+
+- (void) dealloc;
+
+- (SourceCell *) initWithSource:(Source *)source;
+
+@end
+
+@implementation SourceCell
+
+- (void) dealloc {
+ [icon_ release];
+ [origin_ release];
+ [description_ release];
+ [label_ release];
+ [super dealloc];
+}
+
+- (SourceCell *) initWithSource:(Source *)source {
+ if ((self = [super init]) != nil) {
+ if (icon_ == nil)
+ icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]];
+ if (icon_ == nil)
+ icon_ = [UIImage applicationImageNamed:@"unknown.png"];
+ icon_ = [icon_ retain];
+
+ origin_ = [[source name] retain];
+ label_ = [[source uri] retain];
+ description_ = [[source description] retain];
+ } return self;
+}
+
+- (void) drawContentInRect:(CGRect)rect selected:(BOOL)selected {
+ if (icon_ != nil)
+ [icon_ drawInRect:CGRectMake(10, 10, 30, 30)];
+
+ if (selected)
+ UISetColor(White_);
+
+ if (!selected)
+ UISetColor(Black_);
+ [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:240 withFont:Font18Bold_ ellipsis:2];
+
+ if (!selected)
+ UISetColor(Blue_);
+ [label_ drawAtPoint:CGPointMake(58, 29) forWidth:225 withFont:Font12_ ellipsis:2];
+
+ if (!selected)
+ UISetColor(Gray_);
+ [description_ drawAtPoint:CGPointMake(12, 46) forWidth:280 withFont:Font14_ ellipsis:2];
+
+ [super drawContentInRect:rect selected:selected];
+}
+
+@end
+/* }}} */
+/* Source Table {{{ */
+@interface SourceTable : RVPage {
+ _transient Database *database_;
+ UISectionList *list_;
+ NSMutableArray *sources_;
+ UIActionSheet *alert_;
+ int offset_;
+
+ NSString *href_;
+ UIProgressHUD *hud_;
+ NSError *error_;
+
+ //NSURLConnection *installer_;
+ NSURLConnection *trivial_bz2_;
+ NSURLConnection *trivial_gz_;
+ //NSURLConnection *automatic_;
+
+ BOOL trivial_;
+}
+
+- (id) initWithBook:(RVBook *)book database:(Database *)database;
+
+@end
+
+@implementation SourceTable
+
+- (void) _deallocConnection:(NSURLConnection *)connection {
+ if (connection != nil) {
+ [connection cancel];
+ //[connection setDelegate:nil];
+ [connection release];
+ }
+}
+
+- (void) dealloc {
+ [[list_ table] setDelegate:nil];
+ [list_ setDataSource:nil];
+
+ if (href_ != nil)
+ [href_ release];
+ if (hud_ != nil)
+ [hud_ release];
+ if (error_ != nil)
+ [error_ release];
+
+ //[self _deallocConnection:installer_];
+ [self _deallocConnection:trivial_gz_];
+ [self _deallocConnection:trivial_bz2_];
+ //[self _deallocConnection:automatic_];
+
+ [sources_ release];
+ [list_ release];
+ [super dealloc];
+}
+
+- (int) numberOfSectionsInSectionList:(UISectionList *)list {
+ return offset_ == 0 ? 1 : 2;
+}
+
+- (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section {
+ switch (section + (offset_ == 0 ? 1 : 0)) {
+ case 0: return @"Entered by User";
+ case 1: return @"Installed by Packages";
+
+ default:
+ _assert(false);
+ return nil;
+ }
+}
+
+- (int) sectionList:(UISectionList *)list rowForSection:(int)section {
+ switch (section + (offset_ == 0 ? 1 : 0)) {
+ case 0: return 0;
+ case 1: return offset_;
+
+ default:
+ _assert(false);
+ return -1;
+ }
+}
+
+- (int) numberOfRowsInTable:(UITable *)table {
+ return [sources_ count];