+static const float TextViewOffset_ = 22;
+
+UITextView *GetTextView(NSString *value, float left, bool html) {
+ UITextView *text([[[UITextView alloc] initWithFrame:CGRectMake(left, 3, 310 - left, 1000)] autorelease]);
+ [text setEditable:NO];
+ [text setTextSize:16];
+ /*if (html)
+ [text setHTML:value];
+ else*/
+ [text setText:value];
+ [text setEnabled:NO];
+
+ [text setBackgroundColor:Clear_];
+
+ CGRect frame = [text frame];
+ [text setFrame:frame];
+ CGRect rect = [text visibleTextRect];
+ frame.size.height = rect.size.height;
+ [text setFrame:frame];
+
+ return text;
+}
+
+NSString *Simplify(NSString *title) {
+ const char *data = [title UTF8String];
+ size_t size = [title length];
+
+ Pcre title_r("^(.*?)( \\(.*\\))?$");
+ if (title_r(data, size))
+ return title_r[1];
+ else
+ return title;
+}
+/* }}} */
+
+@class Package;
+@class Source;
+
+@protocol ProgressDelegate
+- (void) setProgressError:(NSString *)error;
+- (void) setProgressTitle:(NSString *)title;
+- (void) setProgressPercent:(float)percent;
+- (void) addProgressOutput:(NSString *)output;
+@end
+
+@protocol CydiaDelegate
+- (void) installPackage:(Package *)package;
+- (void) removePackage:(Package *)package;
+- (void) slideUp:(UIAlertSheet *)alert;
+- (void) distUpgrade;
+@end
+
+/* Status Delegation {{{ */
+class Status :
+ public pkgAcquireStatus
+{
+ private:
+ _transient id<ProgressDelegate> delegate_;
+
+ public:
+ Status() :
+ delegate_(nil)
+ {
+ }
+
+ void setDelegate(id delegate) {
+ delegate_ = delegate;
+ }
+
+ virtual bool MediaChange(std::string media, std::string drive) {
+ return false;
+ }
+
+ virtual void IMSHit(pkgAcquire::ItemDesc &item) {
+ }
+
+ virtual void Fetch(pkgAcquire::ItemDesc &item) {
+ [delegate_ setProgressTitle:[NSString stringWithCString:("Downloading " + item.ShortDesc).c_str()]];
+ }
+
+ virtual void Done(pkgAcquire::ItemDesc &item) {
+ }
+
+ virtual void Fail(pkgAcquire::ItemDesc &item) {
+ if (
+ item.Owner->Status == pkgAcquire::Item::StatIdle ||
+ item.Owner->Status == pkgAcquire::Item::StatDone
+ )
+ return;
+
+ [delegate_ setProgressError:[NSString stringWithCString:item.Owner->ErrorText.c_str()]];
+ }
+
+ virtual bool Pulse(pkgAcquire *Owner) {
+ bool value = pkgAcquireStatus::Pulse(Owner);
+
+ float percent(
+ double(CurrentBytes + CurrentItems) /
+ double(TotalBytes + TotalItems)
+ );
+
+ [delegate_ setProgressPercent:percent];
+ return value;
+ }
+
+ virtual void Start() {
+ }
+
+ virtual void Stop() {
+ }
+};
+/* }}} */
+/* Progress Delegation {{{ */
+class Progress :
+ public OpProgress
+{
+ private:
+ _transient id<ProgressDelegate> delegate_;
+
+ protected:
+ virtual void Update() {
+ [delegate_ setProgressTitle:[NSString stringWithCString:Op.c_str()]];
+ [delegate_ setProgressPercent:(Percent / 100)];
+ }
+
+ public:
+ Progress() :
+ delegate_(nil)
+ {
+ }
+
+ void setDelegate(id delegate) {
+ delegate_ = delegate;
+ }
+
+ virtual void Done() {
+ [delegate_ setProgressPercent:1];
+ }
+};
+/* }}} */
+
+/* Database Interface {{{ */
+@interface Database : NSObject {
+ pkgCacheFile cache_;
+ pkgRecords *records_;
+ pkgProblemResolver *resolver_;
+ pkgAcquire *fetcher_;
+ FileFd *lock_;
+ SPtr<pkgPackageManager> manager_;
+ pkgSourceList *list_;
+
+ NSMutableDictionary *sources_;
+ NSMutableArray *packages_;
+
+ _transient id delegate_;
+ Status status_;
+ Progress progress_;
+ int statusfd_;
+}
+
+- (void) _readStatus:(NSNumber *)fd;
+- (void) _readOutput:(NSNumber *)fd;
+
+- (Package *) packageWithName:(NSString *)name;
+
+- (Database *) init;
+- (pkgCacheFile &) cache;
+- (pkgRecords *) records;
+- (pkgProblemResolver *) resolver;
+- (pkgAcquire &) fetcher;
+- (NSArray *) packages;
+- (void) reloadData;
+
+- (void) prepare;
+- (void) perform;
+- (void) upgrade;
+- (void) update;
+
+- (void) updateWithStatus:(Status &)status;
+
+- (void) setDelegate:(id)delegate;
+- (Source *) getSource:(const pkgCache::PkgFileIterator &)file;
+@end
+/* }}} */
+
+/* Source Class {{{ */
+@interface Source : NSObject {
+ NSString *description_;
+ NSString *label_;
+ NSString *origin_;
+
+ NSString *uri_;
+ NSString *distribution_;
+ NSString *type_;
+ NSString *version_;
+
+ NSString *defaultIcon_;
+
+ BOOL trusted_;
+}
+
+- (Source *) initWithMetaIndex:(metaIndex *)index;
+
+- (BOOL) trusted;
+
+- (NSString *) uri;
+- (NSString *) distribution;
+- (NSString *) type;
+
+- (NSString *) description;
+- (NSString *) label;
+- (NSString *) origin;
+- (NSString *) version;
+
+- (NSString *) defaultIcon;
+@end
+
+@implementation Source
+
+- (void) dealloc {
+ [uri_ release];
+ [distribution_ release];
+ [type_ release];
+
+ if (description_ != nil)
+ [description_ release];
+ if (label_ != nil)
+ [label_ release];
+ if (origin_ != nil)
+ [origin_ release];
+ if (version_ != nil)
+ [version_ release];
+ if (defaultIcon_ != nil)
+ [defaultIcon_ release];
+
+ [super dealloc];
+}
+
+- (Source *) initWithMetaIndex:(metaIndex *)index {
+ if ((self = [super init]) != nil) {
+ trusted_ = index->IsTrusted();
+
+ uri_ = [[NSString stringWithCString:index->GetURI().c_str()] retain];
+ distribution_ = [[NSString stringWithCString:index->GetDist().c_str()] retain];
+ type_ = [[NSString stringWithCString:index->GetType()] retain];
+
+ description_ = nil;
+ label_ = nil;
+ origin_ = nil;
+ version_ = nil;
+ defaultIcon_ = nil;
+
+ debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index));
+ if (dindex != NULL) {
+ std::ifstream release(dindex->MetaIndexFile("Release").c_str());
+ std::string line;
+ while (std::getline(release, line)) {
+ std::string::size_type colon(line.find(':'));
+ if (colon == std::string::npos)
+ continue;
+
+ std::string name(line.substr(0, colon));
+ std::string value(line.substr(colon + 1));
+ while (!value.empty() && value[0] == ' ')
+ value = value.substr(1);
+
+ if (name == "Default-Icon")
+ defaultIcon_ = [[NSString stringWithCString:value.c_str()] retain];
+ else if (name == "Description")
+ description_ = [[NSString stringWithCString:value.c_str()] retain];
+ else if (name == "Label")
+ label_ = [[NSString stringWithCString:value.c_str()] retain];
+ else if (name == "Origin")
+ origin_ = [[NSString stringWithCString:value.c_str()] retain];
+ else if (name == "Version")
+ version_ = [[NSString stringWithCString:value.c_str()] retain];
+ }
+ }
+ } return self;
+}
+
+- (BOOL) trusted {
+ return trusted_;
+}
+
+- (NSString *) uri {
+ return uri_;
+}
+
+- (NSString *) distribution {
+ return distribution_;
+}
+
+- (NSString *) type {
+ return type_;
+}
+
+- (NSString *) description {
+ return description_;
+}
+
+- (NSString *) label {
+ return label_;
+}
+
+- (NSString *) origin {
+ return origin_;
+}
+
+- (NSString *) version {
+ return version_;
+}
+
+- (NSString *) defaultIcon {
+ return defaultIcon_;
+}
+
+@end
+/* }}} */
+/* Package Class {{{ */
+NSString *Scour(const char *field, const char *begin, const char *end) {
+ size_t i(0), l(strlen(field));
+
+ for (;;) {
+ const char *name = begin + i;
+ const char *colon = name + l;
+ const char *value = colon + 1;
+
+ if (
+ value < end &&
+ *colon == ':' &&
+ memcmp(name, field, l) == 0
+ ) {
+ while (value != end && value[0] == ' ')
+ ++value;
+ const char *line = std::find(value, end, '\n');
+ while (line != value && line[-1] == ' ')
+ --line;
+ return [NSString stringWithCString:value length:(line - value)];
+ } else {
+ begin = std::find(begin, end, '\n');
+ if (begin == end)
+ return nil;
+ ++begin;
+ }
+ }
+}
+
+@interface Package : NSObject {
+ pkgCache::PkgIterator iterator_;
+ _transient Database *database_;
+ pkgCache::VerIterator version_;
+ pkgCache::VerFileIterator file_;
+ Source *source_;
+
+ NSString *latest_;
+ NSString *installed_;
+
+ NSString *id_;
+ NSString *name_;
+ NSString *tagline_;
+ NSString *icon_;
+ NSString *website_;
+}
+
+- (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database version:(pkgCache::VerIterator)version file:(pkgCache::VerFileIterator)file;
++ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database;
+
+- (NSString *) section;
+- (Address *) maintainer;
+- (size_t) size;
+- (NSString *) description;
+- (NSString *) index;
+
+- (NSDate *) seen;
+
+- (NSString *) latest;
+- (NSString *) installed;
+- (BOOL) upgradable;
+- (BOOL) essential;
+- (BOOL) broken;
+
+- (NSString *) id;
+- (NSString *) name;
+- (NSString *) tagline;
+- (NSString *) icon;
+- (NSString *) website;
+
+- (Source *) source;
+
+- (BOOL) matches:(NSString *)text;
+
+- (NSComparisonResult) compareByName:(Package *)package;
+- (NSComparisonResult) compareBySection:(Package *)package;
+- (NSComparisonResult) compareBySectionAndName:(Package *)package;
+- (NSComparisonResult) compareForChanges:(Package *)package;
+
+- (void) install;
+- (void) remove;
+
+- (NSNumber *) isSearchedForBy:(NSString *)search;
+- (NSNumber *) isInstalledInSection:(NSString *)section;
+- (NSNumber *) isUninstalledInSection:(NSString *)section;
+
+@end
+
+@implementation Package
+
+- (void) dealloc {
+ [latest_ release];
+ if (installed_ != nil)
+ [installed_ release];
+
+ [id_ release];
+ if (name_ != nil)
+ [name_ release];
+ [tagline_ release];
+ if (icon_ != nil)
+ [icon_ release];
+ if (website_ != nil)
+ [website_ release];
+
+ [source_ release];
+
+ [super dealloc];
+}
+
+- (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database version:(pkgCache::VerIterator)version file:(pkgCache::VerFileIterator)file {
+ if ((self = [super init]) != nil) {
+ iterator_ = iterator;
+ database_ = database;
+
+ version_ = version;
+ file_ = file;
+
+ pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
+
+ const char *begin, *end;
+ parser->GetRec(begin, end);
+
+ latest_ = [[NSString stringWithCString:version_.VerStr()] retain];
+ installed_ = iterator_.CurrentVer().end() ? nil : [[NSString stringWithCString:iterator_.CurrentVer().VerStr()] retain];
+
+ id_ = [[[NSString stringWithCString:iterator_.Name()] lowercaseString] retain];
+ name_ = Scour("Name", begin, end);
+ if (name_ != nil)
+ name_ = [name_ retain];
+ tagline_ = [[NSString stringWithCString:parser->ShortDesc().c_str()] retain];
+ icon_ = Scour("Icon", begin, end);
+ if (icon_ != nil)
+ icon_ = [icon_ retain];
+ website_ = Scour("Website", begin, end);
+ if (website_ != nil)
+ website_ = [website_ retain];
+
+ source_ = [[database_ getSource:file_.File()] retain];
+
+ NSMutableDictionary *metadata = [Packages_ objectForKey:id_];
+ if (metadata == nil || [metadata count] == 0) {
+ metadata = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ now_, @"FirstSeen",
+ nil];
+
+ [Packages_ setObject:metadata forKey:id_];
+ }
+ } return self;
+}
+
++ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database {
+ for (pkgCache::VerIterator version = iterator.VersionList(); !version.end(); ++version)
+ for (pkgCache::VerFileIterator file = version.FileList(); !file.end(); ++file)
+ return [[[Package alloc]
+ initWithIterator:iterator
+ database:database
+ version:version
+ file:file]
+ autorelease];
+ return nil;
+}
+
+- (NSString *) section {
+ const char *section = iterator_.Section();
+ return section == NULL ? nil : [[NSString stringWithCString:section] stringByReplacingCharacter:'_' withCharacter:' '];
+}
+
+- (Address *) maintainer {
+ pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
+ return [Address addressWithString:[NSString stringWithCString:parser->Maintainer().c_str()]];
+}
+
+- (size_t) size {
+ return version_->InstalledSize;
+}
+
+- (NSString *) description {
+ pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
+ NSString *description([NSString stringWithCString:parser->LongDesc().c_str()]);
+
+ NSArray *lines = [description componentsSeparatedByString:@"\n"];
+ NSMutableArray *trimmed = [NSMutableArray arrayWithCapacity:([lines count] - 1)];
+ if ([lines count] < 2)
+ return nil;
+
+ NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
+ for (size_t i(1); i != [lines count]; ++i) {
+ NSString *trim = [[lines objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace];
+ [trimmed addObject:trim];
+ }
+
+ return [trimmed componentsJoinedByString:@"\n"];
+}
+
+- (NSString *) index {
+ NSString *index = [[[self name] substringToIndex:1] uppercaseString];
+ return [index length] != 0 && isalpha([index characterAtIndex:0]) ? index : @"123";
+}
+
+- (NSDate *) seen {
+ return [[Packages_ objectForKey:id_] objectForKey:@"FirstSeen"];
+}
+
+- (NSString *) latest {
+ return latest_;
+}
+
+- (NSString *) installed {
+ return installed_;
+}
+
+- (BOOL) upgradable {
+ if (NSString *installed = [self installed])
+ return [[self latest] compare:installed] != NSOrderedSame ? YES : NO;
+ else
+ return [self essential];
+}
+
+- (BOOL) essential {
+ return (iterator_->Flags & pkgCache::Flag::Essential) == 0 ? NO : YES;
+}
+
+- (BOOL) broken {
+ return (*[database_ cache])[iterator_].InstBroken();
+}
+
+- (NSString *) id {
+ return id_;
+}
+
+- (NSString *) name {
+ return name_ == nil ? id_ : name_;
+}
+
+- (NSString *) tagline {
+ return tagline_;
+}
+
+- (NSString *) icon {
+ return icon_;
+}
+
+- (NSString *) website {
+ return website_;
+}
+
+- (Source *) source {
+ return source_;
+}
+
+- (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;
+}
+
+- (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 caseInsensitiveCompare:rhs];
+}
+
+- (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];
+ if (result != NSOrderedSame)
+ return result;
+ }
+
+ return NSOrderedSame;
+}
+
+- (NSComparisonResult) compareBySectionAndName:(Package *)package {
+ NSString *lhs = [self section];
+ NSString *rhs = [package section];
+
+ if (lhs == NULL && rhs != NULL)
+ return NSOrderedAscending;
+ else if (lhs != NULL && rhs == NULL)
+ return NSOrderedDescending;
+ else if (lhs != NULL && rhs != NULL) {
+ NSComparisonResult result = [lhs compare:rhs];
+ if (result != NSOrderedSame)
+ return result;
+ }
+
+ return [self compareByName:package];
+}
+
+- (NSComparisonResult) compareForChanges:(Package *)package {
+ BOOL lhs = [self upgradable];
+ BOOL rhs = [package upgradable];
+
+ if (lhs != rhs)
+ return lhs ? NSOrderedAscending : NSOrderedDescending;
+ else if (!lhs) {
+ switch ([[self seen] compare:[package seen]]) {
+ case NSOrderedAscending:
+ return NSOrderedDescending;
+ case NSOrderedSame:
+ break;
+ case NSOrderedDescending:
+ return NSOrderedAscending;
+ default:
+ _assert(false);
+ }
+ }
+
+ return [self compareByName:package];
+}
+
+- (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 *) isSearchedForBy:(NSString *)search {
+ return [NSNumber numberWithBool:[self matches:search]];
+}
+
+- (NSNumber *) isInstalledInSection:(NSString *)section {
+ return [NSNumber numberWithBool:([self installed] != nil && (section == nil || [section isEqualToString:[self section]]))];
+}
+
+- (NSNumber *) isUninstalledInSection:(NSString *)section {
+ return [NSNumber numberWithBool:([self installed] == nil && (section == nil || [section isEqualToString:[self section]]))];
+}
+
+@end
+/* }}} */
+/* Section Class {{{ */
+@interface Section : NSObject {
+ NSString *name_;
+ size_t row_;
+ size_t count_;
+}
+
+- (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];
+}
+
+- (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
+/* }}} */
+
+/* Database Implementation {{{ */
+@implementation Database
+
+- (void) dealloc {
+ _assert(false);
+ [super dealloc];
+}
+
+- (void) _readStatus:(NSNumber *)fd {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
+ std::istream is(&ib);
+ std::string line;
+
+ const char *error;
+ int offset;
+ pcre *code = pcre_compile("^([^:]*):([^:]*):([^:]*):(.*)$", 0, &error, &offset, NULL);
+
+ pcre_extra *study = NULL;
+ int capture;
+ pcre_fullinfo(code, study, PCRE_INFO_CAPTURECOUNT, &capture);
+ int matches[(capture + 1) * 3];
+
+ while (std::getline(is, line)) {
+ const char *data(line.c_str());
+
+ _assert(pcre_exec(code, study, data, line.size(), 0, 0, matches, sizeof(matches) / sizeof(matches[0])) >= 0);
+
+ std::istringstream buffer(line.substr(matches[6], matches[7] - matches[6]));
+ float percent;
+ buffer >> percent;
+ [delegate_ setProgressPercent:(percent / 100)];
+
+ NSString *string = [NSString stringWithCString:(data + matches[8]) length:(matches[9] - matches[8])];
+ std::string type(line.substr(matches[2], matches[3] - matches[2]));
+
+ if (type == "pmerror")
+ [delegate_ setProgressError:string];
+ else if (type == "pmstatus")
+ [delegate_ setProgressTitle:string];
+ else if (type == "pmconffile")
+ ;
+ else _assert(false);
+ }
+
+ [pool release];
+ _assert(false);
+}
+
+- (void) _readOutput:(NSNumber *)fd {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
+ std::istream is(&ib);
+ std::string line;
+
+ while (std::getline(is, line))
+ [delegate_ addProgressOutput:[NSString stringWithCString:line.c_str()]];
+
+ [pool release];
+ _assert(false);
+}
+
+- (Package *) packageWithName:(NSString *)name {
+ pkgCache::PkgIterator iterator(cache_->FindPkg([name UTF8String]));
+ return iterator.end() ? nil : [Package packageWithIterator:iterator database:self];
+}
+
+- (Database *) init {
+ if ((self = [super init]) != nil) {
+ 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);
+ statusfd_ = fds[1];
+
+ [NSThread
+ detachNewThreadSelector:@selector(_readStatus:)
+ toTarget:self
+ withObject:[[NSNumber numberWithInt:fds[0]] retain]
+ ];
+
+ _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_;
+}
+
+- (pkgRecords *) records {
+ return records_;
+}
+
+- (pkgProblemResolver *) resolver {
+ return resolver_;
+}
+
+- (pkgAcquire &) fetcher {
+ return *fetcher_;
+}
+
+- (NSArray *) packages {
+ return packages_;
+}
+
+- (void) reloadData {
+ _error->Discard();
+ delete list_;
+ manager_ = NULL;
+ delete lock_;
+ delete fetcher_;
+ delete resolver_;
+ delete records_;
+ cache_.Close();
+
+ if (!cache_.Open(progress_, true)) {
+ fprintf(stderr, "repairing corrupted database...\n");
+ _error->Discard();
+ [self updateWithStatus:status_];
+ _assert(cache_.Open(progress_, true));
+ }
+
+ now_ = [[NSDate date] retain];
+
+ records_ = new pkgRecords(cache_);
+ resolver_ = new pkgProblemResolver(cache_);
+ fetcher_ = new pkgAcquire(&status_);
+ lock_ = NULL;
+
+ list_ = new pkgSourceList();
+ _assert(list_->ReadMainList());
+
+ [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])
+ if ([package source] != nil || [package installed] != nil)
+ [packages_ addObject:package];
+}
+
+- (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 {
+ pkgSourceList list;
+ _assert(list.ReadMainList());
+
+ /*std::map<std::string> before;
+
+ for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source)
+ before.add((*source)->GetURI().c_str());
+ exit(0);*/
+
+ if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue)
+ return;
+
+ _system->UnLock();
+ pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
+
+ if (result == pkgPackageManager::Failed)
+ return;
+ if (_error->PendingError())
+ return;
+ if (result != pkgPackageManager::Completed)
+ return;
+
+ _assert(list.ReadMainList());
+}
+
+- (void) upgrade {
+ _assert(cache_->DelCount() == 0 && cache_->InstCount() == 0);
+ _assert(pkgApplyStatus(cache_));
+
+ if (cache_->BrokenCount() != 0) {
+ _assert(pkgFixBroken(cache_));
+ _assert(cache_->BrokenCount() == 0);
+ _assert(pkgMinimizeUpgrade(cache_));
+ }
+
+ _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"];
+ }
+}
+
+- (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
+/* }}} */
+
+/* RVPage Interface {{{ */
+@class RVBook;
+
+@interface RVPage : UIView {
+ _transient RVBook *book_;
+ _transient id delegate_;
+}
+
+- (NSString *) title;
+- (NSString *) backButtonTitle;
+- (NSString *) rightButtonTitle;
+- (NSString *) leftButtonTitle;
+- (UIView *) accessoryView;
+
+- (void) _rightButtonClicked;
+- (void) _leftButtonClicked;
+
+- (void) setPageActive:(BOOL)active;
+- (void) resetViewAnimated:(BOOL)animated;
+
+- (void) setTitle:(NSString *)title;
+- (void) setBackButtonTitle:(NSString *)title;
+
+- (void) reloadButtons;
+- (void) reloadData;
+
+- (id) initWithBook:(RVBook *)book;
+
+- (void) setDelegate:(id)delegate;
+
+@end
+/* }}} */
+/* Reset View {{{ */
+@protocol RVDelegate
+- (void) setPageActive:(BOOL)active with:(id)object;
+- (void) resetViewAnimated:(BOOL)animated with:(id)object;
+- (void) reloadDataWith:(id)object;
+@end
+
+@interface RVBook : UIView {
+ NSMutableArray *pages_;
+ UINavigationBar *navbar_;
+ UITransitionView *transition_;
+ BOOL resetting_;
+ _transient id delegate_;
+}
+
+- (id) initWithFrame:(CGRect)frame;
+- (void) setDelegate:(id)delegate;
+
+- (void) setPage:(RVPage *)page;
+
+- (void) pushPage:(RVPage *)page;
+- (void) popPages:(unsigned)pages;
+
+- (void) setPrompt:(NSString *)prompt;
+
+- (void) resetViewAnimated:(BOOL)animated;
+- (void) resetViewAnimated:(BOOL)animated toPage:(RVPage *)page;
+
+- (void) setTitle:(NSString *)title forPage:(RVPage *)page;
+- (void) setBackButtonTitle:(NSString *)title forPage:(RVPage *)page;
+- (void) reloadButtonsForPage:(RVPage *)page;
+
+- (void) reloadData;
+
+- (CGRect) pageBounds;
+
+@end
+
+@implementation RVBook
+
+- (void) dealloc {
+ [navbar_ setDelegate:nil];
+
+ [pages_ release];
+ [navbar_ release];
+ [transition_ release];
+ [super dealloc];
+}
+
+- (void) navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button {
+ _assert([pages_ count] != 0);
+ RVPage *page = [pages_ lastObject];
+ switch (button) {
+ case 0: [page _rightButtonClicked]; break;
+ case 1: [page _leftButtonClicked]; break;
+ }
+}
+
+- (void) navigationBar:(UINavigationBar *)navbar poppedItem:(UINavigationItem *)item {
+ _assert([pages_ count] != 0);
+ if (!resetting_)
+ [[pages_ lastObject] setPageActive:NO];
+ [pages_ removeLastObject];
+ if (!resetting_)
+ [self resetViewAnimated:YES toPage:[pages_ lastObject]];
+}
+
+- (id) initWithFrame:(CGRect)frame {
+ if ((self = [super initWithFrame:frame]) != nil) {
+ pages_ = [[NSMutableArray arrayWithCapacity:4] retain];
+
+ struct CGRect bounds = [self bounds];
+ CGSize navsize = [UINavigationBar defaultSizeWithPrompt];
+ CGRect navrect = {{0, 0}, navsize};
+
+ navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
+ [self addSubview:navbar_];
+
+ [navbar_ setBarStyle:1];
+ [navbar_ setDelegate:self];
+
+ [navbar_ setPrompt:@""];
+
+ transition_ = [[UITransitionView alloc] initWithFrame:CGRectMake(
+ bounds.origin.x, bounds.origin.y + navsize.height, bounds.size.width, bounds.size.height - navsize.height
+ )];
+
+ [self addSubview:transition_];
+ } return self;
+}
+
+- (void) setDelegate:(id)delegate {
+ delegate_ = delegate;
+}
+
+- (void) setPage:(RVPage *)page {
+ if ([pages_ count] != 0)
+ [[pages_ lastObject] setPageActive:NO];
+
+ [navbar_ disableAnimation];
+ resetting_ = true;
+ for (unsigned i(0), pages([pages_ count]); i != pages; ++i)
+ [navbar_ popNavigationItem];
+ resetting_ = false;
+
+ [self pushPage:page];
+ [navbar_ enableAnimation];
+}
+
+- (void) pushPage:(RVPage *)page {
+ if ([pages_ count] != 0)
+ [[pages_ lastObject] setPageActive:NO];
+
+ NSString *title = Simplify([page title]);
+
+ NSString *backButtonTitle = [page backButtonTitle];
+ if (backButtonTitle == nil)
+ backButtonTitle = title;
+
+ UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:title] autorelease];
+ [navitem setBackButtonTitle:backButtonTitle];
+ [navbar_ pushNavigationItem:navitem];
+
+ BOOL animated = [pages_ count] == 0 ? NO : YES;
+ [transition_ transition:(animated ? 1 : 0) toView:page];
+ [page setPageActive:YES];
+
+ [pages_ addObject:page];
+ [self reloadButtonsForPage:page];
+
+ [navbar_ setAccessoryView:[page accessoryView] animate:animated goingBack:NO];
+}
+
+- (void) popPages:(unsigned)pages {
+ if (pages == 0)