I think this made the dropbar almost work on iOS 7.
[cydia.git] / MobileCydia.mm
1 /* Cydia - iPhone UIKit Front-End for Debian APT
2  * Copyright (C) 2008-2013  Jay Freeman (saurik)
3 */
4
5 /* GNU General Public License, Version 3 {{{ */
6 /*
7  * Cydia is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published
9  * by the Free Software Foundation, either version 3 of the License,
10  * or (at your option) any later version.
11  *
12  * Cydia is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Cydia.  If not, see <http://www.gnu.org/licenses/>.
19 **/
20 /* }}} */
21
22 // XXX: wtf/FastMalloc.h... wtf?
23 #define USE_SYSTEM_MALLOC 1
24
25 /* #include Directives {{{ */
26 #include "CyteKit/UCPlatform.h"
27 #include "CyteKit/Localize.h"
28
29 #include <objc/objc.h>
30 #include <objc/runtime.h>
31
32 #include <CoreGraphics/CoreGraphics.h>
33 #include <Foundation/Foundation.h>
34
35 #if 0
36 #define DEPLOYMENT_TARGET_MACOSX 1
37 #define CF_BUILDING_CF 1
38 #include <CoreFoundation/CFInternal.h>
39 #endif
40
41 #include <CoreFoundation/CFPriv.h>
42 #include <CoreFoundation/CFUniChar.h>
43
44 #include <SystemConfiguration/SystemConfiguration.h>
45
46 #include <UIKit/UIKit.h>
47 #include "iPhonePrivate.h"
48
49 #include <IOKit/IOKitLib.h>
50
51 #include <QuartzCore/CALayer.h>
52
53 #include <WebCore/WebCoreThread.h>
54 #include <WebKit/DOMHTMLIFrameElement.h>
55
56 #include <algorithm>
57 #include <iomanip>
58 #include <sstream>
59 #include <string>
60
61 #include <ext/stdio_filebuf.h>
62
63 #undef ABS
64
65 #include <apt-pkg/acquire.h>
66 #include <apt-pkg/acquire-item.h>
67 #include <apt-pkg/algorithms.h>
68 #include <apt-pkg/cachefile.h>
69 #include <apt-pkg/clean.h>
70 #include <apt-pkg/configuration.h>
71 #include <apt-pkg/debindexfile.h>
72 #include <apt-pkg/debmetaindex.h>
73 #include <apt-pkg/error.h>
74 #include <apt-pkg/init.h>
75 #include <apt-pkg/mmap.h>
76 #include <apt-pkg/pkgrecords.h>
77 #include <apt-pkg/sha1.h>
78 #include <apt-pkg/sourcelist.h>
79 #include <apt-pkg/sptr.h>
80 #include <apt-pkg/strutl.h>
81 #include <apt-pkg/tagfile.h>
82
83 #include <apr-1/apr_pools.h>
84
85 #include <sys/types.h>
86 #include <sys/stat.h>
87 #include <sys/sysctl.h>
88 #include <sys/param.h>
89 #include <sys/mount.h>
90 #include <sys/reboot.h>
91
92 #include <fcntl.h>
93 #include <notify.h>
94 #include <dlfcn.h>
95
96 extern "C" {
97 #include <mach-o/nlist.h>
98 }
99
100 #include <cstdio>
101 #include <cstdlib>
102 #include <cstring>
103
104 #include <errno.h>
105
106 #include <Cytore.hpp>
107 #include "Sources.h"
108
109 #include <CydiaSubstrate/CydiaSubstrate.h>
110 #include "Menes/Menes.h"
111
112 #include "CyteKit/IndirectDelegate.h"
113 #include "CyteKit/PerlCompatibleRegEx.hpp"
114 #include "CyteKit/TableViewCell.h"
115 #include "CyteKit/WebScriptObject-Cyte.h"
116 #include "CyteKit/WebViewController.h"
117 #include "CyteKit/WebViewTableViewCell.h"
118 #include "CyteKit/stringWithUTF8Bytes.h"
119
120 #include "Cydia/MIMEAddress.h"
121 #include "Cydia/LoadingViewController.h"
122 #include "Cydia/ProgressEvent.h"
123
124 #include "SDURLCache/SDURLCache.h"
125 /* }}} */
126
127 /* Profiler {{{ */
128 struct timeval _ltv;
129 bool _itv;
130
131 #define _timestamp ({ \
132     struct timeval tv; \
133     gettimeofday(&tv, NULL); \
134     tv.tv_sec * 1000000 + tv.tv_usec; \
135 })
136
137 typedef std::vector<class ProfileTime *> TimeList;
138 TimeList times_;
139
140 class ProfileTime {
141   private:
142     const char *name_;
143     uint64_t total_;
144     uint64_t count_;
145
146   public:
147     ProfileTime(const char *name) :
148         name_(name),
149         total_(0)
150     {
151         times_.push_back(this);
152     }
153
154     void AddTime(uint64_t time) {
155         total_ += time;
156         ++count_;
157     }
158
159     void Print() {
160         if (total_ != 0)
161             std::cerr << std::setw(5) << count_ << ", " << std::setw(7) << total_ << " : " << name_ << std::endl;
162         total_ = 0;
163         count_ = 0;
164     }
165 };
166
167 class ProfileTimer {
168   private:
169     ProfileTime &time_;
170     uint64_t start_;
171
172   public:
173     ProfileTimer(ProfileTime &time) :
174         time_(time),
175         start_(_timestamp)
176     {
177     }
178
179     ~ProfileTimer() {
180         time_.AddTime(_timestamp - start_);
181     }
182 };
183
184 void PrintTimes() {
185     for (TimeList::const_iterator i(times_.begin()); i != times_.end(); ++i)
186         (*i)->Print();
187     std::cerr << "========" << std::endl;
188 }
189
190 #define _profile(name) { \
191     static ProfileTime name(#name); \
192     ProfileTimer _ ## name(name);
193
194 #define _end }
195 /* }}} */
196
197 // XXX: I hate clang. Apple: please get over your petty hatred of GPL and fix your gcc fork
198 #define synchronized(lock) \
199     synchronized(static_cast<NSObject *>(lock))
200
201 extern NSString *Cydia_;
202
203 #define lprintf(args...) fprintf(stderr, args)
204
205 #define ForRelease 1
206 #define TraceLogging (1 && !ForRelease)
207 #define HistogramInsertionSort (!ForRelease ? 0 : 0)
208 #define ProfileTimes (0 && !ForRelease)
209 #define ForSaurik (0 && !ForRelease)
210 #define LogBrowser (0 && !ForRelease)
211 #define TrackResize (0 && !ForRelease)
212 #define ManualRefresh (1 && !ForRelease)
213 #define ShowInternals (0 && !ForRelease)
214 #define AlwaysReload (0 && !ForRelease)
215 #define TryIndexedCollation (0 && !ForRelease)
216
217 #if !TraceLogging
218 #undef _trace
219 #define _trace(args...)
220 #endif
221
222 #if !ProfileTimes
223 #undef _profile
224 #define _profile(name) {
225 #undef _end
226 #define _end }
227 #define PrintTimes() do {} while (false)
228 #endif
229
230 // Hash Functions/Structures {{{
231 extern "C" uint32_t hashlittle(const void *key, size_t length, uint32_t initval = 0);
232
233 union SplitHash {
234     uint32_t u32;
235     uint16_t u16[2];
236 };
237 // }}}
238
239 static bool ShowPromoted_;
240
241 static NSString *Colon_;
242 NSString *Elision_;
243 static NSString *Error_;
244 static NSString *Warning_;
245
246 static NSString *Cache_;
247
248 static bool AprilFools_;
249
250 static void (*$SBSSetInterceptsMenuButtonForever)(bool);
251
252 static CFStringRef (*$MGCopyAnswer)(CFStringRef);
253
254 static NSString *UniqueIdentifier(UIDevice *device = nil) {
255     if (kCFCoreFoundationVersionNumber < 800) // iOS 7.x
256         return [device ?: [UIDevice currentDevice] uniqueIdentifier];
257     else
258         return [(id)$MGCopyAnswer(CFSTR("UniqueDeviceID")) autorelease];
259 }
260
261 static bool IsReachable(const char *name) {
262     SCNetworkReachabilityFlags flags; {
263         SCNetworkReachabilityRef reachability(SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, name));
264         SCNetworkReachabilityGetFlags(reachability, &flags);
265         CFRelease(reachability);
266     }
267
268     // XXX: this elaborate mess is what Apple is using to determine this? :(
269     // XXX: do we care if the user has to intervene? maybe that's ok?
270     return
271         (flags & kSCNetworkReachabilityFlagsReachable) != 0 && (
272             (flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0 || (
273                 (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0 ||
274                 (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0
275             ) && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0 ||
276             (flags & kSCNetworkReachabilityFlagsIsWWAN) != 0
277         )
278     ;
279 }
280
281 static const NSUInteger UIViewAutoresizingFlexibleBoth(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
282
283 static _finline NSString *CydiaURL(NSString *path) {
284     char page[26];
285     page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = 's';
286     page[5] = ':'; page[6] = '/'; page[7] = '/'; page[8] = 'c'; page[9] = 'y';
287     page[10] = 'd'; page[11] = 'i'; page[12] = 'a'; page[13] = '.'; page[14] = 's';
288     page[15] = 'a'; page[16] = 'u'; page[17] = 'r'; page[18] = 'i'; page[19] = 'k';
289     page[20] = '.'; page[21] = 'c'; page[22] = 'o'; page[23] = 'm'; page[24] = '/';
290     page[25] = '\0';
291     return [[NSString stringWithUTF8String:page] stringByAppendingString:path];
292 }
293
294 static void ReapZombie(pid_t pid) {
295     int status;
296   wait:
297     if (waitpid(pid, &status, 0) == -1)
298         if (errno == EINTR)
299             goto wait;
300         else _assert(false);
301 }
302
303 static _finline void UpdateExternalStatus(uint64_t newStatus) {
304     int notify_token;
305     if (notify_register_check("com.saurik.Cydia.status", &notify_token) == NOTIFY_STATUS_OK) {
306         notify_set_state(notify_token, newStatus);
307         notify_cancel(notify_token);
308     }
309     notify_post("com.saurik.Cydia.status");
310 }
311
312 static CGFloat CYStatusBarHeight() {
313     CGSize size([[UIApplication sharedApplication] statusBarFrame].size);
314     return UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) ? size.height : size.width;
315 }
316
317 /* NSForcedOrderingSearch doesn't work on the iPhone */
318 static const NSStringCompareOptions MatchCompareOptions_ = NSLiteralSearch | NSCaseInsensitiveSearch;
319 static const NSStringCompareOptions LaxCompareOptions_ = NSNumericSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch | NSCaseInsensitiveSearch;
320 static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareNumerically | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
321
322 /* Insertion Sort {{{ */
323
324 CFIndex SKBSearch_(const void *element, CFIndex elementSize, const void *list, CFIndex count, CFComparatorFunction comparator, void *context) {
325     const char *ptr = (const char *)list;
326     while (0 < count) {
327         CFIndex half = count / 2;
328         const char *probe = ptr + elementSize * half;
329         CFComparisonResult cr = comparator(element, probe, context);
330     if (0 == cr) return (probe - (const char *)list) / elementSize;
331         ptr = (cr < 0) ? ptr : probe + elementSize;
332         count = (cr < 0) ? half : (half + (count & 1) - 1);
333     }
334     return (ptr - (const char *)list) / elementSize;
335 }
336
337 CFIndex CFBSearch_(const void *element, CFIndex elementSize, const void *list, CFIndex count, CFComparatorFunction comparator, void *context) {
338     const char *ptr = (const char *)list;
339     while (0 < count) {
340         CFIndex half = count / 2;
341         const char *probe = ptr + elementSize * half;
342         CFComparisonResult cr = comparator(element, probe, context);
343     if (0 == cr) return (probe - (const char *)list) / elementSize;
344         ptr = (cr < 0) ? ptr : probe + elementSize;
345         count = (cr < 0) ? half : (half + (count & 1) - 1);
346     }
347     return (ptr - (const char *)list) / elementSize;
348 }
349
350 void CFArrayInsertionSortValues(CFMutableArrayRef array, CFRange range, CFComparatorFunction comparator, void *context) {
351     if (range.length == 0)
352         return;
353     const void **values(new const void *[range.length]);
354     CFArrayGetValues(array, range, values);
355
356 #if HistogramInsertionSort > 0
357     uint32_t total(0), *offsets(new uint32_t[range.length]);
358 #endif
359
360     for (CFIndex index(1); index != range.length; ++index) {
361         const void *value(values[index]);
362         //CFIndex correct(SKBSearch_(&value, sizeof(const void *), values, index, comparator, context));
363         CFIndex correct(index);
364         while (comparator(value, values[correct - 1], context) == kCFCompareLessThan) {
365 #if HistogramInsertionSort > 1
366             NSLog(@"%@ < %@", value, values[correct - 1]);
367 #endif
368             if (--correct == 0)
369                 break;
370         }
371         if (correct != index) {
372             size_t offset(index - correct);
373 #if HistogramInsertionSort
374             total += offset;
375             ++offsets[offset];
376             if (offset > 10)
377                 NSLog(@"Heavy Insertion Displacement: %u = %@", offset, value);
378 #endif
379             memmove(values + correct + 1, values + correct, sizeof(const void *) * offset);
380             values[correct] = value;
381         }
382     }
383
384     CFArrayReplaceValues(array, range, values, range.length);
385     delete [] values;
386
387 #if HistogramInsertionSort > 0
388     for (CFIndex index(0); index != range.length; ++index)
389         if (offsets[index] != 0)
390             NSLog(@"Insertion Displacement [%u]: %u", index, offsets[index]);
391     NSLog(@"Average Insertion Displacement: %f", double(total) / range.length);
392     delete [] offsets;
393 #endif
394 }
395
396 /* }}} */
397
398 /* Apple Bug Fixes {{{ */
399 @implementation UIWebDocumentView (Cydia)
400
401 - (void) _setScrollerOffset:(CGPoint)offset {
402     UIScroller *scroller([self _scroller]);
403
404     CGSize size([scroller contentSize]);
405     CGSize bounds([scroller bounds].size);
406
407     CGPoint max;
408     max.x = size.width - bounds.width;
409     max.y = size.height - bounds.height;
410
411     // wtf Apple?!
412     if (max.x < 0)
413         max.x = 0;
414     if (max.y < 0)
415         max.y = 0;
416
417     offset.x = offset.x < 0 ? 0 : offset.x > max.x ? max.x : offset.x;
418     offset.y = offset.y < 0 ? 0 : offset.y > max.y ? max.y : offset.y;
419
420     [scroller setOffset:offset];
421 }
422
423 @end
424 /* }}} */
425
426 NSUInteger DOMNodeList$countByEnumeratingWithState$objects$count$(DOMNodeList *self, SEL sel, NSFastEnumerationState *state, id *objects, NSUInteger count) {
427     size_t length([self length] - state->state);
428     if (length <= 0)
429         return 0;
430     else if (length > count)
431         length = count;
432     for (size_t i(0); i != length; ++i)
433         objects[i] = [self item:state->state++];
434     state->itemsPtr = objects;
435     state->mutationsPtr = (unsigned long *) self;
436     return length;
437 }
438
439 /* Cydia NSString Additions {{{ */
440 @interface NSString (Cydia)
441 - (NSComparisonResult) compareByPath:(NSString *)other;
442 - (NSString *) stringByAddingPercentEscapesIncludingReserved;
443 @end
444
445 @implementation NSString (Cydia)
446
447 - (NSComparisonResult) compareByPath:(NSString *)other {
448     NSString *prefix = [self commonPrefixWithString:other options:0];
449     size_t length = [prefix length];
450
451     NSRange lrange = NSMakeRange(length, [self length] - length);
452     NSRange rrange = NSMakeRange(length, [other length] - length);
453
454     lrange = [self rangeOfString:@"/" options:0 range:lrange];
455     rrange = [other rangeOfString:@"/" options:0 range:rrange];
456
457     NSComparisonResult value;
458
459     if (lrange.location == NSNotFound && rrange.location == NSNotFound)
460         value = NSOrderedSame;
461     else if (lrange.location == NSNotFound)
462         value = NSOrderedAscending;
463     else if (rrange.location == NSNotFound)
464         value = NSOrderedDescending;
465     else
466         value = NSOrderedSame;
467
468     NSString *lpath = lrange.location == NSNotFound ? [self substringFromIndex:length] :
469         [self substringWithRange:NSMakeRange(length, lrange.location - length)];
470     NSString *rpath = rrange.location == NSNotFound ? [other substringFromIndex:length] :
471         [other substringWithRange:NSMakeRange(length, rrange.location - length)];
472
473     NSComparisonResult result = [lpath compare:rpath];
474     return result == NSOrderedSame ? value : result;
475 }
476
477 - (NSString *) stringByAddingPercentEscapesIncludingReserved {
478     return [(id)CFURLCreateStringByAddingPercentEscapes(
479         kCFAllocatorDefault,
480         (CFStringRef) self,
481         NULL,
482         CFSTR(";/?:@&=+$,"),
483         kCFStringEncodingUTF8
484     ) autorelease];
485 }
486
487 @end
488 /* }}} */
489
490 /* C++ NSString Wrapper Cache {{{ */
491 static _finline CFStringRef CYStringCreate(const char *data, size_t size) {
492     return size == 0 ? NULL :
493         CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const uint8_t *>(data), size, kCFStringEncodingUTF8, NO, kCFAllocatorNull) ?:
494         CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const uint8_t *>(data), size, kCFStringEncodingISOLatin1, NO, kCFAllocatorNull);
495 }
496
497 static _finline CFStringRef CYStringCreate(const char *data) {
498     return CYStringCreate(data, strlen(data));
499 }
500
501 class CYString {
502   private:
503     char *data_;
504     size_t size_;
505     CFStringRef cache_;
506
507     _finline void clear_() {
508         if (cache_ != NULL) {
509             CFRelease(cache_);
510             cache_ = NULL;
511         }
512     }
513
514   public:
515     _finline bool empty() const {
516         return size_ == 0;
517     }
518
519     _finline size_t size() const {
520         return size_;
521     }
522
523     _finline char *data() const {
524         return data_;
525     }
526
527     _finline void clear() {
528         size_ = 0;
529         clear_();
530     }
531
532     _finline CYString() :
533         data_(0),
534         size_(0),
535         cache_(NULL)
536     {
537     }
538
539     _finline ~CYString() {
540         clear_();
541     }
542
543     void operator =(const CYString &rhs) {
544         data_ = rhs.data_;
545         size_ = rhs.size_;
546
547         if (rhs.cache_ == nil)
548             cache_ = NULL;
549         else
550             cache_ = reinterpret_cast<CFStringRef>(CFRetain(rhs.cache_));
551     }
552
553     void copy(apr_pool_t *pool) {
554         char *temp(reinterpret_cast<char *>(apr_palloc(pool, size_ + 1)));
555         memcpy(temp, data_, size_);
556         temp[size_] = '\0';
557         data_ = temp;
558     }
559
560     void set(apr_pool_t *pool, const char *data, size_t size) {
561         if (size == 0)
562             clear();
563         else {
564             clear_();
565
566             data_ = const_cast<char *>(data);
567             size_ = size;
568
569             if (pool != NULL)
570                 copy(pool);
571         }
572     }
573
574     _finline void set(apr_pool_t *pool, const char *data) {
575         set(pool, data, data == NULL ? 0 : strlen(data));
576     }
577
578     _finline void set(apr_pool_t *pool, const std::string &rhs) {
579         set(pool, rhs.data(), rhs.size());
580     }
581
582     bool operator ==(const CYString &rhs) const {
583         return size_ == rhs.size_ && memcmp(data_, rhs.data_, size_) == 0;
584     }
585
586     _finline operator CFStringRef() {
587         if (cache_ == NULL)
588             cache_ = CYStringCreate(data_, size_);
589         return cache_;
590     }
591
592     _finline operator id() {
593         return (NSString *) static_cast<CFStringRef>(*this);
594     }
595
596     _finline operator const char *() {
597         return reinterpret_cast<const char *>(data_);
598     }
599 };
600 /* }}} */
601 /* C++ NSString Algorithm Adapters {{{ */
602 extern "C" {
603     CF_EXPORT CFHashCode CFStringHashNSString(CFStringRef str);
604 }
605
606 struct NSStringMapHash :
607     std::unary_function<NSString *, size_t>
608 {
609     _finline size_t operator ()(NSString *value) const {
610         return CFStringHashNSString((CFStringRef) value);
611     }
612 };
613
614 struct NSStringMapLess :
615     std::binary_function<NSString *, NSString *, bool>
616 {
617     _finline bool operator ()(NSString *lhs, NSString *rhs) const {
618         return [lhs compare:rhs] == NSOrderedAscending;
619     }
620 };
621
622 struct NSStringMapEqual :
623     std::binary_function<NSString *, NSString *, bool>
624 {
625     _finline bool operator ()(NSString *lhs, NSString *rhs) const {
626         return CFStringCompare((CFStringRef) lhs, (CFStringRef) rhs, 0) == kCFCompareEqualTo;
627         //CFEqual((CFTypeRef) lhs, (CFTypeRef) rhs);
628         //[lhs isEqualToString:rhs];
629     }
630 };
631 /* }}} */
632
633 /* CoreGraphics Primitives {{{ */
634 class CYColor {
635   private:
636     CGColorRef color_;
637
638     static CGColorRef Create_(CGColorSpaceRef space, float red, float green, float blue, float alpha) {
639         CGFloat color[] = {red, green, blue, alpha};
640         return CGColorCreate(space, color);
641     }
642
643   public:
644     CYColor() :
645         color_(NULL)
646     {
647     }
648
649     CYColor(CGColorSpaceRef space, float red, float green, float blue, float alpha) :
650         color_(Create_(space, red, green, blue, alpha))
651     {
652         Set(space, red, green, blue, alpha);
653     }
654
655     void Clear() {
656         if (color_ != NULL)
657             CGColorRelease(color_);
658     }
659
660     ~CYColor() {
661         Clear();
662     }
663
664     void Set(CGColorSpaceRef space, float red, float green, float blue, float alpha) {
665         Clear();
666         color_ = Create_(space, red, green, blue, alpha);
667     }
668
669     operator CGColorRef() {
670         return color_;
671     }
672 };
673 /* }}} */
674
675 /* Random Global Variables {{{ */
676 static int PulseInterval_ = 500000;
677
678 static const NSString *UI_;
679
680 static int Finish_;
681 static bool RestartSubstrate_;
682 static NSArray *Finishes_;
683
684 #define SpringBoard_ "/System/Library/LaunchDaemons/com.apple.SpringBoard.plist"
685 #define NotifyConfig_ "/etc/notify.conf"
686
687 static bool Queuing_;
688
689 static CYColor Blue_;
690 static CYColor Blueish_;
691 static CYColor Black_;
692 static CYColor Off_;
693 static CYColor White_;
694 static CYColor Gray_;
695 static CYColor Green_;
696 static CYColor Purple_;
697 static CYColor Purplish_;
698
699 static UIColor *InstallingColor_;
700 static UIColor *RemovingColor_;
701
702 static NSString *App_;
703
704 static BOOL Advanced_;
705 static BOOL Ignored_;
706
707 static _H<UIFont> Font12_;
708 static _H<UIFont> Font12Bold_;
709 static _H<UIFont> Font14_;
710 static _H<UIFont> Font18Bold_;
711 static _H<UIFont> Font22Bold_;
712
713 static const char *Machine_ = NULL;
714 static _H<NSString> System_;
715 static NSString *SerialNumber_ = nil;
716 static NSString *ChipID_ = nil;
717 static NSString *BBSNum_ = nil;
718 static _H<NSString> Token_;
719 static _H<NSString> UniqueID_;
720 static _H<NSString> UserAgent_;
721 static _H<NSString> Product_;
722 static _H<NSString> Safari_;
723
724 static CFLocaleRef Locale_;
725 static NSArray *Languages_;
726 static CGColorSpaceRef space_;
727
728 static NSDictionary *SectionMap_;
729 static NSMutableDictionary *Metadata_;
730 static _transient NSMutableDictionary *Settings_;
731 static _transient NSString *Role_;
732 static _transient NSMutableDictionary *Packages_;
733 static _transient NSMutableDictionary *Values_;
734 static _transient NSMutableDictionary *Sections_;
735 _H<NSMutableDictionary> Sources_;
736 static _transient NSNumber *Version_;
737 bool Changed_;
738 static time_t now_;
739
740 bool IsWildcat_;
741 static CGFloat ScreenScale_;
742 static NSString *Idiom_;
743 static _H<NSString> Firmware_;
744 static NSString *Major_;
745
746 static _H<NSMutableDictionary> SessionData_;
747 static _H<NSObject> HostConfig_;
748 static _H<NSMutableSet> BridgedHosts_;
749 static _H<NSMutableSet> TokenHosts_;
750 static _H<NSMutableSet> InsecureHosts_;
751 static _H<NSMutableSet> PipelinedHosts_;
752 static _H<NSMutableSet> CachedURLs_;
753
754 static NSString *kCydiaProgressEventTypeError = @"Error";
755 static NSString *kCydiaProgressEventTypeInformation = @"Information";
756 static NSString *kCydiaProgressEventTypeStatus = @"Status";
757 static NSString *kCydiaProgressEventTypeWarning = @"Warning";
758 /* }}} */
759
760 /* Display Helpers {{{ */
761 inline float Interpolate(float begin, float end, float fraction) {
762     return (end - begin) * fraction + begin;
763 }
764
765 static _finline const char *StripVersion_(const char *version) {
766     const char *colon(strchr(version, ':'));
767     return colon == NULL ? version : colon + 1;
768 }
769
770 NSString *LocalizeSection(NSString *section) {
771     static Pcre title_r("^(.*?) \\((.*)\\)$");
772     if (title_r(section)) {
773         NSString *parent(title_r[1]);
774         NSString *child(title_r[2]);
775
776         return [NSString stringWithFormat:UCLocalize("PARENTHETICAL"),
777             LocalizeSection(parent),
778             LocalizeSection(child)
779         ];
780     }
781
782     return [[NSBundle mainBundle] localizedStringForKey:section value:nil table:@"Sections"];
783 }
784
785 NSString *Simplify(NSString *title) {
786     const char *data = [title UTF8String];
787     size_t size = [title length];
788
789     static Pcre square_r("^\\[(.*)\\]$");
790     if (square_r(data, size))
791         return Simplify(square_r[1]);
792
793     static Pcre paren_r("^\\((.*)\\)$");
794     if (paren_r(data, size))
795         return Simplify(paren_r[1]);
796
797     static Pcre title_r("^(.*?) \\((.*)\\)$");
798     if (title_r(data, size))
799         return Simplify(title_r[1]);
800
801     return title;
802 }
803 /* }}} */
804
805 NSString *GetLastUpdate() {
806     NSDate *update = [Metadata_ objectForKey:@"LastUpdate"];
807
808     if (update == nil)
809         return UCLocalize("NEVER_OR_UNKNOWN");
810
811     CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
812     CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) update);
813
814     CFRelease(formatter);
815
816     return [(NSString *) formatted autorelease];
817 }
818
819 bool isSectionVisible(NSString *section) {
820     NSDictionary *metadata([Sections_ objectForKey:(section ?: @"")]);
821     NSNumber *hidden(metadata == nil ? nil : [metadata objectForKey:@"Hidden"]);
822     return hidden == nil || ![hidden boolValue];
823 }
824
825 static NSObject *CYIOGetValue(const char *path, NSString *property) {
826     io_registry_entry_t entry(IORegistryEntryFromPath(kIOMasterPortDefault, path));
827     if (entry == MACH_PORT_NULL)
828         return nil;
829
830     CFTypeRef value(IORegistryEntryCreateCFProperty(entry, (CFStringRef) property, kCFAllocatorDefault, 0));
831     IOObjectRelease(entry);
832
833     if (value == NULL)
834         return nil;
835     return [(id) value autorelease];
836 }
837
838 static NSString *CYHex(NSData *data, bool reverse = false) {
839     if (data == nil)
840         return nil;
841
842     size_t length([data length]);
843     uint8_t bytes[length];
844     [data getBytes:bytes];
845
846     char string[length * 2 + 1];
847     for (size_t i(0); i != length; ++i)
848         sprintf(string + i * 2, "%.2x", bytes[reverse ? length - i - 1 : i]);
849
850     return [NSString stringWithUTF8String:string];
851 }
852
853 @class Cydia;
854
855 /* Delegate Prototypes {{{ */
856 @class Package;
857 @class Source;
858 @class CydiaProgressEvent;
859
860 @protocol DatabaseDelegate
861 - (void) repairWithSelector:(SEL)selector;
862 - (void) setConfigurationData:(NSString *)data;
863 - (void) addProgressEventOnMainThread:(CydiaProgressEvent *)event forTask:(NSString *)task;
864 @end
865
866 @class CYPackageController;
867
868 @protocol CydiaDelegate
869 - (void) returnToCydia;
870 - (void) saveState;
871 - (void) retainNetworkActivityIndicator;
872 - (void) releaseNetworkActivityIndicator;
873 - (void) clearPackage:(Package *)package;
874 - (void) installPackage:(Package *)package;
875 - (void) installPackages:(NSArray *)packages;
876 - (void) removePackage:(Package *)package;
877 - (void) beginUpdate;
878 - (BOOL) updating;
879 - (void) distUpgrade;
880 - (void) loadData;
881 - (void) updateData;
882 - (void) _saveConfig;
883 - (void) syncData;
884 - (void) addSource:(NSDictionary *)source;
885 - (void) addTrivialSource:(NSString *)href;
886 - (void) showSettings;
887 - (UIProgressHUD *) addProgressHUD;
888 - (void) removeProgressHUD:(UIProgressHUD *)hud;
889 - (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item;
890 - (void) reloadDataWithInvocation:(NSInvocation *)invocation;
891 @end
892 /* }}} */
893
894 /* Status Delegation {{{ */
895 class Status :
896     public pkgAcquireStatus
897 {
898   private:
899     _transient NSObject<ProgressDelegate> *delegate_;
900     bool cancelled_;
901
902   public:
903     Status() :
904         delegate_(nil),
905         cancelled_(false)
906     {
907     }
908
909     void setDelegate(NSObject<ProgressDelegate> *delegate) {
910         delegate_ = delegate;
911     }
912
913     NSObject<ProgressDelegate> *getDelegate() const {
914         return delegate_;
915     }
916
917     virtual bool MediaChange(std::string media, std::string drive) {
918         return false;
919     }
920
921     virtual void IMSHit(pkgAcquire::ItemDesc &item) {
922         Done(item);
923     }
924
925     virtual void Fetch(pkgAcquire::ItemDesc &item) {
926         NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
927         CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), name] ofType:kCydiaProgressEventTypeStatus forItem:item]);
928         [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
929     }
930
931     virtual void Done(pkgAcquire::ItemDesc &item) {
932         NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
933         CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithFormat:Colon_, UCLocalize("DONE"), name] ofType:kCydiaProgressEventTypeStatus forItem:item]);
934         [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
935     }
936
937     virtual void Fail(pkgAcquire::ItemDesc &item) {
938         if (
939             item.Owner->Status == pkgAcquire::Item::StatIdle ||
940             item.Owner->Status == pkgAcquire::Item::StatDone
941         )
942             return;
943
944         std::string &error(item.Owner->ErrorText);
945         if (error.empty())
946             return;
947
948         CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:kCydiaProgressEventTypeError forItem:item]);
949         [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
950     }
951
952     virtual bool Pulse(pkgAcquire *Owner) {
953         bool value = pkgAcquireStatus::Pulse(Owner);
954
955         double percent(
956             double(CurrentBytes + CurrentItems) /
957             double(TotalBytes + TotalItems)
958         );
959
960         [delegate_ performSelectorOnMainThread:@selector(setProgressStatus:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:
961             [NSNumber numberWithDouble:percent], @"Percent",
962
963             [NSNumber numberWithDouble:CurrentBytes], @"Current",
964             [NSNumber numberWithDouble:TotalBytes], @"Total",
965             [NSNumber numberWithDouble:CurrentCPS], @"Speed",
966         nil] waitUntilDone:YES];
967
968         if (value && ![delegate_ isProgressCancelled])
969             return true;
970         else {
971             cancelled_ = true;
972             return false;
973         }
974     }
975
976     _finline bool WasCancelled() const {
977         return cancelled_;
978     }
979
980     virtual void Start() {
981         pkgAcquireStatus::Start();
982         [delegate_ performSelectorOnMainThread:@selector(setProgressCancellable:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
983     }
984
985     virtual void Stop() {
986         pkgAcquireStatus::Stop();
987         [delegate_ performSelectorOnMainThread:@selector(setProgressCancellable:) withObject:[NSNumber numberWithBool:NO] waitUntilDone:YES];
988         [delegate_ performSelectorOnMainThread:@selector(setProgressStatus:) withObject:nil waitUntilDone:YES];
989     }
990 };
991 /* }}} */
992 /* Database Interface {{{ */
993 typedef std::map< unsigned long, _H<Source> > SourceMap;
994
995 @interface Database : NSObject {
996     NSZone *zone_;
997     apr_pool_t *pool_;
998
999     unsigned era_;
1000
1001     pkgCacheFile cache_;
1002     pkgDepCache::Policy *policy_;
1003     pkgRecords *records_;
1004     pkgProblemResolver *resolver_;
1005     pkgAcquire *fetcher_;
1006     FileFd *lock_;
1007     SPtr<pkgPackageManager> manager_;
1008     pkgSourceList *list_;
1009
1010     SourceMap sourceMap_;
1011     _H<NSMutableArray> sourceList_;
1012
1013     CFMutableArrayRef packages_;
1014
1015     _transient NSObject<DatabaseDelegate> *delegate_;
1016     _transient NSObject<ProgressDelegate> *progress_;
1017
1018     Status status_;
1019
1020     int cydiafd_;
1021     int statusfd_;
1022     FILE *input_;
1023
1024     std::map<const char *, _H<NSString> > sections_;
1025 }
1026
1027 + (Database *) sharedInstance;
1028 - (unsigned) era;
1029
1030 - (void) _readCydia:(NSNumber *)fd;
1031 - (void) _readStatus:(NSNumber *)fd;
1032 - (void) _readOutput:(NSNumber *)fd;
1033
1034 - (FILE *) input;
1035
1036 - (Package *) packageWithName:(NSString *)name;
1037
1038 - (pkgCacheFile &) cache;
1039 - (pkgDepCache::Policy *) policy;
1040 - (pkgRecords *) records;
1041 - (pkgProblemResolver *) resolver;
1042 - (pkgAcquire &) fetcher;
1043 - (pkgSourceList &) list;
1044 - (NSArray *) packages;
1045 - (NSArray *) sources;
1046 - (Source *) sourceWithKey:(NSString *)key;
1047 - (void) reloadDataWithInvocation:(NSInvocation *)invocation;
1048
1049 - (void) configure;
1050 - (bool) prepare;
1051 - (void) perform;
1052 - (bool) upgrade;
1053 - (void) update;
1054
1055 - (void) updateWithStatus:(Status &)status;
1056
1057 - (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate;
1058
1059 - (void) setProgressDelegate:(NSObject<ProgressDelegate> *)delegate;
1060 - (NSObject<ProgressDelegate> *) progressDelegate;
1061
1062 - (Source *) getSource:(pkgCache::PkgFileIterator)file;
1063
1064 - (NSString *) mappedSectionForPointer:(const char *)pointer;
1065
1066 @end
1067 /* }}} */
1068 /* ProgressEvent Implementation {{{ */
1069 @implementation CydiaProgressEvent
1070
1071 + (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type {
1072     return [[[CydiaProgressEvent alloc] initWithMessage:message ofType:type] autorelease];
1073 }
1074
1075 + (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package {
1076     CydiaProgressEvent *event([self eventWithMessage:message ofType:type]);
1077     [event setPackage:package];
1078     return event;
1079 }
1080
1081 + (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forItem:(pkgAcquire::ItemDesc &)item {
1082     CydiaProgressEvent *event([self eventWithMessage:message ofType:type]);
1083
1084     NSString *description([NSString stringWithUTF8String:item.Description.c_str()]);
1085     NSArray *fields([description componentsSeparatedByString:@" "]);
1086     [event setItem:fields];
1087
1088     if ([fields count] > 3) {
1089         [event setPackage:[fields objectAtIndex:2]];
1090         [event setVersion:[fields objectAtIndex:3]];
1091     }
1092
1093     [event setURL:[NSString stringWithUTF8String:item.URI.c_str()]];
1094
1095     return event;
1096 }
1097
1098 + (NSArray *) _attributeKeys {
1099     return [NSArray arrayWithObjects:
1100         @"item",
1101         @"message",
1102         @"package",
1103         @"type",
1104         @"url",
1105         @"version",
1106     nil];
1107 }
1108
1109 - (NSArray *) attributeKeys {
1110     return [[self class] _attributeKeys];
1111 }
1112
1113 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1114     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1115 }
1116
1117 - (id) initWithMessage:(NSString *)message ofType:(NSString *)type {
1118     if ((self = [super init]) != nil) {
1119         message_ = message;
1120         type_ = type;
1121     } return self;
1122 }
1123
1124 - (NSString *) message {
1125     return message_;
1126 }
1127
1128 - (NSString *) type {
1129     return type_;
1130 }
1131
1132 - (NSArray *) item {
1133     return (id) item_ ?: [NSNull null];
1134 }
1135
1136 - (void) setItem:(NSArray *)item {
1137     item_ = item;
1138 }
1139
1140 - (NSString *) package {
1141     return (id) package_ ?: [NSNull null];
1142 }
1143
1144 - (void) setPackage:(NSString *)package {
1145     package_ = package;
1146 }
1147
1148 - (NSString *) url {
1149     return (id) url_ ?: [NSNull null];
1150 }
1151
1152 - (void) setURL:(NSString *)url {
1153     url_ = url;
1154 }
1155
1156 - (void) setVersion:(NSString *)version {
1157     version_ = version;
1158 }
1159
1160 - (NSString *) version {
1161     return (id) version_ ?: [NSNull null];
1162 }
1163
1164 - (NSString *) compound:(NSString *)value {
1165     if (value != nil) {
1166         NSString *mode(nil); {
1167             NSString *type([self type]);
1168             if ([type isEqualToString:kCydiaProgressEventTypeError])
1169                 mode = UCLocalize("ERROR");
1170             else if ([type isEqualToString:kCydiaProgressEventTypeWarning])
1171                 mode = UCLocalize("WARNING");
1172         }
1173
1174         if (mode != nil)
1175             value = [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), mode, value];
1176     }
1177
1178     return value;
1179 }
1180
1181 - (NSString *) compoundMessage {
1182     return [self compound:[self message]];
1183 }
1184
1185 - (NSString *) compoundTitle {
1186     NSString *title;
1187
1188     if (package_ == nil)
1189         title = nil;
1190     else if (Package *package = [[Database sharedInstance] packageWithName:package_])
1191         title = [package name];
1192     else
1193         title = package_;
1194
1195     return [self compound:title];
1196 }
1197
1198 @end
1199 /* }}} */
1200
1201 // Cytore Definitions {{{
1202 struct PackageValue :
1203     Cytore::Block
1204 {
1205     Cytore::Offset<PackageValue> next_;
1206
1207     uint32_t index_ : 23;
1208     uint32_t subscribed_ : 1;
1209     uint32_t : 8;
1210
1211     int32_t first_;
1212     int32_t last_;
1213
1214     uint16_t vhash_;
1215     uint16_t nhash_;
1216
1217     char version_[8];
1218     char name_[];
1219 };
1220
1221 struct MetaValue :
1222     Cytore::Block
1223 {
1224     uint32_t active_;
1225     Cytore::Offset<PackageValue> packages_[1 << 16];
1226 };
1227
1228 static Cytore::File<MetaValue> MetaFile_;
1229 // }}}
1230 // Cytore Helper Functions {{{
1231 static PackageValue *PackageFind(const char *name, size_t length, bool *fail = NULL) {
1232     SplitHash nhash = { hashlittle(name, length) };
1233
1234     PackageValue *metadata;
1235
1236     Cytore::Offset<PackageValue> *offset(&MetaFile_->packages_[nhash.u16[0]]);
1237     offset: if (offset->IsNull()) {
1238         *offset = MetaFile_.New<PackageValue>(length + 1);
1239         metadata = &MetaFile_.Get(*offset);
1240
1241         if (metadata == NULL) {
1242             if (fail != NULL)
1243                 *fail = true;
1244
1245             metadata = new PackageValue();
1246             memset(metadata, 0, sizeof(*metadata));
1247         }
1248
1249         memcpy(metadata->name_, name, length + 1);
1250         metadata->nhash_ = nhash.u16[1];
1251     } else {
1252         metadata = &MetaFile_.Get(*offset);
1253
1254         if (metadata->nhash_ != nhash.u16[1] || strncmp(metadata->name_, name, length + 1) != 0) {
1255             offset = &metadata->next_;
1256             goto offset;
1257         }
1258     }
1259
1260     return metadata;
1261 }
1262
1263 static void PackageImport(const void *key, const void *value, void *context) {
1264     bool &fail(*reinterpret_cast<bool *>(context));
1265
1266     char buffer[1024];
1267     if (!CFStringGetCString((CFStringRef) key, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
1268         NSLog(@"failed to import package %@", key);
1269         return;
1270     }
1271
1272     PackageValue *metadata(PackageFind(buffer, strlen(buffer), &fail));
1273     NSDictionary *package((NSDictionary *) value);
1274
1275     if (NSNumber *subscribed = [package objectForKey:@"IsSubscribed"])
1276         if ([subscribed boolValue] && !metadata->subscribed_)
1277             metadata->subscribed_ = true;
1278
1279     if (NSDate *date = [package objectForKey:@"FirstSeen"]) {
1280         time_t time([date timeIntervalSince1970]);
1281         if (metadata->first_ > time || metadata->first_ == 0)
1282             metadata->first_ = time;
1283     }
1284
1285     NSDate *date([package objectForKey:@"LastSeen"]);
1286     NSString *version([package objectForKey:@"LastVersion"]);
1287
1288     if (date != nil && version != nil) {
1289         time_t time([date timeIntervalSince1970]);
1290         if (metadata->last_ < time || metadata->last_ == 0)
1291             if (CFStringGetCString((CFStringRef) version, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
1292                 size_t length(strlen(buffer));
1293                 uint16_t vhash(hashlittle(buffer, length));
1294
1295                 size_t capped(std::min<size_t>(8, length));
1296                 char *latest(buffer + length - capped);
1297
1298                 strncpy(metadata->version_, latest, sizeof(metadata->version_));
1299                 metadata->vhash_ = vhash;
1300
1301                 metadata->last_ = time;
1302             }
1303     }
1304 }
1305 // }}}
1306
1307 /* Source Class {{{ */
1308 @interface Source : NSObject {
1309     unsigned era_;
1310     Database *database_;
1311     metaIndex *index_;
1312
1313     CYString depiction_;
1314     CYString description_;
1315     CYString label_;
1316     CYString origin_;
1317     CYString support_;
1318
1319     CYString uri_;
1320     CYString distribution_;
1321     CYString type_;
1322     CYString base_;
1323     CYString version_;
1324
1325     _H<NSString> host_;
1326     _H<NSString> authority_;
1327
1328     CYString defaultIcon_;
1329
1330     _H<NSMutableDictionary> record_;
1331     BOOL trusted_;
1332 }
1333
1334 - (Source *) initWithMetaIndex:(metaIndex *)index forDatabase:(Database *)database inPool:(apr_pool_t *)pool;
1335
1336 - (NSComparisonResult) compareByName:(Source *)source;
1337
1338 - (NSString *) depictionForPackage:(NSString *)package;
1339 - (NSString *) supportForPackage:(NSString *)package;
1340
1341 - (metaIndex *) metaIndex;
1342 - (NSDictionary *) record;
1343 - (BOOL) trusted;
1344
1345 - (NSString *) rooturi;
1346 - (NSString *) distribution;
1347 - (NSString *) type;
1348
1349 - (NSString *) key;
1350 - (NSString *) host;
1351
1352 - (NSString *) name;
1353 - (NSString *) shortDescription;
1354 - (NSString *) label;
1355 - (NSString *) origin;
1356 - (NSString *) version;
1357
1358 - (NSString *) defaultIcon;
1359 - (NSURL *) iconURL;
1360
1361 @end
1362
1363 @implementation Source
1364
1365 - (void) _clear {
1366     uri_.clear();
1367     distribution_.clear();
1368     type_.clear();
1369
1370     base_.clear();
1371
1372     description_.clear();
1373     label_.clear();
1374     origin_.clear();
1375     depiction_.clear();
1376     support_.clear();
1377     version_.clear();
1378     defaultIcon_.clear();
1379
1380     record_ = nil;
1381     host_ = nil;
1382     authority_ = nil;
1383 }
1384
1385 + (NSString *) webScriptNameForSelector:(SEL)selector {
1386     if (false);
1387     else if (selector == @selector(addSection:))
1388         return @"addSection";
1389     else if (selector == @selector(getField:))
1390         return @"getField";
1391     else if (selector == @selector(removeSection:))
1392         return @"removeSection";
1393     else if (selector == @selector(remove))
1394         return @"remove";
1395     else
1396         return nil;
1397 }
1398
1399 + (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
1400     return [self webScriptNameForSelector:selector] == nil;
1401 }
1402
1403 + (NSArray *) _attributeKeys {
1404     return [NSArray arrayWithObjects:
1405         @"baseuri",
1406         @"distribution",
1407         @"host",
1408         @"key",
1409         @"iconuri",
1410         @"label",
1411         @"name",
1412         @"origin",
1413         @"rooturi",
1414         @"sections",
1415         @"shortDescription",
1416         @"trusted",
1417         @"type",
1418         @"version",
1419     nil];
1420 }
1421
1422 - (NSArray *) attributeKeys {
1423     return [[self class] _attributeKeys];
1424 }
1425
1426 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1427     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1428 }
1429
1430 - (metaIndex *) metaIndex {
1431     return index_;
1432 }
1433
1434 - (void) setMetaIndex:(metaIndex *)index inPool:(apr_pool_t *)pool {
1435     [self _clear];
1436
1437     trusted_ = index->IsTrusted();
1438
1439     uri_.set(pool, index->GetURI());
1440     distribution_.set(pool, index->GetDist());
1441     type_.set(pool, index->GetType());
1442
1443     debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index));
1444     if (dindex != NULL) {
1445         base_.set(pool, dindex->MetaIndexURI(""));
1446
1447         FileFd fd;
1448         if (!fd.Open(dindex->MetaIndexFile("Release"), FileFd::ReadOnly))
1449             _error->Discard();
1450         else {
1451             pkgTagFile tags(&fd);
1452
1453             pkgTagSection section;
1454             tags.Step(section);
1455
1456             struct {
1457                 const char *name_;
1458                 CYString *value_;
1459             } names[] = {
1460                 {"default-icon", &defaultIcon_},
1461                 {"depiction", &depiction_},
1462                 {"description", &description_},
1463                 {"label", &label_},
1464                 {"origin", &origin_},
1465                 {"support", &support_},
1466                 {"version", &version_},
1467             };
1468
1469             for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i) {
1470                 const char *start, *end;
1471
1472                 if (section.Find(names[i].name_, start, end)) {
1473                     CYString &value(*names[i].value_);
1474                     value.set(pool, start, end - start);
1475                 }
1476             }
1477         }
1478     }
1479
1480     record_ = [Sources_ objectForKey:[self key]];
1481
1482     NSURL *url([NSURL URLWithString:uri_]);
1483
1484     host_ = [url host];
1485     if (host_ != nil)
1486         host_ = [host_ lowercaseString];
1487
1488     if (host_ != nil)
1489         authority_ = host_;
1490     else
1491         authority_ = [url path];
1492 }
1493
1494 - (Source *) initWithMetaIndex:(metaIndex *)index forDatabase:(Database *)database inPool:(apr_pool_t *)pool {
1495     if ((self = [super init]) != nil) {
1496         era_ = [database era];
1497         database_ = database;
1498         index_ = index;
1499
1500         [self setMetaIndex:index inPool:pool];
1501     } return self;
1502 }
1503
1504 - (NSString *) getField:(NSString *)name {
1505 @synchronized (database_) {
1506     if ([database_ era] != era_ || index_ == NULL)
1507         return nil;
1508
1509     debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index_));
1510     if (dindex == NULL)
1511         return nil;
1512
1513     FileFd fd;
1514     if (!fd.Open(dindex->MetaIndexFile("Release"), FileFd::ReadOnly)) {
1515          _error->Discard();
1516          return nil;
1517     }
1518
1519     pkgTagFile tags(&fd);
1520
1521     pkgTagSection section;
1522     tags.Step(section);
1523
1524     const char *start, *end;
1525     if (!section.Find([name UTF8String], start, end))
1526         return (NSString *) [NSNull null];
1527
1528     return [NSString stringWithString:[(NSString *) CYStringCreate(start, end - start) autorelease]];
1529 } }
1530
1531 - (NSComparisonResult) compareByName:(Source *)source {
1532     NSString *lhs = [self name];
1533     NSString *rhs = [source name];
1534
1535     if ([lhs length] != 0 && [rhs length] != 0) {
1536         unichar lhc = [lhs characterAtIndex:0];
1537         unichar rhc = [rhs characterAtIndex:0];
1538
1539         if (isalpha(lhc) && !isalpha(rhc))
1540             return NSOrderedAscending;
1541         else if (!isalpha(lhc) && isalpha(rhc))
1542             return NSOrderedDescending;
1543     }
1544
1545     return [lhs compare:rhs options:LaxCompareOptions_];
1546 }
1547
1548 - (NSString *) depictionForPackage:(NSString *)package {
1549     return depiction_.empty() ? nil : [static_cast<id>(depiction_) stringByReplacingOccurrencesOfString:@"*" withString:package];
1550 }
1551
1552 - (NSString *) supportForPackage:(NSString *)package {
1553     return support_.empty() ? nil : [static_cast<id>(support_) stringByReplacingOccurrencesOfString:@"*" withString:package];
1554 }
1555
1556 - (NSArray *) sections {
1557     return record_ == nil ? (id) [NSNull null] : [record_ objectForKey:@"Sections"] ?: [NSArray array];
1558 }
1559
1560 - (void) _addSection:(NSString *)section {
1561     if (record_ == nil)
1562         return;
1563     else if (NSMutableArray *sections = [record_ objectForKey:@"Sections"]) {
1564         if (![sections containsObject:section]) {
1565             [sections addObject:section];
1566             Changed_ = true;
1567         }
1568     } else {
1569         [record_ setObject:[NSMutableArray arrayWithObject:section] forKey:@"Sections"];
1570         Changed_ = true;
1571     }
1572 }
1573
1574 - (bool) addSection:(NSString *)section {
1575     if (record_ == nil)
1576         return false;
1577
1578     [self performSelectorOnMainThread:@selector(_addSection:) withObject:section waitUntilDone:NO];
1579     return true;
1580 }
1581
1582 - (void) _removeSection:(NSString *)section {
1583     if (record_ == nil)
1584         return;
1585
1586     if (NSMutableArray *sections = [record_ objectForKey:@"Sections"])
1587         if ([sections containsObject:section]) {
1588             [sections removeObject:section];
1589             Changed_ = true;
1590         }
1591 }
1592
1593 - (bool) removeSection:(NSString *)section {
1594     if (record_ == nil)
1595         return false;
1596
1597     [self performSelectorOnMainThread:@selector(_removeSection:) withObject:section waitUntilDone:NO];
1598     return true;
1599 }
1600
1601 - (void) _remove {
1602     [Sources_ removeObjectForKey:[self key]];
1603     Changed_ = true;
1604 }
1605
1606 - (bool) remove {
1607     bool value(record_ != nil);
1608     [self performSelectorOnMainThread:@selector(_remove) withObject:nil waitUntilDone:NO];
1609     return value;
1610 }
1611
1612 - (NSDictionary *) record {
1613     return record_;
1614 }
1615
1616 - (BOOL) trusted {
1617     return trusted_;
1618 }
1619
1620 - (NSString *) rooturi {
1621     return uri_;
1622 }
1623
1624 - (NSString *) distribution {
1625     return distribution_;
1626 }
1627
1628 - (NSString *) type {
1629     return type_;
1630 }
1631
1632 - (NSString *) baseuri {
1633     return base_.empty() ? nil : (id) base_;
1634 }
1635
1636 - (NSString *) iconuri {
1637     if (NSString *base = [self baseuri])
1638         return [base stringByAppendingString:@"CydiaIcon.png"];
1639
1640     return nil;
1641 }
1642
1643 - (NSURL *) iconURL {
1644     if (NSString *uri = [self iconuri])
1645         return [NSURL URLWithString:uri];
1646     return nil;
1647 }
1648
1649 - (NSString *) key {
1650     return [NSString stringWithFormat:@"%@:%@:%@", (NSString *) type_, (NSString *) uri_, (NSString *) distribution_];
1651 }
1652
1653 - (NSString *) host {
1654     return host_;
1655 }
1656
1657 - (NSString *) name {
1658     return origin_.empty() ? (id) authority_ : origin_;
1659 }
1660
1661 - (NSString *) shortDescription {
1662     return description_;
1663 }
1664
1665 - (NSString *) label {
1666     return label_.empty() ? (id) authority_ : label_;
1667 }
1668
1669 - (NSString *) origin {
1670     return origin_;
1671 }
1672
1673 - (NSString *) version {
1674     return version_;
1675 }
1676
1677 - (NSString *) defaultIcon {
1678     return defaultIcon_;
1679 }
1680
1681 @end
1682 /* }}} */
1683 /* CydiaOperation Class {{{ */
1684 @interface CydiaOperation : NSObject {
1685     _H<NSString> operator_;
1686     _H<NSString> value_;
1687 }
1688
1689 - (NSString *) operator;
1690 - (NSString *) value;
1691
1692 @end
1693
1694 @implementation CydiaOperation
1695
1696 - (id) initWithOperator:(const char *)_operator value:(const char *)value {
1697     if ((self = [super init]) != nil) {
1698         operator_ = [NSString stringWithUTF8String:_operator];
1699         value_ = [NSString stringWithUTF8String:value];
1700     } return self;
1701 }
1702
1703 + (NSArray *) _attributeKeys {
1704     return [NSArray arrayWithObjects:
1705         @"operator",
1706         @"value",
1707     nil];
1708 }
1709
1710 - (NSArray *) attributeKeys {
1711     return [[self class] _attributeKeys];
1712 }
1713
1714 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1715     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1716 }
1717
1718 - (NSString *) operator {
1719     return operator_;
1720 }
1721
1722 - (NSString *) value {
1723     return value_;
1724 }
1725
1726 @end
1727 /* }}} */
1728 /* CydiaClause Class {{{ */
1729 @interface CydiaClause : NSObject {
1730     _H<NSString> package_;
1731     _H<CydiaOperation> version_;
1732 }
1733
1734 - (NSString *) package;
1735 - (CydiaOperation *) version;
1736
1737 @end
1738
1739 @implementation CydiaClause
1740
1741 - (id) initWithIterator:(pkgCache::DepIterator &)dep {
1742     if ((self = [super init]) != nil) {
1743         package_ = [NSString stringWithUTF8String:dep.TargetPkg().Name()];
1744
1745         if (const char *version = dep.TargetVer())
1746             version_ = [[[CydiaOperation alloc] initWithOperator:dep.CompType() value:version] autorelease];
1747         else
1748             version_ = (id) [NSNull null];
1749     } return self;
1750 }
1751
1752 + (NSArray *) _attributeKeys {
1753     return [NSArray arrayWithObjects:
1754         @"package",
1755         @"version",
1756     nil];
1757 }
1758
1759 - (NSArray *) attributeKeys {
1760     return [[self class] _attributeKeys];
1761 }
1762
1763 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1764     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1765 }
1766
1767 - (NSString *) package {
1768     return package_;
1769 }
1770
1771 - (CydiaOperation *) version {
1772     return version_;
1773 }
1774
1775 @end
1776 /* }}} */
1777 /* CydiaRelation Class {{{ */
1778 @interface CydiaRelation : NSObject {
1779     _H<NSString> relationship_;
1780     _H<NSMutableArray> clauses_;
1781 }
1782
1783 - (NSString *) relationship;
1784 - (NSArray *) clauses;
1785
1786 @end
1787
1788 @implementation CydiaRelation
1789
1790 - (id) initWithIterator:(pkgCache::DepIterator &)dep {
1791     if ((self = [super init]) != nil) {
1792         relationship_ = [NSString stringWithUTF8String:dep.DepType()];
1793         clauses_ = [NSMutableArray arrayWithCapacity:8];
1794
1795         pkgCache::DepIterator start;
1796         pkgCache::DepIterator end;
1797         dep.GlobOr(start, end); // ++dep
1798
1799         _forever {
1800             [clauses_ addObject:[[[CydiaClause alloc] initWithIterator:start] autorelease]];
1801
1802             // yes, seriously. (wtf?)
1803             if (start == end)
1804                 break;
1805             ++start;
1806         }
1807     } return self;
1808 }
1809
1810 + (NSArray *) _attributeKeys {
1811     return [NSArray arrayWithObjects:
1812         @"clauses",
1813         @"relationship",
1814     nil];
1815 }
1816
1817 - (NSArray *) attributeKeys {
1818     return [[self class] _attributeKeys];
1819 }
1820
1821 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1822     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1823 }
1824
1825 - (NSString *) relationship {
1826     return relationship_;
1827 }
1828
1829 - (NSArray *) clauses {
1830     return clauses_;
1831 }
1832
1833 - (void) addClause:(CydiaClause *)clause {
1834     [clauses_ addObject:clause];
1835 }
1836
1837 @end
1838 /* }}} */
1839 /* Package Class {{{ */
1840 struct ParsedPackage {
1841     CYString md5sum_;
1842     CYString tagline_;
1843
1844     CYString architecture_;
1845     CYString icon_;
1846
1847     CYString depiction_;
1848     CYString homepage_;
1849     CYString author_;
1850
1851     CYString support_;
1852 };
1853
1854 @interface Package : NSObject {
1855     uint32_t era_ : 25;
1856     uint32_t role_ : 3;
1857     uint32_t essential_ : 1;
1858     uint32_t obsolete_ : 1;
1859     uint32_t ignored_ : 1;
1860     uint32_t pooled_ : 1;
1861
1862     apr_pool_t *pool_;
1863
1864     uint32_t rank_;
1865
1866     _transient Database *database_;
1867
1868     pkgCache::VerIterator version_;
1869     pkgCache::PkgIterator iterator_;
1870     pkgCache::VerFileIterator file_;
1871
1872     CYString id_;
1873     CYString name_;
1874
1875     CYString latest_;
1876     CYString installed_;
1877
1878     const char *section_;
1879     _transient NSString *section$_;
1880
1881     _H<Source> source_;
1882
1883     PackageValue *metadata_;
1884     ParsedPackage *parsed_;
1885
1886     _H<NSMutableArray> tags_;
1887 }
1888
1889 - (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
1890 + (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
1891
1892 - (pkgCache::PkgIterator) iterator;
1893 - (void) parse;
1894
1895 - (NSString *) section;
1896 - (NSString *) simpleSection;
1897
1898 - (NSString *) longSection;
1899 - (NSString *) shortSection;
1900
1901 - (NSString *) uri;
1902
1903 - (MIMEAddress *) maintainer;
1904 - (size_t) size;
1905 - (NSString *) longDescription;
1906 - (NSString *) shortDescription;
1907 - (unichar) index;
1908
1909 - (PackageValue *) metadata;
1910 - (time_t) seen;
1911
1912 - (bool) subscribed;
1913 - (bool) setSubscribed:(bool)subscribed;
1914
1915 - (BOOL) ignored;
1916
1917 - (NSString *) latest;
1918 - (NSString *) installed;
1919 - (BOOL) uninstalled;
1920
1921 - (BOOL) valid;
1922 - (BOOL) upgradableAndEssential:(BOOL)essential;
1923 - (BOOL) essential;
1924 - (BOOL) broken;
1925 - (BOOL) unfiltered;
1926 - (BOOL) visible;
1927
1928 - (BOOL) half;
1929 - (BOOL) halfConfigured;
1930 - (BOOL) halfInstalled;
1931 - (BOOL) hasMode;
1932 - (NSString *) mode;
1933
1934 - (NSString *) id;
1935 - (NSString *) name;
1936 - (UIImage *) icon;
1937 - (NSString *) homepage;
1938 - (NSString *) depiction;
1939 - (MIMEAddress *) author;
1940
1941 - (NSString *) support;
1942
1943 - (NSArray *) files;
1944 - (NSArray *) warnings;
1945 - (NSArray *) applications;
1946
1947 - (Source *) source;
1948
1949 - (uint32_t) rank;
1950 - (BOOL) matches:(NSArray *)query;
1951
1952 - (bool) hasSupportingRole;
1953 - (BOOL) hasTag:(NSString *)tag;
1954 - (NSString *) primaryPurpose;
1955 - (NSArray *) purposes;
1956 - (bool) isCommercial;
1957
1958 - (void) setIndex:(size_t)index;
1959
1960 - (CYString &) cyname;
1961
1962 - (uint32_t) compareBySection:(NSArray *)sections;
1963
1964 - (void) install;
1965 - (void) remove;
1966
1967 - (bool) isUnfilteredAndSearchedForBy:(NSArray *)query;
1968 - (bool) isUnfilteredAndSelectedForBy:(NSString *)search;
1969 - (bool) isInstalledAndUnfiltered:(NSNumber *)number;
1970 - (bool) isVisibleInSection:(NSString *)section;
1971 - (bool) isVisibleInSource:(Source *)source;
1972
1973 @end
1974
1975 uint32_t PackageChangesRadix(Package *self, void *) {
1976     union {
1977         uint32_t key;
1978
1979         struct {
1980             uint32_t timestamp : 30;
1981             uint32_t ignored : 1;
1982             uint32_t upgradable : 1;
1983         } bits;
1984     } value;
1985
1986     bool upgradable([self upgradableAndEssential:YES]);
1987     value.bits.upgradable = upgradable ? 1 : 0;
1988
1989     if (upgradable) {
1990         value.bits.timestamp = 0;
1991         value.bits.ignored = [self ignored] ? 0 : 1;
1992         value.bits.upgradable = 1;
1993     } else {
1994         value.bits.timestamp = [self seen] >> 2;
1995         value.bits.ignored = 0;
1996         value.bits.upgradable = 0;
1997     }
1998
1999     return _not(uint32_t) - value.key;
2000 }
2001
2002 uint32_t PackagePrefixRadix(Package *self, void *context) {
2003     size_t offset(reinterpret_cast<size_t>(context));
2004     CYString &name([self cyname]);
2005
2006     size_t size(name.size());
2007     if (size == 0)
2008         return 0;
2009     char *text(name.data());
2010
2011     size_t zeros;
2012     if (!isdigit(text[0]))
2013         zeros = 0;
2014     else {
2015         size_t digits(1);
2016         while (size != digits && isdigit(text[digits]))
2017             if (++digits == 4)
2018                 break;
2019         zeros = 4 - digits;
2020     }
2021
2022     uint8_t data[4];
2023
2024     if (offset == 0 && zeros != 0) {
2025         memset(data, '0', zeros);
2026         memcpy(data + zeros, text, 4 - zeros);
2027     } else {
2028         /* XXX: there's some danger here if you request a non-zero offset < 4 and it gets zero padded */
2029         if (size <= offset - zeros)
2030             return 0;
2031
2032         text += offset - zeros;
2033         size -= offset - zeros;
2034
2035         if (size >= 4)
2036             memcpy(data, text, 4);
2037         else {
2038             memcpy(data, text, size);
2039             memset(data + size, 0, 4 - size);
2040         }
2041
2042         for (size_t i(0); i != 4; ++i)
2043             if (isalpha(data[i]))
2044                 data[i] |= 0x20;
2045     }
2046
2047     if (offset == 0)
2048         if (data[0] == '@')
2049             data[0] = 0x7f;
2050         else
2051             data[0] = (data[0] & 0x1f) | "\x80\x00\xc0\x40"[data[0] >> 6];
2052
2053     /* XXX: ntohl may be more honest */
2054     return OSSwapInt32(*reinterpret_cast<uint32_t *>(data));
2055 }
2056
2057 CYString &(*PackageName)(Package *self, SEL sel);
2058
2059 CFComparisonResult PackageNameCompare(Package *lhs, Package *rhs, void *arg) {
2060     _profile(PackageNameCompare)
2061         CYString &lhi(PackageName(lhs, @selector(cyname)));
2062         CYString &rhi(PackageName(rhs, @selector(cyname)));
2063         CFStringRef lhn(lhi), rhn(rhi);
2064
2065         if (lhn == NULL)
2066             return rhn == NULL ? NSOrderedSame : NSOrderedAscending;
2067         else if (rhn == NULL)
2068             return NSOrderedDescending;
2069
2070         _profile(PackageNameCompare$NumbersLast)
2071             if (!lhi.empty() && !rhi.empty()) {
2072                 UniChar lhc(CFStringGetCharacterAtIndex(lhn, 0));
2073                 UniChar rhc(CFStringGetCharacterAtIndex(rhn, 0));
2074                 bool lha(CFUniCharIsMemberOf(lhc, kCFUniCharLetterCharacterSet));
2075                 if (lha != CFUniCharIsMemberOf(rhc, kCFUniCharLetterCharacterSet))
2076                     return lha ? NSOrderedAscending : NSOrderedDescending;
2077             }
2078         _end
2079
2080         CFIndex length = CFStringGetLength(lhn);
2081
2082         _profile(PackageNameCompare$Compare)
2083             return CFStringCompareWithOptionsAndLocale(lhn, rhn, CFRangeMake(0, length), LaxCompareFlags_, Locale_);
2084         _end
2085     _end
2086 }
2087
2088 CFComparisonResult PackageNameCompare_(Package **lhs, Package **rhs, void *context) {
2089     return PackageNameCompare(*lhs, *rhs, context);
2090 }
2091
2092 struct PackageNameOrdering :
2093     std::binary_function<Package *, Package *, bool>
2094 {
2095     _finline bool operator ()(Package *lhs, Package *rhs) const {
2096         return PackageNameCompare(lhs, rhs, NULL) == NSOrderedAscending;
2097     }
2098 };
2099
2100 @implementation Package
2101
2102 - (NSString *) description {
2103     return [NSString stringWithFormat:@"<Package:%@>", static_cast<NSString *>(name_)];
2104 }
2105
2106 - (void) dealloc {
2107     if (!pooled_)
2108         apr_pool_destroy(pool_);
2109     if (parsed_ != NULL)
2110         delete parsed_;
2111     [super dealloc];
2112 }
2113
2114 + (NSString *) webScriptNameForSelector:(SEL)selector {
2115     if (false);
2116     else if (selector == @selector(clear))
2117         return @"clear";
2118     else if (selector == @selector(getField:))
2119         return @"getField";
2120     else if (selector == @selector(getRecord))
2121         return @"getRecord";
2122     else if (selector == @selector(hasTag:))
2123         return @"hasTag";
2124     else if (selector == @selector(install))
2125         return @"install";
2126     else if (selector == @selector(remove))
2127         return @"remove";
2128     else
2129         return nil;
2130 }
2131
2132 + (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
2133     return [self webScriptNameForSelector:selector] == nil;
2134 }
2135
2136 + (NSArray *) _attributeKeys {
2137     return [NSArray arrayWithObjects:
2138         @"applications",
2139         @"architecture",
2140         @"author",
2141         @"depiction",
2142         @"essential",
2143         @"homepage",
2144         @"icon",
2145         @"id",
2146         @"installed",
2147         @"latest",
2148         @"longDescription",
2149         @"longSection",
2150         @"maintainer",
2151         @"md5sum",
2152         @"mode",
2153         @"name",
2154         @"purposes",
2155         @"relations",
2156         @"section",
2157         @"selection",
2158         @"shortDescription",
2159         @"shortSection",
2160         @"simpleSection",
2161         @"size",
2162         @"source",
2163         @"state",
2164         @"support",
2165         @"tags",
2166         @"warnings",
2167     nil];
2168 }
2169
2170 - (NSArray *) attributeKeys {
2171     return [[self class] _attributeKeys];
2172 }
2173
2174 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
2175     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
2176 }
2177
2178 - (NSArray *) relations {
2179 @synchronized (database_) {
2180     NSMutableArray *relations([NSMutableArray arrayWithCapacity:16]);
2181     for (pkgCache::DepIterator dep(version_.DependsList()); !dep.end(); ++dep)
2182         [relations addObject:[[[CydiaRelation alloc] initWithIterator:dep] autorelease]];
2183     return relations;
2184 } }
2185
2186 - (NSString *) architecture {
2187     [self parse];
2188 @synchronized (database_) {
2189     return parsed_->architecture_.empty() ? [NSNull null] : (id) parsed_->architecture_;
2190 } }
2191
2192 - (NSString *) getField:(NSString *)name {
2193 @synchronized (database_) {
2194     if ([database_ era] != era_ || file_.end())
2195         return nil;
2196
2197     pkgRecords::Parser &parser([database_ records]->Lookup(file_));
2198
2199     const char *start, *end;
2200     if (!parser.Find([name UTF8String], start, end))
2201         return (NSString *) [NSNull null];
2202
2203     return [NSString stringWithString:[(NSString *) CYStringCreate(start, end - start) autorelease]];
2204 } }
2205
2206 - (NSString *) getRecord {
2207 @synchronized (database_) {
2208     if ([database_ era] != era_ || file_.end())
2209         return nil;
2210
2211     pkgRecords::Parser &parser([database_ records]->Lookup(file_));
2212
2213     const char *start, *end;
2214     parser.GetRec(start, end);
2215
2216     return [NSString stringWithString:[(NSString *) CYStringCreate(start, end - start) autorelease]];
2217 } }
2218
2219 - (void) parse {
2220     if (parsed_ != NULL)
2221         return;
2222 @synchronized (database_) {
2223     if ([database_ era] != era_ || file_.end())
2224         return;
2225
2226     ParsedPackage *parsed(new ParsedPackage);
2227     parsed_ = parsed;
2228
2229     _profile(Package$parse)
2230         pkgRecords::Parser *parser;
2231
2232         _profile(Package$parse$Lookup)
2233             parser = &[database_ records]->Lookup(file_);
2234         _end
2235
2236         CYString bugs;
2237         CYString website;
2238
2239         _profile(Package$parse$Find)
2240             struct {
2241                 const char *name_;
2242                 CYString *value_;
2243             } names[] = {
2244                 {"architecture", &parsed->architecture_},
2245                 {"icon", &parsed->icon_},
2246                 {"depiction", &parsed->depiction_},
2247                 {"homepage", &parsed->homepage_},
2248                 {"website", &website},
2249                 {"bugs", &bugs},
2250                 {"support", &parsed->support_},
2251                 {"author", &parsed->author_},
2252                 {"md5sum", &parsed->md5sum_},
2253             };
2254
2255             for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i) {
2256                 const char *start, *end;
2257
2258                 if (parser->Find(names[i].name_, start, end)) {
2259                     CYString &value(*names[i].value_);
2260                     _profile(Package$parse$Value)
2261                         value.set(pool_, start, end - start);
2262                     _end
2263                 }
2264             }
2265         _end
2266
2267         _profile(Package$parse$Tagline)
2268             const char *start, *end;
2269             if (parser->ShortDesc(start, end)) {
2270                 const char *stop(reinterpret_cast<const char *>(memchr(start, '\n', end - start)));
2271                 if (stop == NULL)
2272                     stop = end;
2273                 while (stop != start && stop[-1] == '\r')
2274                     --stop;
2275                 parsed->tagline_.set(pool_, start, stop - start);
2276             }
2277         _end
2278
2279         _profile(Package$parse$Retain)
2280             if (parsed->homepage_.empty())
2281                 parsed->homepage_ = website;
2282             if (parsed->homepage_ == parsed->depiction_)
2283                 parsed->homepage_.clear();
2284             if (parsed->support_.empty())
2285                 parsed->support_ = bugs;
2286         _end
2287     _end
2288 } }
2289
2290 - (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database {
2291     if ((self = [super init]) != nil) {
2292     _profile(Package$initWithVersion)
2293         if (pool == NULL)
2294             apr_pool_create(&pool_, NULL);
2295         else {
2296             pool_ = pool;
2297             pooled_ = true;
2298         }
2299
2300         database_ = database;
2301         era_ = [database era];
2302
2303         version_ = version;
2304
2305         pkgCache::PkgIterator iterator(version.ParentPkg());
2306         iterator_ = iterator;
2307
2308         _profile(Package$initWithVersion$Version)
2309             if (!version_.end())
2310                 file_ = version_.FileList();
2311             else {
2312                 pkgCache &cache([database_ cache]);
2313                 file_ = pkgCache::VerFileIterator(cache, cache.VerFileP);
2314             }
2315         _end
2316
2317         _profile(Package$initWithVersion$Cache)
2318             name_.set(NULL, iterator.Display());
2319
2320             latest_.set(NULL, StripVersion_(version_.VerStr()));
2321
2322             pkgCache::VerIterator current(iterator.CurrentVer());
2323             if (!current.end())
2324                 installed_.set(NULL, StripVersion_(current.VerStr()));
2325         _end
2326
2327         _profile(Package$initWithVersion$Tags)
2328             pkgCache::TagIterator tag(iterator.TagList());
2329             if (!tag.end()) {
2330                 tags_ = [NSMutableArray arrayWithCapacity:8];
2331
2332                 goto tag; for (; !tag.end(); ++tag) tag: {
2333                     const char *name(tag.Name());
2334                     NSString *string((NSString *) CYStringCreate(name));
2335                     if (string == nil)
2336                         continue;
2337
2338                     [tags_ addObject:[string autorelease]];
2339
2340                     if (role_ == 0 && strncmp(name, "role::", 6) == 0 /*&& strcmp(name, "role::leaper") != 0*/) {
2341                         if (strcmp(name + 6, "enduser") == 0)
2342                             role_ = 1;
2343                         else if (strcmp(name + 6, "hacker") == 0)
2344                             role_ = 2;
2345                         else if (strcmp(name + 6, "developer") == 0)
2346                             role_ = 3;
2347                         else if (strcmp(name + 6, "cydia") == 0)
2348                             role_ = 7;
2349                         else
2350                             role_ = 4;
2351                     }
2352
2353                     if (strncmp(name, "cydia::", 7) == 0) {
2354                         if (strcmp(name + 7, "essential") == 0)
2355                             essential_ = true;
2356                         else if (strcmp(name + 7, "obsolete") == 0)
2357                             obsolete_ = true;
2358                     }
2359                 }
2360             }
2361         _end
2362
2363         _profile(Package$initWithVersion$Metadata)
2364             const char *mixed(iterator.Name());
2365             size_t size(strlen(mixed));
2366             char lower[size + 1];
2367
2368             for (size_t i(0); i != size; ++i)
2369                 lower[i] = mixed[i] | 0x20;
2370             lower[size] = '\0';
2371
2372             PackageValue *metadata(PackageFind(lower, size));
2373             metadata_ = metadata;
2374
2375             id_.set(NULL, metadata->name_, size);
2376
2377             const char *latest(version_.VerStr());
2378             size_t length(strlen(latest));
2379
2380             uint16_t vhash(hashlittle(latest, length));
2381
2382             size_t capped(std::min<size_t>(8, length));
2383             latest = latest + length - capped;
2384
2385             if (metadata->first_ == 0)
2386                 metadata->first_ = now_;
2387
2388             if (metadata->vhash_ != vhash || strncmp(metadata->version_, latest, sizeof(metadata->version_)) != 0) {
2389                 strncpy(metadata->version_, latest, sizeof(metadata->version_));
2390                 metadata->vhash_ = vhash;
2391                 metadata->last_ = now_;
2392             } else if (metadata->last_ == 0)
2393                 metadata->last_ = metadata->first_;
2394         _end
2395
2396         _profile(Package$initWithVersion$Section)
2397             section_ = version_.Section();
2398         _end
2399
2400         _profile(Package$initWithVersion$Flags)
2401             essential_ |= ((iterator->Flags & pkgCache::Flag::Essential) == 0 ? NO : YES);
2402             ignored_ = iterator->SelectedState == pkgCache::State::Hold;
2403         _end
2404     _end } return self;
2405 }
2406
2407 + (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database {
2408     pkgCache::VerIterator version;
2409
2410     _profile(Package$packageWithIterator$GetCandidateVer)
2411         version = [database policy]->GetCandidateVer(iterator);
2412     _end
2413
2414     if (version.end())
2415         return nil;
2416
2417     Package *package;
2418
2419     _profile(Package$packageWithIterator$Allocate)
2420         package = [Package allocWithZone:zone];
2421     _end
2422
2423     _profile(Package$packageWithIterator$Initialize)
2424         package = [package
2425             initWithVersion:version
2426             withZone:zone
2427             inPool:pool
2428             database:database
2429         ];
2430     _end
2431
2432     _profile(Package$packageWithIterator$Autorelease)
2433         package = [package autorelease];
2434     _end
2435
2436     return package;
2437 }
2438
2439 - (pkgCache::PkgIterator) iterator {
2440     return iterator_;
2441 }
2442
2443 - (NSString *) section {
2444     if (section$_ == nil) {
2445         if (section_ == NULL)
2446             return nil;
2447
2448         _profile(Package$section$mappedSectionForPointer)
2449             section$_ = [database_ mappedSectionForPointer:section_];
2450         _end
2451     } return section$_;
2452 }
2453
2454 - (NSString *) simpleSection {
2455     if (NSString *section = [self section])
2456         return Simplify(section);
2457     else
2458         return nil;
2459 }
2460
2461 - (NSString *) longSection {
2462     return LocalizeSection([self section]);
2463 }
2464
2465 - (NSString *) shortSection {
2466     return [[NSBundle mainBundle] localizedStringForKey:[self simpleSection] value:nil table:@"Sections"];
2467 }
2468
2469 - (NSString *) uri {
2470     return nil;
2471 #if 0
2472     pkgIndexFile *index;
2473     pkgCache::PkgFileIterator file(file_.File());
2474     if (![database_ list].FindIndex(file, index))
2475         return nil;
2476     return [NSString stringWithUTF8String:iterator_->Path];
2477     //return [NSString stringWithUTF8String:file.Site()];
2478     //return [NSString stringWithUTF8String:index->ArchiveURI(file.FileName()).c_str()];
2479 #endif
2480 }
2481
2482 - (MIMEAddress *) maintainer {
2483 @synchronized (database_) {
2484     if ([database_ era] != era_ || file_.end())
2485         return nil;
2486
2487     pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
2488     const std::string &maintainer(parser->Maintainer());
2489     return maintainer.empty() ? nil : [MIMEAddress addressWithString:[NSString stringWithUTF8String:maintainer.c_str()]];
2490 } }
2491
2492 - (NSString *) md5sum {
2493     return parsed_ == NULL ? nil : (id) parsed_->md5sum_;
2494 }
2495
2496 - (size_t) size {
2497 @synchronized (database_) {
2498     if ([database_ era] != era_ || version_.end())
2499         return 0;
2500
2501     return version_->InstalledSize;
2502 } }
2503
2504 - (NSString *) longDescription {
2505 @synchronized (database_) {
2506     if ([database_ era] != era_ || file_.end())
2507         return nil;
2508
2509     pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
2510     NSString *description([NSString stringWithUTF8String:parser->LongDesc().c_str()]);
2511
2512     NSArray *lines = [description componentsSeparatedByString:@"\n"];
2513     NSMutableArray *trimmed = [NSMutableArray arrayWithCapacity:([lines count] - 1)];
2514     if ([lines count] < 2)
2515         return nil;
2516
2517     NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
2518     for (size_t i(1), e([lines count]); i != e; ++i) {
2519         NSString *trim = [[lines objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace];
2520         [trimmed addObject:trim];
2521     }
2522
2523     return [trimmed componentsJoinedByString:@"\n"];
2524 } }
2525
2526 - (NSString *) shortDescription {
2527     if (parsed_ != NULL)
2528         return static_cast<NSString *>(parsed_->tagline_);
2529
2530 @synchronized (database_) {
2531     pkgRecords::Parser &parser([database_ records]->Lookup(file_));
2532
2533     const char *start, *end;
2534     if (!parser.ShortDesc(start, end))
2535         return nil;
2536
2537     if (end - start > 200)
2538         end = start + 200;
2539
2540     /*
2541     if (const char *stop = reinterpret_cast<const char *>(memchr(start, '\n', end - start)))
2542         end = stop;
2543
2544     while (end != start && end[-1] == '\r')
2545         --end;
2546     */
2547
2548     return [(id) CYStringCreate(start, end - start) autorelease];
2549 } }
2550
2551 - (unichar) index {
2552     _profile(Package$index)
2553         CFStringRef name((CFStringRef) [self name]);
2554         if (CFStringGetLength(name) == 0)
2555             return '#';
2556         UniChar character(CFStringGetCharacterAtIndex(name, 0));
2557         if (!CFUniCharIsMemberOf(character, kCFUniCharLetterCharacterSet))
2558             return '#';
2559         return toupper(character);
2560     _end
2561 }
2562
2563 - (PackageValue *) metadata {
2564     return metadata_;
2565 }
2566
2567 - (time_t) seen {
2568     PackageValue *metadata([self metadata]);
2569     return metadata->subscribed_ ? metadata->last_ : metadata->first_;
2570 }
2571
2572 - (bool) subscribed {
2573     return [self metadata]->subscribed_;
2574 }
2575
2576 - (bool) setSubscribed:(bool)subscribed {
2577     PackageValue *metadata([self metadata]);
2578     if (metadata->subscribed_ == subscribed)
2579         return false;
2580     metadata->subscribed_ = subscribed;
2581     return true;
2582 }
2583
2584 - (BOOL) ignored {
2585     return ignored_;
2586 }
2587
2588 - (NSString *) latest {
2589     return latest_;
2590 }
2591
2592 - (NSString *) installed {
2593     return installed_;
2594 }
2595
2596 - (BOOL) uninstalled {
2597     return installed_.empty();
2598 }
2599
2600 - (BOOL) valid {
2601     return !version_.end();
2602 }
2603
2604 - (BOOL) upgradableAndEssential:(BOOL)essential {
2605     _profile(Package$upgradableAndEssential)
2606         pkgCache::VerIterator current(iterator_.CurrentVer());
2607         if (current.end())
2608             return essential && essential_;
2609         else
2610             return !version_.end() && version_ != current;
2611     _end
2612 }
2613
2614 - (BOOL) essential {
2615     return essential_;
2616 }
2617
2618 - (BOOL) broken {
2619     return [database_ cache][iterator_].InstBroken();
2620 }
2621
2622 - (BOOL) unfiltered {
2623     _profile(Package$unfiltered$obsolete)
2624         if (_unlikely(obsolete_))
2625             return false;
2626     _end
2627
2628     _profile(Package$unfiltered$hasSupportingRole)
2629         if (_unlikely(![self hasSupportingRole]))
2630             return false;
2631     _end
2632
2633     return true;
2634 }
2635
2636 - (BOOL) visible {
2637     if (![self unfiltered])
2638         return false;
2639
2640     NSString *section;
2641
2642     _profile(Package$visible$section)
2643         section = [self section];
2644     _end
2645
2646     _profile(Package$visible$isSectionVisible)
2647         if (!isSectionVisible(section))
2648             return false;
2649     _end
2650
2651     return true;
2652 }
2653
2654 - (BOOL) half {
2655     unsigned char current(iterator_->CurrentState);
2656     return current == pkgCache::State::HalfConfigured || current == pkgCache::State::HalfInstalled;
2657 }
2658
2659 - (BOOL) halfConfigured {
2660     return iterator_->CurrentState == pkgCache::State::HalfConfigured;
2661 }
2662
2663 - (BOOL) halfInstalled {
2664     return iterator_->CurrentState == pkgCache::State::HalfInstalled;
2665 }
2666
2667 - (BOOL) hasMode {
2668 @synchronized (database_) {
2669     if ([database_ era] != era_ || iterator_.end())
2670         return nil;
2671
2672     pkgDepCache::StateCache &state([database_ cache][iterator_]);
2673     return state.Mode != pkgDepCache::ModeKeep;
2674 } }
2675
2676 - (NSString *) mode {
2677 @synchronized (database_) {
2678     if ([database_ era] != era_ || iterator_.end())
2679         return nil;
2680
2681     pkgDepCache::StateCache &state([database_ cache][iterator_]);
2682
2683     switch (state.Mode) {
2684         case pkgDepCache::ModeDelete:
2685             if ((state.iFlags & pkgDepCache::Purge) != 0)
2686                 return @"PURGE";
2687             else
2688                 return @"REMOVE";
2689         case pkgDepCache::ModeKeep:
2690             if ((state.iFlags & pkgDepCache::ReInstall) != 0)
2691                 return @"REINSTALL";
2692             /*else if ((state.iFlags & pkgDepCache::AutoKept) != 0)
2693                 return nil;*/
2694             else
2695                 return nil;
2696         case pkgDepCache::ModeInstall:
2697             /*if ((state.iFlags & pkgDepCache::ReInstall) != 0)
2698                 return @"REINSTALL";
2699             else*/ switch (state.Status) {
2700                 case -1:
2701                     return @"DOWNGRADE";
2702                 case 0:
2703                     return @"INSTALL";
2704                 case 1:
2705                     return @"UPGRADE";
2706                 case 2:
2707                     return @"NEW_INSTALL";
2708                 _nodefault
2709             }
2710         _nodefault
2711     }
2712 } }
2713
2714 - (NSString *) id {
2715     return id_;
2716 }
2717
2718 - (NSString *) name {
2719     return name_.empty() ? id_ : name_;
2720 }
2721
2722 - (UIImage *) icon {
2723     NSString *section = [self simpleSection];
2724
2725     UIImage *icon(nil);
2726     if (parsed_ != NULL)
2727         if (NSString *href = parsed_->icon_)
2728             if ([href hasPrefix:@"file:///"])
2729                 icon = [UIImage imageAtPath:[[href substringFromIndex:7] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
2730     if (icon == nil) if (section != nil)
2731         icon = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, [section stringByReplacingOccurrencesOfString:@" " withString:@"_"]]];
2732     if (icon == nil) if (Source *source = [self source]) if (NSString *dicon = [source defaultIcon])
2733         if ([dicon hasPrefix:@"file:///"])
2734             icon = [UIImage imageAtPath:[[dicon substringFromIndex:7] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
2735     if (icon == nil)
2736         icon = [UIImage applicationImageNamed:@"unknown.png"];
2737     return icon;
2738 }
2739
2740 - (NSString *) homepage {
2741     return parsed_ == NULL ? nil : static_cast<NSString *>(parsed_->homepage_);
2742 }
2743
2744 - (NSString *) depiction {
2745     return parsed_ != NULL && !parsed_->depiction_.empty() ? parsed_->depiction_ : [[self source] depictionForPackage:id_];
2746 }
2747
2748 - (MIMEAddress *) author {
2749     return parsed_ == NULL || parsed_->author_.empty() ? nil : [MIMEAddress addressWithString:parsed_->author_];
2750 }
2751
2752 - (NSString *) support {
2753     return parsed_ != NULL && !parsed_->support_.empty() ? parsed_->support_ : [[self source] supportForPackage:id_];
2754 }
2755
2756 - (NSArray *) files {
2757     NSString *path = [NSString stringWithFormat:@"/var/lib/dpkg/info/%@.list", static_cast<NSString *>(id_)];
2758     NSMutableArray *files = [NSMutableArray arrayWithCapacity:128];
2759
2760     std::ifstream fin;
2761     fin.open([path UTF8String]);
2762     if (!fin.is_open())
2763         return nil;
2764
2765     std::string line;
2766     while (std::getline(fin, line))
2767         [files addObject:[NSString stringWithUTF8String:line.c_str()]];
2768
2769     return files;
2770 }
2771
2772 - (NSString *) state {
2773 @synchronized (database_) {
2774     if ([database_ era] != era_ || file_.end())
2775         return nil;
2776
2777     switch (iterator_->CurrentState) {
2778         case pkgCache::State::NotInstalled:
2779             return @"NotInstalled";
2780         case pkgCache::State::UnPacked:
2781             return @"UnPacked";
2782         case pkgCache::State::HalfConfigured:
2783             return @"HalfConfigured";
2784         case pkgCache::State::HalfInstalled:
2785             return @"HalfInstalled";
2786         case pkgCache::State::ConfigFiles:
2787             return @"ConfigFiles";
2788         case pkgCache::State::Installed:
2789             return @"Installed";
2790         case pkgCache::State::TriggersAwaited:
2791             return @"TriggersAwaited";
2792         case pkgCache::State::TriggersPending:
2793             return @"TriggersPending";
2794     }
2795
2796     return (NSString *) [NSNull null];
2797 } }
2798
2799 - (NSString *) selection {
2800 @synchronized (database_) {
2801     if ([database_ era] != era_ || file_.end())
2802         return nil;
2803
2804     switch (iterator_->SelectedState) {
2805         case pkgCache::State::Unknown:
2806             return @"Unknown";
2807         case pkgCache::State::Install:
2808             return @"Install";
2809         case pkgCache::State::Hold:
2810             return @"Hold";
2811         case pkgCache::State::DeInstall:
2812             return @"DeInstall";
2813         case pkgCache::State::Purge:
2814             return @"Purge";
2815     }
2816
2817     return (NSString *) [NSNull null];
2818 } }
2819
2820 - (NSArray *) warnings {
2821     NSMutableArray *warnings([NSMutableArray arrayWithCapacity:4]);
2822     const char *name(iterator_.Name());
2823
2824     size_t length(strlen(name));
2825     if (length < 2) invalid:
2826         [warnings addObject:UCLocalize("ILLEGAL_PACKAGE_IDENTIFIER")];
2827     else for (size_t i(0); i != length; ++i)
2828         if (
2829             /* XXX: technically this is not allowed */
2830             (name[i] < 'A' || name[i] > 'Z') &&
2831             (name[i] < 'a' || name[i] > 'z') &&
2832             (name[i] < '0' || name[i] > '9') &&
2833             (i == 0 || name[i] != '+' && name[i] != '-' && name[i] != '.')
2834         ) goto invalid;
2835
2836     if (strcmp(name, "cydia") != 0) {
2837         bool cydia = false;
2838         bool user = false;
2839         bool _private = false;
2840         bool stash = false;
2841
2842         bool repository = [[self section] isEqualToString:@"Repositories"];
2843
2844         if (NSArray *files = [self files])
2845             for (NSString *file in files)
2846                 if (!cydia && [file isEqualToString:@"/Applications/Cydia.app"])
2847                     cydia = true;
2848                 else if (!user && [file isEqualToString:@"/User"])
2849                     user = true;
2850                 else if (!_private && [file isEqualToString:@"/private"])
2851                     _private = true;
2852                 else if (!stash && [file isEqualToString:@"/var/stash"])
2853                     stash = true;
2854
2855         /* XXX: this is not sensitive enough. only some folders are valid. */
2856         if (cydia && !repository)
2857             [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"Cydia.app"]];
2858         if (user)
2859             [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"/User"]];
2860         if (_private)
2861             [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"/private"]];
2862         if (stash)
2863             [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"/var/stash"]];
2864     }
2865
2866     return [warnings count] == 0 ? nil : warnings;
2867 }
2868
2869 - (NSArray *) applications {
2870     NSString *me([[NSBundle mainBundle] bundleIdentifier]);
2871
2872     NSMutableArray *applications([NSMutableArray arrayWithCapacity:2]);
2873
2874     static Pcre application_r("^/Applications/(.*)\\.app/Info.plist$");
2875     if (NSArray *files = [self files])
2876         for (NSString *file in files)
2877             if (application_r(file)) {
2878                 NSDictionary *info([NSDictionary dictionaryWithContentsOfFile:file]);
2879                 NSString *id([info objectForKey:@"CFBundleIdentifier"]);
2880                 if ([id isEqualToString:me])
2881                     continue;
2882
2883                 NSString *display([info objectForKey:@"CFBundleDisplayName"]);
2884                 if (display == nil)
2885                     display = application_r[1];
2886
2887                 NSString *bundle([file stringByDeletingLastPathComponent]);
2888                 NSString *icon([info objectForKey:@"CFBundleIconFile"]);
2889                 // XXX: maybe this should check if this is really a string, not just for length
2890                 if (icon == nil || ![icon respondsToSelector:@selector(length)] || [icon length] == 0)
2891                     icon = @"icon.png";
2892                 NSURL *url([NSURL fileURLWithPath:[bundle stringByAppendingPathComponent:icon]]);
2893
2894                 NSMutableArray *application([NSMutableArray arrayWithCapacity:2]);
2895                 [applications addObject:application];
2896
2897                 [application addObject:id];
2898                 [application addObject:display];
2899                 [application addObject:url];
2900             }
2901
2902     return [applications count] == 0 ? nil : applications;
2903 }
2904
2905 - (Source *) source {
2906     if (source_ == nil) {
2907         @synchronized (database_) {
2908             if ([database_ era] != era_ || file_.end())
2909                 source_ = (Source *) [NSNull null];
2910             else
2911                 source_ = [database_ getSource:file_.File()] ?: (Source *) [NSNull null];
2912         }
2913     }
2914
2915     return source_ == (Source *) [NSNull null] ? nil : source_;
2916 }
2917
2918 - (uint32_t) rank {
2919     return rank_;
2920 }
2921
2922 - (BOOL) matches:(NSArray *)query {
2923     if (query == nil || [query count] == 0)
2924         return NO;
2925
2926     rank_ = 0;
2927
2928     NSString *string;
2929     NSRange range;
2930     NSUInteger length;
2931
2932     string = [self name];
2933     length = [string length];
2934
2935     for (NSString *term in query) {
2936         range = [string rangeOfString:term options:MatchCompareOptions_];
2937         if (range.location != NSNotFound)
2938             rank_ -= 6 * 1000000 / length;
2939     }
2940
2941     if (rank_ == 0) {
2942         string = [self id];
2943         length = [string length];
2944
2945         for (NSString *term in query) {
2946             range = [string rangeOfString:term options:MatchCompareOptions_];
2947             if (range.location != NSNotFound)
2948                 rank_ -= 6 * 1000000 / length;
2949         }
2950     }
2951
2952     string = [self shortDescription];
2953     length = [string length];
2954     NSUInteger stop(std::min<NSUInteger>(length, 200));
2955
2956     for (NSString *term in query) {
2957         range = [string rangeOfString:term options:MatchCompareOptions_ range:NSMakeRange(0, stop)];
2958         if (range.location != NSNotFound)
2959             rank_ -= 2 * 100000;
2960     }
2961
2962     return rank_ != 0;
2963 }
2964
2965 - (bool) hasSupportingRole {
2966     if (role_ == 0)
2967         return true;
2968     if (role_ == 1)
2969         return true;
2970     if ([Role_ isEqualToString:@"User"])
2971         return false;
2972     if (role_ == 2)
2973         return true;
2974     if ([Role_ isEqualToString:@"Hacker"])
2975         return false;
2976     if (role_ == 3)
2977         return true;
2978     if ([Role_ isEqualToString:@"Developer"])
2979         return false;
2980     _assert(false);
2981 }
2982
2983 - (NSArray *) tags {
2984     return tags_;
2985 }
2986
2987 - (BOOL) hasTag:(NSString *)tag {
2988     return tags_ == nil ? NO : [tags_ containsObject:tag];
2989 }
2990
2991 - (NSString *) primaryPurpose {
2992     for (NSString *tag in (NSArray *) tags_)
2993         if ([tag hasPrefix:@"purpose::"])
2994             return [tag substringFromIndex:9];
2995     return nil;
2996 }
2997
2998 - (NSArray *) purposes {
2999     NSMutableArray *purposes([NSMutableArray arrayWithCapacity:2]);
3000     for (NSString *tag in (NSArray *) tags_)
3001         if ([tag hasPrefix:@"purpose::"])
3002             [purposes addObject:[tag substringFromIndex:9]];
3003     return [purposes count] == 0 ? nil : purposes;
3004 }
3005
3006 - (bool) isCommercial {
3007     return [self hasTag:@"cydia::commercial"];
3008 }
3009
3010 - (void) setIndex:(size_t)index {
3011     if (metadata_->index_ != index)
3012         metadata_->index_ = index;
3013 }
3014
3015 - (CYString &) cyname {
3016     return name_.empty() ? id_ : name_;
3017 }
3018
3019 - (uint32_t) compareBySection:(NSArray *)sections {
3020     NSString *section([self section]);
3021     for (size_t i(0), e([sections count]); i != e; ++i) {
3022         if ([section isEqualToString:[[sections objectAtIndex:i] name]])
3023             return i;
3024     }
3025
3026     return _not(uint32_t);
3027 }
3028
3029 - (void) clear {
3030 @synchronized (database_) {
3031     pkgProblemResolver *resolver = [database_ resolver];
3032     resolver->Clear(iterator_);
3033
3034     pkgCacheFile &cache([database_ cache]);
3035     cache->SetReInstall(iterator_, false);
3036     cache->MarkKeep(iterator_, false);
3037 } }
3038
3039 - (void) install {
3040 @synchronized (database_) {
3041     pkgProblemResolver *resolver = [database_ resolver];
3042     resolver->Clear(iterator_);
3043     resolver->Protect(iterator_);
3044
3045     pkgCacheFile &cache([database_ cache]);
3046     cache->SetReInstall(iterator_, false);
3047     cache->MarkInstall(iterator_, false);
3048
3049     pkgDepCache::StateCache &state((*cache)[iterator_]);
3050     if (!state.Install())
3051         cache->SetReInstall(iterator_, true);
3052 } }
3053
3054 - (void) remove {
3055 @synchronized (database_) {
3056     pkgProblemResolver *resolver = [database_ resolver];
3057     resolver->Clear(iterator_);
3058     resolver->Remove(iterator_);
3059     resolver->Protect(iterator_);
3060
3061     pkgCacheFile &cache([database_ cache]);
3062     cache->SetReInstall(iterator_, false);
3063     cache->MarkDelete(iterator_, true);
3064 } }
3065
3066 - (bool) isUnfilteredAndSearchedForBy:(NSArray *)query {
3067     _profile(Package$isUnfilteredAndSearchedForBy)
3068         bool value(true);
3069
3070         _profile(Package$isUnfilteredAndSearchedForBy$Unfiltered)
3071             value &= [self unfiltered];
3072         _end
3073
3074         _profile(Package$isUnfilteredAndSearchedForBy$Match)
3075             value &= [self matches:query];
3076         _end
3077
3078         return value;
3079     _end
3080 }
3081
3082 - (bool) isUnfilteredAndSelectedForBy:(NSString *)search {
3083     if ([search length] == 0)
3084         return false;
3085
3086     _profile(Package$isUnfilteredAndSelectedForBy)
3087         bool value(true);
3088
3089         _profile(Package$isUnfilteredAndSelectedForBy$Unfiltered)
3090             value &= [self unfiltered];
3091         _end
3092
3093         _profile(Package$isUnfilteredAndSelectedForBy$Match)
3094             value &= [[self name] compare:search options:MatchCompareOptions_ range:NSMakeRange(0, [search length])] == NSOrderedSame;
3095         _end
3096
3097         return value;
3098     _end
3099 }
3100
3101 - (bool) isInstalledAndUnfiltered:(NSNumber *)number {
3102     return ![self uninstalled] && (![number boolValue] && role_ != 7 || [self unfiltered]);
3103 }
3104
3105 - (bool) isVisibleInSection:(NSString *)name {
3106     NSString *section([self section]);
3107
3108     return (
3109         name == nil ||
3110         section == nil && [name length] == 0 ||
3111         [name isEqualToString:section]
3112     ) && [self visible];
3113 }
3114
3115 - (bool) isVisibleInSource:(Source *)source {
3116     return [self source] == source && [self visible];
3117 }
3118
3119 @end
3120 /* }}} */
3121 /* Section Class {{{ */
3122 @interface Section : NSObject {
3123     _H<NSString> name_;
3124     unichar index_;
3125     size_t row_;
3126     size_t count_;
3127     _H<NSString> localized_;
3128 }
3129
3130 - (NSComparisonResult) compareByLocalized:(Section *)section;
3131 - (Section *) initWithName:(NSString *)name localized:(NSString *)localized;
3132 - (Section *) initWithName:(NSString *)name localize:(BOOL)localize;
3133 - (Section *) initWithName:(NSString *)name row:(size_t)row localize:(BOOL)localize;
3134 - (Section *) initWithIndex:(unichar)index row:(size_t)row;
3135 - (NSString *) name;
3136 - (unichar) index;
3137
3138 - (size_t) row;
3139 - (size_t) count;
3140
3141 - (void) addToRow;
3142 - (void) addToCount;
3143
3144 - (void) setCount:(size_t)count;
3145 - (NSString *) localized;
3146
3147 @end
3148
3149 @implementation Section
3150
3151 - (NSComparisonResult) compareByLocalized:(Section *)section {
3152     NSString *lhs(localized_);
3153     NSString *rhs([section localized]);
3154
3155     /*if ([lhs length] != 0 && [rhs length] != 0) {
3156         unichar lhc = [lhs characterAtIndex:0];
3157         unichar rhc = [rhs characterAtIndex:0];
3158
3159         if (isalpha(lhc) && !isalpha(rhc))
3160             return NSOrderedAscending;
3161         else if (!isalpha(lhc) && isalpha(rhc))
3162             return NSOrderedDescending;
3163     }*/
3164
3165     return [lhs compare:rhs options:LaxCompareOptions_];
3166 }
3167
3168 - (Section *) initWithName:(NSString *)name localized:(NSString *)localized {
3169     if ((self = [self initWithName:name localize:NO]) != nil) {
3170         if (localized != nil)
3171             localized_ = localized;
3172     } return self;
3173 }
3174
3175 - (Section *) initWithName:(NSString *)name localize:(BOOL)localize {
3176     return [self initWithName:name row:0 localize:localize];
3177 }
3178
3179 - (Section *) initWithName:(NSString *)name row:(size_t)row localize:(BOOL)localize {
3180     if ((self = [super init]) != nil) {
3181         name_ = name;
3182         index_ = '\0';
3183         row_ = row;
3184         if (localize)
3185             localized_ = LocalizeSection(name_);
3186     } return self;
3187 }
3188
3189 /* XXX: localize the index thingees */
3190 - (Section *) initWithIndex:(unichar)index row:(size_t)row {
3191     if ((self = [super init]) != nil) {
3192         name_ = [NSString stringWithCharacters:&index length:1];
3193         index_ = index;
3194         row_ = row;
3195     } return self;
3196 }
3197
3198 - (NSString *) name {
3199     return name_;
3200 }
3201
3202 - (unichar) index {
3203     return index_;
3204 }
3205
3206 - (size_t) row {
3207     return row_;
3208 }
3209
3210 - (size_t) count {
3211     return count_;
3212 }
3213
3214 - (void) addToRow {
3215     ++row_;
3216 }
3217
3218 - (void) addToCount {
3219     ++count_;
3220 }
3221
3222 - (void) setCount:(size_t)count {
3223     count_ = count;
3224 }
3225
3226 - (NSString *) localized {
3227     return localized_;
3228 }
3229
3230 @end
3231 /* }}} */
3232
3233 class CydiaLogCleaner :
3234     public pkgArchiveCleaner
3235 {
3236   protected:
3237     virtual void Erase(const char *File, std::string Pkg, std::string Ver, struct stat &St) {
3238         unlink(File);
3239     }
3240 };
3241
3242 /* Database Implementation {{{ */
3243 @implementation Database
3244
3245 + (Database *) sharedInstance {
3246     static _H<Database> instance;
3247     if (instance == nil)
3248         instance = [[[Database alloc] init] autorelease];
3249     return instance;
3250 }
3251
3252 - (unsigned) era {
3253     return era_;
3254 }
3255
3256 - (void) releasePackages {
3257     CFArrayApplyFunction(packages_, CFRangeMake(0, CFArrayGetCount(packages_)), reinterpret_cast<CFArrayApplierFunction>(&CFRelease), NULL);
3258     CFArrayRemoveAllValues(packages_);
3259 }
3260
3261 - (void) dealloc {
3262     // XXX: actually implement this thing
3263     _assert(false);
3264     [self releasePackages];
3265     apr_pool_destroy(pool_);
3266     NSRecycleZone(zone_);
3267     [super dealloc];
3268 }
3269
3270 - (void) _readCydia:(NSNumber *)fd {
3271     __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
3272     std::istream is(&ib);
3273     std::string line;
3274
3275     static Pcre finish_r("^finish:([^:]*)$");
3276
3277     while (std::getline(is, line)) {
3278         NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
3279
3280         const char *data(line.c_str());
3281         size_t size = line.size();
3282         lprintf("C:%s\n", data);
3283
3284         if (finish_r(data, size)) {
3285             NSString *finish = finish_r[1];
3286             int index = [Finishes_ indexOfObject:finish];
3287             if (index != INT_MAX && index > Finish_)
3288                 Finish_ = index;
3289         }
3290
3291         [pool release];
3292     }
3293
3294     _assume(false);
3295 }
3296
3297 - (void) _readStatus:(NSNumber *)fd {
3298     __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
3299     std::istream is(&ib);
3300     std::string line;
3301
3302     static Pcre conffile_r("^status: [^ ]* : conffile-prompt : (.*?) *$");
3303     static Pcre pmstatus_r("^([^:]*):([^:]*):([^:]*):(.*)$");
3304
3305     while (std::getline(is, line)) {
3306         NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
3307
3308         const char *data(line.c_str());
3309         size_t size(line.size());
3310         lprintf("S:%s\n", data);
3311
3312         if (conffile_r(data, size)) {
3313             // status: /fail : conffile-prompt : '/fail' '/fail.dpkg-new' 1 1
3314             [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:conffile_r[1] waitUntilDone:YES];
3315         } else if (strncmp(data, "status: ", 8) == 0) {
3316             // status: <package>: {unpacked,half-configured,installed}
3317             CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 8)] ofType:kCydiaProgressEventTypeStatus]);
3318             [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
3319         } else if (strncmp(data, "processing: ", 12) == 0) {
3320             // processing: configure: config-test
3321             CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 12)] ofType:kCydiaProgressEventTypeStatus]);
3322             [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
3323         } else if (pmstatus_r(data, size)) {
3324             std::string type([pmstatus_r[1] UTF8String]);
3325
3326             NSString *package = pmstatus_r[2];
3327             if ([package isEqualToString:@"dpkg-exec"])
3328                 package = nil;
3329
3330             float percent([pmstatus_r[3] floatValue]);
3331             [progress_ performSelectorOnMainThread:@selector(setProgressPercent:) withObject:[NSNumber numberWithFloat:(percent / 100)] waitUntilDone:YES];
3332
3333             NSString *string = pmstatus_r[4];
3334
3335             if (type == "pmerror") {
3336                 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:kCydiaProgressEventTypeError forPackage:package]);
3337                 [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
3338             } else if (type == "pmstatus") {
3339                 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:kCydiaProgressEventTypeStatus forPackage:package]);
3340                 [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
3341             } else if (type == "pmconffile")
3342                 [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:string waitUntilDone:YES];
3343             else
3344                 lprintf("E:unknown pmstatus\n");
3345         } else
3346             lprintf("E:unknown status\n");
3347
3348         [pool release];
3349     }
3350
3351     _assume(false);
3352 }
3353
3354 - (void) _readOutput:(NSNumber *)fd {
3355     __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
3356     std::istream is(&ib);
3357     std::string line;
3358
3359     while (std::getline(is, line)) {
3360         NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
3361
3362         lprintf("O:%s\n", line.c_str());
3363
3364         CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:line.c_str()] ofType:kCydiaProgressEventTypeInformation]);
3365         [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
3366
3367         [pool release];
3368     }
3369
3370     _assume(false);
3371 }
3372
3373 - (FILE *) input {
3374     return input_;
3375 }
3376
3377 - (Package *) packageWithName:(NSString *)name {
3378     if (name == nil)
3379         return nil;
3380 @synchronized (self) {
3381     if (static_cast<pkgDepCache *>(cache_) == NULL)
3382         return nil;
3383     pkgCache::PkgIterator iterator(cache_->FindPkg([name UTF8String]));
3384     return iterator.end() ? nil : [Package packageWithIterator:iterator withZone:NULL inPool:NULL database:self];
3385 } }
3386
3387 - (id) init {
3388     if ((self = [super init]) != nil) {
3389         policy_ = NULL;
3390         records_ = NULL;
3391         resolver_ = NULL;
3392         fetcher_ = NULL;
3393         lock_ = NULL;
3394
3395         zone_ = NSCreateZone(1024 * 1024, 256 * 1024, NO);
3396         apr_pool_create(&pool_, NULL);
3397
3398         size_t capacity(MetaFile_->active_);
3399         if (capacity == 0)
3400             capacity = 16384;
3401         else
3402             capacity += 1024;
3403
3404         packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL);
3405         sourceList_ = [NSMutableArray arrayWithCapacity:16];
3406
3407         int fds[2];
3408
3409         _assert(pipe(fds) != -1);
3410         cydiafd_ = fds[1];
3411
3412         _config->Set("APT::Keep-Fds::", cydiafd_);
3413         setenv("CYDIA", [[[[NSNumber numberWithInt:cydiafd_] stringValue] stringByAppendingString:@" 1"] UTF8String], _not(int));
3414
3415         [NSThread
3416             detachNewThreadSelector:@selector(_readCydia:)
3417             toTarget:self
3418             withObject:[NSNumber numberWithInt:fds[0]]
3419         ];
3420
3421         _assert(pipe(fds) != -1);
3422         statusfd_ = fds[1];
3423
3424         [NSThread
3425             detachNewThreadSelector:@selector(_readStatus:)
3426             toTarget:self
3427             withObject:[NSNumber numberWithInt:fds[0]]
3428         ];
3429
3430         _assert(pipe(fds) != -1);
3431         _assert(dup2(fds[0], 0) != -1);
3432         _assert(close(fds[0]) != -1);
3433
3434         input_ = fdopen(fds[1], "a");
3435
3436         _assert(pipe(fds) != -1);
3437         _assert(dup2(fds[1], 1) != -1);
3438         _assert(close(fds[1]) != -1);
3439
3440         [NSThread
3441             detachNewThreadSelector:@selector(_readOutput:)
3442             toTarget:self
3443             withObject:[NSNumber numberWithInt:fds[0]]
3444         ];
3445     } return self;
3446 }
3447
3448 - (pkgCacheFile &) cache {
3449     return cache_;
3450 }
3451
3452 - (pkgDepCache::Policy *) policy {
3453     return policy_;
3454 }
3455
3456 - (pkgRecords *) records {
3457     return records_;
3458 }
3459
3460 - (pkgProblemResolver *) resolver {
3461     return resolver_;
3462 }
3463
3464 - (pkgAcquire &) fetcher {
3465     return *fetcher_;
3466 }
3467
3468 - (pkgSourceList &) list {
3469     return *list_;
3470 }
3471
3472 - (NSArray *) packages {
3473     return (NSArray *) packages_;
3474 }
3475
3476 - (NSArray *) sources {
3477     return sourceList_;
3478 }
3479
3480 - (Source *) sourceWithKey:(NSString *)key {
3481     for (Source *source in [self sources]) {
3482         if ([[source key] isEqualToString:key])
3483             return source;
3484     } return nil;
3485 }
3486
3487 - (bool) popErrorWithTitle:(NSString *)title {
3488     bool fatal(false);
3489
3490     while (!_error->empty()) {
3491         std::string error;
3492         bool warning(!_error->PopMessage(error));
3493         if (!warning)
3494             fatal = true;
3495
3496         for (;;) {
3497             size_t size(error.size());
3498             if (size == 0 || error[size - 1] != '\n')
3499                 break;
3500             error.resize(size - 1);
3501         }
3502
3503         lprintf("%c:[%s]\n", warning ? 'W' : 'E', error.c_str());
3504
3505         static Pcre no_pubkey("^GPG error:.* NO_PUBKEY .*$");
3506         if (warning && no_pubkey(error.c_str()))
3507             continue;
3508
3509         [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? kCydiaProgressEventTypeWarning : kCydiaProgressEventTypeError)] forTask:title];
3510     }
3511
3512     return fatal;
3513 }
3514
3515 - (bool) popErrorWithTitle:(NSString *)title forOperation:(bool)success {
3516     return [self popErrorWithTitle:title] || !success;
3517 }
3518
3519 - (void) reloadDataWithInvocation:(NSInvocation *)invocation {
3520 @synchronized (self) {
3521     ++era_;
3522
3523     [self releasePackages];
3524
3525     sourceMap_.clear();
3526     [sourceList_ removeAllObjects];
3527
3528     _error->Discard();
3529
3530     delete list_;
3531     list_ = NULL;
3532     manager_ = NULL;
3533     delete lock_;
3534     lock_ = NULL;
3535     delete fetcher_;
3536     fetcher_ = NULL;
3537     delete resolver_;
3538     resolver_ = NULL;
3539     delete records_;
3540     records_ = NULL;
3541     delete policy_;
3542     policy_ = NULL;
3543
3544     cache_.Close();
3545
3546     apr_pool_clear(pool_);
3547
3548     NSRecycleZone(zone_);
3549     zone_ = NSCreateZone(1024 * 1024, 256 * 1024, NO);
3550
3551     int chk(creat("/tmp/cydia.chk", 0644));
3552     if (chk != -1)
3553         close(chk);
3554
3555     if (invocation != nil)
3556         [invocation invoke];
3557
3558     NSString *title(UCLocalize("DATABASE"));
3559
3560     list_ = new pkgSourceList();
3561     if ([self popErrorWithTitle:title forOperation:list_->ReadMainList()])
3562         return;
3563
3564     for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
3565         Source *object([[[Source alloc] initWithMetaIndex:*source forDatabase:self inPool:pool_] autorelease]);
3566         [sourceList_ addObject:object];
3567     }
3568
3569     _trace();
3570     OpProgress progress;
3571   open:
3572     if (!cache_.Open(progress, true)) {
3573         // XXX: what if there are errors, but Open() == true? this should be merged with popError:
3574         while (!_error->empty()) {
3575             std::string error;
3576             bool warning(!_error->PopMessage(error));
3577
3578             lprintf("cache_.Open():[%s]\n", error.c_str());
3579
3580             [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? kCydiaProgressEventTypeWarning : kCydiaProgressEventTypeError)] forTask:title];
3581
3582             SEL repair(NULL);
3583             if (false);
3584             else if (error == "dpkg was interrupted, you must manually run 'dpkg --configure -a' to correct the problem. ")
3585                 repair = @selector(configure);
3586             //else if (error == "The package lists or status file could not be parsed or opened.")
3587             //    repair = @selector(update);
3588             // else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
3589             // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
3590             // else if (error == "Malformed Status line")
3591             // else if (error == "The list of sources could not be read.")
3592
3593             if (repair != NULL) {
3594                 _error->Discard();
3595                 [delegate_ repairWithSelector:repair];
3596                 goto open;
3597             }
3598         }
3599
3600         return;
3601     }
3602     _trace();
3603
3604     unlink("/tmp/cydia.chk");
3605
3606     now_ = [[NSDate date] timeIntervalSince1970];
3607
3608     policy_ = new pkgDepCache::Policy();
3609     records_ = new pkgRecords(cache_);
3610     resolver_ = new pkgProblemResolver(cache_);
3611     fetcher_ = new pkgAcquire(&status_);
3612     lock_ = NULL;
3613
3614     if (cache_->DelCount() != 0 || cache_->InstCount() != 0) {
3615         [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("COUNTS_NONZERO_EX") ofType:kCydiaProgressEventTypeError] forTask:title];
3616         return;
3617     }
3618
3619     if ([self popErrorWithTitle:title forOperation:pkgApplyStatus(cache_)])
3620         return;
3621
3622     if (cache_->BrokenCount() != 0) {
3623         if ([self popErrorWithTitle:title forOperation:pkgFixBroken(cache_)])
3624             return;
3625
3626         if (cache_->BrokenCount() != 0) {
3627             [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("STILL_BROKEN_EX") ofType:kCydiaProgressEventTypeError] forTask:title];
3628             return;
3629         }
3630
3631         if ([self popErrorWithTitle:title forOperation:pkgMinimizeUpgrade(cache_)])
3632             return;
3633     }
3634
3635     for (Source *object in (id) sourceList_) {
3636         metaIndex *source([object metaIndex]);
3637         std::vector<pkgIndexFile *> *indices = source->GetIndexFiles();
3638         for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
3639             // XXX: this could be more intelligent
3640             if (dynamic_cast<debPackagesIndex *>(*index) != NULL) {
3641                 pkgCache::PkgFileIterator cached((*index)->FindInCache(cache_));
3642                 if (!cached.end())
3643                     sourceMap_[cached->ID] = object;
3644             }
3645     }
3646
3647     {
3648         /*std::vector<Package *> packages;
3649         packages.reserve(std::max(10000U, [packages_ count] + 1000));
3650         packages_ = nil;*/
3651
3652         _trace();
3653
3654         for (pkgCache::PkgIterator iterator = cache_->PkgBegin(); !iterator.end(); ++iterator)
3655             if (Package *package = [Package packageWithIterator:iterator withZone:zone_ inPool:pool_ database:self])
3656                 //packages.push_back(package);
3657                 CFArrayAppendValue(packages_, CFRetain(package));
3658
3659         _trace();
3660
3661         /*if (packages.empty())
3662             packages_ = [[NSArray alloc] init];
3663         else
3664             packages_ = [[NSArray alloc] initWithObjects:&packages.front() count:packages.size()];
3665         _trace();*/
3666
3667         [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(16)];
3668         [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(4)];
3669         [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(0)];
3670
3671         /*_trace();
3672         PrintTimes();
3673         _trace();*/
3674
3675         _trace();
3676
3677         /*if (!packages.empty())
3678             CFQSortArray(&packages.front(), packages.size(), sizeof(packages.front()), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare_), NULL);*/
3679         //std::sort(packages.begin(), packages.end(), PackageNameOrdering());
3680
3681         //CFArraySortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
3682
3683         CFArrayInsertionSortValues(packages_, CFRangeMake(0, CFArrayGetCount(packages_)), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
3684
3685         //[packages_ sortUsingFunction:reinterpret_cast<NSComparisonResult (*)(id, id, void *)>(&PackageNameCompare) context:NULL];
3686
3687         _trace();
3688
3689         size_t count(CFArrayGetCount(packages_));
3690         MetaFile_->active_ = count;
3691
3692         for (size_t index(0); index != count; ++index)
3693             [(Package *) CFArrayGetValueAtIndex(packages_, index) setIndex:index];
3694
3695         _trace();
3696     }
3697 } }
3698
3699 - (void) clear {
3700 @synchronized (self) {
3701     delete resolver_;
3702     resolver_ = new pkgProblemResolver(cache_);
3703
3704     for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator)
3705         if (!cache_[iterator].Keep())
3706             cache_->MarkKeep(iterator, false);
3707         else if ((cache_[iterator].iFlags & pkgDepCache::ReInstall) != 0)
3708             cache_->SetReInstall(iterator, false);
3709 } }
3710
3711 - (void) configure {
3712     NSString *dpkg = [NSString stringWithFormat:@"dpkg --configure -a --status-fd %u", statusfd_];
3713     _trace();
3714     system([dpkg UTF8String]);
3715     _trace();
3716 }
3717
3718 - (bool) clean {
3719 @synchronized (self) {
3720     // XXX: I don't remember this condition
3721     if (lock_ != NULL)
3722         return false;
3723
3724     FileFd Lock;
3725     Lock.Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
3726
3727     NSString *title(UCLocalize("CLEAN_ARCHIVES"));
3728
3729     if ([self popErrorWithTitle:title])
3730         return false;
3731
3732     pkgAcquire fetcher;
3733     fetcher.Clean(_config->FindDir("Dir::Cache::Archives"));
3734
3735     CydiaLogCleaner cleaner;
3736     if ([self popErrorWithTitle:title forOperation:cleaner.Go(_config->FindDir("Dir::Cache::Archives") + "partial/", cache_)])
3737         return false;
3738
3739     return true;
3740 } }
3741
3742 - (bool) prepare {
3743     fetcher_->Shutdown();
3744
3745     pkgRecords records(cache_);
3746
3747     lock_ = new FileFd();
3748     lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
3749
3750     NSString *title(UCLocalize("PREPARE_ARCHIVES"));
3751
3752     if ([self popErrorWithTitle:title])
3753         return false;
3754
3755     pkgSourceList list;
3756     if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
3757         return false;
3758
3759     manager_ = (_system->CreatePM(cache_));
3760     if ([self popErrorWithTitle:title forOperation:manager_->GetArchives(fetcher_, &list, &records)])
3761         return false;
3762
3763     return true;
3764 }
3765
3766 - (void) perform {
3767     bool substrate(RestartSubstrate_);
3768     RestartSubstrate_ = false;
3769
3770     NSString *title(UCLocalize("PERFORM_SELECTIONS"));
3771
3772     NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
3773         pkgSourceList list;
3774         if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
3775             return;
3776         for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
3777             [before addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
3778     }
3779
3780     [delegate_ performSelectorOnMainThread:@selector(retainNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
3781
3782     if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue) {
3783         _trace();
3784         [self popErrorWithTitle:title];
3785         return;
3786     }
3787
3788     bool failed = false;
3789     for (pkgAcquire::ItemIterator item = fetcher_->ItemsBegin(); item != fetcher_->ItemsEnd(); item++) {
3790         if ((*item)->Status == pkgAcquire::Item::StatDone && (*item)->Complete)
3791             continue;
3792         if ((*item)->Status == pkgAcquire::Item::StatIdle)
3793             continue;
3794
3795         std::string uri = (*item)->DescURI();
3796         std::string error = (*item)->ErrorText;
3797
3798         lprintf("pAf:%s:%s\n", uri.c_str(), error.c_str());
3799         failed = true;
3800
3801         CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:kCydiaProgressEventTypeError]);
3802         [delegate_ addProgressEventOnMainThread:event forTask:title];
3803     }
3804
3805     [delegate_ performSelectorOnMainThread:@selector(releaseNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
3806
3807     if (failed) {
3808         _trace();
3809         return;
3810     }
3811
3812     if (substrate)
3813         RestartSubstrate_ = true;
3814
3815     _system->UnLock();
3816     pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
3817     if ([self popErrorWithTitle:title])
3818         return;
3819
3820     if (result == pkgPackageManager::Failed) {
3821         _trace();
3822         return;
3823     }
3824
3825     if (result != pkgPackageManager::Completed) {
3826         _trace();
3827         return;
3828     }
3829
3830     NSMutableArray *after = [NSMutableArray arrayWithCapacity:16]; {
3831         pkgSourceList list;
3832         if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
3833             return;
3834         for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
3835             [after addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
3836     }
3837
3838     if (![before isEqualToArray:after])
3839         [self update];
3840 }
3841
3842 - (bool) upgrade {
3843     NSString *title(UCLocalize("UPGRADE"));
3844     if ([self popErrorWithTitle:title forOperation:pkgDistUpgrade(cache_)])
3845         return false;
3846     return true;
3847 }
3848
3849 - (void) update {
3850     [self updateWithStatus:status_];
3851 }
3852
3853 - (void) updateWithStatus:(Status &)status {
3854     NSString *title(UCLocalize("REFRESHING_DATA"));
3855
3856     pkgSourceList list;
3857     if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
3858         return;
3859
3860     FileFd lock;
3861     lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
3862     if ([self popErrorWithTitle:title])
3863         return;
3864
3865     [delegate_ performSelectorOnMainThread:@selector(retainNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
3866
3867     bool success(ListUpdate(status, list, PulseInterval_));
3868     if (status.WasCancelled())
3869         _error->Discard();
3870     else {
3871         [self popErrorWithTitle:title forOperation:success];
3872         [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"];
3873         Changed_ = true;
3874     }
3875
3876     [delegate_ performSelectorOnMainThread:@selector(releaseNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
3877 }
3878
3879 - (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate {
3880     delegate_ = delegate;
3881 }
3882
3883 - (void) setProgressDelegate:(NSObject<ProgressDelegate> *)delegate {
3884     progress_ = delegate;
3885     status_.setDelegate(delegate);
3886 }
3887
3888 - (NSObject<ProgressDelegate> *) progressDelegate {
3889     return progress_;
3890 }
3891
3892 - (Source *) getSource:(pkgCache::PkgFileIterator)file {
3893     SourceMap::const_iterator i(sourceMap_.find(file->ID));
3894     return i == sourceMap_.end() ? nil : i->second;
3895 }
3896
3897 - (NSString *) mappedSectionForPointer:(const char *)section {
3898     _H<NSString> *mapped;
3899
3900     _profile(Database$mappedSectionForPointer$Cache)
3901         mapped = &sections_[section];
3902     _end
3903
3904     if (*mapped == NULL) {
3905         size_t length(strlen(section));
3906         char spaced[length + 1];
3907
3908         _profile(Database$mappedSectionForPointer$Replace)
3909             for (size_t index(0); index != length; ++index)
3910                 spaced[index] = section[index] == '_' ? ' ' : section[index];
3911             spaced[length] = '\0';
3912         _end
3913
3914         NSString *string;
3915
3916         _profile(Database$mappedSectionForPointer$stringWithUTF8String)
3917             string = [NSString stringWithUTF8String:spaced];
3918         _end
3919
3920         _profile(Database$mappedSectionForPointer$Map)
3921             string = [SectionMap_ objectForKey:string] ?: string;
3922         _end
3923
3924         *mapped = string;
3925     } return *mapped;
3926 }
3927
3928 @end
3929 /* }}} */
3930
3931 static _H<NSMutableSet> Diversions_;
3932
3933 @interface Diversion : NSObject {
3934     Pcre pattern_;
3935     _H<NSString> key_;
3936     _H<NSString> format_;
3937 }
3938
3939 @end
3940
3941 @implementation Diversion
3942
3943 - (id) initWithFrom:(NSString *)from to:(NSString *)to {
3944     if ((self = [super init]) != nil) {
3945         pattern_ = [from UTF8String];
3946         key_ = from;
3947         format_ = to;
3948     } return self;
3949 }
3950
3951 - (NSString *) divert:(NSString *)url {
3952     return !pattern_(url) ? nil : pattern_->*format_;
3953 }
3954
3955 + (NSURL *) divertURL:(NSURL *)url {
3956   divert:
3957     NSString *href([url absoluteString]);
3958
3959     for (Diversion *diversion in (id) Diversions_)
3960         if (NSString *diverted = [diversion divert:href]) {
3961 #if !ForRelease
3962             NSLog(@"div: %@", diverted);
3963 #endif
3964             url = [NSURL URLWithString:diverted];
3965             goto divert;
3966         }
3967
3968     return url;
3969 }
3970
3971 - (NSString *) key {
3972     return key_;
3973 }
3974
3975 - (NSUInteger) hash {
3976     return [key_ hash];
3977 }
3978
3979 - (BOOL) isEqual:(Diversion *)object {
3980     return self == object || [self class] == [object class] && [key_ isEqual:[object key]];
3981 }
3982
3983 @end
3984
3985 @interface CydiaObject : NSObject {
3986     _H<CyteWebViewController> indirect_;
3987     _transient id delegate_;
3988 }
3989
3990 - (id) initWithDelegate:(IndirectDelegate *)indirect;
3991
3992 @end
3993
3994 @class CydiaObject;
3995
3996 @interface CydiaWebViewController : CyteWebViewController {
3997     _H<CydiaObject> cydia_;
3998 }
3999
4000 + (void) addDiversion:(Diversion *)diversion;
4001 + (NSURLRequest *) requestWithHeaders:(NSURLRequest *)request;
4002 + (void) didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame withCydia:(CydiaObject *)cydia;
4003 - (void) setDelegate:(id)delegate;
4004
4005 @end
4006
4007 /* Web Scripting {{{ */
4008 @implementation CydiaObject
4009
4010 - (id) initWithDelegate:(IndirectDelegate *)indirect {
4011     if ((self = [super init]) != nil) {
4012         indirect_ = (CyteWebViewController *) indirect;
4013     } return self;
4014 }
4015
4016 - (void) setDelegate:(id)delegate {
4017     delegate_ = delegate;
4018 }
4019
4020 + (NSArray *) _attributeKeys {
4021     return [NSArray arrayWithObjects:
4022         @"bbsnum",
4023         @"build",
4024         @"coreFoundationVersionNumber",
4025         @"device",
4026         @"ecid",
4027         @"firmware",
4028         @"hostname",
4029         @"idiom",
4030         @"mcc",
4031         @"mnc",
4032         @"model",
4033         @"operator",
4034         @"role",
4035         @"serial",
4036         @"token",
4037         @"version",
4038     nil];
4039 }
4040
4041 - (NSArray *) attributeKeys {
4042     return [[self class] _attributeKeys];
4043 }
4044
4045 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
4046     return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
4047 }
4048
4049 - (NSString *) version {
4050     return Cydia_;
4051 }
4052
4053 - (NSString *) build {
4054     return System_;
4055 }
4056
4057 - (NSString *) coreFoundationVersionNumber {
4058     return [NSString stringWithFormat:@"%.2f", kCFCoreFoundationVersionNumber];
4059 }
4060
4061 - (NSString *) device {
4062     return UniqueIdentifier();
4063 }
4064
4065 - (NSString *) firmware {
4066     return [[UIDevice currentDevice] systemVersion];
4067 }
4068
4069 - (NSString *) hostname {
4070     return [[UIDevice currentDevice] name];
4071 }
4072
4073 - (NSString *) idiom {
4074     return (id) Idiom_ ?: [NSNull null];
4075 }
4076
4077 - (NSString *) mcc {
4078     if (CFStringRef (*$CTSIMSupportCopyMobileSubscriberCountryCode)(CFAllocatorRef) = reinterpret_cast<CFStringRef (*)(CFAllocatorRef)>(dlsym(RTLD_DEFAULT, "CTSIMSupportCopyMobileSubscriberCountryCode")))
4079         return [(NSString *) (*$CTSIMSupportCopyMobileSubscriberCountryCode)(kCFAllocatorDefault) autorelease];
4080     return nil;
4081 }
4082
4083 - (NSString *) mnc {
4084     if (CFStringRef (*$CTSIMSupportCopyMobileSubscriberNetworkCode)(CFAllocatorRef) = reinterpret_cast<CFStringRef (*)(CFAllocatorRef)>(dlsym(RTLD_DEFAULT, "CTSIMSupportCopyMobileSubscriberNetworkCode")))
4085         return [(NSString *) (*$CTSIMSupportCopyMobileSubscriberNetworkCode)(kCFAllocatorDefault) autorelease];
4086     return nil;
4087 }
4088
4089 - (NSString *) operator {
4090     if (CFStringRef (*$CTRegistrationCopyOperatorName)(CFAllocatorRef) = reinterpret_cast<CFStringRef (*)(CFAllocatorRef)>(dlsym(RTLD_DEFAULT, "CTRegistrationCopyOperatorName")))
4091         return [(NSString *) (*$CTRegistrationCopyOperatorName)(kCFAllocatorDefault) autorelease];
4092     return nil;
4093 }
4094
4095 - (NSString *) bbsnum {
4096     return (id) BBSNum_ ?: [NSNull null];
4097 }
4098
4099 - (NSString *) ecid {
4100     return (id) ChipID_ ?: [NSNull null];
4101 }
4102
4103 - (NSString *) serial {
4104     return SerialNumber_;
4105 }
4106
4107 - (NSString *) role {
4108     return (id) Role_ ?: [NSNull null];
4109 }
4110
4111 - (NSString *) model {
4112     return [NSString stringWithUTF8String:Machine_];
4113 }
4114
4115 - (NSString *) token {
4116     return (id) Token_ ?: [NSNull null];
4117 }
4118
4119 + (NSString *) webScriptNameForSelector:(SEL)selector {
4120     if (false);
4121     else if (selector == @selector(addBridgedHost:))
4122         return @"addBridgedHost";
4123     else if (selector == @selector(addInsecureHost:))
4124         return @"addInsecureHost";
4125     else if (selector == @selector(addInternalRedirect::))
4126         return @"addInternalRedirect";
4127     else if (selector == @selector(addPipelinedHost:scheme:))
4128         return @"addPipelinedHost";
4129     else if (selector == @selector(addSource:::))
4130         return @"addSource";
4131     else if (selector == @selector(addTokenHost:))
4132         return @"addTokenHost";
4133     else if (selector == @selector(addTrivialSource:))
4134         return @"addTrivialSource";
4135     else if (selector == @selector(close))
4136         return @"close";
4137     else if (selector == @selector(du:))
4138         return @"du";
4139     else if (selector == @selector(stringWithFormat:arguments:))
4140         return @"format";
4141     else if (selector == @selector(getAllSources))
4142         return @"getAllSources";
4143     else if (selector == @selector(getKernelNumber:))
4144         return @"getKernelNumber";
4145     else if (selector == @selector(getKernelString:))
4146         return @"getKernelString";
4147     else if (selector == @selector(getInstalledPackages))
4148         return @"getInstalledPackages";
4149     else if (selector == @selector(getIORegistryEntry::))
4150         return @"getIORegistryEntry";
4151     else if (selector == @selector(getLocaleIdentifier))
4152         return @"getLocaleIdentifier";
4153     else if (selector == @selector(getPreferredLanguages))
4154         return @"getPreferredLanguages";
4155     else if (selector == @selector(getPackageById:))
4156         return @"getPackageById";
4157     else if (selector == @selector(getMetadataKeys))
4158         return @"getMetadataKeys";
4159     else if (selector == @selector(getMetadataValue:))
4160         return @"getMetadataValue";
4161     else if (selector == @selector(getSessionValue:))
4162         return @"getSessionValue";
4163     else if (selector == @selector(installPackages:))
4164         return @"installPackages";
4165     else if (selector == @selector(isReachable:))
4166         return @"isReachable";
4167     else if (selector == @selector(localizedStringForKey:value:table:))
4168         return @"localize";
4169     else if (selector == @selector(popViewController:))
4170         return @"popViewController";
4171     else if (selector == @selector(refreshSources))
4172         return @"refreshSources";
4173     else if (selector == @selector(registerFrame:))
4174         return @"registerFrame";
4175     else if (selector == @selector(removeButton))
4176         return @"removeButton";
4177     else if (selector == @selector(saveConfig))
4178         return @"saveConfig";
4179     else if (selector == @selector(setMetadataValue::))
4180         return @"setMetadataValue";
4181     else if (selector == @selector(setSessionValue::))
4182         return @"setSessionValue";
4183     else if (selector == @selector(setShowPromoted:))
4184         return @"setShowPromoted";
4185     else if (selector == @selector(substitutePackageNames:))
4186         return @"substitutePackageNames";
4187     else if (selector == @selector(scrollToBottom:))
4188         return @"scrollToBottom";
4189     else if (selector == @selector(setAllowsNavigationAction:))
4190         return @"setAllowsNavigationAction";
4191     else if (selector == @selector(setBadgeValue:))
4192         return @"setBadgeValue";
4193     else if (selector == @selector(setButtonImage:withStyle:toFunction:))
4194         return @"setButtonImage";
4195     else if (selector == @selector(setButtonTitle:withStyle:toFunction:))
4196         return @"setButtonTitle";
4197     else if (selector == @selector(setHidesBackButton:))
4198         return @"setHidesBackButton";
4199     else if (selector == @selector(setHidesNavigationBar:))
4200         return @"setHidesNavigationBar";
4201     else if (selector == @selector(setNavigationBarStyle:))
4202         return @"setNavigationBarStyle";
4203     else if (selector == @selector(setNavigationBarTintRed:green:blue:alpha:))
4204         return @"setNavigationBarTintColor";
4205     else if (selector == @selector(setPasteboardString:))
4206         return @"setPasteboardString";
4207     else if (selector == @selector(setPasteboardURL:))
4208         return @"setPasteboardURL";
4209     else if (selector == @selector(setScrollAlwaysBounceVertical:))
4210         return @"setScrollAlwaysBounceVertical";
4211     else if (selector == @selector(setScrollIndicatorStyle:))
4212         return @"setScrollIndicatorStyle";
4213     else if (selector == @selector(setToken:))
4214         return @"setToken";
4215     else if (selector == @selector(setViewportWidth:))
4216         return @"setViewportWidth";
4217     else if (selector == @selector(statfs:))
4218         return @"statfs";
4219     else if (selector == @selector(supports:))
4220         return @"supports";
4221     else if (selector == @selector(unload))
4222         return @"unload";
4223     else
4224         return nil;
4225 }
4226
4227 + (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
4228     return [self webScriptNameForSelector:selector] == nil;
4229 }
4230
4231 - (BOOL) supports:(NSString *)feature {
4232     return [feature isEqualToString:@"window.open"];
4233 }
4234
4235 - (void) unload {
4236     [delegate_ performSelectorOnMainThread:@selector(unloadData) withObject:nil waitUntilDone:NO];
4237 }
4238
4239 - (void) setScrollAlwaysBounceVertical:(NSNumber *)value {
4240     [indirect_ performSelectorOnMainThread:@selector(setScrollAlwaysBounceVerticalNumber:) withObject:value waitUntilDone:NO];
4241 }
4242
4243 - (void) setScrollIndicatorStyle:(NSString *)style {
4244     [indirect_ performSelectorOnMainThread:@selector(setScrollIndicatorStyleWithName:) withObject:style waitUntilDone:NO];
4245 }
4246
4247 - (void) addInternalRedirect:(NSString *)from :(NSString *)to {
4248     [CydiaWebViewController performSelectorOnMainThread:@selector(addDiversion:) withObject:[[[Diversion alloc] initWithFrom:from to:to] autorelease] waitUntilDone:NO];
4249 }
4250
4251 - (NSNumber *) getKernelNumber:(NSString *)name {
4252     const char *string([name UTF8String]);
4253
4254     size_t size;
4255     if (sysctlbyname(string, NULL, &size, NULL, 0) == -1)
4256         return (id) [NSNull null];
4257
4258     if (size != sizeof(int))
4259         return (id) [NSNull null];
4260
4261     int value;
4262     if (sysctlbyname(string, &value, &size, NULL, 0) == -1)
4263         return (id) [NSNull null];
4264
4265     return [NSNumber numberWithInt:value];
4266 }
4267
4268 - (NSString *) getKernelString:(NSString *)name {
4269     const char *string([name UTF8String]);
4270
4271     size_t size;
4272     if (sysctlbyname(string, NULL, &size, NULL, 0) == -1)
4273         return (id) [NSNull null];
4274
4275     char value[size + 1];
4276     if (sysctlbyname(string, value, &size, NULL, 0) == -1)
4277         return (id) [NSNull null];
4278
4279     // XXX: just in case you request something ludicrous
4280     value[size] = '\0';
4281
4282     return [NSString stringWithCString:value];
4283 }
4284
4285 - (NSObject *) getIORegistryEntry:(NSString *)path :(NSString *)entry {
4286     NSObject *value(CYIOGetValue([path UTF8String], entry));
4287
4288     if (value != nil)
4289         if ([value isKindOfClass:[NSData class]])
4290             value = CYHex((NSData *) value);
4291
4292     return value;
4293 }
4294
4295 - (NSArray *) getMetadataKeys {
4296 @synchronized (Values_) {
4297     return [Values_ allKeys];
4298 } }
4299
4300 - (void) registerFrame:(DOMHTMLIFrameElement *)iframe {
4301     WebFrame *frame([iframe contentFrame]);
4302     [indirect_ registerFrame:frame];
4303 }
4304
4305 - (void) _setShowPromoted:(NSNumber *)value {
4306     [Metadata_ setObject:value forKey:@"ShowPromoted"];
4307     Changed_ = true;
4308 }
4309
4310 - (void) setShowPromoted:(NSNumber *)value {
4311     [self performSelectorOnMainThread:@selector(_setShowPromoted:) withObject:value waitUntilDone:NO];
4312 }
4313
4314 - (id) getMetadataValue:(NSString *)key {
4315 @synchronized (Values_) {
4316     return [Values_ objectForKey:key];
4317 } }
4318
4319 - (void) setMetadataValue:(NSString *)key :(NSString *)value {
4320 @synchronized (Values_) {
4321     if (value == nil || value == (id) [WebUndefined undefined] || value == (id) [NSNull null])
4322         [Values_ removeObjectForKey:key];
4323     else
4324         [Values_ setObject:value forKey:key];
4325
4326     [delegate_ performSelectorOnMainThread:@selector(updateValues) withObject:nil waitUntilDone:YES];
4327 } }
4328
4329 - (id) getSessionValue:(NSString *)key {
4330 @synchronized (SessionData_) {
4331     return [SessionData_ objectForKey:key];
4332 } }
4333
4334 - (void) setSessionValue:(NSString *)key :(NSString *)value {
4335 @synchronized (SessionData_) {
4336     if (value == (id) [WebUndefined undefined])
4337         [SessionData_ removeObjectForKey:key];
4338     else
4339         [SessionData_ setObject:value forKey:key];
4340 } }
4341
4342 - (void) addBridgedHost:(NSString *)host {
4343 @synchronized (HostConfig_) {
4344     [BridgedHosts_ addObject:host];
4345 } }
4346
4347 - (void) addInsecureHost:(NSString *)host {
4348 @synchronized (HostConfig_) {
4349     [InsecureHosts_ addObject:host];
4350 } }
4351
4352 - (void) addTokenHost:(NSString *)host {
4353 @synchronized (HostConfig_) {
4354     [TokenHosts_ addObject:host];
4355 } }
4356
4357 - (void) addPipelinedHost:(NSString *)host scheme:(NSString *)scheme {
4358 @synchronized (HostConfig_) {
4359     if (scheme != (id) [WebUndefined undefined])
4360         host = [NSString stringWithFormat:@"%@:%@", [scheme lowercaseString], host];
4361
4362     [PipelinedHosts_ addObject:host];
4363 } }
4364
4365 - (void) popViewController:(NSNumber *)value {
4366     if (value == (id) [WebUndefined undefined])
4367         value = [NSNumber numberWithBool:YES];
4368     [indirect_ performSelectorOnMainThread:@selector(popViewControllerWithNumber:) withObject:value waitUntilDone:NO];
4369 }
4370
4371 - (void) addSource:(NSString *)href :(NSString *)distribution :(WebScriptObject *)sections {
4372     NSMutableArray *array([NSMutableArray arrayWithCapacity:[sections count]]);
4373
4374     for (NSString *section in sections)
4375         [array addObject:section];
4376
4377     [delegate_ performSelectorOnMainThread:@selector(addSource:) withObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
4378         @"deb", @"Type",
4379         href, @"URI",
4380         distribution, @"Distribution",
4381         array, @"Sections",
4382     nil] waitUntilDone:NO];
4383 }
4384
4385 - (void) addTrivialSource:(NSString *)href {
4386     [delegate_ performSelectorOnMainThread:@selector(addTrivialSource:) withObject:href waitUntilDone:NO];
4387 }
4388
4389 - (void) refreshSources {
4390     [delegate_ performSelectorOnMainThread:@selector(syncData) withObject:nil waitUntilDone:NO];
4391 }
4392
4393 - (void) saveConfig {
4394     [delegate_ performSelectorOnMainThread:@selector(_saveConfig) withObject:nil waitUntilDone:NO];
4395 }
4396
4397 - (NSArray *) getAllSources {
4398     return [[Database sharedInstance] sources];
4399 }
4400
4401 - (NSArray *) getInstalledPackages {
4402     Database *database([Database sharedInstance]);
4403 @synchronized (database) {
4404     NSArray *packages([database packages]);
4405     NSMutableArray *installed([NSMutableArray arrayWithCapacity:1024]);
4406     for (Package *package in packages)
4407         if (![package uninstalled])
4408             [installed addObject:package];
4409     return installed;
4410 } }
4411
4412 - (Package *) getPackageById:(NSString *)id {
4413     if (Package *package = [[Database sharedInstance] packageWithName:id]) {
4414         [package parse];
4415         return package;
4416     } else
4417         return (Package *) [NSNull null];
4418 }
4419
4420 - (NSString *) getLocaleIdentifier {
4421     return Locale_ == NULL ? (NSString *) [NSNull null] : (NSString *) CFLocaleGetIdentifier(Locale_);
4422 }
4423
4424 - (NSArray *) getPreferredLanguages {
4425     return Languages_;
4426 }
4427
4428 - (NSArray *) statfs:(NSString *)path {
4429     struct statfs stat;
4430
4431     if (path == nil || statfs([path UTF8String], &stat) == -1)
4432         return nil;
4433
4434     return [NSArray arrayWithObjects:
4435         [NSNumber numberWithUnsignedLong:stat.f_bsize],
4436         [NSNumber numberWithUnsignedLong:stat.f_blocks],
4437         [NSNumber numberWithUnsignedLong:stat.f_bfree],
4438     nil];
4439 }
4440
4441 - (NSNumber *) du:(NSString *)path {
4442     NSNumber *value(nil);
4443
4444     int fds[2];
4445     _assert(pipe(fds) != -1);
4446
4447     pid_t pid(ExecFork());
4448     if (pid == 0) {
4449         _assert(dup2(fds[1], 1) != -1);
4450         _assert(close(fds[0]) != -1);
4451         _assert(close(fds[1]) != -1);
4452         /* XXX: this should probably not use du */
4453         execl("/usr/libexec/cydia/du", "du", "-s", [path UTF8String], NULL);
4454         exit(1);
4455         _assert(false);
4456     }
4457
4458     _assert(close(fds[1]) != -1);
4459
4460     if (FILE *du = fdopen(fds[0], "r")) {
4461         char line[1024];
4462         while (fgets(line, sizeof(line), du) != NULL) {
4463             size_t length(strlen(line));
4464             while (length != 0 && line[length - 1] == '\n')
4465                 line[--length] = '\0';
4466             if (char *tab = strchr(line, '\t')) {
4467                 *tab = '\0';
4468                 value = [NSNumber numberWithUnsignedLong:strtoul(line, NULL, 0)];
4469             }
4470         }
4471
4472         fclose(du);
4473     } else _assert(close(fds[0]));
4474
4475     ReapZombie(pid);
4476
4477     return value;
4478 }
4479
4480 - (void) close {
4481     [indirect_ performSelectorOnMainThread:@selector(close) withObject:nil waitUntilDone:NO];
4482 }
4483
4484 - (NSNumber *) isReachable:(NSString *)name {
4485     return [NSNumber numberWithBool:IsReachable([name UTF8String])];
4486 }
4487
4488 - (void) installPackages:(NSArray *)packages {
4489     [delegate_ performSelectorOnMainThread:@selector(installPackages:) withObject:packages waitUntilDone:NO];
4490 }
4491
4492 - (NSString *) substitutePackageNames:(NSString *)message {
4493     NSMutableArray *words([[[message componentsSeparatedByString:@" "] mutableCopy] autorelease]);
4494     for (size_t i(0), e([words count]); i != e; ++i) {
4495         NSString *word([words objectAtIndex:i]);
4496         if (Package *package = [[Database sharedInstance] packageWithName:word])
4497             [words replaceObjectAtIndex:i withObject:[package name]];
4498     }
4499
4500     return [words componentsJoinedByString:@" "];
4501 }
4502
4503 - (void) removeButton {
4504     [indirect_ removeButton];
4505 }
4506
4507 - (void) setButtonImage:(NSString *)button withStyle:(NSString *)style toFunction:(id)function {
4508     [indirect_ setButtonImage:button withStyle:style toFunction:function];
4509 }
4510
4511 - (void) setButtonTitle:(NSString *)button withStyle:(NSString *)style toFunction:(id)function {
4512     [indirect_ setButtonTitle:button withStyle:style toFunction:function];
4513 }
4514
4515 - (void) setBadgeValue:(id)value {
4516     [indirect_ performSelectorOnMainThread:@selector(setBadgeValue:) withObject:value waitUntilDone:NO];
4517 }
4518
4519 - (void) setAllowsNavigationAction:(NSString *)value {
4520     [indirect_ performSelectorOnMainThread:@selector(setAllowsNavigationActionByNumber:) withObject:value waitUntilDone:NO];
4521 }
4522
4523 - (void) setHidesBackButton:(NSString *)value {
4524     [indirect_ performSelectorOnMainThread:@selector(setHidesBackButtonByNumber:) withObject:value waitUntilDone:NO];
4525 }
4526
4527 - (void) setHidesNavigationBar:(NSString *)value {
4528     [indirect_ performSelectorOnMainThread:@selector(setHidesNavigationBarByNumber:) withObject:value waitUntilDone:NO];
4529 }
4530
4531 - (void) setNavigationBarStyle:(NSString *)value {
4532     [indirect_ performSelectorOnMainThread:@selector(setNavigationBarStyle:) withObject:value waitUntilDone:NO];
4533 }
4534
4535 - (void) setNavigationBarTintRed:(NSNumber *)red green:(NSNumber *)green blue:(NSNumber *)blue alpha:(NSNumber *)alpha {
4536     float opacity(alpha == (id) [WebUndefined undefined] ? 1 : [alpha floatValue]);
4537     UIColor *color([UIColor colorWithRed:[red floatValue] green:[green floatValue] blue:[blue floatValue] alpha:opacity]);
4538     [indirect_ performSelectorOnMainThread:@selector(setNavigationBarTintColor:) withObject:color waitUntilDone:NO];
4539 }
4540
4541 - (void) setPasteboardString:(NSString *)value {
4542     [[objc_getClass("UIPasteboard") generalPasteboard] setString:value];
4543 }
4544
4545 - (void) setPasteboardURL:(NSString *)value {
4546     [[objc_getClass("UIPasteboard") generalPasteboard] setURL:[NSURL URLWithString:value]];
4547 }
4548
4549 - (void) _setToken:(NSString *)token {
4550     Token_ = token;
4551
4552     if (token == nil)
4553         [Metadata_ removeObjectForKey:@"Token"];
4554     else
4555         [Metadata_ setObject:Token_ forKey:@"Token"];
4556
4557     Changed_ = true;
4558 }
4559
4560 - (void) setToken:(NSString *)token {
4561     [self performSelectorOnMainThread:@selector(_setToken:) withObject:token waitUntilDone:NO];
4562 }
4563
4564 - (void) scrollToBottom:(NSNumber *)animated {
4565     [indirect_ performSelectorOnMainThread:@selector(scrollToBottomAnimated:) withObject:animated waitUntilDone:NO];
4566 }
4567
4568 - (void) setViewportWidth:(float)width {
4569     [indirect_ setViewportWidthOnMainThread:width];
4570 }
4571
4572 - (NSString *) stringWithFormat:(NSString *)format arguments:(WebScriptObject *)arguments {
4573     //NSLog(@"SWF:\"%@\" A:%@", format, [arguments description]);
4574     unsigned count([arguments count]);
4575     id values[count];
4576     for (unsigned i(0); i != count; ++i)
4577         values[i] = [arguments objectAtIndex:i];
4578     return [[[NSString alloc] initWithFormat:format arguments:reinterpret_cast<va_list>(values)] autorelease];
4579 }
4580
4581 - (NSString *) localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)table {
4582     if (reinterpret_cast<id>(value) == [WebUndefined undefined])
4583         value = nil;
4584     if (reinterpret_cast<id>(table) == [WebUndefined undefined])
4585         table = nil;
4586     return [[NSBundle mainBundle] localizedStringForKey:key value:value table:table];
4587 }
4588
4589 @end
4590 /* }}} */
4591
4592 @interface NSURL (CydiaSecure)
4593 @end
4594
4595 @implementation NSURL (CydiaSecure)
4596
4597 - (bool) isCydiaSecure {
4598     if ([[[self scheme] lowercaseString] isEqualToString:@"https"])
4599         return true;
4600
4601     @synchronized (HostConfig_) {
4602         if ([InsecureHosts_ containsObject:[self host]])
4603             return true;
4604     }
4605
4606     return false;
4607 }
4608
4609 @end
4610
4611 /* Cydia Browser Controller {{{ */
4612 @implementation CydiaWebViewController
4613
4614 - (NSURL *) navigationURL {
4615     return request_ == nil ? nil : [NSURL URLWithString:[NSString stringWithFormat:@"cydia://url/%@", [[request_ URL] absoluteString]]];
4616 }
4617
4618 + (void) _initialize {
4619     [super _initialize];
4620
4621     Diversions_ = [NSMutableSet setWithCapacity:0];
4622 }
4623
4624 + (void) addDiversion:(Diversion *)diversion {
4625     [Diversions_ addObject:diversion];
4626 }
4627
4628 - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
4629     [super webView:view didClearWindowObject:window forFrame:frame];
4630     [CydiaWebViewController didClearWindowObject:window forFrame:frame withCydia:cydia_];
4631 }
4632
4633 + (void) didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame withCydia:(CydiaObject *)cydia {
4634     WebDataSource *source([frame dataSource]);
4635     NSURLResponse *response([source response]);
4636     NSURL *url([response URL]);
4637     NSString *scheme([[url scheme] lowercaseString]);
4638
4639     bool bridged(false);
4640
4641     @synchronized (HostConfig_) {
4642         if ([scheme isEqualToString:@"file"])
4643             bridged = true;
4644         else if ([scheme isEqualToString:@"https"])
4645             if ([BridgedHosts_ containsObject:[url host]])
4646                 bridged = true;
4647     }
4648
4649     if (bridged)
4650         [window setValue:cydia forKey:@"cydia"];
4651 }
4652
4653 - (void) _setupMail:(MFMailComposeViewController *)controller {
4654     [controller addAttachmentData:[NSData dataWithContentsOfFile:@"/tmp/cydia.log"] mimeType:@"text/plain" fileName:@"cydia.log"];
4655
4656     system("/usr/bin/dpkg -l >/tmp/dpkgl.log");
4657     [controller addAttachmentData:[NSData dataWithContentsOfFile:@"/tmp/dpkgl.log"] mimeType:@"text/plain" fileName:@"dpkgl.log"];
4658 }
4659
4660 - (NSURL *) URLWithURL:(NSURL *)url {
4661     return [Diversion divertURL:url];
4662 }
4663
4664 - (NSURLRequest *) webView:(WebView *)view resource:(id)resource willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response fromDataSource:(WebDataSource *)source {
4665     return [CydiaWebViewController requestWithHeaders:[super webView:view resource:resource willSendRequest:request redirectResponse:response fromDataSource:source]];
4666 }
4667
4668 + (NSURLRequest *) requestWithHeaders:(NSURLRequest *)request {
4669     NSMutableURLRequest *copy([[request mutableCopy] autorelease]);
4670
4671     NSURL *url([copy URL]);
4672     NSString *href([url absoluteString]);
4673     NSString *host([url host]);
4674
4675     if ([href hasPrefix:@"https://cydia.saurik.com/TSS/"]) {
4676         if (NSString *agent = [copy valueForHTTPHeaderField:@"X-User-Agent"]) {
4677             [copy setValue:agent forHTTPHeaderField:@"User-Agent"];
4678             [copy setValue:nil forHTTPHeaderField:@"X-User-Agent"];
4679         }
4680
4681         [copy setValue:nil forHTTPHeaderField:@"Referer"];
4682         [copy setValue:nil forHTTPHeaderField:@"Origin"];
4683
4684         [copy setURL:[NSURL URLWithString:[@"http://gs.apple.com/TSS/" stringByAppendingString:[href substringFromIndex:29]]]];
4685         return copy;
4686     }
4687
4688     if ([copy valueForHTTPHeaderField:@"X-Cydia-Cf"] == nil)
4689         [copy setValue:[NSString stringWithFormat:@"%.2f", kCFCoreFoundationVersionNumber] forHTTPHeaderField:@"X-Cydia-Cf"];
4690     if (Machine_ != NULL && [copy valueForHTTPHeaderField:@"X-Machine"] == nil)
4691         [copy setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"];
4692
4693     bool bridged;
4694     bool token;
4695
4696     @synchronized (HostConfig_) {
4697         bridged = [BridgedHosts_ containsObject:host];
4698         token = [TokenHosts_ containsObject:host];
4699     }
4700
4701     if ([url isCydiaSecure]) {
4702         if (bridged) {
4703             if (UniqueID_ != nil && [copy valueForHTTPHeaderField:@"X-Cydia-Id"] == nil)
4704                 [copy setValue:UniqueID_ forHTTPHeaderField:@"X-Cydia-Id"];
4705         } else if (token) {
4706             if (Token_ != nil && [copy valueForHTTPHeaderField:@"X-Cydia-Token"] == nil)
4707                 [copy setValue:Token_ forHTTPHeaderField:@"X-Cydia-Token"];
4708         }
4709     }
4710
4711     return copy;
4712 }
4713
4714 - (void) setDelegate:(id)delegate {
4715     [super setDelegate:delegate];
4716     [cydia_ setDelegate:delegate];
4717 }
4718
4719 - (NSString *) applicationNameForUserAgent {
4720     return UserAgent_;
4721 }
4722
4723 - (id) init {
4724     if ((self = [super initWithWidth:0 ofClass:[CydiaWebViewController class]]) != nil) {
4725         cydia_ = [[[CydiaObject alloc] initWithDelegate:indirect_] autorelease];
4726     } return self;
4727 }
4728
4729 @end
4730
4731 @interface AppCacheController : CydiaWebViewController {
4732 }
4733
4734 @end
4735
4736 @implementation AppCacheController
4737
4738 - (void) didReceiveMemoryWarning {
4739     // XXX: this doesn't work
4740 }
4741
4742 - (bool) retainsNetworkActivityIndicator {
4743     return false;
4744 }
4745
4746 @end
4747 /* }}} */
4748
4749 // CydiaScript {{{
4750 @interface NSObject (CydiaScript)
4751 - (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context;
4752 @end
4753
4754 @implementation NSObject (CydiaScript)
4755
4756 - (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
4757     return self;
4758 }
4759
4760 @end
4761
4762 @implementation NSArray (CydiaScript)
4763
4764 - (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
4765     WebScriptObject *object([context evaluateWebScript:@"[]"]);
4766     for (size_t i(0), e([self count]); i != e; ++i)
4767         [object setWebScriptValueAtIndex:i value:[[self objectAtIndex:i] Cydia$webScriptObjectInContext:context]];
4768     return object;
4769 }
4770
4771 @end
4772
4773 @implementation NSDictionary (CydiaScript)
4774
4775 - (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
4776     WebScriptObject *object([context evaluateWebScript:@"({})"]);
4777     for (id i in self)
4778         [object setValue:[[self objectForKey:i] Cydia$webScriptObjectInContext:context] forKey:i];
4779     return object;
4780 }
4781
4782 @end
4783 // }}}
4784
4785 /* Confirmation Controller {{{ */
4786 bool DepSubstrate(const pkgCache::VerIterator &iterator) {
4787     if (!iterator.end())
4788         for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) {
4789             if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends)
4790                 continue;
4791             pkgCache::PkgIterator package(dep.TargetPkg());
4792             if (package.end())
4793                 continue;
4794             if (strcmp(package.Name(), "mobilesubstrate") == 0)
4795                 return true;
4796         }
4797
4798     return false;
4799 }
4800
4801 @protocol ConfirmationControllerDelegate
4802 - (void) cancelAndClear:(bool)clear;
4803 - (void) confirmWithNavigationController:(UINavigationController *)navigation;
4804 - (void) queue;
4805 @end
4806
4807 @interface ConfirmationController : CydiaWebViewController {
4808     _transient Database *database_;
4809
4810     _H<UIAlertView> essential_;
4811
4812     _H<NSDictionary> changes_;
4813     _H<NSMutableArray> issues_;
4814     _H<NSDictionary> sizes_;
4815
4816     BOOL substrate_;
4817 }
4818
4819 - (id) initWithDatabase:(Database *)database;
4820
4821 @end
4822
4823 @implementation ConfirmationController
4824
4825 - (void) complete {
4826     if (substrate_)
4827         RestartSubstrate_ = true;
4828     [delegate_ confirmWithNavigationController:[self navigationController]];
4829 }
4830
4831 - (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
4832     NSString *context([alert context]);
4833
4834     if ([context isEqualToString:@"remove"]) {
4835         if (button == [alert cancelButtonIndex])
4836             [self dismissModalViewControllerAnimated:YES];
4837         else if (button == [alert firstOtherButtonIndex]) {
4838             [self performSelector:@selector(complete) withObject:nil afterDelay:0];
4839         }
4840
4841         [alert dismissWithClickedButtonIndex:-1 animated:YES];
4842     } else if ([context isEqualToString:@"unable"]) {
4843         [self dismissModalViewControllerAnimated:YES];
4844         [alert dismissWithClickedButtonIndex:-1 animated:YES];
4845     } else {
4846         [super alertView:alert clickedButtonAtIndex:button];
4847     }
4848 }
4849
4850 - (void) _doContinue {
4851     [delegate_ cancelAndClear:NO];
4852     [self dismissModalViewControllerAnimated:YES];
4853 }
4854
4855 - (id) invokeDefaultMethodWithArguments:(NSArray *)args {
4856     [self performSelectorOnMainThread:@selector(_doContinue) withObject:nil waitUntilDone:NO];
4857     return nil;
4858 }
4859
4860 - (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
4861     [super webView:view didClearWindowObject:window forFrame:frame];
4862
4863     [window setValue:[[NSDictionary dictionaryWithObjectsAndKeys:
4864         (id) changes_, @"changes",
4865         (id) issues_, @"issues",
4866         (id) sizes_, @"sizes",
4867         self, @"queue",
4868     nil] Cydia$webScriptObjectInContext:window] forKey:@"cydiaConfirm"];
4869 }
4870
4871 - (id) initWithDatabase:(Database *)database {
4872     if ((self = [super init]) != nil) {
4873         database_ = database;
4874
4875         NSMutableArray *installs([NSMutableArray arrayWithCapacity:16]);
4876         NSMutableArray *reinstalls([NSMutableArray arrayWithCapacity:16]);
4877         NSMutableArray *upgrades([NSMutableArray arrayWithCapacity:16]);
4878         NSMutableArray *downgrades([NSMutableArray arrayWithCapacity:16]);
4879         NSMutableArray *removes([NSMutableArray arrayWithCapacity:16]);
4880
4881         bool remove(false);
4882
4883         pkgCacheFile &cache([database_ cache]);
4884         NSArray *packages([database_ packages]);
4885         pkgDepCache::Policy *policy([database_ policy]);
4886
4887         issues_ = [NSMutableArray arrayWithCapacity:4];
4888
4889         for (Package *package in packages) {
4890             pkgCache::PkgIterator iterator([package iterator]);
4891             NSString *name([package id]);
4892
4893             if ([package broken]) {
4894                 NSMutableArray *reasons([NSMutableArray arrayWithCapacity:4]);
4895
4896                 [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
4897                     name, @"package",
4898                     reasons, @"reasons",
4899                 nil]];
4900
4901                 pkgCache::VerIterator ver(cache[iterator].InstVerIter(cache));
4902                 if (ver.end())
4903                     continue;
4904
4905                 for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
4906                     pkgCache::DepIterator start;
4907                     pkgCache::DepIterator end;
4908                     dep.GlobOr(start, end); // ++dep
4909
4910                     if (!cache->IsImportantDep(end))
4911                         continue;
4912                     if ((cache[end] & pkgDepCache::DepGInstall) != 0)
4913                         continue;
4914
4915                     NSMutableArray *clauses([NSMutableArray arrayWithCapacity:4]);
4916
4917                     [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys:
4918                         [NSString stringWithUTF8String:start.DepType()], @"relationship",
4919                         clauses, @"clauses",
4920                     nil]];
4921
4922                     _forever {
4923                         NSString *reason, *installed((NSString *) [WebUndefined undefined]);
4924
4925                         pkgCache::PkgIterator target(start.TargetPkg());
4926                         if (target->ProvidesList != 0)
4927                             reason = @"missing";
4928                         else {
4929                             pkgCache::VerIterator ver(cache[target].InstVerIter(cache));
4930                             if (!ver.end()) {
4931                                 reason = @"installed";
4932                                 installed = [NSString stringWithUTF8String:ver.VerStr()];
4933                             } else if (!cache[target].CandidateVerIter(cache).end())
4934                                 reason = @"uninstalled";
4935                             else if (target->ProvidesList == 0)
4936                                 reason = @"uninstallable";
4937                             else
4938                                 reason = @"virtual";
4939                         }
4940
4941                         NSDictionary *version(start.TargetVer() == 0 ? [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys:
4942                             [NSString stringWithUTF8String:start.CompType()], @"operator",
4943                             [NSString stringWithUTF8String:start.TargetVer()], @"value",
4944                         nil]);
4945
4946                         [clauses addObject:[NSDictionary dictionaryWithObjectsAndKeys:
4947                             [NSString stringWithUTF8String:start.TargetPkg().Name()], @"package",
4948                             version, @"version",
4949                             reason, @"reason",
4950                             installed, @"installed",
4951                         nil]];
4952
4953                         // yes, seriously. (wtf?)
4954                         if (start == end)
4955                             break;
4956                         ++start;
4957                     }
4958                 }
4959             }
4960
4961             pkgDepCache::StateCache &state(cache[iterator]);
4962
4963             static Pcre special_r("^(firmware$|gsc\\.|cy\\+)");
4964
4965             if (state.NewInstall())
4966                 [installs addObject:name];
4967             // XXX: else if (state.Install())
4968             else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
4969                 [reinstalls addObject:name];
4970             // XXX: move before previous if
4971             else if (state.Upgrade())
4972                 [upgrades addObject:name];
4973             else if (state.Downgrade())
4974                 [downgrades addObject:name];
4975             else if (!state.Delete())
4976                 // XXX: _assert(state.Keep());
4977                 continue;
4978             else if (special_r(name))
4979                 [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
4980                     [NSNull null], @"package",
4981                     [NSArray arrayWithObjects:
4982                         [NSDictionary dictionaryWithObjectsAndKeys:
4983                             @"Conflicts", @"relationship",
4984                             [NSArray arrayWithObjects:
4985                                 [NSDictionary dictionaryWithObjectsAndKeys:
4986                                     name, @"package",
4987                                     [NSNull null], @"version",
4988                                     @"installed", @"reason",
4989                                 nil],
4990                             nil], @"clauses",
4991                         nil],
4992                     nil], @"reasons",
4993                 nil]];
4994             else {
4995                 if ([package essential])
4996                     remove = true;
4997                 [removes addObject:name];
4998             }
4999
5000             substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
5001             substrate_ |= DepSubstrate(iterator.CurrentVer());
5002         }
5003
5004         if (!remove)
5005             essential_ = nil;
5006         else if (Advanced_) {
5007             NSString *parenthetical(UCLocalize("PARENTHETICAL"));
5008
5009             essential_ = [[[UIAlertView alloc]
5010                 initWithTitle:UCLocalize("REMOVING_ESSENTIALS")
5011                 message:UCLocalize("REMOVING_ESSENTIALS_EX")
5012                 delegate:self
5013                 cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")]
5014                 otherButtonTitles:
5015                     [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")],
5016                 nil
5017             ] autorelease];
5018
5019             [essential_ setContext:@"remove"];
5020             [essential_ setNumberOfRows:2];
5021         } else {
5022             essential_ = [[[UIAlertView alloc]
5023                 initWithTitle:UCLocalize("UNABLE_TO_COMPLY")
5024                 message:UCLocalize("UNABLE_TO_COMPLY_EX")
5025                 delegate:self
5026                 cancelButtonTitle:UCLocalize("OKAY")
5027                 otherButtonTitles:nil
5028             ] autorelease];
5029
5030             [essential_ setContext:@"unable"];
5031         }
5032
5033         changes_ = [NSDictionary dictionaryWithObjectsAndKeys:
5034             installs, @"installs",
5035             reinstalls, @"