+ SourceMap sources_;
+ NSMutableArray *packages_;
+
+ _transient NSObject<ConfigurationDelegate, ProgressDelegate> *delegate_;
+ Status status_;
+ Progress progress_;
+
+ int cydiafd_;
+ int statusfd_;
+ FILE *input_;
+}
+
++ (Database *) sharedInstance;
+- (unsigned) era;
+
+- (void) _readCydia:(NSNumber *)fd;
+- (void) _readStatus:(NSNumber *)fd;
+- (void) _readOutput:(NSNumber *)fd;
+
+- (FILE *) input;
+
+- (Package *) packageWithName:(NSString *)name;
+
+- (pkgCacheFile &) cache;
+- (pkgDepCache::Policy *) policy;
+- (pkgRecords *) records;
+- (pkgProblemResolver *) resolver;
+- (pkgAcquire &) fetcher;
+- (pkgSourceList &) list;
+- (NSArray *) packages;
+- (NSArray *) sources;
+- (void) reloadData;
+
+- (void) configure;
+- (bool) prepare;
+- (void) perform;
+- (bool) upgrade;
+- (void) update;
+
+- (void) setVisible;
+
+- (void) updateWithStatus:(Status &)status;
+
+- (void) setDelegate:(id)delegate;
+- (Source *) getSource:(pkgCache::PkgFileIterator)file;
+@end
+/* }}} */
+/* Delegate Helpers {{{ */
+@implementation NSObject (ProgressDelegate)
+
+- (void) _setProgressErrorPackage:(NSArray *)args {
+ [self performSelector:@selector(setProgressError:forPackage:)
+ withObject:[args objectAtIndex:0]
+ withObject:([args count] == 1 ? nil : [args objectAtIndex:1])
+ ];
+}
+
+- (void) _setProgressErrorTitle:(NSArray *)args {
+ [self performSelector:@selector(setProgressError:withTitle:)
+ withObject:[args objectAtIndex:0]
+ withObject:([args count] == 1 ? nil : [args objectAtIndex:1])
+ ];
+}
+
+- (void) _setProgressError:(NSString *)error withTitle:(NSString *)title {
+ [self performSelectorOnMainThread:@selector(_setProgressErrorTitle:)
+ withObject:[NSArray arrayWithObjects:error, title, nil]
+ waitUntilDone:YES
+ ];
+}
+
+- (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
+ Package *package = id == nil ? nil : [[Database sharedInstance] packageWithName:id];
+
+ [self performSelector:@selector(setProgressError:withTitle:)
+ withObject:error
+ withObject:(package == nil ? id : [package name])
+ ];
+}
+
+@end
+/* }}} */
+
+/* Source Class {{{ */
+@interface Source : NSObject {
+ CYString depiction_;
+ CYString description_;
+ CYString label_;
+ CYString origin_;
+ CYString support_;
+
+ CYString uri_;
+ CYString distribution_;
+ CYString type_;
+ CYString version_;
+
+ NSString *host_;
+ NSString *authority_;
+
+ CYString defaultIcon_;
+
+ NSDictionary *record_;
+ BOOL trusted_;
+}
+
+- (Source *) initWithMetaIndex:(metaIndex *)index inPool:(apr_pool_t *)pool;
+
+- (NSComparisonResult) compareByNameAndType:(Source *)source;
+
+- (NSString *) depictionForPackage:(NSString *)package;
+- (NSString *) supportForPackage:(NSString *)package;
+
+- (NSDictionary *) record;
+- (BOOL) trusted;
+
+- (NSString *) uri;
+- (NSString *) distribution;
+- (NSString *) type;
+- (NSString *) key;
+- (NSString *) host;
+
+- (NSString *) name;
+- (NSString *) description;
+- (NSString *) label;
+- (NSString *) origin;
+- (NSString *) version;
+
+- (NSString *) defaultIcon;
+
+@end
+
+@implementation Source
+
+- (void) _clear {
+ uri_.clear();
+ distribution_.clear();
+ type_.clear();
+
+ description_.clear();
+ label_.clear();
+ origin_.clear();
+ depiction_.clear();
+ support_.clear();
+ version_.clear();
+ defaultIcon_.clear();
+
+ if (record_ != nil) {
+ [record_ release];
+ record_ = nil;
+ }
+
+ if (host_ != nil) {
+ [host_ release];
+ host_ = nil;
+ }
+
+ if (authority_ != nil) {
+ [authority_ release];
+ authority_ = nil;
+ }
+}
+
+- (void) dealloc {
+ [self _clear];
+ [super dealloc];
+}
+
++ (NSArray *) _attributeKeys {
+ return [NSArray arrayWithObjects:@"description", @"distribution", @"host", @"key", @"label", @"name", @"origin", @"trusted", @"type", @"uri", @"version", nil];
+}
+
+- (NSArray *) attributeKeys {
+ return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+ return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (void) setMetaIndex:(metaIndex *)index inPool:(apr_pool_t *)pool {
+ [self _clear];
+
+ trusted_ = index->IsTrusted();
+
+ uri_.set(pool, index->GetURI());
+ distribution_.set(pool, index->GetDist());
+ type_.set(pool, index->GetType());
+
+ debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index));
+ if (dindex != NULL) {
+ FileFd fd;
+ if (!fd.Open(dindex->MetaIndexFile("Release"), FileFd::ReadOnly))
+ _error->Discard();
+ else {
+ pkgTagFile tags(&fd);
+
+ pkgTagSection section;
+ tags.Step(section);
+
+ struct {
+ const char *name_;
+ CYString *value_;
+ } names[] = {
+ {"default-icon", &defaultIcon_},
+ {"depiction", &depiction_},
+ {"description", &description_},
+ {"label", &label_},
+ {"origin", &origin_},
+ {"support", &support_},
+ {"version", &version_},
+ };
+
+ for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i) {
+ const char *start, *end;
+
+ if (section.Find(names[i].name_, start, end)) {
+ CYString &value(*names[i].value_);
+ value.set(pool, start, end - start);
+ }
+ }
+ }
+ }
+
+ record_ = [Sources_ objectForKey:[self key]];
+ if (record_ != nil)
+ record_ = [record_ retain];
+
+ NSURL *url([NSURL URLWithString:uri_]);
+
+ host_ = [url host];
+ if (host_ != nil)
+ host_ = [[host_ lowercaseString] retain];
+
+ if (host_ != nil)
+ authority_ = host_;
+ else
+ authority_ = [url path];
+
+ if (authority_ != nil)
+ authority_ = [authority_ retain];
+}
+
+- (Source *) initWithMetaIndex:(metaIndex *)index inPool:(apr_pool_t *)pool {
+ if ((self = [super init]) != nil) {
+ [self setMetaIndex:index inPool:pool];
+ } return self;
+}
+
+- (NSComparisonResult) compareByNameAndType:(Source *)source {
+ NSDictionary *lhr = [self record];
+ NSDictionary *rhr = [source record];
+
+ if (lhr != rhr)
+ return lhr == nil ? NSOrderedDescending : NSOrderedAscending;
+
+ NSString *lhs = [self name];
+ NSString *rhs = [source 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:LaxCompareOptions_];
+}
+
+- (NSString *) depictionForPackage:(NSString *)package {
+ return depiction_.empty() ? nil : [depiction_ stringByReplacingOccurrencesOfString:@"*" withString:package];
+}
+
+- (NSString *) supportForPackage:(NSString *)package {
+ return support_.empty() ? nil : [support_ stringByReplacingOccurrencesOfString:@"*" withString:package];
+}
+
+- (NSDictionary *) record {
+ return record_;
+}
+
+- (BOOL) trusted {
+ return trusted_;
+}
+
+- (NSString *) uri {
+ return uri_;
+}
+
+- (NSString *) distribution {
+ return distribution_;
+}
+
+- (NSString *) type {
+ return type_;
+}
+
+- (NSString *) key {
+ return [NSString stringWithFormat:@"%@:%@:%@", (NSString *) type_, (NSString *) uri_, (NSString *) distribution_];
+}
+
+- (NSString *) host {
+ return host_;
+}
+
+- (NSString *) name {
+ return origin_.empty() ? authority_ : origin_;
+}
+
+- (NSString *) description {
+ return description_;
+}
+
+- (NSString *) label {
+ return label_.empty() ? authority_ : label_;
+}
+
+- (NSString *) origin {
+ return origin_;
+}
+
+- (NSString *) version {
+ return version_;
+}
+
+- (NSString *) defaultIcon {
+ return defaultIcon_;
+}
+
+@end
+/* }}} */
+/* Relationship Class {{{ */
+@interface Relationship : NSObject {
+ NSString *type_;
+ NSString *id_;
+}
+
+- (NSString *) type;
+- (NSString *) id;
+- (NSString *) name;
+
+@end
+
+@implementation Relationship
+
+- (void) dealloc {
+ [type_ release];
+ [id_ release];
+ [super dealloc];
+}
+
+- (NSString *) type {
+ return type_;
+}
+
+- (NSString *) id {
+ return id_;
+}
+
+- (NSString *) name {
+ _assert(false);
+ return nil;
+}
+
+@end
+/* }}} */
+/* Package Class {{{ */
+@interface Package : NSObject {
+ unsigned era_;
+ apr_pool_t *pool_;
+
+ pkgCache::VerIterator version_;
+ pkgCache::PkgIterator iterator_;
+ _transient Database *database_;
+ pkgCache::VerFileIterator file_;
+
+ Source *source_;
+ bool cached_;
+ bool parsed_;
+
+ CYString section_;
+ NSString *section$_;
+ bool essential_;
+ bool required_;
+ bool visible_;
+ bool obsolete_;
+
+ NSString *latest_;
+ CYString installed_;
+
+ CYString id_;
+ CYString name_;
+ CYString tagline_;
+ CYString icon_;
+ CYString depiction_;
+ CYString homepage_;
+
+ CYString sponsor_;
+ Address *sponsor$_;
+
+ CYString author_;
+ Address *author$_;
+
+ CYString bugs_;
+ CYString support_;
+ NSMutableArray *tags_;
+ NSString *role_;
+
+ NSArray *relationships_;
+
+ NSMutableDictionary *metadata_;
+ _transient NSDate *firstSeen_;
+ _transient NSDate *lastSeen_;
+ bool subscribed_;
+}
+
+- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
++ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
+
+- (pkgCache::PkgIterator) iterator;
+- (void) parse;
+
+- (NSString *) section;
+- (NSString *) simpleSection;
+
+- (NSString *) longSection;
+- (NSString *) shortSection;
+
+- (NSString *) uri;
+
+- (Address *) maintainer;
+- (size_t) size;
+- (NSString *) longDescription;
+- (NSString *) shortDescription;
+- (unichar) index;
+
+- (NSMutableDictionary *) metadata;
+- (NSDate *) seen;
+- (BOOL) subscribed;
+- (BOOL) ignored;
+
+- (NSString *) latest;
+- (NSString *) installed;
+- (BOOL) uninstalled;
+
+- (BOOL) valid;
+- (BOOL) upgradableAndEssential:(BOOL)essential;
+- (BOOL) essential;
+- (BOOL) broken;
+- (BOOL) unfiltered;
+- (BOOL) visible;
+
+- (BOOL) half;
+- (BOOL) halfConfigured;
+- (BOOL) halfInstalled;
+- (BOOL) hasMode;
+- (NSString *) mode;
+
+- (void) setVisible;
+
+- (NSString *) id;
+- (NSString *) name;
+- (UIImage *) icon;
+- (NSString *) homepage;
+- (NSString *) depiction;
+- (Address *) author;
+
+- (NSString *) support;
+
+- (NSArray *) files;
+- (NSArray *) relationships;
+- (NSArray *) warnings;
+- (NSArray *) applications;
+
+- (Source *) source;
+- (NSString *) role;
+
+- (BOOL) matches:(NSString *)text;
+
+- (bool) hasSupportingRole;
+- (BOOL) hasTag:(NSString *)tag;
+- (NSString *) primaryPurpose;
+- (NSArray *) purposes;
+- (bool) isCommercial;
+
+- (CYString &) cyname;
+
+- (uint32_t) compareBySection:(NSArray *)sections;
+
+- (uint32_t) compareForChanges;
+
+- (void) install;
+- (void) remove;
+
+- (bool) isUnfilteredAndSearchedForBy:(NSString *)search;
+- (bool) isUnfilteredAndSelectedForBy:(NSString *)search;
+- (bool) isInstalledAndVisible:(NSNumber *)number;
+- (bool) isVisibleInSection:(NSString *)section;
+- (bool) isVisibleInSource:(Source *)source;
+
+@end
+
+uint32_t PackageChangesRadix(Package *self, void *) {
+ union {
+ uint32_t key;
+
+ struct {
+ uint32_t timestamp : 30;
+ uint32_t ignored : 1;
+ uint32_t upgradable : 1;
+ } bits;
+ } value;
+
+ bool upgradable([self upgradableAndEssential:YES]);
+ value.bits.upgradable = upgradable ? 1 : 0;
+
+ if (upgradable) {
+ 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;
+}
+
+_finline static void Stifle(uint8_t &value) {
+}
+
+uint32_t PackagePrefixRadix(Package *self, void *context) {
+ size_t offset(reinterpret_cast<size_t>(context));
+ CYString &name([self cyname]);
+
+ size_t size(name.size());
+ if (size == 0)
+ return 0;
+ char *text(name.data());
+
+ size_t zeros;
+ if (!isdigit(text[0]))
+ zeros = 0;
+ else {
+ size_t digits(1);
+ while (size != digits && isdigit(text[digits]))
+ if (++digits == 4)
+ break;
+ zeros = 4 - digits;
+ }
+
+ uint8_t data[4];
+
+ // 0.607997
+
+ if (offset == 0 && zeros != 0) {
+ memset(data, '0', zeros);
+ memcpy(data + zeros, text, 4 - zeros);
+ } else {
+ /* XXX: there's some danger here if you request a non-zero offset < 4 and it gets zero padded */
+ if (size <= offset - zeros)
+ return 0;
+
+ text += offset - zeros;
+ size -= offset - zeros;
+
+ if (size >= 4)
+ memcpy(data, text, 4);
+ else {
+ memcpy(data, text, size);
+ memset(data + size, 0, 4 - size);
+ }
+
+ for (size_t i(0); i != 4; ++i)
+ if (isalpha(data[i]))
+ data[i] &= 0xdf;
+ }
+
+ if (offset == 0)
+ data[0] = (data[0] & 0x3f) | "\x80\x00\xc0\x40"[data[0] >> 6];
+
+ /* XXX: ntohl may be more honest */
+ return OSSwapInt32(*reinterpret_cast<uint32_t *>(data));
+}
+
+CYString &(*PackageName)(Package *self, SEL sel);
+
+CFComparisonResult PackageNameCompare(Package *lhs, Package *rhs, void *arg) {
+ _profile(PackageNameCompare)
+ CYString &lhi(PackageName(lhs, @selector(cyname)));
+ CYString &rhi(PackageName(rhs, @selector(cyname)));
+ CFStringRef lhn(lhi), rhn(rhi);
+
+ if (lhn == NULL)
+ return rhn == NULL ? NSOrderedSame : NSOrderedAscending;
+ else if (rhn == NULL)
+ return NSOrderedDescending;
+
+ _profile(PackageNameCompare$NumbersLast)
+ if (!lhi.empty() && !rhi.empty()) {
+ UniChar lhc(CFStringGetCharacterAtIndex(lhn, 0));
+ UniChar rhc(CFStringGetCharacterAtIndex(rhn, 0));
+ bool lha(CFUniCharIsMemberOf(lhc, kCFUniCharLetterCharacterSet));
+ if (lha != CFUniCharIsMemberOf(rhc, kCFUniCharLetterCharacterSet))
+ return lha ? NSOrderedAscending : NSOrderedDescending;
+ }
+ _end
+
+ CFIndex length = CFStringGetLength(lhn);
+
+ _profile(PackageNameCompare$Compare)
+ return CFStringCompareWithOptionsAndLocale(lhn, rhn, CFRangeMake(0, length), LaxCompareFlags_, Locale_);
+ _end
+ _end
+}
+
+CFComparisonResult PackageNameCompare_(Package **lhs, Package **rhs, void *context) {
+ return PackageNameCompare(*lhs, *rhs, context);
+}
+
+struct PackageNameOrdering :
+ std::binary_function<Package *, Package *, bool>
+{
+ _finline bool operator ()(Package *lhs, Package *rhs) const {
+ return PackageNameCompare(lhs, rhs, NULL) == NSOrderedAscending;
+ }
+};
+
+@implementation Package
+
+- (NSString *) description {
+ return [NSString stringWithFormat:@"<Package:%@>", static_cast<NSString *>(name_)];
+}
+
+- (void) dealloc {
+ if (source_ != nil)
+ [source_ release];
+ if (section$_ != nil)
+ [section$_ release];
+
+ if (latest_ != nil)
+ [latest_ release];
+
+ if (sponsor$_ != nil)
+ [sponsor$_ release];
+ if (author$_ != nil)
+ [author$_ release];
+ if (tags_ != nil)
+ [tags_ release];
+ if (role_ != nil)
+ [role_ release];
+
+ if (relationships_ != nil)
+ [relationships_ release];
+ if (metadata_ != nil)
+ [metadata_ release];
+
+ [super dealloc];
+}
+
++ (NSString *) webScriptNameForSelector:(SEL)selector {
+ if (selector == @selector(hasTag:))
+ return @"hasTag";
+ else
+ return nil;
+}
+
++ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
+ return [self webScriptNameForSelector:selector] == nil;
+}
+
++ (NSArray *) _attributeKeys {
+ return [NSArray arrayWithObjects:@"applications", @"author", @"depiction", @"longDescription", @"essential", @"homepage", @"icon", @"id", @"installed", @"latest", @"longSection", @"maintainer", @"mode", @"name", @"purposes", @"section", @"shortDescription", @"shortSection", @"simpleSection", @"size", @"source", @"sponsor", @"support", @"warnings", nil];
+}
+
+- (NSArray *) attributeKeys {
+ return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+ return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (void) parse {
+ if (parsed_)
+ return;
+ parsed_ = true;
+ if (file_.end())
+ return;
+
+ _profile(Package$parse)
+ pkgRecords::Parser *parser;
+
+ _profile(Package$parse$Lookup)
+ parser = &[database_ records]->Lookup(file_);
+ _end
+
+ CYString website;
+
+ _profile(Package$parse$Find)
+ struct {
+ const char *name_;
+ CYString *value_;
+ } names[] = {
+ {"icon", &icon_},
+ {"depiction", &depiction_},
+ {"homepage", &homepage_},
+ {"website", &website},
+ {"bugs", &bugs_},
+ {"support", &support_},
+ {"sponsor", &sponsor_},
+ {"author", &author_},
+ };
+
+ for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i) {
+ const char *start, *end;
+
+ if (parser->Find(names[i].name_, start, end)) {
+ CYString &value(*names[i].value_);
+ _profile(Package$parse$Value)
+ value.set(pool_, start, end - start);
+ _end
+ }
+ }
+ _end
+
+ _profile(Package$parse$Tagline)
+ const char *start, *end;
+ if (parser->ShortDesc(start, end)) {
+ const char *stop(reinterpret_cast<const char *>(memchr(start, '\n', end - start)));
+ if (stop == NULL)
+ stop = end;
+ while (stop != start && stop[-1] == '\r')
+ --stop;
+ tagline_.set(pool_, start, stop - start);
+ }
+ _end
+
+ _profile(Package$parse$Retain)
+ if (homepage_.empty())
+ homepage_ = website;
+ if (homepage_ == depiction_)
+ homepage_.clear();
+ _end
+ _end
+}
+
+- (void) setVisible {
+ visible_ = required_ && [self unfiltered];
+}
+
+- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database {
+ if ((self = [super init]) != nil) {
+ _profile(Package$initWithVersion)
+ @synchronized (database) {
+ era_ = [database era];
+ pool_ = pool;
+
+ version_ = version;
+ iterator_ = version.ParentPkg();
+ database_ = database;
+
+ _profile(Package$initWithVersion$Latest)
+ latest_ = (NSString *) StripVersion(version_.VerStr());
+ _end
+
+ pkgCache::VerIterator current;
+ _profile(Package$initWithVersion$Versions)
+ current = iterator_.CurrentVer();
+ if (!current.end())
+ installed_.set(pool_, StripVersion_(current.VerStr()));
+
+ if (!version_.end())
+ file_ = version_.FileList();
+ else {
+ pkgCache &cache([database_ cache]);
+ file_ = pkgCache::VerFileIterator(cache, cache.VerFileP);
+ }
+ _end
+
+ _profile(Package$initWithVersion$Name)
+ id_.set(pool_, iterator_.Name());
+ name_.set(pool, iterator_.Display());
+ _end
+
+ if (!file_.end()) {
+ _profile(Package$initWithVersion$Source)
+ source_ = [database_ getSource:file_.File()];
+ if (source_ != nil)
+ [source_ retain];
+ cached_ = true;
+ _end
+ }
+
+ required_ = true;
+
+ _profile(Package$initWithVersion$Tags)
+ pkgCache::TagIterator tag(iterator_.TagList());
+ if (!tag.end()) {
+ tags_ = [[NSMutableArray alloc] initWithCapacity:8];
+ do {
+ const char *name(tag.Name());
+ [tags_ addObject:(NSString *)CFCString(name)];
+ if (role_ == nil && strncmp(name, "role::", 6) == 0 /*&& strcmp(name, "role::leaper") != 0*/)
+ role_ = (NSString *) CFCString(name + 6);
+ if (required_ && strncmp(name, "require::", 9) == 0 && (
+ true
+ ))
+ required_ = false;
+ ++tag;
+ } while (!tag.end());
+ }
+ _end
+
+ bool changed(false);
+ NSString *key([id_ lowercaseString]);
+
+ _profile(Package$initWithVersion$Metadata)
+ metadata_ = [Packages_ objectForKey:key];
+
+ if (metadata_ == nil) {
+ firstSeen_ = now_;
+
+ metadata_ = [[NSMutableDictionary dictionaryWithObjectsAndKeys:
+ firstSeen_, @"FirstSeen",
+ latest_, @"LastVersion",
+ nil] mutableCopy];
+
+ changed = true;
+ } else {
+ firstSeen_ = [metadata_ objectForKey:@"FirstSeen"];
+ lastSeen_ = [metadata_ objectForKey:@"LastSeen"];
+
+ if (NSNumber *subscribed = [metadata_ objectForKey:@"IsSubscribed"])
+ subscribed_ = [subscribed boolValue];
+
+ NSString *version([metadata_ objectForKey:@"LastVersion"]);
+
+ if (firstSeen_ == nil) {
+ firstSeen_ = lastSeen_ == nil ? now_ : lastSeen_;
+ [metadata_ setObject:firstSeen_ forKey:@"FirstSeen"];
+ changed = true;
+ }
+
+ if (version == nil) {
+ [metadata_ setObject:latest_ forKey:@"LastVersion"];
+ changed = true;
+ } else if (![version isEqualToString:latest_]) {
+ [metadata_ setObject:latest_ forKey:@"LastVersion"];
+ lastSeen_ = now_;
+ [metadata_ setObject:lastSeen_ forKey:@"LastSeen"];
+ changed = true;
+ }
+ }
+
+ metadata_ = [metadata_ retain];
+
+ if (changed) {
+ [Packages_ setObject:metadata_ forKey:key];
+ Changed_ = true;
+ }
+ _end
+
+ _profile(Package$initWithVersion$Section)
+ section_.set(pool_, iterator_.Section());
+ _end
+
+ obsolete_ = [self hasTag:@"cydia::obsolete"];
+ essential_ = ((iterator_->Flags & pkgCache::Flag::Essential) == 0 ? NO : YES) || [self hasTag:@"cydia::essential"];
+ [self setVisible];
+ } _end } return self;
+}
+
++ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database {
+@synchronized ([Database class]) {
+ pkgCache::VerIterator version;
+
+ _profile(Package$packageWithIterator$GetCandidateVer)
+ version = [database policy]->GetCandidateVer(iterator);
+ _end
+
+ if (version.end())
+ return nil;
+
+ return [[[Package alloc]
+ initWithVersion:version
+ withZone:zone
+ inPool:pool
+ database:database
+ ] autorelease];
+} }
+
+- (pkgCache::PkgIterator) iterator {
+ return iterator_;
+}
+
+- (NSString *) section {
+ if (section$_ == nil) {
+ if (section_.empty())
+ return nil;
+
+ std::replace(section_.data(), section_.data() + section_.size(), ' ', '_');
+ NSString *name(section_);
+
+ lookup:
+ if (NSDictionary *value = [SectionMap_ objectForKey:name])
+ if (NSString *rename = [value objectForKey:@"Rename"]) {
+ name = rename;
+ goto lookup;
+ }
+
+ section$_ = [[name stringByReplacingCharacter:'_' withCharacter:' '] retain];
+ } return section$_;
+}
+
+- (NSString *) simpleSection {
+ if (NSString *section = [self section])
+ return Simplify(section);
+ else
+ return nil;
+}
+
+- (NSString *) longSection {
+ return LocalizeSection([self section]);
+}
+
+- (NSString *) shortSection {
+ return [[NSBundle mainBundle] localizedStringForKey:[self simpleSection] value:nil table:@"Sections"];
+}
+
+- (NSString *) uri {
+ return nil;
+#if 0
+ pkgIndexFile *index;
+ pkgCache::PkgFileIterator file(file_.File());
+ if (![database_ list].FindIndex(file, index))
+ return nil;
+ return [NSString stringWithUTF8String:iterator_->Path];
+ //return [NSString stringWithUTF8String:file.Site()];
+ //return [NSString stringWithUTF8String:index->ArchiveURI(file.FileName()).c_str()];
+#endif
+}
+
+- (Address *) maintainer {
+ if (file_.end())
+ return nil;
+ pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
+ const std::string &maintainer(parser->Maintainer());
+ return maintainer.empty() ? nil : [Address addressWithString:[NSString stringWithUTF8String:maintainer.c_str()]];
+}
+
+- (size_t) size {
+ return version_.end() ? 0 : version_->InstalledSize;
+}
+
+- (NSString *) longDescription {
+ if (file_.end())
+ return nil;
+ pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
+ NSString *description([NSString stringWithUTF8String: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), e([lines count]); i != e; ++i) {
+ NSString *trim = [[lines objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace];
+ [trimmed addObject:trim];
+ }
+
+ return [trimmed componentsJoinedByString:@"\n"];
+}
+
+- (NSString *) shortDescription {
+ return tagline_;
+}
+
+- (unichar) index {
+ _profile(Package$index)
+ CFStringRef name((CFStringRef) [self name]);
+ if (CFStringGetLength(name) == 0)
+ return '#';
+ UniChar character(CFStringGetCharacterAtIndex(name, 0));
+ if (!CFUniCharIsMemberOf(character, kCFUniCharLetterCharacterSet))
+ return '#';
+ return toupper(character);
+ _end
+}
+
+- (NSMutableDictionary *) metadata {
+ return metadata_;
+}
+
+- (NSDate *) seen {
+ if (subscribed_ && lastSeen_ != nil)
+ return lastSeen_;
+ return firstSeen_;
+}
+
+- (BOOL) subscribed {
+ return subscribed_;
+}
+
+- (BOOL) ignored {
+ NSDictionary *metadata([self metadata]);
+ if (NSNumber *ignored = [metadata objectForKey:@"IsIgnored"])
+ return [ignored boolValue];
+ else
+ return false;
+}
+
+- (NSString *) latest {
+ return latest_;
+}
+
+- (NSString *) installed {
+ return installed_;
+}
+
+- (BOOL) uninstalled {
+ return installed_.empty();
+}
+
+- (BOOL) valid {
+ return !version_.end();
+}
+
+- (BOOL) upgradableAndEssential:(BOOL)essential {
+ _profile(Package$upgradableAndEssential)
+ pkgCache::VerIterator current(iterator_.CurrentVer());
+ if (current.end())
+ return essential && essential_ && visible_;
+ else
+ return !version_.end() && version_ != current;// && (!essential || ![database_ cache][iterator_].Keep());
+ _end
+}
+
+- (BOOL) essential {
+ return essential_;
+}
+
+- (BOOL) broken {
+ return [database_ cache][iterator_].InstBroken();
+}
+
+- (BOOL) unfiltered {
+ NSString *section([self section]);
+ return !obsolete_ && [self hasSupportingRole] && (section == nil || isSectionVisible(section));
+}
+
+- (BOOL) visible {
+ return visible_;
+}
+
+- (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::ReInstall) != 0)
+ return @"REINSTALL";
+ /*else 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";
+ _nodefault
+ }
+ _nodefault
+ }
+}
+
+- (NSString *) id {
+ return id_;
+}
+
+- (NSString *) name {
+ return name_.empty() ? id_ : name_;
+}
+
+- (UIImage *) icon {
+ NSString *section = [self simpleSection];
+
+ UIImage *icon(nil);
+ if (!icon_.empty())
+ if ([icon_ hasPrefix:@"file:///"])
+ // XXX: correct escaping
+ icon = [UIImage imageAtPath:[icon_ substringFromIndex:7]];
+ if (icon == nil) if (section != nil)
+ icon = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]];
+ if (icon == nil) if (source_ != nil) if (NSString *dicon = [source_ defaultIcon])
+ if ([dicon hasPrefix:@"file:///"])
+ // XXX: correct escaping
+ icon = [UIImage imageAtPath:[dicon substringFromIndex:7]];
+ if (icon == nil)
+ icon = [UIImage applicationImageNamed:@"unknown.png"];
+ return icon;
+}
+
+- (NSString *) homepage {
+ return homepage_;
+}
+
+- (NSString *) depiction {
+ return !depiction_.empty() ? depiction_ : [[self source] depictionForPackage:id_];
+}
+
+- (Address *) sponsor {
+ if (sponsor$_ == nil) {
+ if (sponsor_.empty())
+ return nil;
+ sponsor$_ = [[Address addressWithString:sponsor_] retain];
+ } return sponsor$_;
+}
+
+- (Address *) author {
+ if (author$_ == nil) {
+ if (author_.empty())
+ return nil;
+ author$_ = [[Address addressWithString:author_] retain];
+ } return author$_;
+}
+
+- (NSString *) support {
+ return !bugs_.empty() ? bugs_ : [[self source] supportForPackage:id_];
+}
+
+- (NSArray *) files {
+ NSString *path = [NSString stringWithFormat:@"/var/lib/dpkg/info/%@.list", static_cast<NSString *>(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:UCLocalize("ILLEGAL_PACKAGE_IDENTIFIER")];
+ else for (size_t i(0); i != length; ++i)
+ if (
+ /* XXX: technically this is not allowed */
+ (name[i] < 'A' || name[i] > 'Z') &&
+ (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 user = false;
+ bool _private = false;
+ bool stash = false;
+
+ bool repository = [[self section] isEqualToString:@"Repositories"];
+
+ if (NSArray *files = [self files])
+ for (NSString *file in files)
+ if (!cydia && [file isEqualToString:@"/Applications/Cydia.app"])
+ cydia = true;
+ else if (!user && [file isEqualToString:@"/User"])
+ user = true;
+ else if (!_private && [file isEqualToString:@"/private"])
+ _private = true;
+ else if (!stash && [file isEqualToString:@"/var/stash"])
+ stash = true;
+
+ /* XXX: this is not sensitive enough. only some folders are valid. */
+ if (cydia && !repository)
+ [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"Cydia.app"]];
+ if (user)
+ [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"/User"]];
+ if (_private)
+ [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"/private"]];
+ if (stash)
+ [warnings addObject:[NSString stringWithFormat:UCLocalize("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_) {
+ @synchronized (database_) {
+ if ([database_ era] != era_ || file_.end())
+ source_ = nil;
+ else {
+ source_ = [database_ getSource:file_.File()];
+ if (source_ != nil)
+ [source_ 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:MatchCompareOptions_];
+ if (range.location != NSNotFound)
+ return YES;
+
+ range = [[self name] rangeOfString:text options:MatchCompareOptions_];
+ if (range.location != NSNotFound)
+ return YES;
+
+ range = [[self shortDescription] rangeOfString:text options:MatchCompareOptions_];
+ 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;
+}
+
+- (bool) isCommercial {
+ return [self hasTag:@"cydia::commercial"];
+}
+
+- (CYString &) cyname {
+ return name_.empty() ? id_ : name_;
+}
+
+- (uint32_t) compareBySection:(NSArray *)sections {
+ NSString *section([self section]);
+ for (size_t i(0), e([sections count]); i != e; ++i) {
+ if ([section isEqualToString:[[sections objectAtIndex:i] name]])
+ return i;
+ }
+
+ return _not(uint32_t);
+}
+
+- (uint32_t) compareForChanges {
+ union {
+ uint32_t key;
+
+ struct {
+ uint32_t timestamp : 30;
+ uint32_t ignored : 1;
+ uint32_t upgradable : 1;
+ } bits;
+ } value;
+
+ bool upgradable([self upgradableAndEssential:YES]);
+ value.bits.upgradable = upgradable ? 1 : 0;
+
+ if (upgradable) {
+ 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) clear {
+ pkgProblemResolver *resolver = [database_ resolver];
+ resolver->Clear(iterator_);
+ resolver->Protect(iterator_);
+}
+
+- (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);
+}
+
+- (bool) isUnfilteredAndSearchedForBy:(NSString *)search {
+ _profile(Package$isUnfilteredAndSearchedForBy)
+ bool value(true);
+
+ _profile(Package$isUnfilteredAndSearchedForBy$Unfiltered)
+ value &= [self unfiltered];
+ _end
+
+ _profile(Package$isUnfilteredAndSearchedForBy$Match)
+ value &= [self matches:search];
+ _end
+
+ return value;
+ _end
+}
+
+- (bool) isUnfilteredAndSelectedForBy:(NSString *)search {
+ if ([search length] == 0)
+ return false;
+
+ _profile(Package$isUnfilteredAndSelectedForBy)
+ bool value(true);
+
+ _profile(Package$isUnfilteredAndSelectedForBy$Unfiltered)
+ value &= [self unfiltered];
+ _end
+
+ _profile(Package$isUnfilteredAndSelectedForBy$Match)
+ value &= [[self name] compare:search options:MatchCompareOptions_ range:NSMakeRange(0, [search length])] == NSOrderedSame;
+ _end
+
+ return value;
+ _end
+}
+
+- (bool) isInstalledAndVisible:(NSNumber *)number {
+ return (![number boolValue] || [self visible]) && ![self uninstalled];
+}
+
+- (bool) isVisibleInSection:(NSString *)name {
+ NSString *section = [self section];
+
+ return
+ [self visible] && (
+ name == nil ||
+ section == nil && [name length] == 0 ||
+ [name isEqualToString:section]
+ );
+}
+
+- (bool) isVisibleInSource:(Source *)source {
+ return [self source] == source && [self visible];
+}
+
+@end
+/* }}} */
+/* Section Class {{{ */
+@interface Section : NSObject {
+ NSString *name_;
+ unichar index_;
+ size_t row_;
+ size_t count_;
+ NSString *localized_;
+}
+
+- (NSComparisonResult) compareByLocalized:(Section *)section;
+- (Section *) initWithName:(NSString *)name localized:(NSString *)localized;
+- (Section *) initWithName:(NSString *)name localize:(BOOL)localize;
+- (Section *) initWithName:(NSString *)name row:(size_t)row localize:(BOOL)localize;
+- (Section *) initWithIndex:(unichar)index row:(size_t)row;
+- (NSString *) name;
+- (unichar) index;
+
+- (size_t) row;
+- (size_t) count;
+
+- (void) addToRow;
+- (void) addToCount;
+
+- (void) setCount:(size_t)count;
+- (NSString *) localized;
+
+@end
+
+@implementation Section
+
+- (void) dealloc {
+ [name_ release];
+ if (localized_ != nil)
+ [localized_ release];
+ [super dealloc];
+}
+
+- (NSComparisonResult) compareByLocalized:(Section *)section {
+ NSString *lhs(localized_);
+ NSString *rhs([section localized]);
+
+ /*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:LaxCompareOptions_];
+}
+
+- (Section *) initWithName:(NSString *)name localized:(NSString *)localized {
+ if ((self = [self initWithName:name localize:NO]) != nil) {
+ if (localized != nil)
+ localized_ = [localized retain];
+ } return self;
+}
+
+- (Section *) initWithName:(NSString *)name localize:(BOOL)localize {
+ return [self initWithName:name row:0 localize:localize];
+}
+
+- (Section *) initWithName:(NSString *)name row:(size_t)row localize:(BOOL)localize {
+ if ((self = [super init]) != nil) {
+ name_ = [name retain];
+ index_ = '\0';
+ row_ = row;
+ if (localize)
+ localized_ = [LocalizeSection(name_) retain];
+ } return self;
+}
+
+/* XXX: localize the index thingees */
+- (Section *) initWithIndex:(unichar)index row:(size_t)row {
+ if ((self = [super init]) != nil) {
+ name_ = [[NSString stringWithCharacters:&index length:1] retain];
+ index_ = index;
+ row_ = row;
+ } return self;
+}
+
+- (NSString *) name {
+ return name_;
+}
+
+- (unichar) index {
+ return index_;
+}
+
+- (size_t) row {
+ return row_;
+}
+
+- (size_t) count {
+ return count_;
+}
+
+- (void) addToRow {
+ ++row_;
+}
+
+- (void) addToCount {
+ ++count_;
+}
+
+- (void) setCount:(size_t)count {
+ count_ = count;
+}
+
+- (NSString *) localized {
+ return localized_;
+}
+
+@end
+/* }}} */
+
+static NSString *Colon_;
+static NSString *Error_;
+static NSString *Warning_;
+
+/* Database Implementation {{{ */
+@implementation Database
+
++ (Database *) sharedInstance {
+ static Database *instance;
+ if (instance == nil)
+ instance = [[Database alloc] init];
+ return instance;
+}
+
+- (unsigned) era {
+ return era_;
+}
+
+- (void) dealloc {
+ _assert(false);
+ NSRecycleZone(zone_);
+ // XXX: malloc_destroy_zone(zone_);
+ apr_pool_destroy(pool_);
+ [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;
+ }
+ }
+
+ _assume(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(_setProgressErrorPackage:)
+ withObject:[NSArray arrayWithObjects:string, id, nil]
+ waitUntilDone:YES
+ ];
+ else if (type == "pmstatus") {
+ [delegate_ setProgressTitle:string];
+ } else if (type == "pmconffile")
+ [delegate_ setConfigurationData:string];
+ else
+ lprintf("E:unknown pmstatus\n");
+ } else
+ lprintf("E:unknown status\n");
+ }
+
+ _assume(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()]];
+ }
+
+ _assume(false);
+}
+
+- (FILE *) input {
+ return input_;
+}
+
+- (Package *) packageWithName:(NSString *)name {
+@synchronized ([Database class]) {
+ if (static_cast<pkgDepCache *>(cache_) == NULL)
+ return nil;
+ pkgCache::PkgIterator iterator(cache_->FindPkg([name UTF8String]));
+ return iterator.end() ? nil : [Package packageWithIterator:iterator withZone:NULL inPool:pool_ database:self];
+} }
+
+- (id) init {
+ if ((self = [super init]) != nil) {
+ policy_ = NULL;
+ records_ = NULL;
+ resolver_ = NULL;
+ fetcher_ = NULL;
+ lock_ = NULL;
+
+ zone_ = NSCreateZone(1024 * 1024, 256 * 1024, NO);
+ apr_pool_create(&pool_, NULL);
+
+ packages_ = [[NSMutableArray alloc] init];
+
+ 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_;
+}
+
+- (pkgSourceList &) list {
+ return *list_;
+}
+
+- (NSArray *) packages {
+ return packages_;
+}
+
+- (NSArray *) sources {
+ NSMutableArray *sources([NSMutableArray arrayWithCapacity:sources_.size()]);
+ for (SourceMap::const_iterator i(sources_.begin()); i != sources_.end(); ++i)
+ [sources addObject:i->second];
+ return sources;
+}
+
+- (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()]];
+
+ NSString *name([NSString stringWithUTF8String:start.TargetPkg().Name()]);
+ if (Package *package = [self packageWithName:name])
+ name = [package name];
+ [failure addObject: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;
+}
+
+- (bool) popErrorWithTitle:(NSString *)title {
+ bool fatal(false);
+ std::string message;
+
+ while (!_error->empty()) {
+ std::string error;
+ bool warning(!_error->PopMessage(error));
+ if (!warning)
+ fatal = true;
+ for (;;) {
+ size_t size(error.size());
+ if (size == 0 || error[size - 1] != '\n')
+ break;
+ error.resize(size - 1);
+ }
+ lprintf("%c:[%s]\n", warning ? 'W' : 'E', error.c_str());
+
+ if (!message.empty())
+ message += "\n\n";
+ message += error;
+ }
+
+ if (fatal && !message.empty())
+ [delegate_ _setProgressError:[NSString stringWithUTF8String:message.c_str()] withTitle:[NSString stringWithFormat:Colon_, fatal ? Error_ : Warning_, title]];
+
+ return fatal;
+}
+
+- (bool) popErrorWithTitle:(NSString *)title forOperation:(bool)success {
+ return [self popErrorWithTitle:title] || !success;
+}
+
+- (void) reloadData { _pooled
+@synchronized ([Database class]) {
+ @synchronized (self) {
+ ++era_;
+ }
+
+ [packages_ removeAllObjects];
+ sources_.clear();
+
+ _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;
+
+ if (now_ != nil) {
+ [now_ release];
+ now_ = nil;
+ }
+
+ cache_.Close();
+
+ apr_pool_clear(pool_);
+ NSRecycleZone(zone_);
+
+ int chk(creat("/tmp/cydia.chk", 0644));
+ if (chk != -1)
+ close(chk);
+
+ NSString *title(UCLocalize("DATABASE"));
+
+ _trace();
+ if (!cache_.Open(progress_, true)) { pop:
+ std::string error;
+ bool warning(!_error->PopMessage(error));
+ 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
+ [delegate_ _setProgressError:[NSString stringWithUTF8String:error.c_str()] withTitle:[NSString stringWithFormat:Colon_, warning ? Warning_ : Error_, title]];
+
+ if (warning)
+ goto pop;
+ _error->Discard();
+ return;
+ }
+ _trace();
+
+ unlink("/tmp/cydia.chk");
+
+ 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();
+ if ([self popErrorWithTitle:title forOperation:list_->ReadMainList()])
+ return;
+
+ if (cache_->DelCount() != 0 || cache_->InstCount() != 0) {
+ [delegate_ _setProgressError:@"COUNTS_NONZERO_EX" withTitle:title];
+ return;
+ }
+
+ if ([self popErrorWithTitle:title forOperation:pkgApplyStatus(cache_)])
+ return;
+
+ if (cache_->BrokenCount() != 0) {
+ if ([self popErrorWithTitle:title forOperation:pkgFixBroken(cache_)])
+ return;
+
+ if (cache_->BrokenCount() != 0) {
+ [delegate_ _setProgressError:@"STILL_BROKEN_EX" withTitle:title];
+ return;
+ }
+
+ if ([self popErrorWithTitle:title forOperation:pkgMinimizeUpgrade(cache_)])
+ return;
+ }
+
+ _trace();
+
+ 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)
+ // XXX: this could be more intelligent
+ if (dynamic_cast<debPackagesIndex *>(*index) != NULL) {
+ pkgCache::PkgFileIterator cached((*index)->FindInCache(cache_));
+ if (!cached.end())
+ sources_[cached->ID] = [[[Source alloc] initWithMetaIndex:*source inPool:pool_] autorelease];
+ }
+ }
+
+ _trace();
+
+ {
+ /*std::vector<Package *> packages;
+ packages.reserve(std::max(10000U, [packages_ count] + 1000));
+ [packages_ release];
+ packages_ = nil;*/
+
+ _trace();
+
+ for (pkgCache::PkgIterator iterator = cache_->PkgBegin(); !iterator.end(); ++iterator)
+ if (Package *package = [Package packageWithIterator:iterator withZone:zone_ inPool:pool_ database:self])
+ //packages.push_back(package);
+ [packages_ addObject:package];
+
+ _trace();
+
+ /*if (packages.empty())
+ packages_ = [[NSArray alloc] init];
+ else
+ packages_ = [[NSArray alloc] initWithObjects:&packages.front() count:packages.size()];
+ _trace();*/
+
+ [packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(16)];
+ [packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(4)];
+ [packages_ radixSortUsingFunction:reinterpret_cast<SKRadixFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(0)];
+
+ /*_trace();
+ PrintTimes();
+ _trace();*/
+
+ _trace();
+
+ /*if (!packages.empty())
+ CFQSortArray(&packages.front(), packages.size(), sizeof(packages.front()), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare_), NULL);*/
+ //std::sort(packages.begin(), packages.end(), PackageNameOrdering());
+
+ //CFArraySortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
+
+ CFArrayInsertionSortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
+
+ //[packages_ sortUsingFunction:reinterpret_cast<NSComparisonResult (*)(id, id, void *)>(&PackageNameCompare) context:NULL];
+
+ _trace();
+ }
+} }
+
+- (void) configure {
+ NSString *dpkg = [NSString stringWithFormat:@"dpkg --configure -a --status-fd %u", statusfd_];
+ system([dpkg UTF8String]);
+}
+
+- (bool) clean {
+ // XXX: I don't remember this condition
+ if (lock_ != NULL)
+ return false;
+
+ FileFd Lock;
+ Lock.Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
+
+ NSString *title(UCLocalize("CLEAN_ARCHIVES"));
+
+ if ([self popErrorWithTitle:title])
+ return false;
+
+ 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 ([self popErrorWithTitle:title forOperation:cleaner.Go(_config->FindDir("Dir::Cache::Archives") + "partial/", cache_)])
+ return false;
+
+ return true;
+}
+
+- (bool) prepare {
+ fetcher_->Shutdown();
+
+ pkgRecords records(cache_);
+
+ lock_ = new FileFd();
+ lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
+
+ NSString *title(UCLocalize("PREPARE_ARCHIVES"));
+
+ if ([self popErrorWithTitle:title])
+ return false;
+
+ pkgSourceList list;
+ if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
+ return false;
+
+ manager_ = (_system->CreatePM(cache_));
+ if ([self popErrorWithTitle:title forOperation:manager_->GetArchives(fetcher_, &list, &records)])
+ return false;
+
+ return true;
+}
+
+- (void) perform {
+ NSString *title(UCLocalize("PERFORM_SELECTIONS"));
+
+ NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
+ pkgSourceList list;
+ if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
+ return;
+ 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;
+ if ((*item)->Status == pkgAcquire::Item::StatIdle)
+ 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(_setProgressErrorPackage:)
+ 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;
+ if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
+ return;
+ 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];
+}
+
+- (bool) upgrade {
+ NSString *title(UCLocalize("UPGRADE"));
+ if ([self popErrorWithTitle:title forOperation:pkgDistUpgrade(cache_)])
+ return false;
+ return true;
+}
+
+- (void) update {
+ [self updateWithStatus:status_];
+}
+
+- (void) setVisible {
+ for (Package *package in packages_)
+ [package setVisible];
+}
+
+- (void) updateWithStatus:(Status &)status {
+ _transient NSObject<ProgressDelegate> *delegate(status.getDelegate());
+ NSString *title(UCLocalize("REFRESHING_DATA"));
+
+ pkgSourceList list;
+ if (!list.ReadMainList())
+ [delegate _setProgressError:@"Unable to read source list." withTitle:title];
+
+ FileFd lock;
+ lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
+ if ([self popErrorWithTitle:title])
+ return;
+
+ if ([self popErrorWithTitle:title forOperation:ListUpdate(status, list, PulseInterval_)])
+ /* XXX: ignore this because users suck and don't understand why refreshing is important: return */
+ /* XXX: why the hell is an empty if statement a clang error? */ (void) 0;
+
+ [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"];
+ Changed_ = true;
+}
+
+- (void) setDelegate:(id)delegate {
+ delegate_ = delegate;
+ status_.setDelegate(delegate);
+ progress_.setDelegate(delegate);
+}
+
+- (Source *) getSource:(pkgCache::PkgFileIterator)file {
+ SourceMap::const_iterator i(sources_.find(file->ID));
+ return i == sources_.end() ? nil : i->second;
+}
+
+@end
+/* }}} */
+
+/* Confirmation Controller {{{ */
+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;
+}
+/* }}} */
+
+/* Web Scripting {{{ */
+@interface CydiaObject : NSObject {
+ id indirect_;
+ id delegate_;
+}
+
+- (id) initWithDelegate:(IndirectDelegate *)indirect;
+@end
+
+@implementation CydiaObject
+
+- (void) dealloc {
+ [indirect_ release];
+ [super dealloc];
+}
+
+- (id) initWithDelegate:(IndirectDelegate *)indirect {
+ if ((self = [super init]) != nil) {
+ indirect_ = [indirect retain];
+ } return self;
+}
+
+- (void) setDelegate:(id)delegate {
+ delegate_ = delegate;
+}
+
++ (NSArray *) _attributeKeys {
+ return [NSArray arrayWithObjects:@"device", @"firewire", @"imei", @"mac", @"serial", nil];
+}
+
+- (NSArray *) attributeKeys {
+ return [[self class] _attributeKeys];
+}
+
++ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
+ return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
+}
+
+- (NSString *) device {
+ return [[UIDevice currentDevice] uniqueIdentifier];
+}
+
+#if 0 // XXX: implement!
+- (NSString *) mac {
+ if (![indirect_ promptForSensitive:@"Mac Address"])
+ return nil;
+}
+
+- (NSString *) serial {
+ if (![indirect_ promptForSensitive:@"Serial #"])
+ return nil;
+}
+
+- (NSString *) firewire {
+ if (![indirect_ promptForSensitive:@"Firewire GUID"])
+ return nil;
+}
+
+- (NSString *) imei {
+ if (![indirect_ promptForSensitive:@"IMEI"])
+ return nil;
+}
+#endif
+
++ (NSString *) webScriptNameForSelector:(SEL)selector {
+ if (selector == @selector(close))
+ return @"close";
+ else if (selector == @selector(getInstalledPackages))
+ return @"getInstalledPackages";
+ else if (selector == @selector(getPackageById:))
+ return @"getPackageById";
+ else if (selector == @selector(installPackages:))
+ return @"installPackages";
+ else if (selector == @selector(setAutoPopup:))
+ return @"setAutoPopup";
+ else if (selector == @selector(setButtonImage:withStyle:toFunction:))
+ return @"setButtonImage";
+ else if (selector == @selector(setButtonTitle:withStyle:toFunction:))
+ return @"setButtonTitle";
+ else if (selector == @selector(setFinishHook:))
+ return @"setFinishHook";
+ else if (selector == @selector(setPopupHook:))
+ return @"setPopupHook";
+ else if (selector == @selector(setSpecial:))
+ return @"setSpecial";
+ else if (selector == @selector(setToken:))
+ return @"setToken";
+ else if (selector == @selector(setViewportWidth:))
+ return @"setViewportWidth";
+ else if (selector == @selector(supports:))
+ return @"supports";
+ else if (selector == @selector(stringWithFormat:arguments:))
+ return @"format";
+ else if (selector == @selector(localizedStringForKey:value:table:))
+ return @"localize";
+ else if (selector == @selector(du:))
+ return @"du";
+ else if (selector == @selector(statfs:))
+ return @"statfs";
+ else
+ return nil;
+}
+
++ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
+ return [self webScriptNameForSelector:selector] == nil;
+}
+
+- (BOOL) supports:(NSString *)feature {
+ return [feature isEqualToString:@"window.open"];
+}
+
+- (NSArray *) getInstalledPackages {
+ NSArray *packages([[Database sharedInstance] packages]);
+ NSMutableArray *installed([NSMutableArray arrayWithCapacity:[packages count]]);
+ for (Package *package in packages)
+ if ([package installed] != nil)
+ [installed addObject:package];
+ return installed;
+}
+
+- (Package *) getPackageById:(NSString *)id {
+ Package *package([[Database sharedInstance] packageWithName:id]);
+ [package parse];
+ return package;
+}
+
+- (NSArray *) statfs:(NSString *)path {
+ struct statfs stat;
+
+ if (path == nil || statfs([path UTF8String], &stat) == -1)
+ return nil;
+
+ return [NSArray arrayWithObjects:
+ [NSNumber numberWithUnsignedLong:stat.f_bsize],
+ [NSNumber numberWithUnsignedLong:stat.f_blocks],
+ [NSNumber numberWithUnsignedLong:stat.f_bfree],
+ nil];
+}
+
+- (NSNumber *) du:(NSString *)path {
+ NSNumber *value(nil);
+
+ int fds[2];
+ _assert(pipe(fds) != -1);
+
+ pid_t pid(ExecFork());
+ if (pid == 0) {
+ _assert(dup2(fds[1], 1) != -1);
+ _assert(close(fds[0]) != -1);
+ _assert(close(fds[1]) != -1);
+ /* XXX: this should probably not use du */
+ execl("/usr/libexec/cydia/du", "du", "-s", [path UTF8String], NULL);
+ exit(1);
+ _assert(false);
+ }
+
+ _assert(close(fds[1]) != -1);
+
+ if (FILE *du = fdopen(fds[0], "r")) {
+ char line[1024];
+ while (fgets(line, sizeof(line), du) != NULL) {
+ size_t length(strlen(line));
+ while (length != 0 && line[length - 1] == '\n')
+ line[--length] = '\0';
+ if (char *tab = strchr(line, '\t')) {
+ *tab = '\0';
+ value = [NSNumber numberWithUnsignedLong:strtoul(line, NULL, 0)];
+ }
+ }
+
+ fclose(du);
+ } else _assert(close(fds[0]));
+
+ int status;
+ wait:
+ if (waitpid(pid, &status, 0) == -1)
+ if (errno == EINTR)
+ goto wait;
+ else _assert(false);
+
+ return value;
+}
+
+- (void) close {
+ [indirect_ close];
+}
+
+- (void) installPackages:(NSArray *)packages {
+ [delegate_ performSelectorOnMainThread:@selector(installPackages:) withObject:packages waitUntilDone:NO];
+}
+
+- (void) setAutoPopup:(BOOL)popup {
+ [indirect_ setAutoPopup:popup];
+}
+
+- (void) setButtonImage:(NSString *)button withStyle:(NSString *)style toFunction:(id)function {
+ [indirect_ setButtonImage:button withStyle:style toFunction:function];
+}
+
+- (void) setButtonTitle:(NSString *)button withStyle:(NSString *)style toFunction:(id)function {
+ [indirect_ setButtonTitle:button withStyle:style toFunction:function];
+}
+
+- (void) setSpecial:(id)function {
+ [indirect_ setSpecial:function];
+}
+
+- (void) setToken:(NSString *)token {
+ if (Token_ != nil)
+ [Token_ release];
+ Token_ = [token retain];
+
+ [Metadata_ setObject:Token_ forKey:@"Token"];
+ Changed_ = true;
+}
+
+- (void) setFinishHook:(id)function {
+ [indirect_ setFinishHook:function];
+}
+
+- (void) setPopupHook:(id)function {
+ [indirect_ setPopupHook:function];
+}
+
+- (void) setViewportWidth:(float)width {
+ [indirect_ setViewportWidth:width];
+}
+
+- (NSString *) stringWithFormat:(NSString *)format arguments:(WebScriptObject *)arguments {
+ //NSLog(@"SWF:\"%@\" A:%@", format, [arguments description]);
+ unsigned count([arguments count]);
+ id values[count];
+ for (unsigned i(0); i != count; ++i)
+ values[i] = [arguments objectAtIndex:i];
+ return [[[NSString alloc] initWithFormat:format arguments:*(reinterpret_cast<va_list *>(&values))] autorelease];
+}
+
+- (NSString *) localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)table {
+ if (reinterpret_cast<id>(value) == [WebUndefined undefined])
+ value = nil;
+ if (reinterpret_cast<id>(table) == [WebUndefined undefined])
+ table = nil;
+ return [[NSBundle mainBundle] localizedStringForKey:key value:value table:table];
+}
+
+@end
+/* }}} */
+
+/* Cydia Browser Controller {{{ */
+@interface CYBrowserController : BrowserController {
+ CydiaObject *cydia_;
+}
+
+@end
+
+@implementation CYBrowserController
+
+- (void) dealloc {
+ [cydia_ release];
+ [super dealloc];
+}
+
+- (void) setHeaders:(NSDictionary *)headers forHost:(NSString *)host {
+}
+
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+ [super webView:sender didClearWindowObject:window forFrame:frame];
+
+ WebDataSource *source([frame dataSource]);
+ NSURLResponse *response([source response]);
+ NSURL *url([response URL]);
+ NSString *scheme([url scheme]);
+
+ NSHTTPURLResponse *http;
+ if (scheme != nil && ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]))
+ http = (NSHTTPURLResponse *) response;
+ else
+ http = nil;
+
+ NSDictionary *headers([http allHeaderFields]);
+ NSString *host([url host]);
+ [self setHeaders:headers forHost:host];
+
+ if (
+ [host isEqualToString:@"cydia.saurik.com"] ||
+ [host hasSuffix:@".cydia.saurik.com"] ||
+ [scheme isEqualToString:@"file"]
+ )
+ [window setValue:cydia_ forKey:@"cydia"];
+}
+
+- (void) _setMoreHeaders:(NSMutableURLRequest *)request {
+ if (System_ != NULL)
+ [request setValue:System_ forHTTPHeaderField:@"X-System"];
+ if (Machine_ != NULL)
+ [request setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"];
+ if (Token_ != nil)
+ [request setValue:Token_ forHTTPHeaderField:@"X-Cydia-Token"];
+ if (Role_ != nil)
+ [request setValue:Role_ forHTTPHeaderField:@"X-Role"];
+}
+
+- (NSURLRequest *) webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)source {
+ NSMutableURLRequest *copy = [request mutableCopy];
+ [self _setMoreHeaders:copy];
+ return copy;
+}
+
+- (void) setDelegate:(id)delegate {
+ [super setDelegate:delegate];
+ [cydia_ setDelegate:delegate];
+}
+
+- (id) init {
+ if ((self = [super initWithWidth:0 ofClass:[CYBrowserController class]]) != nil) {
+ cydia_ = [[CydiaObject alloc] initWithDelegate:indirect_];
+
+ WebView *webview([document_ webView]);
+
+ Package *package([[Database sharedInstance] packageWithName:@"cydia"]);
+
+ NSString *application = package == nil ? @"Cydia" : [NSString
+ stringWithFormat:@"Cydia/%@",
+ [package installed]
+ ];
+
+ if (Safari_ != nil)
+ application = [NSString stringWithFormat:@"Safari/%@ %@", Safari_, application];
+ if (Build_ != nil)
+ application = [NSString stringWithFormat:@"Mobile/%@ %@", Build_, application];
+ if (Product_ != nil)
+ application = [NSString stringWithFormat:@"Version/%@ %@", Product_, application];
+
+ [webview setApplicationNameForUserAgent:application];
+ } return self;
+}
+
+@end
+/* }}} */
+
+/* Confirmation {{{ */
+@protocol ConfirmationControllerDelegate
+- (void) cancelAndClear:(bool)clear;
+- (void) confirmWithNavigationController:(UINavigationController *)navigation;
+- (void) queue;
+@end
+
+@interface ConfirmationController : CYBrowserController {
+ _transient Database *database_;
+ UIAlertView *essential_;
+ NSArray *changes_;
+ NSArray *issues_;
+ NSArray *sizes_;
+ BOOL substrate_;
+}
+
+- (id) initWithDatabase:(Database *)database;
+
+@end
+
+@implementation ConfirmationController
+
+- (void) dealloc {
+ [changes_ release];
+ if (issues_ != nil)
+ [issues_ release];
+ [sizes_ release];
+ if (essential_ != nil)
+ [essential_ release];
+ [super dealloc];
+}
+
+- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
+ NSString *context([alert context]);
+
+ if ([context isEqualToString:@"remove"]) {
+ if (button == [alert cancelButtonIndex]) {
+ [self dismissModalViewControllerAnimated:YES];
+ } else if (button == [alert firstOtherButtonIndex]) {
+ if (substrate_)
+ Finish_ = 2;
+ [delegate_ confirmWithNavigationController:[self navigationController]];
+ }
+
+ [alert dismissWithClickedButtonIndex:-1 animated:YES];
+ } else if ([context isEqualToString:@"unable"]) {
+ [self dismissModalViewControllerAnimated:YES];
+ [alert dismissWithClickedButtonIndex:-1 animated:YES];
+ } else {
+ [super alertView:alert clickedButtonAtIndex:button];
+ }
+}
+
+- (id) invokeDefaultMethodWithArguments:(NSArray *)args {
+ [self dismissModalViewControllerAnimated:YES];
+ [delegate_ cancelAndClear:NO];
+
+ return nil;
+}
+
+- (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
+ [super webView:sender didClearWindowObject:window forFrame:frame];
+ [window setValue:changes_ forKey:@"changes"];
+ [window setValue:issues_ forKey:@"issues"];
+ [window setValue:sizes_ forKey:@"sizes"];
+ [window setValue:self forKey:@"queue"];
+}
+
+- (id) initWithDatabase:(Database *)database {
+ if ((self = [super init]) != nil) {
+ database_ = database;
+
+ [[self navigationItem] setTitle:UCLocalize("CONFIRM")];
+
+ 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 (Package *package in packages) {
+ 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_) {
+ NSString *parenthetical(UCLocalize("PARENTHETICAL"));
+
+ essential_ = [[UIAlertView alloc]
+ initWithTitle:UCLocalize("REMOVING_ESSENTIALS")
+ message:UCLocalize("REMOVING_ESSENTIALS_EX")
+ delegate:self
+ cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")]
+ otherButtonTitles:[NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")], nil
+ ];
+
+ [essential_ setContext:@"remove"];
+ } else {
+ essential_ = [[UIAlertView alloc]
+ initWithTitle:UCLocalize("UNABLE_TO_COMPLY")
+ message:UCLocalize("UNABLE_TO_COMPLY_EX")
+ delegate:self
+ cancelButtonTitle:UCLocalize("OKAY")
+ otherButtonTitles:nil
+ ];
+
+ [essential_ setContext:@"unable"];
+ }
+
+ 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()),
+ nil];
+
+ [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"confirm" ofType:@"html"]]];
+
+ UIBarButtonItem *leftItem = [[UIBarButtonItem alloc]
+ initWithTitle:UCLocalize("CANCEL")
+ // OLD: [NSString stringWithFormat:UCLocalize("SLASH_DELIMITED"), UCLocalize("CANCEL"), UCLocalize("QUEUE")]
+ style:UIBarButtonItemStylePlain
+ target:self
+ action:@selector(cancelButtonClicked)
+ ];
+ [[self navigationItem] setLeftBarButtonItem:leftItem];
+ [leftItem release];
+ } return self;
+}
+
+- (void) applyRightButton {
+ UIBarButtonItem *rightItem = [[UIBarButtonItem alloc]
+ initWithTitle:UCLocalize("CONFIRM")
+ style:UIBarButtonItemStylePlain
+ target:self
+ action:@selector(confirmButtonClicked)
+ ];
+#if !AlwaysReload && !IgnoreInstall
+ if (issues_ == nil && ![self isLoading]) [[self navigationItem] setRightBarButtonItem:rightItem];
+ else [super applyRightButton];
+#else
+ [[self navigationItem] setRightBarButtonItem:nil];
+#endif
+ [rightItem release];
+}
+
+- (void) cancelButtonClicked {
+ [self dismissModalViewControllerAnimated:YES];
+ [delegate_ cancelAndClear:YES];
+}
+
+#if !AlwaysReload
+- (void) confirmButtonClicked {
+#if IgnoreInstall
+ return;
+#endif
+ if (essential_ != nil)
+ [essential_ show];
+ else {
+ if (substrate_)
+ Finish_ = 2;
+ [delegate_ confirmWithNavigationController:[self navigationController]];
+ }
+}
+#endif
+
+@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 Controller {{{ */
+@interface ProgressController : CYViewController <
+ ConfigurationDelegate,
+ ProgressDelegate
+> {
+ _transient Database *database_;
+ UIProgressBar *progress_;
+ UITextView *output_;
+ UITextLabel *status_;
+ UIPushButton *close_;
+ BOOL running_;
+ SHA1SumValue springlist_;
+ SHA1SumValue notifyconf_;
+ NSString *title_;
+}
+
+- (id) initWithDatabase:(Database *)database delegate:(id)delegate;
+
+- (void) _retachThread;
+- (void) _detachNewThreadData:(ProgressData *)data;
+- (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title;
+
+- (BOOL) isRunning;
+
+@end
+
+@protocol ProgressControllerDelegate
+- (void) progressControllerIsComplete:(ProgressController *)sender;
+@end
+
+@implementation ProgressController
+
+- (void) dealloc {
+ [database_ setDelegate:nil];
+ [progress_ release];
+ [output_ release];
+ [status_ release];
+ [close_ release];
+ if (title_ != nil)
+ [title_ release];
+ [super dealloc];
+}
+
+- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
+ if ((self = [super init]) != nil) {
+ database_ = database;
+ [database_ setDelegate:self];
+ delegate_ = delegate;
+
+ [[self view] setBackgroundColor:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]];
+
+ progress_ = [[UIProgressBar alloc] init];
+ [progress_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
+ [progress_ setStyle:0];
+
+ status_ = [[UITextLabel alloc] init];
+ [status_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
+ [status_ setColor:[UIColor whiteColor]];
+ [status_ setBackgroundColor:[UIColor clearColor]];
+ [status_ setCentersHorizontally:YES];
+ //[status_ setFont:font];
+
+ output_ = [[UITextView alloc] init];
+
+ [output_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
+ //[output_ setTextFont:@"Courier New"];
+ [output_ setFont:[[output_ font] fontWithSize:12]];
+ [output_ setTextColor:[UIColor whiteColor]];
+ [output_ setBackgroundColor:[UIColor clearColor]];
+ [output_ setMarginTop:0];
+ [output_ setAllowsRubberBanding:YES];
+ [output_ setEditable:NO];
+ [[self view] addSubview:output_];
+
+ close_ = [[UIPushButton alloc] init];
+ [close_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin)];
+ [close_ setAutosizesToFit:NO];
+ [close_ setDrawsShadow:YES];
+ [close_ setStretchBackground:YES];
+ [close_ setEnabled:YES];
+ [close_ setTitleFont:[UIFont boldSystemFontOfSize:22]];
+ [close_ addTarget:self action:@selector(closeButtonPushed) forEvents:UIControlEventTouchUpInside];
+ [close_ setBackground:[UIImage applicationImageNamed:@"green-up.png"] forState:0];
+ [close_ setBackground:[UIImage applicationImageNamed:@"green-dn.png"] forState:1];
+ } return self;
+}
+
+- (void) positionViews {
+ CGRect bounds = [[self view] bounds];
+ CGSize prgsize = [UIProgressBar defaultSize];
+
+ CGRect prgrect = {{
+ (bounds.size.width - prgsize.width) / 2,
+ bounds.size.height - prgsize.height - 64
+ }, prgsize};
+
+ float closewidth = bounds.size.width - 20;
+ if (closewidth > 300) closewidth = 300;
+
+ [progress_ setFrame:prgrect];
+ [status_ setFrame:CGRectMake(
+ 10,
+ bounds.size.height - prgsize.height - 94,
+ bounds.size.width - 20,
+ 24
+ )];
+ [output_ setFrame:CGRectMake(
+ 10,
+ 20,
+ bounds.size.width - 20,
+ bounds.size.height - 106
+ )];
+ [close_ setFrame:CGRectMake(
+ (bounds.size.width - closewidth) / 2,
+ bounds.size.height - prgsize.height - 94,
+ closewidth,
+ 32 + prgsize.height
+ )];
+}
+
+- (void) viewWillAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+ [[self navigationItem] setHidesBackButton:YES];
+ [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack];
+
+ [self positionViews];
+}
+
+- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
+ [self positionViews];
+}
+
+- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
+ NSString *context([alert context]);
+
+ if ([context isEqualToString:@"conffile"]) {
+ FILE *input = [database_ input];
+ if (button == [alert cancelButtonIndex]) fprintf(input, "N\n");
+ else if (button == [alert firstOtherButtonIndex]) fprintf(input, "Y\n");
+ fflush(input);
+ }
+}
+
+- (void) closeButtonPushed {
+ running_ = NO;
+
+ UpdateExternalStatus(0);
+
+ switch (Finish_) {
+ case 0:
+ [self dismissModalViewControllerAnimated:YES];
+ break;
+
+ case 1:
+ [delegate_ terminateWithSuccess];
+ /*if ([delegate_ respondsToSelector:@selector(suspendWithAnimation:)])
+ [delegate_ suspendWithAnimation:YES];
+ else
+ [delegate_ suspend];*/
+ 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 {
+ [[self navigationItem] setTitle:UCLocalize("COMPLETE")];
+
+ [[self view] addSubview:close_];
+ [progress_ removeFromSuperview];
+ [status_ removeFromSuperview];
+
+ [database_ popErrorWithTitle:title_];
+ [delegate_ progressControllerIsComplete:self];
+
+ if (Finish_ < 4) {
+ FileFd file;
+ if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
+ _error->Discard();
+ else {
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ if (!(notifyconf_ == sha1.Result()))
+ Finish_ = 4;
+ }
+ }
+
+ if (Finish_ < 3) {
+ FileFd file;
+ if (!file.Open(SpringBoard_, FileFd::ReadOnly))
+ _error->Discard();
+ else {
+ 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:UCLocalize("RETURN_TO_CYDIA")]; break; /* XXX: Maybe UCLocalize("DONE")? */
+ case 1: [close_ setTitle:UCLocalize("CLOSE_CYDIA")]; break;
+ case 2: [close_ setTitle:UCLocalize("RESTART_SPRINGBOARD")]; break;
+ case 3: [close_ setTitle:UCLocalize("RELOAD_SPRINGBOARD")]; break;
+ case 4: [close_ setTitle:UCLocalize("REBOOT_DEVICE")]; break;
+ }
+
+ system("su -c /usr/bin/uicache mobile");
+
+ UpdateExternalStatus(Finish_ == 0 ? 2 : 0);
+
+ [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 {
+ UpdateExternalStatus(1);
+
+ if (title_ != nil)
+ [title_ release];
+ if (title == nil)
+ title_ = nil;
+ else
+ title_ = [title retain];
+
+ [[self navigationItem] setTitle:title_];
+
+ [status_ setText:nil];
+ [output_ setText:@""];
+ [progress_ setProgress:0];
+
+ [close_ removeFromSuperview];
+ [[self view] addSubview:progress_];
+ [[self view] addSubview:status_];
+
+ [delegate_ setStatusBarShowsProgress:YES];
+ running_ = YES;
+
+ {
+ FileFd file;
+ if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
+ _error->Discard();
+ else {
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ notifyconf_ = sha1.Result();
+ }
+ }
+
+ {
+ FileFd file;
+ if (!file.Open(SpringBoard_, FileFd::ReadOnly))
+ _error->Discard();
+ else {
+ MMap mmap(file, MMap::ReadOnly);
+ SHA1Summation sha1;
+ sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
+ springlist_ = sha1.Result();
+ }
+ }
+
+ [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:UCLocalize("REPAIRING")
+ ];
+}
+
+- (void) setConfigurationData:(NSString *)data {
+ [self
+ performSelectorOnMainThread:@selector(_setConfigurationData:)
+ withObject:data
+ waitUntilDone:YES
+ ];
+}
+
+- (void) setProgressError:(NSString *)error withTitle:(NSString *)title {
+ CYActionSheet *sheet([[[CYActionSheet alloc]
+ initWithTitle:title
+ buttons:[NSArray arrayWithObjects:UCLocalize("OKAY"), nil]
+ defaultButtonIndex:0
+ ] autorelease]);
+
+ [sheet setMessage:error];
+ [sheet yieldToPopupAlertAnimated:YES];
+ [sheet dismiss];
+}
+
+- (void) setProgressTitle:(NSString *)title {
+ [self
+ performSelectorOnMainThread:@selector(_setProgressTitle:)
+ withObject:title
+ waitUntilDone:YES
+ ];
+}
+
+- (void) setProgressPercent:(float)percent {
+ [self
+ performSelectorOnMainThread:@selector(_setProgressPercent:)
+ withObject:[NSNumber numberWithFloat:percent]
+ waitUntilDone:YES
+ ];
+}
+
+- (void) startProgress {
+}
+
+- (void) addProgressOutput:(NSString *)output {
+ [self
+ performSelectorOnMainThread:@selector(_addProgressOutput:)
+ withObject:output
+ waitUntilDone:YES
+ ];
+}
+
+- (bool) isCancelling:(size_t)received {
+ return false;
+}
+
+- (void) _setConfigurationData:(NSString *)data {
+ static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
+
+ if (!conffile_r(data)) {
+ lprintf("E:invalid conffile\n");
+ return;
+ }
+
+ NSString *ofile = conffile_r[1];
+ //NSString *nfile = conffile_r[2];
+
+ UIAlertView *alert = [[[UIAlertView alloc]
+ initWithTitle:UCLocalize("CONFIGURATION_UPGRADE")
+ message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
+ delegate:self
+ cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
+ otherButtonTitles:UCLocalize("ACCEPT_NEW_COPY"),
+ // XXX: UCLocalize("SEE_WHAT_CHANGED"),
+ nil
+ ] autorelease];
+
+ [alert setContext:@"conffile"];
+ [alert show];
+}
+
+- (void) _setProgressTitle:(NSString *)title {
+ NSMutableArray *words([[title componentsSeparatedByString:@" "] mutableCopy]);
+ for (size_t i(0), e([words count]); i != e; ++i) {
+ NSString *word([words objectAtIndex:i]);
+ if (Package *package = [database_ packageWithName:word])
+ [words replaceObjectAtIndex:i withObject:[package name]];
+ }
+
+ [status_ setText:[words componentsJoinedByString:@" "]];
+}
+
+- (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
+/* }}} */
+
+/* Cell Content View {{{ */
+@protocol ContentDelegate
+- (void) drawContentRect:(CGRect)rect;
+@end
+
+@interface ContentView : UIView {
+ _transient id<ContentDelegate> delegate_;
+}
+
+@end
+
+@implementation ContentView
+- (id) initWithFrame:(CGRect)frame {
+ if ((self = [super initWithFrame:frame]) != nil) {
+ /* Fix landscape stretching. */
+ [self setNeedsDisplayOnBoundsChange:YES];
+ } return self;
+}
+
+- (void) setDelegate:(id<ContentDelegate>)delegate {
+ delegate_ = delegate;
+}
+
+- (void) drawRect:(CGRect)rect {
+ [super drawRect:rect];
+ [delegate_ drawContentRect:rect];
+}
+@end
+/* }}} */
+/* Package Cell {{{ */
+@interface PackageCell : UITableViewCell <
+ ContentDelegate
+> {
+ UIImage *icon_;
+ NSString *name_;
+ NSString *description_;
+ bool commercial_;
+ NSString *source_;
+ UIImage *badge_;
+ Package *package_;
+ UIColor *color_;
+ ContentView *content_;
+ BOOL faded_;
+ float fade_;
+ UIImage *placard_;
+}
+
+- (PackageCell *) init;
+- (void) setPackage:(Package *)package;
+
++ (int) heightForPackage:(Package *)package;
+- (void) drawContentRect:(CGRect)rect;