]> git.saurik.com Git - cydia.git/blob - Cydia.mm
I think the future is now.
[cydia.git] / Cydia.mm
1 /* Cydia - iPhone UIKit Front-End for Debian APT
2 * Copyright (C) 2008 Jay Freeman (saurik)
3 */
4
5 /*
6 * Redistribution and use in source and binary
7 * forms, with or without modification, are permitted
8 * provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the
11 * above copyright notice, this list of conditions
12 * and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the
14 * above copyright notice, this list of conditions
15 * and the following disclaimer in the documentation
16 * and/or other materials provided with the
17 * distribution.
18 * 3. The name of the author may not be used to endorse
19 * or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS''
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
24 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
33 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38 // XXX: wtf/FastMalloc.h... wtf?
39 #define USE_SYSTEM_MALLOC 1
40
41 /* #include Directives {{{ */
42 #import "UICaboodle.h"
43
44 #include <objc/objc.h>
45 #include <objc/runtime.h>
46
47 #include <CoreGraphics/CoreGraphics.h>
48 #include <GraphicsServices/GraphicsServices.h>
49 #include <Foundation/Foundation.h>
50
51 #import <QuartzCore/CALayer.h>
52
53 #import <UIKit/UIKit.h>
54
55 // XXX: remove
56 #import <MessageUI/MailComposeController.h>
57
58 #include <sstream>
59 #include <string>
60
61 #include <ext/stdio_filebuf.h>
62
63 #include <apt-pkg/acquire.h>
64 #include <apt-pkg/acquire-item.h>
65 #include <apt-pkg/algorithms.h>
66 #include <apt-pkg/cachefile.h>
67 #include <apt-pkg/clean.h>
68 #include <apt-pkg/configuration.h>
69 #include <apt-pkg/debmetaindex.h>
70 #include <apt-pkg/error.h>
71 #include <apt-pkg/init.h>
72 #include <apt-pkg/mmap.h>
73 #include <apt-pkg/pkgrecords.h>
74 #include <apt-pkg/sha1.h>
75 #include <apt-pkg/sourcelist.h>
76 #include <apt-pkg/sptr.h>
77 #include <apt-pkg/strutl.h>
78
79 #include <sys/types.h>
80 #include <sys/stat.h>
81 #include <sys/sysctl.h>
82 #include <sys/param.h>
83 #include <sys/mount.h>
84
85 #include <notify.h>
86 #include <dlfcn.h>
87
88 extern "C" {
89 #include <mach-o/nlist.h>
90 }
91
92 #include <cstdio>
93 #include <cstdlib>
94 #include <cstring>
95
96 #include <errno.h>
97 #include <pcre.h>
98
99 #import "BrowserView.h"
100 #import "ResetView.h"
101
102 #import "substrate.h"
103 /* }}} */
104
105 //#define _finline __attribute__((force_inline))
106 #define _finline inline
107
108 struct timeval _ltv;
109 bool _itv;
110
111 #define _limit(count) do { \
112 static size_t _count(0); \
113 if (++_count == count) \
114 exit(0); \
115 } while (false)
116
117 static uint64_t profile_;
118
119 #define _timestamp ({ \
120 struct timeval tv; \
121 gettimeofday(&tv, NULL); \
122 tv.tv_sec * 1000000 + tv.tv_usec; \
123 })
124
125 /* Objective-C Handle<> {{{ */
126 template <typename Type_>
127 class _H {
128 typedef _H<Type_> This_;
129
130 private:
131 Type_ *value_;
132
133 _finline void Retain_() {
134 if (value_ != nil)
135 [value_ retain];
136 }
137
138 _finline void Clear_() {
139 if (value_ != nil)
140 [value_ release];
141 }
142
143 public:
144 _finline _H(Type_ *value = NULL, bool mended = false) :
145 value_(value)
146 {
147 if (!mended)
148 Retain_();
149 }
150
151 _finline ~_H() {
152 Clear_();
153 }
154
155 _finline This_ &operator =(Type_ *value) {
156 if (value_ != value) {
157 Clear_();
158 value_ = value;
159 Retain_();
160 } return this;
161 }
162 };
163 /* }}} */
164
165 #define _pooled _H<NSAutoreleasePool> _pool([[NSAutoreleasePool alloc] init], true);
166
167 void NSLogPoint(const char *fix, const CGPoint &point) {
168 NSLog(@"%s(%g,%g)", fix, point.x, point.y);
169 }
170
171 void NSLogRect(const char *fix, const CGRect &rect) {
172 NSLog(@"%s(%g,%g)+(%g,%g)", fix, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
173 }
174
175 /* NSForcedOrderingSearch doesn't work on the iPhone */
176 static const NSStringCompareOptions BaseCompareOptions_ = NSNumericSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch;
177 static const NSStringCompareOptions ForcedCompareOptions_ = BaseCompareOptions_;
178 static const NSStringCompareOptions LaxCompareOptions_ = BaseCompareOptions_ | NSCaseInsensitiveSearch;
179
180 /* iPhoneOS 2.0 Compatibility {{{ */
181 #ifdef __OBJC2__
182 @interface UITextView (iPhoneOS)
183 - (void) setTextSize:(float)size;
184 @end
185
186 @implementation UITextView (iPhoneOS)
187
188 - (void) setTextSize:(float)size {
189 [self setFont:[[self font] fontWithSize:size]];
190 }
191
192 @end
193 #endif
194 /* }}} */
195
196 extern NSString * const kCAFilterNearest;
197
198 /* Information Dictionaries {{{ */
199 @interface NSMutableArray (Cydia)
200 - (void) addInfoDictionary:(NSDictionary *)info;
201 @end
202
203 @implementation NSMutableArray (Cydia)
204
205 - (void) addInfoDictionary:(NSDictionary *)info {
206 [self addObject:info];
207 }
208
209 @end
210
211 @interface NSMutableDictionary (Cydia)
212 - (void) addInfoDictionary:(NSDictionary *)info;
213 @end
214
215 @implementation NSMutableDictionary (Cydia)
216
217 - (void) addInfoDictionary:(NSDictionary *)info {
218 NSString *bundle = [info objectForKey:@"CFBundleIdentifier"];
219 [self setObject:info forKey:bundle];
220 }
221
222 @end
223 /* }}} */
224 /* Pop Transitions {{{ */
225 @interface PopTransitionView : UITransitionView {
226 }
227
228 @end
229
230 @implementation PopTransitionView
231
232 - (void) transitionViewDidComplete:(UITransitionView *)view fromView:(UIView *)from toView:(UIView *)to {
233 if (from != nil && to == nil)
234 [self removeFromSuperview];
235 }
236
237 @end
238
239 @implementation UIView (PopUpView)
240
241 - (void) popFromSuperviewAnimated:(BOOL)animated {
242 [[self superview] transition:(animated ? UITransitionPushFromTop : UITransitionNone) toView:nil];
243 }
244
245 - (void) popSubview:(UIView *)view {
246 UITransitionView *transition([[[PopTransitionView alloc] initWithFrame:[self bounds]] autorelease]);
247 [transition setDelegate:transition];
248 [self addSubview:transition];
249
250 UIView *blank = [[[UIView alloc] initWithFrame:[transition bounds]] autorelease];
251 [transition transition:UITransitionNone toView:blank];
252 [transition transition:UITransitionPushFromBottom toView:view];
253 }
254
255 @end
256 /* }}} */
257
258 #define lprintf(args...) fprintf(stderr, args)
259
260 #define ForRelease 0
261 #define ForSaurik (1 && !ForRelease)
262 #define IgnoreInstall (0 && !ForRelease)
263 #define RecycleWebViews 0
264 #define AlwaysReload (1 && !ForRelease)
265
266 #if ForRelease
267 #undef _trace
268 #define _trace(args...)
269 #endif
270
271 /* Radix Sort {{{ */
272 @interface NSMutableArray (Radix)
273 - (void) radixSortUsingSelector:(SEL)selector withObject:(id)object;
274 @end
275
276 @implementation NSMutableArray (Radix)
277
278 - (void) radixSortUsingSelector:(SEL)selector withObject:(id)object {
279 NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:"L12@0:4@8"]]);
280 [invocation setSelector:selector];
281 [invocation setArgument:&object atIndex:2];
282
283 size_t count([self count]);
284
285 struct RadixItem {
286 size_t index;
287 uint32_t key;
288 } *swap(new RadixItem[count * 2]), *lhs(swap), *rhs(swap + count);
289
290 for (size_t i(0); i != count; ++i) {
291 RadixItem &item(lhs[i]);
292 item.index = i;
293
294 id object([self objectAtIndex:i]);
295 [invocation setTarget:object];
296
297 [invocation invoke];
298 [invocation getReturnValue:&item.key];
299 }
300
301 static const size_t width = 32;
302 static const size_t bits = 11;
303 static const size_t slots = 1 << bits;
304 static const size_t passes = (width + (bits - 1)) / bits;
305
306 size_t *hist(new size_t[slots]);
307
308 for (size_t pass(0); pass != passes; ++pass) {
309 memset(hist, 0, sizeof(size_t) * slots);
310
311 for (size_t i(0); i != count; ++i) {
312 uint32_t key(lhs[i].key);
313 key >>= pass * bits;
314 key &= _not(uint32_t) >> width - bits;
315 ++hist[key];
316 }
317
318 size_t offset(0);
319 for (size_t i(0); i != slots; ++i) {
320 size_t local(offset);
321 offset += hist[i];
322 hist[i] = local;
323 }
324
325 for (size_t i(0); i != count; ++i) {
326 uint32_t key(lhs[i].key);
327 key >>= pass * bits;
328 key &= _not(uint32_t) >> width - bits;
329 rhs[hist[key]++] = lhs[i];
330 }
331
332 RadixItem *tmp(lhs);
333 lhs = rhs;
334 rhs = tmp;
335 }
336
337 delete [] hist;
338
339 NSMutableArray *values([NSMutableArray arrayWithCapacity:count]);
340 for (size_t i(0); i != count; ++i)
341 [values addObject:[self objectAtIndex:lhs[i].index]];
342 [self setArray:values];
343
344 delete [] swap;
345 }
346
347 @end
348 /* }}} */
349
350 /* Apple Bug Fixes {{{ */
351 @implementation UIWebDocumentView (Cydia)
352
353 - (void) _setScrollerOffset:(CGPoint)offset {
354 UIScroller *scroller([self _scroller]);
355
356 CGSize size([scroller contentSize]);
357 CGSize bounds([scroller bounds].size);
358
359 CGPoint max;
360 max.x = size.width - bounds.width;
361 max.y = size.height - bounds.height;
362
363 // wtf Apple?!
364 if (max.x < 0)
365 max.x = 0;
366 if (max.y < 0)
367 max.y = 0;
368
369 offset.x = offset.x < 0 ? 0 : offset.x > max.x ? max.x : offset.x;
370 offset.y = offset.y < 0 ? 0 : offset.y > max.y ? max.y : offset.y;
371
372 [scroller setOffset:offset];
373 }
374
375 @end
376 /* }}} */
377
378 typedef enum {
379 kUIControlEventMouseDown = 1 << 0,
380 kUIControlEventMouseMovedInside = 1 << 2, // mouse moved inside control target
381 kUIControlEventMouseMovedOutside = 1 << 3, // mouse moved outside control target
382 kUIControlEventMouseUpInside = 1 << 6, // mouse up inside control target
383 kUIControlEventMouseUpOutside = 1 << 7, // mouse up outside control target
384 kUIControlAllEvents = (kUIControlEventMouseDown | kUIControlEventMouseMovedInside | kUIControlEventMouseMovedOutside | kUIControlEventMouseUpInside | kUIControlEventMouseUpOutside)
385 } UIControlEventMasks;
386
387 NSUInteger DOMNodeList$countByEnumeratingWithState$objects$count$(DOMNodeList *self, SEL sel, NSFastEnumerationState *state, id *objects, NSUInteger count) {
388 size_t length([self length] - state->state);
389 if (length <= 0)
390 return 0;
391 else if (length > count)
392 length = count;
393 for (size_t i(0); i != length; ++i)
394 objects[i] = [self item:state->state++];
395 state->itemsPtr = objects;
396 state->mutationsPtr = (unsigned long *) self;
397 return length;
398 }
399
400 @interface NSString (UIKit)
401 - (NSString *) stringByAddingPercentEscapes;
402 - (NSString *) stringByReplacingCharacter:(unsigned short)arg0 withCharacter:(unsigned short)arg1;
403 @end
404
405 @interface NSString (Cydia)
406 + (NSString *) stringWithUTF8Bytes:(const char *)bytes length:(int)length;
407 - (NSComparisonResult) compareByPath:(NSString *)other;
408 @end
409
410 @implementation NSString (Cydia)
411
412 + (NSString *) stringWithUTF8Bytes:(const char *)bytes length:(int)length {
413 return [[[NSString alloc] initWithBytes:bytes length:length encoding:NSUTF8StringEncoding] autorelease];
414 }
415
416 - (NSComparisonResult) compareByPath:(NSString *)other {
417 NSString *prefix = [self commonPrefixWithString:other options:0];
418 size_t length = [prefix length];
419
420 NSRange lrange = NSMakeRange(length, [self length] - length);
421 NSRange rrange = NSMakeRange(length, [other length] - length);
422
423 lrange = [self rangeOfString:@"/" options:0 range:lrange];
424 rrange = [other rangeOfString:@"/" options:0 range:rrange];
425
426 NSComparisonResult value;
427
428 if (lrange.location == NSNotFound && rrange.location == NSNotFound)
429 value = NSOrderedSame;
430 else if (lrange.location == NSNotFound)
431 value = NSOrderedAscending;
432 else if (rrange.location == NSNotFound)
433 value = NSOrderedDescending;
434 else
435 value = NSOrderedSame;
436
437 NSString *lpath = lrange.location == NSNotFound ? [self substringFromIndex:length] :
438 [self substringWithRange:NSMakeRange(length, lrange.location - length)];
439 NSString *rpath = rrange.location == NSNotFound ? [other substringFromIndex:length] :
440 [other substringWithRange:NSMakeRange(length, rrange.location - length)];
441
442 NSComparisonResult result = [lpath compare:rpath];
443 return result == NSOrderedSame ? value : result;
444 }
445
446 @end
447
448 /* Perl-Compatible RegEx {{{ */
449 class Pcre {
450 private:
451 pcre *code_;
452 pcre_extra *study_;
453 int capture_;
454 int *matches_;
455 const char *data_;
456
457 public:
458 Pcre(const char *regex) :
459 study_(NULL)
460 {
461 const char *error;
462 int offset;
463 code_ = pcre_compile(regex, 0, &error, &offset, NULL);
464
465 if (code_ == NULL) {
466 lprintf("%d:%s\n", offset, error);
467 _assert(false);
468 }
469
470 pcre_fullinfo(code_, study_, PCRE_INFO_CAPTURECOUNT, &capture_);
471 matches_ = new int[(capture_ + 1) * 3];
472 }
473
474 ~Pcre() {
475 pcre_free(code_);
476 delete matches_;
477 }
478
479 NSString *operator [](size_t match) {
480 return [NSString stringWithUTF8Bytes:(data_ + matches_[match * 2]) length:(matches_[match * 2 + 1] - matches_[match * 2])];
481 }
482
483 bool operator ()(NSString *data) {
484 // XXX: length is for characters, not for bytes
485 return operator ()([data UTF8String], [data length]);
486 }
487
488 bool operator ()(const char *data, size_t size) {
489 data_ = data;
490 return pcre_exec(code_, study_, data, size, 0, 0, matches_, (capture_ + 1) * 3) >= 0;
491 }
492 };
493 /* }}} */
494 /* Mime Addresses {{{ */
495 @interface Address : NSObject {
496 NSString *name_;
497 NSString *address_;
498 }
499
500 - (NSString *) name;
501 - (NSString *) address;
502
503 + (Address *) addressWithString:(NSString *)string;
504 - (Address *) initWithString:(NSString *)string;
505 @end
506
507 @implementation Address
508
509 - (void) dealloc {
510 [name_ release];
511 if (address_ != nil)
512 [address_ release];
513 [super dealloc];
514 }
515
516 - (NSString *) name {
517 return name_;
518 }
519
520 - (NSString *) address {
521 return address_;
522 }
523
524 + (Address *) addressWithString:(NSString *)string {
525 return [[[Address alloc] initWithString:string] autorelease];
526 }
527
528 + (NSArray *) _attributeKeys {
529 return [NSArray arrayWithObjects:@"address", @"name", nil];
530 }
531
532 - (NSArray *) attributeKeys {
533 return [[self class] _attributeKeys];
534 }
535
536 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
537 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
538 }
539
540 - (Address *) initWithString:(NSString *)string {
541 if ((self = [super init]) != nil) {
542 const char *data = [string UTF8String];
543 size_t size = [string length];
544
545 static Pcre address_r("^\"?(.*)\"? <([^>]*)>$");
546
547 if (address_r(data, size)) {
548 name_ = [address_r[1] retain];
549 address_ = [address_r[2] retain];
550 } else {
551 name_ = [string retain];
552 address_ = nil;
553 }
554 } return self;
555 }
556
557 @end
558 /* }}} */
559 /* CoreGraphics Primitives {{{ */
560 class CGColor {
561 private:
562 CGColorRef color_;
563
564 public:
565 CGColor() :
566 color_(NULL)
567 {
568 }
569
570 CGColor(CGColorSpaceRef space, float red, float green, float blue, float alpha) :
571 color_(NULL)
572 {
573 Set(space, red, green, blue, alpha);
574 }
575
576 void Clear() {
577 if (color_ != NULL)
578 CGColorRelease(color_);
579 }
580
581 ~CGColor() {
582 Clear();
583 }
584
585 void Set(CGColorSpaceRef space, float red, float green, float blue, float alpha) {
586 Clear();
587 float color[] = {red, green, blue, alpha};
588 color_ = CGColorCreate(space, color);
589 }
590
591 operator CGColorRef() {
592 return color_;
593 }
594 };
595 /* }}} */
596
597 extern "C" void UISetColor(CGColorRef color);
598
599 /* Random Global Variables {{{ */
600 static const int PulseInterval_ = 50000;
601 static const int ButtonBarHeight_ = 48;
602 static const float KeyboardTime_ = 0.3f;
603
604 #define SpringBoard_ "/System/Library/LaunchDaemons/com.apple.SpringBoard.plist"
605 #define SandboxTemplate_ "/usr/share/sandbox/SandboxTemplate.sb"
606 #define NotifyConfig_ "/etc/notify.conf"
607
608 static CGColor Blue_;
609 static CGColor Blueish_;
610 static CGColor Black_;
611 static CGColor Off_;
612 static CGColor White_;
613 static CGColor Gray_;
614
615 static NSString *App_;
616 static NSString *Home_;
617 static BOOL Sounds_Keyboard_;
618
619 static BOOL Advanced_;
620 static BOOL Loaded_;
621 static BOOL Ignored_;
622
623 static UIFont *Font12_;
624 static UIFont *Font12Bold_;
625 static UIFont *Font14_;
626 static UIFont *Font18Bold_;
627 static UIFont *Font22Bold_;
628
629 static const char *Machine_ = NULL;
630 static const NSString *UniqueID_ = nil;
631 static const NSString *Build_ = nil;
632
633 CFLocaleRef Locale_;
634 CGColorSpaceRef space_;
635
636 bool bootstrap_;
637 bool reload_;
638
639 static NSDictionary *SectionMap_;
640 static NSMutableDictionary *Metadata_;
641 static _transient NSMutableDictionary *Settings_;
642 static _transient NSString *Role_;
643 static _transient NSMutableDictionary *Packages_;
644 static _transient NSMutableDictionary *Sections_;
645 static _transient NSMutableDictionary *Sources_;
646 static bool Changed_;
647 static NSDate *now_;
648
649 #if RecycleWebViews
650 static NSMutableArray *Documents_;
651 #endif
652
653 NSString *GetLastUpdate() {
654 NSDate *update = [Metadata_ objectForKey:@"LastUpdate"];
655
656 if (update == nil)
657 return @"Never or Unknown";
658
659 CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
660 CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) update);
661
662 CFRelease(formatter);
663
664 return [(NSString *) formatted autorelease];
665 }
666 /* }}} */
667 /* Display Helpers {{{ */
668 inline float Interpolate(float begin, float end, float fraction) {
669 return (end - begin) * fraction + begin;
670 }
671
672 NSString *SizeString(double size) {
673 bool negative = size < 0;
674 if (negative)
675 size = -size;
676
677 unsigned power = 0;
678 while (size > 1024) {
679 size /= 1024;
680 ++power;
681 }
682
683 static const char *powers_[] = {"B", "kB", "MB", "GB"};
684
685 return [NSString stringWithFormat:@"%s%.1f %s", (negative ? "-" : ""), size, powers_[power]];
686 }
687
688 NSString *StripVersion(NSString *version) {
689 NSRange colon = [version rangeOfString:@":"];
690 if (colon.location != NSNotFound)
691 version = [version substringFromIndex:(colon.location + 1)];
692 return version;
693 }
694
695 NSString *Simplify(NSString *title) {
696 const char *data = [title UTF8String];
697 size_t size = [title length];
698
699 static Pcre square_r("^\\[(.*)\\]$");
700 if (square_r(data, size))
701 return Simplify(square_r[1]);
702
703 static Pcre paren_r("^\\((.*)\\)$");
704 if (paren_r(data, size))
705 return Simplify(paren_r[1]);
706
707 static Pcre title_r("^(.*?) \\(.*\\)$");
708 if (title_r(data, size))
709 return Simplify(title_r[1]);
710
711 return title;
712 }
713 /* }}} */
714
715 bool isSectionVisible(NSString *section) {
716 NSDictionary *metadata = [Sections_ objectForKey:section];
717 NSNumber *hidden = metadata == nil ? nil : [metadata objectForKey:@"Hidden"];
718 return hidden == nil || ![hidden boolValue];
719 }
720
721 /* Delegate Prototypes {{{ */
722 @class Package;
723 @class Source;
724
725 @interface NSObject (ProgressDelegate)
726 @end
727
728 @implementation NSObject(ProgressDelegate)
729
730 - (void) _setProgressError:(NSArray *)args {
731 [self performSelector:@selector(setProgressError:forPackage:)
732 withObject:[args objectAtIndex:0]
733 withObject:([args count] == 1 ? nil : [args objectAtIndex:1])
734 ];
735 }
736
737 @end
738
739 @protocol ProgressDelegate
740 - (void) setProgressError:(NSString *)error forPackage:(NSString *)id;
741 - (void) setProgressTitle:(NSString *)title;
742 - (void) setProgressPercent:(float)percent;
743 - (void) startProgress;
744 - (void) addProgressOutput:(NSString *)output;
745 - (bool) isCancelling:(size_t)received;
746 @end
747
748 @protocol ConfigurationDelegate
749 - (void) repairWithSelector:(SEL)selector;
750 - (void) setConfigurationData:(NSString *)data;
751 @end
752
753 @protocol CydiaDelegate
754 - (void) installPackage:(Package *)package;
755 - (void) removePackage:(Package *)package;
756 - (void) slideUp:(UIActionSheet *)alert;
757 - (void) distUpgrade;
758 - (void) updateData;
759 - (void) syncData;
760 - (void) askForSettings;
761 - (UIProgressHUD *) addProgressHUD;
762 - (RVPage *) pageForURL:(NSURL *)url hasTag:(int *)tag;
763 - (RVPage *) pageForPackage:(NSString *)name;
764 - (void) openMailToURL:(NSURL *)url;
765 - (void) clearFirstResponder;
766 @end
767 /* }}} */
768
769 /* Status Delegation {{{ */
770 class Status :
771 public pkgAcquireStatus
772 {
773 private:
774 _transient NSObject<ProgressDelegate> *delegate_;
775
776 public:
777 Status() :
778 delegate_(nil)
779 {
780 }
781
782 void setDelegate(id delegate) {
783 delegate_ = delegate;
784 }
785
786 virtual bool MediaChange(std::string media, std::string drive) {
787 return false;
788 }
789
790 virtual void IMSHit(pkgAcquire::ItemDesc &item) {
791 }
792
793 virtual void Fetch(pkgAcquire::ItemDesc &item) {
794 //NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
795 [delegate_ setProgressTitle:[NSString stringWithUTF8String:("Downloading " + item.ShortDesc).c_str()]];
796 }
797
798 virtual void Done(pkgAcquire::ItemDesc &item) {
799 }
800
801 virtual void Fail(pkgAcquire::ItemDesc &item) {
802 if (
803 item.Owner->Status == pkgAcquire::Item::StatIdle ||
804 item.Owner->Status == pkgAcquire::Item::StatDone
805 )
806 return;
807
808 std::string &error(item.Owner->ErrorText);
809 if (error.empty())
810 return;
811
812 NSString *description([NSString stringWithUTF8String:item.Description.c_str()]);
813 NSArray *fields([description componentsSeparatedByString:@" "]);
814 NSString *source([fields count] == 0 ? nil : [fields objectAtIndex:0]);
815
816 [delegate_ performSelectorOnMainThread:@selector(_setProgressError:)
817 withObject:[NSArray arrayWithObjects:
818 [NSString stringWithUTF8String:error.c_str()],
819 source,
820 nil]
821 waitUntilDone:YES
822 ];
823 }
824
825 virtual bool Pulse(pkgAcquire *Owner) {
826 bool value = pkgAcquireStatus::Pulse(Owner);
827
828 float percent(
829 double(CurrentBytes + CurrentItems) /
830 double(TotalBytes + TotalItems)
831 );
832
833 [delegate_ setProgressPercent:percent];
834 return [delegate_ isCancelling:CurrentBytes] ? false : value;
835 }
836
837 virtual void Start() {
838 [delegate_ startProgress];
839 }
840
841 virtual void Stop() {
842 }
843 };
844 /* }}} */
845 /* Progress Delegation {{{ */
846 class Progress :
847 public OpProgress
848 {
849 private:
850 _transient id<ProgressDelegate> delegate_;
851
852 protected:
853 virtual void Update() {
854 [delegate_ setProgressTitle:[NSString stringWithUTF8String:Op.c_str()]];
855 [delegate_ setProgressPercent:(Percent / 100)];
856 }
857
858 public:
859 Progress() :
860 delegate_(nil)
861 {
862 }
863
864 void setDelegate(id delegate) {
865 delegate_ = delegate;
866 }
867
868 virtual void Done() {
869 [delegate_ setProgressPercent:1];
870 }
871 };
872 /* }}} */
873
874 /* Database Interface {{{ */
875 @interface Database : NSObject {
876 pkgCacheFile cache_;
877 pkgDepCache::Policy *policy_;
878 pkgRecords *records_;
879 pkgProblemResolver *resolver_;
880 pkgAcquire *fetcher_;
881 FileFd *lock_;
882 SPtr<pkgPackageManager> manager_;
883 pkgSourceList *list_;
884
885 NSMutableDictionary *sources_;
886 NSMutableArray *packages_;
887
888 _transient NSObject<ConfigurationDelegate, ProgressDelegate> *delegate_;
889 Status status_;
890 Progress progress_;
891
892 int cydiafd_;
893 int statusfd_;
894 FILE *input_;
895 }
896
897 + (Database *) sharedInstance;
898
899 - (void) _readCydia:(NSNumber *)fd;
900 - (void) _readStatus:(NSNumber *)fd;
901 - (void) _readOutput:(NSNumber *)fd;
902
903 - (FILE *) input;
904
905 - (Package *) packageWithName:(NSString *)name;
906
907 - (pkgCacheFile &) cache;
908 - (pkgDepCache::Policy *) policy;
909 - (pkgRecords *) records;
910 - (pkgProblemResolver *) resolver;
911 - (pkgAcquire &) fetcher;
912 - (pkgSourceList &) list;
913 - (NSArray *) packages;
914 - (NSArray *) sources;
915 - (void) reloadData;
916
917 - (void) configure;
918 - (void) prepare;
919 - (void) perform;
920 - (void) upgrade;
921 - (void) update;
922
923 - (void) updateWithStatus:(Status &)status;
924
925 - (void) setDelegate:(id)delegate;
926 - (Source *) getSource:(const pkgCache::PkgFileIterator &)file;
927 @end
928 /* }}} */
929
930 /* Source Class {{{ */
931 @interface Source : NSObject {
932 NSString *description_;
933 NSString *label_;
934 NSString *origin_;
935
936 NSString *uri_;
937 NSString *distribution_;
938 NSString *type_;
939 NSString *version_;
940
941 NSString *defaultIcon_;
942
943 NSDictionary *record_;
944 BOOL trusted_;
945 }
946
947 - (Source *) initWithMetaIndex:(metaIndex *)index;
948
949 - (NSComparisonResult) compareByNameAndType:(Source *)source;
950
951 - (NSDictionary *) record;
952 - (BOOL) trusted;
953
954 - (NSString *) uri;
955 - (NSString *) distribution;
956 - (NSString *) type;
957 - (NSString *) key;
958 - (NSString *) host;
959
960 - (NSString *) name;
961 - (NSString *) description;
962 - (NSString *) label;
963 - (NSString *) origin;
964 - (NSString *) version;
965
966 - (NSString *) defaultIcon;
967
968 @end
969
970 @implementation Source
971
972 #define _clear(field) \
973 if (field != nil) \
974 [field release]; \
975 field = nil;
976
977 - (void) _clear {
978 _clear(uri_)
979 _clear(distribution_)
980 _clear(type_)
981
982 _clear(description_)
983 _clear(label_)
984 _clear(origin_)
985 _clear(version_)
986 _clear(defaultIcon_)
987 _clear(record_)
988 }
989
990 - (void) dealloc {
991 [self _clear];
992 [super dealloc];
993 }
994
995 + (NSArray *) _attributeKeys {
996 return [NSArray arrayWithObjects:@"description", @"distribution", @"host", @"key", @"label", @"name", @"origin", @"trusted", @"type", @"uri", @"version", nil];
997 }
998
999 - (NSArray *) attributeKeys {
1000 return [[self class] _attributeKeys];
1001 }
1002
1003 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1004 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1005 }
1006
1007 - (void) setMetaIndex:(metaIndex *)index {
1008 [self _clear];
1009
1010 trusted_ = index->IsTrusted();
1011
1012 uri_ = [[NSString stringWithUTF8String:index->GetURI().c_str()] retain];
1013 distribution_ = [[NSString stringWithUTF8String:index->GetDist().c_str()] retain];
1014 type_ = [[NSString stringWithUTF8String:index->GetType()] retain];
1015
1016 debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index));
1017 if (dindex != NULL) {
1018 std::ifstream release(dindex->MetaIndexFile("Release").c_str());
1019 std::string line;
1020 while (std::getline(release, line)) {
1021 std::string::size_type colon(line.find(':'));
1022 if (colon == std::string::npos)
1023 continue;
1024
1025 std::string name(line.substr(0, colon));
1026 std::string value(line.substr(colon + 1));
1027 while (!value.empty() && value[0] == ' ')
1028 value = value.substr(1);
1029
1030 if (name == "Default-Icon")
1031 defaultIcon_ = [[NSString stringWithUTF8String:value.c_str()] retain];
1032 else if (name == "Description")
1033 description_ = [[NSString stringWithUTF8String:value.c_str()] retain];
1034 else if (name == "Label")
1035 label_ = [[NSString stringWithUTF8String:value.c_str()] retain];
1036 else if (name == "Origin")
1037 origin_ = [[NSString stringWithUTF8String:value.c_str()] retain];
1038 else if (name == "Version")
1039 version_ = [[NSString stringWithUTF8String:value.c_str()] retain];
1040 }
1041 }
1042
1043 record_ = [Sources_ objectForKey:[self key]];
1044 if (record_ != nil)
1045 record_ = [record_ retain];
1046 }
1047
1048 - (Source *) initWithMetaIndex:(metaIndex *)index {
1049 if ((self = [super init]) != nil) {
1050 [self setMetaIndex:index];
1051 } return self;
1052 }
1053
1054 - (NSComparisonResult) compareByNameAndType:(Source *)source {
1055 NSDictionary *lhr = [self record];
1056 NSDictionary *rhr = [source record];
1057
1058 if (lhr != rhr)
1059 return lhr == nil ? NSOrderedDescending : NSOrderedAscending;
1060
1061 NSString *lhs = [self name];
1062 NSString *rhs = [source name];
1063
1064 if ([lhs length] != 0 && [rhs length] != 0) {
1065 unichar lhc = [lhs characterAtIndex:0];
1066 unichar rhc = [rhs characterAtIndex:0];
1067
1068 if (isalpha(lhc) && !isalpha(rhc))
1069 return NSOrderedAscending;
1070 else if (!isalpha(lhc) && isalpha(rhc))
1071 return NSOrderedDescending;
1072 }
1073
1074 return [lhs compare:rhs options:LaxCompareOptions_];
1075 }
1076
1077 - (NSDictionary *) record {
1078 return record_;
1079 }
1080
1081 - (BOOL) trusted {
1082 return trusted_;
1083 }
1084
1085 - (NSString *) uri {
1086 return uri_;
1087 }
1088
1089 - (NSString *) distribution {
1090 return distribution_;
1091 }
1092
1093 - (NSString *) type {
1094 return type_;
1095 }
1096
1097 - (NSString *) key {
1098 return [NSString stringWithFormat:@"%@:%@:%@", type_, uri_, distribution_];
1099 }
1100
1101 - (NSString *) host {
1102 return [[[NSURL URLWithString:[self uri]] host] lowercaseString];
1103 }
1104
1105 - (NSString *) name {
1106 return origin_ == nil ? [self host] : origin_;
1107 }
1108
1109 - (NSString *) description {
1110 return description_;
1111 }
1112
1113 - (NSString *) label {
1114 return label_ == nil ? [self host] : label_;
1115 }
1116
1117 - (NSString *) origin {
1118 return origin_;
1119 }
1120
1121 - (NSString *) version {
1122 return version_;
1123 }
1124
1125 - (NSString *) defaultIcon {
1126 return defaultIcon_;
1127 }
1128
1129 @end
1130 /* }}} */
1131 /* Relationship Class {{{ */
1132 @interface Relationship : NSObject {
1133 NSString *type_;
1134 NSString *id_;
1135 }
1136
1137 - (NSString *) type;
1138 - (NSString *) id;
1139 - (NSString *) name;
1140
1141 @end
1142
1143 @implementation Relationship
1144
1145 - (void) dealloc {
1146 [type_ release];
1147 [id_ release];
1148 [super dealloc];
1149 }
1150
1151 - (NSString *) type {
1152 return type_;
1153 }
1154
1155 - (NSString *) id {
1156 return id_;
1157 }
1158
1159 - (NSString *) name {
1160 _assert(false);
1161 return nil;
1162 }
1163
1164 @end
1165 /* }}} */
1166 /* Package Class {{{ */
1167 @interface Package : NSObject {
1168 pkgCache::PkgIterator iterator_;
1169 _transient Database *database_;
1170 pkgCache::VerIterator version_;
1171 pkgCache::VerFileIterator file_;
1172
1173 Source *source_;
1174 bool cached_;
1175
1176 NSString *section_;
1177
1178 NSString *latest_;
1179 NSString *installed_;
1180
1181 NSString *id_;
1182 NSString *name_;
1183 NSString *tagline_;
1184 NSString *icon_;
1185 NSString *depiction_;
1186 NSString *homepage_;
1187 Address *sponsor_;
1188 Address *author_;
1189 NSArray *tags_;
1190 NSString *role_;
1191
1192 NSArray *relationships_;
1193 }
1194
1195 - (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database;
1196 + (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database;
1197
1198 - (pkgCache::PkgIterator) iterator;
1199
1200 - (NSString *) section;
1201 - (NSString *) simpleSection;
1202
1203 - (NSString *) uri;
1204
1205 - (Address *) maintainer;
1206 - (size_t) size;
1207 - (NSString *) description;
1208 - (NSString *) index;
1209
1210 - (NSMutableDictionary *) metadata;
1211 - (NSDate *) seen;
1212 - (BOOL) subscribed;
1213 - (BOOL) ignored;
1214
1215 - (NSString *) latest;
1216 - (NSString *) installed;
1217
1218 - (BOOL) valid;
1219 - (BOOL) upgradableAndEssential:(BOOL)essential;
1220 - (BOOL) essential;
1221 - (BOOL) broken;
1222 - (BOOL) unfiltered;
1223 - (BOOL) visible;
1224
1225 - (BOOL) half;
1226 - (BOOL) halfConfigured;
1227 - (BOOL) halfInstalled;
1228 - (BOOL) hasMode;
1229 - (NSString *) mode;
1230
1231 - (NSString *) id;
1232 - (NSString *) name;
1233 - (NSString *) tagline;
1234 - (UIImage *) icon;
1235 - (NSString *) homepage;
1236 - (NSString *) depiction;
1237 - (Address *) author;
1238
1239 - (NSArray *) files;
1240 - (NSArray *) relationships;
1241 - (NSArray *) warnings;
1242 - (NSArray *) applications;
1243
1244 - (Source *) source;
1245 - (NSString *) role;
1246
1247 - (BOOL) matches:(NSString *)text;
1248
1249 - (bool) hasSupportingRole;
1250 - (BOOL) hasTag:(NSString *)tag;
1251 - (NSString *) primaryPurpose;
1252 - (NSArray *) purposes;
1253
1254 - (NSComparisonResult) compareByName:(Package *)package;
1255 - (NSComparisonResult) compareBySection:(Package *)package;
1256
1257 - (uint32_t) compareForChanges;
1258
1259 - (void) install;
1260 - (void) remove;
1261
1262 - (NSNumber *) isUnfilteredAndSearchedForBy:(NSString *)search;
1263 - (NSNumber *) isInstalledAndVisible:(NSNumber *)number;
1264 - (NSNumber *) isVisiblyUninstalledInSection:(NSString *)section;
1265 - (NSNumber *) isVisibleInSource:(Source *)source;
1266
1267 @end
1268
1269 @implementation Package
1270
1271 - (void) dealloc {
1272 if (source_ != nil)
1273 [source_ release];
1274
1275 if (section_ != nil)
1276 [section_ release];
1277
1278 [latest_ release];
1279 if (installed_ != nil)
1280 [installed_ release];
1281
1282 [id_ release];
1283 if (name_ != nil)
1284 [name_ release];
1285 [tagline_ release];
1286 if (icon_ != nil)
1287 [icon_ release];
1288 if (depiction_ != nil)
1289 [depiction_ release];
1290 if (homepage_ != nil)
1291 [homepage_ release];
1292 if (sponsor_ != nil)
1293 [sponsor_ release];
1294 if (author_ != nil)
1295 [author_ release];
1296 if (tags_ != nil)
1297 [tags_ release];
1298 if (role_ != nil)
1299 [role_ release];
1300
1301 if (relationships_ != nil)
1302 [relationships_ release];
1303
1304 [super dealloc];
1305 }
1306
1307 + (NSArray *) _attributeKeys {
1308 return [NSArray arrayWithObjects:@"applications", @"author", @"depiction", @"description", @"essential", @"homepage", @"icon", @"id", @"installed", @"latest", @"maintainer", @"name", @"purposes", @"section", @"size", @"source", @"sponsor", @"tagline", @"warnings", nil];
1309 }
1310
1311 - (NSArray *) attributeKeys {
1312 return [[self class] _attributeKeys];
1313 }
1314
1315 + (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1316 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1317 }
1318
1319 - (Package *) initWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database {
1320 if ((self = [super init]) != nil) {
1321 iterator_ = iterator;
1322 database_ = database;
1323
1324 version_ = [database_ policy]->GetCandidateVer(iterator_);
1325 NSString *latest = version_.end() ? nil : [NSString stringWithUTF8String:version_.VerStr()];
1326 latest_ = latest == nil ? nil : [StripVersion(latest) retain];
1327
1328 pkgCache::VerIterator current = iterator_.CurrentVer();
1329 NSString *installed = current.end() ? nil : [NSString stringWithUTF8String:current.VerStr()];
1330 installed_ = [StripVersion(installed) retain];
1331
1332 if (!version_.end())
1333 file_ = version_.FileList();
1334 else {
1335 pkgCache &cache([database_ cache]);
1336 file_ = pkgCache::VerFileIterator(cache, cache.VerFileP);
1337 }
1338
1339 id_ = [[NSString stringWithUTF8String:iterator_.Name()] retain];
1340
1341 if (!file_.end()) {
1342 pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
1343
1344 const char *begin, *end;
1345 parser->GetRec(begin, end);
1346
1347 NSString *website(nil);
1348 NSString *sponsor(nil);
1349 NSString *author(nil);
1350 NSString *tag(nil);
1351
1352 struct {
1353 const char *name_;
1354 NSString **value_;
1355 } names[] = {
1356 {"name", &name_},
1357 {"icon", &icon_},
1358 {"depiction", &depiction_},
1359 {"homepage", &homepage_},
1360 {"website", &website},
1361 {"sponsor", &sponsor},
1362 {"author", &author},
1363 {"tag", &tag},
1364 };
1365
1366 while (begin != end)
1367 if (*begin == '\n') {
1368 ++begin;
1369 continue;
1370 } else if (isblank(*begin)) next: {
1371 begin = static_cast<char *>(memchr(begin + 1, '\n', end - begin - 1));
1372 if (begin == NULL)
1373 break;
1374 } else if (const char *colon = static_cast<char *>(memchr(begin, ':', end - begin))) {
1375 const char *name(begin);
1376 size_t size(colon - begin);
1377
1378 begin = static_cast<char *>(memchr(begin, '\n', end - begin));
1379
1380 {
1381 const char *stop(begin == NULL ? end : begin);
1382 while (stop[-1] == '\r')
1383 --stop;
1384 while (++colon != stop && isblank(*colon));
1385
1386 for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i)
1387 if (strncasecmp(names[i].name_, name, size) == 0) {
1388 NSString *value([NSString stringWithUTF8Bytes:colon length:(stop - colon)]);
1389 *names[i].value_ = value;
1390 break;
1391 }
1392 }
1393
1394 if (begin == NULL)
1395 break;
1396 ++begin;
1397 } else goto next;
1398
1399 if (name_ != nil)
1400 name_ = [name_ retain];
1401 tagline_ = [[NSString stringWithUTF8String:parser->ShortDesc().c_str()] retain];
1402 if (icon_ != nil)
1403 icon_ = [icon_ retain];
1404 if (depiction_ != nil)
1405 depiction_ = [depiction_ retain];
1406 if (homepage_ == nil)
1407 homepage_ = website;
1408 if ([homepage_ isEqualToString:depiction_])
1409 homepage_ = nil;
1410 if (homepage_ != nil)
1411 homepage_ = [homepage_ retain];
1412 if (sponsor != nil)
1413 sponsor_ = [[Address addressWithString:sponsor] retain];
1414 if (author != nil)
1415 author_ = [[Address addressWithString:author] retain];
1416 if (tag != nil)
1417 tags_ = [[tag componentsSeparatedByString:@", "] retain];
1418 }
1419
1420 if (tags_ != nil)
1421 for (int i(0), e([tags_ count]); i != e; ++i) {
1422 NSString *tag = [tags_ objectAtIndex:i];
1423 if ([tag hasPrefix:@"role::"]) {
1424 role_ = [[tag substringFromIndex:6] retain];
1425 break;
1426 }
1427 }
1428
1429 NSString *solid(latest == nil ? installed : latest);
1430 bool changed(false);
1431
1432 NSString *key([id_ lowercaseString]);
1433
1434 NSMutableDictionary *metadata = [Packages_ objectForKey:key];
1435 if (metadata == nil) {
1436 metadata = [[NSMutableDictionary dictionaryWithObjectsAndKeys:
1437 now_, @"FirstSeen",
1438 nil] mutableCopy];
1439
1440 if (solid != nil)
1441 [metadata setObject:solid forKey:@"LastVersion"];
1442 changed = true;
1443 } else {
1444 NSDate *first([metadata objectForKey:@"FirstSeen"]);
1445 NSDate *last([metadata objectForKey:@"LastSeen"]);
1446 NSString *version([metadata objectForKey:@"LastVersion"]);
1447
1448 if (first == nil) {
1449 first = last == nil ? now_ : last;
1450 [metadata setObject:first forKey:@"FirstSeen"];
1451 changed = true;
1452 }
1453
1454 if (solid != nil)
1455 if (version == nil) {
1456 [metadata setObject:solid forKey:@"LastVersion"];
1457 changed = true;
1458 } else if (![version isEqualToString:solid]) {
1459 [metadata setObject:solid forKey:@"LastVersion"];
1460 last = now_;
1461 [metadata setObject:last forKey:@"LastSeen"];
1462 changed = true;
1463 }
1464 }
1465
1466 if (changed) {
1467 [Packages_ setObject:metadata forKey:key];
1468 Changed_ = true;
1469 }
1470 } return self;
1471 }
1472
1473 + (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator database:(Database *)database {
1474 return [[[Package alloc]
1475 initWithIterator:iterator
1476 database:database
1477 ] autorelease];
1478 }
1479
1480 - (pkgCache::PkgIterator) iterator {
1481 return iterator_;
1482 }
1483
1484 - (NSString *) section {
1485 if (section_ != nil)
1486 return section_;
1487
1488 const char *section = iterator_.Section();
1489 if (section == NULL)
1490 return nil;
1491
1492 NSString *name = [[NSString stringWithUTF8String:section] stringByReplacingCharacter:' ' withCharacter:'_'];
1493
1494 lookup:
1495 if (NSDictionary *value = [SectionMap_ objectForKey:name])
1496 if (NSString *rename = [value objectForKey:@"Rename"]) {
1497 name = rename;
1498 goto lookup;
1499 }
1500
1501 section_ = [[name stringByReplacingCharacter:'_' withCharacter:' '] retain];
1502 return section_;
1503 }
1504
1505 - (NSString *) simpleSection {
1506 if (NSString *section = [self section])
1507 return Simplify(section);
1508 else
1509 return nil;
1510 }
1511
1512 - (NSString *) uri {
1513 return nil;
1514 #if 0
1515 pkgIndexFile *index;
1516 pkgCache::PkgFileIterator file(file_.File());
1517 if (![database_ list].FindIndex(file, index))
1518 return nil;
1519 return [NSString stringWithUTF8String:iterator_->Path];
1520 //return [NSString stringWithUTF8String:file.Site()];
1521 //return [NSString stringWithUTF8String:index->ArchiveURI(file.FileName()).c_str()];
1522 #endif
1523 }
1524
1525 - (Address *) maintainer {
1526 if (file_.end())
1527 return nil;
1528 pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
1529 const std::string &maintainer(parser->Maintainer());
1530 return maintainer.empty() ? nil : [Address addressWithString:[NSString stringWithUTF8String:maintainer.c_str()]];
1531 }
1532
1533 - (size_t) size {
1534 return version_.end() ? 0 : version_->InstalledSize;
1535 }
1536
1537 - (NSString *) description {
1538 if (file_.end())
1539 return nil;
1540 pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
1541 NSString *description([NSString stringWithUTF8String:parser->LongDesc().c_str()]);
1542
1543 NSArray *lines = [description componentsSeparatedByString:@"\n"];
1544 NSMutableArray *trimmed = [NSMutableArray arrayWithCapacity:([lines count] - 1)];
1545 if ([lines count] < 2)
1546 return nil;
1547
1548 NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
1549 for (size_t i(1); i != [lines count]; ++i) {
1550 NSString *trim = [[lines objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace];
1551 [trimmed addObject:trim];
1552 }
1553
1554 return [trimmed componentsJoinedByString:@"\n"];
1555 }
1556
1557 - (NSString *) index {
1558 NSString *index = [[[self name] substringToIndex:1] uppercaseString];
1559 return [index length] != 0 && isalpha([index characterAtIndex:0]) ? index : @"123";
1560 }
1561
1562 - (NSMutableDictionary *) metadata {
1563 return [Packages_ objectForKey:[id_ lowercaseString]];
1564 }
1565
1566 - (NSDate *) seen {
1567 NSDictionary *metadata([self metadata]);
1568 if ([self subscribed])
1569 if (NSDate *last = [metadata objectForKey:@"LastSeen"])
1570 return last;
1571 return [metadata objectForKey:@"FirstSeen"];
1572 }
1573
1574 - (BOOL) subscribed {
1575 NSDictionary *metadata([self metadata]);
1576 if (NSNumber *subscribed = [metadata objectForKey:@"IsSubscribed"])
1577 return [subscribed boolValue];
1578 else
1579 return false;
1580 }
1581
1582 - (BOOL) ignored {
1583 NSDictionary *metadata([self metadata]);
1584 if (NSNumber *ignored = [metadata objectForKey:@"IsIgnored"])
1585 return [ignored boolValue];
1586 else
1587 return false;
1588 }
1589
1590 - (NSString *) latest {
1591 return latest_;
1592 }
1593
1594 - (NSString *) installed {
1595 return installed_;
1596 }
1597
1598 - (BOOL) valid {
1599 return !version_.end();
1600 }
1601
1602 - (BOOL) upgradableAndEssential:(BOOL)essential {
1603 pkgCache::VerIterator current = iterator_.CurrentVer();
1604
1605 bool value;
1606 if (current.end())
1607 value = essential && [self essential];
1608 else
1609 value = !version_.end() && version_ != current;// && (!essential || ![database_ cache][iterator_].Keep());
1610 return value;
1611 }
1612
1613 - (BOOL) essential {
1614 return (iterator_->Flags & pkgCache::Flag::Essential) == 0 ? NO : YES;
1615 }
1616
1617 - (BOOL) broken {
1618 return [database_ cache][iterator_].InstBroken();
1619 }
1620
1621 - (BOOL) unfiltered {
1622 NSString *section = [self section];
1623 return section == nil || isSectionVisible(section);
1624 }
1625
1626 - (BOOL) visible {
1627 return [self hasSupportingRole] && [self unfiltered];
1628 }
1629
1630 - (BOOL) half {
1631 unsigned char current = iterator_->CurrentState;
1632 return current == pkgCache::State::HalfConfigured || current == pkgCache::State::HalfInstalled;
1633 }
1634
1635 - (BOOL) halfConfigured {
1636 return iterator_->CurrentState == pkgCache::State::HalfConfigured;
1637 }
1638
1639 - (BOOL) halfInstalled {
1640 return iterator_->CurrentState == pkgCache::State::HalfInstalled;
1641 }
1642
1643 - (BOOL) hasMode {
1644 pkgDepCache::StateCache &state([database_ cache][iterator_]);
1645 return state.Mode != pkgDepCache::ModeKeep;
1646 }
1647
1648 - (NSString *) mode {
1649 pkgDepCache::StateCache &state([database_ cache][iterator_]);
1650
1651 switch (state.Mode) {
1652 case pkgDepCache::ModeDelete:
1653 if ((state.iFlags & pkgDepCache::Purge) != 0)
1654 return @"Purge";
1655 else
1656 return @"Remove";
1657 case pkgDepCache::ModeKeep:
1658 if ((state.iFlags & pkgDepCache::AutoKept) != 0)
1659 return nil;
1660 else
1661 return nil;
1662 case pkgDepCache::ModeInstall:
1663 if ((state.iFlags & pkgDepCache::ReInstall) != 0)
1664 return @"Reinstall";
1665 else switch (state.Status) {
1666 case -1:
1667 return @"Downgrade";
1668 case 0:
1669 return @"Install";
1670 case 1:
1671 return @"Upgrade";
1672 case 2:
1673 return @"New Install";
1674 default:
1675 _assert(false);
1676 }
1677 default:
1678 _assert(false);
1679 }
1680 }
1681
1682 - (NSString *) id {
1683 return id_;
1684 }
1685
1686 - (NSString *) name {
1687 return name_ == nil ? id_ : name_;
1688 }
1689
1690 - (NSString *) tagline {
1691 return tagline_;
1692 }
1693
1694 - (UIImage *) icon {
1695 NSString *section = [self simpleSection];
1696
1697 UIImage *icon(nil);
1698 if (icon_ != nil)
1699 if ([icon_ hasPrefix:@"file:///"])
1700 icon = [UIImage imageAtPath:[icon_ substringFromIndex:7]];
1701 if (icon == nil) if (section != nil)
1702 icon = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]];
1703 if (icon == nil) if (source_ != nil) if (NSString *dicon = [source_ defaultIcon])
1704 if ([dicon hasPrefix:@"file:///"])
1705 icon = [UIImage imageAtPath:[dicon substringFromIndex:7]];
1706 if (icon == nil)
1707 icon = [UIImage applicationImageNamed:@"unknown.png"];
1708 return icon;
1709 }
1710
1711 - (NSString *) homepage {
1712 return homepage_;
1713 }
1714
1715 - (NSString *) depiction {
1716 return depiction_;
1717 }
1718
1719 - (Address *) sponsor {
1720 return sponsor_;
1721 }
1722
1723 - (Address *) author {
1724 return author_;
1725 }
1726
1727 - (NSArray *) files {
1728 NSString *path = [NSString stringWithFormat:@"/var/lib/dpkg/info/%@.list", id_];
1729 NSMutableArray *files = [NSMutableArray arrayWithCapacity:128];
1730
1731 std::ifstream fin;
1732 fin.open([path UTF8String]);
1733 if (!fin.is_open())
1734 return nil;
1735
1736 std::string line;
1737 while (std::getline(fin, line))
1738 [files addObject:[NSString stringWithUTF8String:line.c_str()]];
1739
1740 return files;
1741 }
1742
1743 - (NSArray *) relationships {
1744 return relationships_;
1745 }
1746
1747 - (NSArray *) warnings {
1748 NSMutableArray *warnings([NSMutableArray arrayWithCapacity:4]);
1749 const char *name(iterator_.Name());
1750
1751 size_t length(strlen(name));
1752 if (length < 2) invalid:
1753 [warnings addObject:@"illegal package identifier"];
1754 else for (size_t i(0); i != length; ++i)
1755 if (
1756 /* XXX: technically this is not allowed */
1757 (name[i] < 'A' || name[i] > 'Z') &&
1758 (name[i] < 'a' || name[i] > 'z') &&
1759 (name[i] < '0' || name[i] > '9') &&
1760 (i == 0 || name[i] != '+' && name[i] != '-' && name[i] != '.')
1761 ) goto invalid;
1762
1763 if (strcmp(name, "cydia") != 0) {
1764 bool cydia = false;
1765 bool _private = false;
1766 bool stash = false;
1767
1768 bool repository = [[self section] isEqualToString:@"Repositories"];
1769
1770 if (NSArray *files = [self files])
1771 for (NSString *file in files)
1772 if (!cydia && [file isEqualToString:@"/Applications/Cydia.app"])
1773 cydia = true;
1774 else if (!_private && [file isEqualToString:@"/private"])
1775 _private = true;
1776 else if (!stash && [file isEqualToString:@"/var/stash"])
1777 stash = true;
1778
1779 /* XXX: this is not sensitive enough. only some folders are valid. */
1780 if (cydia && !repository)
1781 [warnings addObject:@"files installed into Cydia.app"];
1782 if (_private)
1783 [warnings addObject:@"files installed with /private/*"];
1784 if (stash)
1785 [warnings addObject:@"files installed to /var/stash"];
1786 }
1787
1788 return [warnings count] == 0 ? nil : warnings;
1789 }
1790
1791 - (NSArray *) applications {
1792 NSString *me([[NSBundle mainBundle] bundleIdentifier]);
1793
1794 NSMutableArray *applications([NSMutableArray arrayWithCapacity:2]);
1795
1796 static Pcre application_r("^/Applications/(.*)\\.app/Info.plist$");
1797 if (NSArray *files = [self files])
1798 for (NSString *file in files)
1799 if (application_r(file)) {
1800 NSDictionary *info([NSDictionary dictionaryWithContentsOfFile:file]);
1801 NSString *id([info objectForKey:@"CFBundleIdentifier"]);
1802 if ([id isEqualToString:me])
1803 continue;
1804
1805 NSString *display([info objectForKey:@"CFBundleDisplayName"]);
1806 if (display == nil)
1807 display = application_r[1];
1808
1809 NSString *bundle([file stringByDeletingLastPathComponent]);
1810 NSString *icon([info objectForKey:@"CFBundleIconFile"]);
1811 if (icon == nil || [icon length] == 0)
1812 icon = @"icon.png";
1813 NSURL *url([NSURL fileURLWithPath:[bundle stringByAppendingPathComponent:icon]]);
1814
1815 NSMutableArray *application([NSMutableArray arrayWithCapacity:2]);
1816 [applications addObject:application];
1817
1818 [application addObject:id];
1819 [application addObject:display];
1820 [application addObject:url];
1821 }
1822
1823 return [applications count] == 0 ? nil : applications;
1824 }
1825
1826 - (Source *) source {
1827 if (!cached_) {
1828 source_ = file_.end() ? nil : [[database_ getSource:file_.File()] retain];
1829 cached_ = true;
1830 }
1831
1832 return source_;
1833 }
1834
1835 - (NSString *) role {
1836 return role_;
1837 }
1838
1839 - (BOOL) matches:(NSString *)text {
1840 if (text == nil)
1841 return NO;
1842
1843 NSRange range;
1844
1845 range = [[self id] rangeOfString:text options:NSCaseInsensitiveSearch];
1846 if (range.location != NSNotFound)
1847 return YES;
1848
1849 range = [[self name] rangeOfString:text options:NSCaseInsensitiveSearch];
1850 if (range.location != NSNotFound)
1851 return YES;
1852
1853 range = [[self tagline] rangeOfString:text options:NSCaseInsensitiveSearch];
1854 if (range.location != NSNotFound)
1855 return YES;
1856
1857 return NO;
1858 }
1859
1860 - (bool) hasSupportingRole {
1861 if (role_ == nil)
1862 return true;
1863 if ([role_ isEqualToString:@"enduser"])
1864 return true;
1865 if ([Role_ isEqualToString:@"User"])
1866 return false;
1867 if ([role_ isEqualToString:@"hacker"])
1868 return true;
1869 if ([Role_ isEqualToString:@"Hacker"])
1870 return false;
1871 if ([role_ isEqualToString:@"developer"])
1872 return true;
1873 if ([Role_ isEqualToString:@"Developer"])
1874 return false;
1875 _assert(false);
1876 }
1877
1878 - (BOOL) hasTag:(NSString *)tag {
1879 return tags_ == nil ? NO : [tags_ containsObject:tag];
1880 }
1881
1882 - (NSString *) primaryPurpose {
1883 for (NSString *tag in tags_)
1884 if ([tag hasPrefix:@"purpose::"])
1885 return [tag substringFromIndex:9];
1886 return nil;
1887 }
1888
1889 - (NSArray *) purposes {
1890 NSMutableArray *purposes([NSMutableArray arrayWithCapacity:2]);
1891 for (NSString *tag in tags_)
1892 if ([tag hasPrefix:@"purpose::"])
1893 [purposes addObject:[tag substringFromIndex:9]];
1894 return [purposes count] == 0 ? nil : purposes;
1895 }
1896
1897 - (NSComparisonResult) compareByName:(Package *)package {
1898 NSString *lhs = [self name];
1899 NSString *rhs = [package name];
1900
1901 if ([lhs length] != 0 && [rhs length] != 0) {
1902 unichar lhc = [lhs characterAtIndex:0];
1903 unichar rhc = [rhs characterAtIndex:0];
1904
1905 if (isalpha(lhc) && !isalpha(rhc))
1906 return NSOrderedAscending;
1907 else if (!isalpha(lhc) && isalpha(rhc))
1908 return NSOrderedDescending;
1909 }
1910
1911 return [lhs compare:rhs options:LaxCompareOptions_];
1912 }
1913
1914 - (NSComparisonResult) compareBySection:(Package *)package {
1915 NSString *lhs = [self section];
1916 NSString *rhs = [package section];
1917
1918 if (lhs == NULL && rhs != NULL)
1919 return NSOrderedAscending;
1920 else if (lhs != NULL && rhs == NULL)
1921 return NSOrderedDescending;
1922 else if (lhs != NULL && rhs != NULL) {
1923 NSComparisonResult result([lhs compare:rhs options:LaxCompareOptions_]);
1924 return result != NSOrderedSame ? result : [lhs compare:rhs options:ForcedCompareOptions_];
1925 }
1926
1927 return NSOrderedSame;
1928 }
1929
1930 - (uint32_t) compareForChanges {
1931 union {
1932 uint32_t key;
1933
1934 struct {
1935 uint32_t timestamp : 30;
1936 uint32_t ignored : 1;
1937 uint32_t upgradable : 1;
1938 } bits;
1939 } value;
1940
1941 bool upgradable([self upgradableAndEssential:YES]);
1942 value.bits.upgradable = upgradable ? 1 : 0;
1943
1944 if (upgradable) {
1945 value.bits.timestamp = 0;
1946 value.bits.ignored = [self ignored] ? 0 : 1;
1947 value.bits.upgradable = 1;
1948 } else {
1949 value.bits.timestamp = static_cast<uint32_t>([[self seen] timeIntervalSince1970]) >> 2;
1950 value.bits.ignored = 0;
1951 value.bits.upgradable = 0;
1952 }
1953
1954 return _not(uint32_t) - value.key;
1955 }
1956
1957 - (void) install {
1958 pkgProblemResolver *resolver = [database_ resolver];
1959 resolver->Clear(iterator_);
1960 resolver->Protect(iterator_);
1961 pkgCacheFile &cache([database_ cache]);
1962 cache->MarkInstall(iterator_, false);
1963 pkgDepCache::StateCache &state((*cache)[iterator_]);
1964 if (!state.Install())
1965 cache->SetReInstall(iterator_, true);
1966 }
1967
1968 - (void) remove {
1969 pkgProblemResolver *resolver = [database_ resolver];
1970 resolver->Clear(iterator_);
1971 resolver->Protect(iterator_);
1972 resolver->Remove(iterator_);
1973 [database_ cache]->MarkDelete(iterator_, true);
1974 }
1975
1976 - (NSNumber *) isUnfilteredAndSearchedForBy:(NSString *)search {
1977 return [NSNumber numberWithBool:(
1978 [self unfiltered] && [self matches:search]
1979 )];
1980 }
1981
1982 - (NSNumber *) isInstalledAndVisible:(NSNumber *)number {
1983 return [NSNumber numberWithBool:(
1984 (![number boolValue] || [self visible]) && [self installed] != nil
1985 )];
1986 }
1987
1988 - (NSNumber *) isVisiblyUninstalledInSection:(NSString *)name {
1989 NSString *section = [self section];
1990
1991 return [NSNumber numberWithBool:(
1992 [self visible] &&
1993 [self installed] == nil && (
1994 name == nil ||
1995 section == nil && [name length] == 0 ||
1996 [name isEqualToString:section]
1997 )
1998 )];
1999 }
2000
2001 - (NSNumber *) isVisibleInSource:(Source *)source {
2002 return [NSNumber numberWithBool:([self source] == source && [self visible])];
2003 }
2004
2005 @end
2006 /* }}} */
2007 /* Section Class {{{ */
2008 @interface Section : NSObject {
2009 NSString *name_;
2010 size_t row_;
2011 size_t count_;
2012 }
2013
2014 - (NSComparisonResult) compareByName:(Section *)section;
2015 - (Section *) initWithName:(NSString *)name;
2016 - (Section *) initWithName:(NSString *)name row:(size_t)row;
2017 - (NSString *) name;
2018 - (size_t) row;
2019 - (size_t) count;
2020 - (void) addToCount;
2021
2022 @end
2023
2024 @implementation Section
2025
2026 - (void) dealloc {
2027 [name_ release];
2028 [super dealloc];
2029 }
2030
2031 - (NSComparisonResult) compareByName:(Section *)section {
2032 NSString *lhs = [self name];
2033 NSString *rhs = [section name];
2034
2035 if ([lhs length] != 0 && [rhs length] != 0) {
2036 unichar lhc = [lhs characterAtIndex:0];
2037 unichar rhc = [rhs characterAtIndex:0];
2038
2039 if (isalpha(lhc) && !isalpha(rhc))
2040 return NSOrderedAscending;
2041 else if (!isalpha(lhc) && isalpha(rhc))
2042 return NSOrderedDescending;
2043 }
2044
2045 return [lhs compare:rhs options:LaxCompareOptions_];
2046 }
2047
2048 - (Section *) initWithName:(NSString *)name {
2049 return [self initWithName:name row:0];
2050 }
2051
2052 - (Section *) initWithName:(NSString *)name row:(size_t)row {
2053 if ((self = [super init]) != nil) {
2054 name_ = [name retain];
2055 row_ = row;
2056 } return self;
2057 }
2058
2059 - (NSString *) name {
2060 return name_;
2061 }
2062
2063 - (size_t) row {
2064 return row_;
2065 }
2066
2067 - (size_t) count {
2068 return count_;
2069 }
2070
2071 - (void) addToCount {
2072 ++count_;
2073 }
2074
2075 @end
2076 /* }}} */
2077
2078 static int Finish_;
2079 static NSArray *Finishes_;
2080
2081 /* Database Implementation {{{ */
2082 @implementation Database
2083
2084 + (Database *) sharedInstance {
2085 static Database *instance;
2086 if (instance == nil)
2087 instance = [[Database alloc] init];
2088 return instance;
2089 }
2090
2091 - (void) dealloc {
2092 _assert(false);
2093 [super dealloc];
2094 }
2095
2096 - (void) _readCydia:(NSNumber *)fd { _pooled
2097 __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
2098 std::istream is(&ib);
2099 std::string line;
2100
2101 static Pcre finish_r("^finish:([^:]*)$");
2102
2103 while (std::getline(is, line)) {
2104 const char *data(line.c_str());
2105 size_t size = line.size();
2106 lprintf("C:%s\n", data);
2107
2108 if (finish_r(data, size)) {
2109 NSString *finish = finish_r[1];
2110 int index = [Finishes_ indexOfObject:finish];
2111 if (index != INT_MAX && index > Finish_)
2112 Finish_ = index;
2113 }
2114 }
2115
2116 _assert(false);
2117 }
2118
2119 - (void) _readStatus:(NSNumber *)fd { _pooled
2120 __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
2121 std::istream is(&ib);
2122 std::string line;
2123
2124 static Pcre conffile_r("^status: [^ ]* : conffile-prompt : (.*?) *$");
2125 static Pcre pmstatus_r("^([^:]*):([^:]*):([^:]*):(.*)$");
2126
2127 while (std::getline(is, line)) {
2128 const char *data(line.c_str());
2129 size_t size = line.size();
2130 lprintf("S:%s\n", data);
2131
2132 if (conffile_r(data, size)) {
2133 [delegate_ setConfigurationData:conffile_r[1]];
2134 } else if (strncmp(data, "status: ", 8) == 0) {
2135 NSString *string = [NSString stringWithUTF8String:(data + 8)];
2136 [delegate_ setProgressTitle:string];
2137 } else if (pmstatus_r(data, size)) {
2138 std::string type([pmstatus_r[1] UTF8String]);
2139 NSString *id = pmstatus_r[2];
2140
2141 float percent([pmstatus_r[3] floatValue]);
2142 [delegate_ setProgressPercent:(percent / 100)];
2143
2144 NSString *string = pmstatus_r[4];
2145
2146 if (type == "pmerror")
2147 [delegate_ performSelectorOnMainThread:@selector(_setProgressError:)
2148 withObject:[NSArray arrayWithObjects:string, id, nil]
2149 waitUntilDone:YES
2150 ];
2151 else if (type == "pmstatus") {
2152 [delegate_ setProgressTitle:string];
2153 } else if (type == "pmconffile")
2154 [delegate_ setConfigurationData:string];
2155 else _assert(false);
2156 } else _assert(false);
2157 }
2158
2159 _assert(false);
2160 }
2161
2162 - (void) _readOutput:(NSNumber *)fd { _pooled
2163 __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
2164 std::istream is(&ib);
2165 std::string line;
2166
2167 while (std::getline(is, line)) {
2168 lprintf("O:%s\n", line.c_str());
2169 [delegate_ addProgressOutput:[NSString stringWithUTF8String:line.c_str()]];
2170 }
2171
2172 _assert(false);
2173 }
2174
2175 - (FILE *) input {
2176 return input_;
2177 }
2178
2179 - (Package *) packageWithName:(NSString *)name {
2180 if (static_cast<pkgDepCache *>(cache_) == NULL)
2181 return nil;
2182 pkgCache::PkgIterator iterator(cache_->FindPkg([name UTF8String]));
2183 return iterator.end() ? nil : [Package packageWithIterator:iterator database:self];
2184 }
2185
2186 - (Database *) init {
2187 if ((self = [super init]) != nil) {
2188 policy_ = NULL;
2189 records_ = NULL;
2190 resolver_ = NULL;
2191 fetcher_ = NULL;
2192 lock_ = NULL;
2193
2194 sources_ = [[NSMutableDictionary dictionaryWithCapacity:16] retain];
2195 packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
2196
2197 int fds[2];
2198
2199 _assert(pipe(fds) != -1);
2200 cydiafd_ = fds[1];
2201
2202 _config->Set("APT::Keep-Fds::", cydiafd_);
2203 setenv("CYDIA", [[[[NSNumber numberWithInt:cydiafd_] stringValue] stringByAppendingString:@" 1"] UTF8String], _not(int));
2204
2205 [NSThread
2206 detachNewThreadSelector:@selector(_readCydia:)
2207 toTarget:self
2208 withObject:[[NSNumber numberWithInt:fds[0]] retain]
2209 ];
2210
2211 _assert(pipe(fds) != -1);
2212 statusfd_ = fds[1];
2213
2214 [NSThread
2215 detachNewThreadSelector:@selector(_readStatus:)
2216 toTarget:self
2217 withObject:[[NSNumber numberWithInt:fds[0]] retain]
2218 ];
2219
2220 _assert(pipe(fds) != -1);
2221 _assert(dup2(fds[0], 0) != -1);
2222 _assert(close(fds[0]) != -1);
2223
2224 input_ = fdopen(fds[1], "a");
2225
2226 _assert(pipe(fds) != -1);
2227 _assert(dup2(fds[1], 1) != -1);
2228 _assert(close(fds[1]) != -1);
2229
2230 [NSThread
2231 detachNewThreadSelector:@selector(_readOutput:)
2232 toTarget:self
2233 withObject:[[NSNumber numberWithInt:fds[0]] retain]
2234 ];
2235 } return self;
2236 }
2237
2238 - (pkgCacheFile &) cache {
2239 return cache_;
2240 }
2241
2242 - (pkgDepCache::Policy *) policy {
2243 return policy_;
2244 }
2245
2246 - (pkgRecords *) records {
2247 return records_;
2248 }
2249
2250 - (pkgProblemResolver *) resolver {
2251 return resolver_;
2252 }
2253
2254 - (pkgAcquire &) fetcher {
2255 return *fetcher_;
2256 }
2257
2258 - (pkgSourceList &) list {
2259 return *list_;
2260 }
2261
2262 - (NSArray *) packages {
2263 return packages_;
2264 }
2265
2266 - (NSArray *) sources {
2267 return [sources_ allValues];
2268 }
2269
2270 - (NSArray *) issues {
2271 if (cache_->BrokenCount() == 0)
2272 return nil;
2273
2274 NSMutableArray *issues([NSMutableArray arrayWithCapacity:4]);
2275
2276 for (Package *package in packages_) {
2277 if (![package broken])
2278 continue;
2279 pkgCache::PkgIterator pkg([package iterator]);
2280
2281 NSMutableArray *entry([NSMutableArray arrayWithCapacity:4]);
2282 [entry addObject:[package name]];
2283 [issues addObject:entry];
2284
2285 pkgCache::VerIterator ver(cache_[pkg].InstVerIter(cache_));
2286 if (ver.end())
2287 continue;
2288
2289 for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
2290 pkgCache::DepIterator start;
2291 pkgCache::DepIterator end;
2292 dep.GlobOr(start, end); // ++dep
2293
2294 if (!cache_->IsImportantDep(end))
2295 continue;
2296 if ((cache_[end] & pkgDepCache::DepGInstall) != 0)
2297 continue;
2298
2299 NSMutableArray *failure([NSMutableArray arrayWithCapacity:4]);
2300 [entry addObject:failure];
2301 [failure addObject:[NSString stringWithUTF8String:start.DepType()]];
2302
2303 Package *package([self packageWithName:[NSString stringWithUTF8String:start.TargetPkg().Name()]]);
2304 [failure addObject:[package name]];
2305
2306 pkgCache::PkgIterator target(start.TargetPkg());
2307 if (target->ProvidesList != 0)
2308 [failure addObject:@"?"];
2309 else {
2310 pkgCache::VerIterator ver(cache_[target].InstVerIter(cache_));
2311 if (!ver.end())
2312 [failure addObject:[NSString stringWithUTF8String:ver.VerStr()]];
2313 else if (!cache_[target].CandidateVerIter(cache_).end())
2314 [failure addObject:@"-"];
2315 else if (target->ProvidesList == 0)
2316 [failure addObject:@"!"];
2317 else
2318 [failure addObject:@"%"];
2319 }
2320
2321 _forever {
2322 if (start.TargetVer() != 0)
2323 [failure addObject:[NSString stringWithFormat:@"%s %s", start.CompType(), start.TargetVer()]];
2324 if (start == end)
2325 break;
2326 ++start;
2327 }
2328 }
2329 }
2330
2331 return issues;
2332 }
2333
2334 - (void) reloadData {
2335 _error->Discard();
2336
2337 delete list_;
2338 list_ = NULL;
2339 manager_ = NULL;
2340 delete lock_;
2341 lock_ = NULL;
2342 delete fetcher_;
2343 fetcher_ = NULL;
2344 delete resolver_;
2345 resolver_ = NULL;
2346 delete records_;
2347 records_ = NULL;
2348 delete policy_;
2349 policy_ = NULL;
2350
2351 cache_.Close();
2352
2353 _trace();
2354 if (!cache_.Open(progress_, true)) {
2355 std::string error;
2356 if (!_error->PopMessage(error))
2357 _assert(false);
2358 _error->Discard();
2359 lprintf("cache_.Open():[%s]\n", error.c_str());
2360
2361 if (error == "dpkg was interrupted, you must manually run 'dpkg --configure -a' to correct the problem. ")
2362 [delegate_ repairWithSelector:@selector(configure)];
2363 else if (error == "The package lists or status file could not be parsed or opened.")
2364 [delegate_ repairWithSelector:@selector(update)];
2365 // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
2366 // else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
2367 // else if (error == "The list of sources could not be read.")
2368 else _assert(false);
2369
2370 return;
2371 }
2372 _trace();
2373
2374 now_ = [[NSDate date] retain];
2375
2376 policy_ = new pkgDepCache::Policy();
2377 records_ = new pkgRecords(cache_);
2378 resolver_ = new pkgProblemResolver(cache_);
2379 fetcher_ = new pkgAcquire(&status_);
2380 lock_ = NULL;
2381
2382 list_ = new pkgSourceList();
2383 _assert(list_->ReadMainList());
2384
2385 _assert(cache_->DelCount() == 0 && cache_->InstCount() == 0);
2386 _assert(pkgApplyStatus(cache_));
2387
2388 if (cache_->BrokenCount() != 0) {
2389 _assert(pkgFixBroken(cache_));
2390 _assert(cache_->BrokenCount() == 0);
2391 _assert(pkgMinimizeUpgrade(cache_));
2392 }
2393
2394 [sources_ removeAllObjects];
2395 for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
2396 std::vector<pkgIndexFile *> *indices = (*source)->GetIndexFiles();
2397 for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
2398 [sources_
2399 setObject:[[[Source alloc] initWithMetaIndex:*source] autorelease]
2400 forKey:[NSNumber numberWithLong:reinterpret_cast<uintptr_t>(*index)]
2401 ];
2402 }
2403
2404 [packages_ removeAllObjects];
2405 _trace();
2406 profile_ = 0;
2407 for (pkgCache::PkgIterator iterator = cache_->PkgBegin(); !iterator.end(); ++iterator)
2408 if (Package *package = [Package packageWithIterator:iterator database:self])
2409 [packages_ addObject:package];
2410 _trace();
2411 [packages_ sortUsingSelector:@selector(compareByName:)];
2412 _trace();
2413 }
2414
2415 - (void) configure {
2416 NSString *dpkg = [NSString stringWithFormat:@"dpkg --configure -a --status-fd %u", statusfd_];
2417 system([dpkg UTF8String]);
2418 }
2419
2420 - (void) clean {
2421 if (lock_ != NULL)
2422 return;
2423
2424 FileFd Lock;
2425 Lock.Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
2426 _assert(!_error->PendingError());
2427
2428 pkgAcquire fetcher;
2429 fetcher.Clean(_config->FindDir("Dir::Cache::Archives"));
2430
2431 class LogCleaner :
2432 public pkgArchiveCleaner
2433 {
2434 protected:
2435 virtual void Erase(const char *File, std::string Pkg, std::string Ver, struct stat &St) {
2436 unlink(File);
2437 }
2438 } cleaner;
2439
2440 if (!cleaner.Go(_config->FindDir("Dir::Cache::Archives") + "partial/", cache_)) {
2441 std::string error;
2442 while (_error->PopMessage(error))
2443 lprintf("ArchiveCleaner: %s\n", error.c_str());
2444 }
2445 }
2446
2447 - (void) prepare {
2448 pkgRecords records(cache_);
2449
2450 lock_ = new FileFd();
2451 lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
2452 _assert(!_error->PendingError());
2453
2454 pkgSourceList list;
2455 // XXX: explain this with an error message
2456 _assert(list.ReadMainList());
2457
2458 manager_ = (_system->CreatePM(cache_));
2459 _assert(manager_->GetArchives(fetcher_, &list, &records));
2460 _assert(!_error->PendingError());
2461 }
2462
2463 - (void) perform {
2464 NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
2465 pkgSourceList list;
2466 _assert(list.ReadMainList());
2467 for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
2468 [before addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
2469 }
2470
2471 if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue) {
2472 _trace();
2473 return;
2474 }
2475
2476 bool failed = false;
2477 for (pkgAcquire::ItemIterator item = fetcher_->ItemsBegin(); item != fetcher_->ItemsEnd(); item++) {
2478 if ((*item)->Status == pkgAcquire::Item::StatDone && (*item)->Complete)
2479 continue;
2480
2481 std::string uri = (*item)->DescURI();
2482 std::string error = (*item)->ErrorText;
2483
2484 lprintf("pAf:%s:%s\n", uri.c_str(), error.c_str());
2485 failed = true;
2486
2487 [delegate_ performSelectorOnMainThread:@selector(_setProgressError:)
2488 withObject:[NSArray arrayWithObjects:
2489 [NSString stringWithUTF8String:error.c_str()],
2490 nil]
2491 waitUntilDone:YES
2492 ];
2493 }
2494
2495 if (failed) {
2496 _trace();
2497 return;
2498 }
2499
2500 _system->UnLock();
2501 pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
2502
2503 if (_error->PendingError()) {
2504 _trace();
2505 return;
2506 }
2507
2508 if (result == pkgPackageManager::Failed) {
2509 _trace();
2510 return;
2511 }
2512
2513 if (result != pkgPackageManager::Completed) {
2514 _trace();
2515 return;
2516 }
2517
2518 NSMutableArray *after = [NSMutableArray arrayWithCapacity:16]; {
2519 pkgSourceList list;
2520 _assert(list.ReadMainList());
2521 for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
2522 [after addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
2523 }
2524
2525 if (![before isEqualToArray:after])
2526 [self update];
2527 }
2528
2529 - (void) upgrade {
2530 _assert(pkgDistUpgrade(cache_));
2531 }
2532
2533 - (void) update {
2534 [self updateWithStatus:status_];
2535 }
2536
2537 - (void) updateWithStatus:(Status &)status {
2538 pkgSourceList list;
2539 _assert(list.ReadMainList());
2540
2541 FileFd lock;
2542 lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
2543 _assert(!_error->PendingError());
2544
2545 pkgAcquire fetcher(&status);
2546 _assert(list.GetIndexes(&fetcher));
2547
2548 if (fetcher.Run(PulseInterval_) != pkgAcquire::Failed) {
2549 bool failed = false;
2550 for (pkgAcquire::ItemIterator item = fetcher.ItemsBegin(); item != fetcher.ItemsEnd(); item++)
2551 if ((*item)->Status != pkgAcquire::Item::StatDone) {
2552 (*item)->Finished();
2553 failed = true;
2554 }
2555
2556 if (!failed && _config->FindB("APT::Get::List-Cleanup", true) == true) {
2557 _assert(fetcher.Clean(_config->FindDir("Dir::State::lists")));
2558 _assert(fetcher.Clean(_config->FindDir("Dir::State::lists") + "partial/"));
2559 }
2560
2561 [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"];
2562 Changed_ = true;
2563 }
2564 }
2565
2566 - (void) setDelegate:(id)delegate {
2567 delegate_ = delegate;
2568 status_.setDelegate(delegate);
2569 progress_.setDelegate(delegate);
2570 }
2571
2572 - (Source *) getSource:(const pkgCache::PkgFileIterator &)file {
2573 pkgIndexFile *index(NULL);
2574 list_->FindIndex(file, index);
2575 return [sources_ objectForKey:[NSNumber numberWithLong:reinterpret_cast<uintptr_t>(index)]];
2576 }
2577
2578 @end
2579 /* }}} */
2580
2581 /* PopUp Windows {{{ */
2582 @interface PopUpView : UIView {
2583 _transient id delegate_;
2584 UITransitionView *transition_;
2585 UIView *overlay_;
2586 }
2587
2588 - (void) cancel;
2589 - (id) initWithView:(UIView *)view delegate:(id)delegate;
2590
2591 @end
2592
2593 @implementation PopUpView
2594
2595 - (void) dealloc {
2596 [transition_ setDelegate:nil];
2597 [transition_ release];
2598 [overlay_ release];
2599 [super dealloc];
2600 }
2601
2602 - (void) cancel {
2603 [transition_ transition:UITransitionPushFromTop toView:nil];
2604 }
2605
2606 - (void) transitionViewDidComplete:(UITransitionView*)view fromView:(UIView*)from toView:(UIView*)to {
2607 if (from != nil && to == nil)
2608 [self removeFromSuperview];
2609 }
2610
2611 - (id) initWithView:(UIView *)view delegate:(id)delegate {
2612 if ((self = [super initWithFrame:[view bounds]]) != nil) {
2613 delegate_ = delegate;
2614
2615 transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
2616 [self addSubview:transition_];
2617
2618 overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]];
2619
2620 [view addSubview:self];
2621
2622 [transition_ setDelegate:self];
2623
2624 UIView *blank = [[[UIView alloc] initWithFrame:[transition_ bounds]] autorelease];
2625 [transition_ transition:UITransitionNone toView:blank];
2626 [transition_ transition:UITransitionPushFromBottom toView:overlay_];
2627 } return self;
2628 }
2629
2630 @end
2631 /* }}} */
2632
2633 /* Mail Composition {{{ */
2634 @interface MailToView : PopUpView {
2635 MailComposeController *controller_;
2636 }
2637
2638 - (id) initWithView:(UIView *)view delegate:(id)delegate url:(NSURL *)url;
2639
2640 @end
2641
2642 @implementation MailToView
2643
2644 - (void) dealloc {
2645 [controller_ release];
2646 [super dealloc];
2647 }
2648
2649 - (void) mailComposeControllerWillAttemptToSend:(MailComposeController *)controller {
2650 NSLog(@"will");
2651 }
2652
2653 - (void) mailComposeControllerDidAttemptToSend:(MailComposeController *)controller mailDelivery:(id)delivery {
2654 NSLog(@"did:%@", delivery);
2655 // [UIApp setStatusBarShowsProgress:NO];
2656 if ([controller error]){
2657 NSArray *buttons = [NSArray arrayWithObjects:@"OK", nil];
2658 UIActionSheet *mailAlertSheet = [[UIActionSheet alloc] initWithTitle:@"Error" buttons:buttons defaultButtonIndex:0 delegate:self context:self];
2659 [mailAlertSheet setBodyText:[controller error]];
2660 [mailAlertSheet popupAlertAnimated:YES];
2661 }
2662 }
2663
2664 - (void) showError {
2665 NSLog(@"%@", [controller_ error]);
2666 NSArray *buttons = [NSArray arrayWithObjects:@"OK", nil];
2667 UIActionSheet *mailAlertSheet = [[UIActionSheet alloc] initWithTitle:@"Error" buttons:buttons defaultButtonIndex:0 delegate:self context:self];
2668 [mailAlertSheet setBodyText:[controller_ error]];
2669 [mailAlertSheet popupAlertAnimated:YES];
2670 }
2671
2672 - (void) deliverMessage { _pooled
2673 setuid(501);
2674 setgid(501);
2675
2676 if (![controller_ deliverMessage])
2677 [self performSelectorOnMainThread:@selector(showError) withObject:nil waitUntilDone:NO];
2678 }
2679
2680 - (void) mailComposeControllerCompositionFinished:(MailComposeController *)controller {
2681 if ([controller_ needsDelivery])
2682 [NSThread detachNewThreadSelector:@selector(deliverMessage) toTarget:self withObject:nil];
2683 else
2684 [self cancel];
2685 }
2686
2687 - (id) initWithView:(UIView *)view delegate:(id)delegate url:(NSURL *)url {
2688 if ((self = [super initWithView:view delegate:delegate]) != nil) {
2689 controller_ = [[MailComposeController alloc] initForContentSize:[overlay_ bounds].size];
2690 [controller_ setDelegate:self];
2691 [controller_ initializeUI];
2692 [controller_ setupForURL:url];
2693
2694 UIView *view([controller_ view]);
2695 [overlay_ addSubview:view];
2696 } return self;
2697 }
2698
2699 @end
2700 /* }}} */
2701 /* Confirmation View {{{ */
2702 bool DepSubstrate(const pkgCache::VerIterator &iterator) {
2703 if (!iterator.end())
2704 for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) {
2705 if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends)
2706 continue;
2707 pkgCache::PkgIterator package(dep.TargetPkg());
2708 if (package.end())
2709 continue;
2710 if (strcmp(package.Name(), "mobilesubstrate") == 0)
2711 return true;
2712 }
2713
2714 return false;
2715 }
2716
2717 @protocol ConfirmationViewDelegate
2718 - (void) cancel;
2719 - (void) confirm;
2720 @end
2721
2722 @interface ConfirmationView : BrowserView {
2723 _transient Database *database_;
2724 UIActionSheet *essential_;
2725 NSArray *changes_;
2726 NSArray *issues_;
2727 NSArray *sizes_;
2728 BOOL substrate_;
2729 }
2730
2731 - (id) initWithBook:(RVBook *)book database:(Database *)database;
2732
2733 @end
2734
2735 @implementation ConfirmationView
2736
2737 - (void) dealloc {
2738 [changes_ release];
2739 if (issues_ != nil)
2740 [issues_ release];
2741 [sizes_ release];
2742 if (essential_ != nil)
2743 [essential_ release];
2744 [super dealloc];
2745 }
2746
2747 - (void) cancel {
2748 [delegate_ cancel];
2749 [book_ popFromSuperviewAnimated:YES];
2750 }
2751
2752 - (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
2753 NSString *context([sheet context]);
2754
2755 if ([context isEqualToString:@"remove"]) {
2756 switch (button) {
2757 case 1:
2758 [self cancel];
2759 break;
2760 case 2:
2761 if (substrate_)
2762 Finish_ = 2;
2763 [delegate_ confirm];
2764 break;
2765 default:
2766 _assert(false);
2767 }
2768
2769 [sheet dismiss];
2770 } else if ([context isEqualToString:@"unable"]) {
2771 [self cancel];
2772 [sheet dismiss];
2773 } else
2774 [super alertSheet:sheet buttonClicked:button];
2775 }
2776
2777 - (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
2778 [window setValue:changes_ forKey:@"changes"];
2779 [window setValue:issues_ forKey:@"issues"];
2780 [window setValue:sizes_ forKey:@"sizes"];
2781 [super webView:sender didClearWindowObject:window forFrame:frame];
2782 }
2783
2784 - (id) initWithBook:(RVBook *)book database:(Database *)database {
2785 if ((self = [super initWithBook:book]) != nil) {
2786 database_ = database;
2787
2788 NSMutableArray *installing = [NSMutableArray arrayWithCapacity:16];
2789 NSMutableArray *reinstalling = [NSMutableArray arrayWithCapacity:16];
2790 NSMutableArray *upgrading = [NSMutableArray arrayWithCapacity:16];
2791 NSMutableArray *downgrading = [NSMutableArray arrayWithCapacity:16];
2792 NSMutableArray *removing = [NSMutableArray arrayWithCapacity:16];
2793
2794 bool remove(false);
2795
2796 pkgDepCache::Policy *policy([database_ policy]);
2797
2798 pkgCacheFile &cache([database_ cache]);
2799 NSArray *packages = [database_ packages];
2800 for (size_t i(0), e = [packages count]; i != e; ++i) {
2801 Package *package = [packages objectAtIndex:i];
2802 pkgCache::PkgIterator iterator = [package iterator];
2803 pkgDepCache::StateCache &state(cache[iterator]);
2804
2805 NSString *name([package name]);
2806
2807 if (state.NewInstall())
2808 [installing addObject:name];
2809 else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
2810 [reinstalling addObject:name];
2811 else if (state.Upgrade())
2812 [upgrading addObject:name];
2813 else if (state.Downgrade())
2814 [downgrading addObject:name];
2815 else if (state.Delete()) {
2816 if ([package essential])
2817 remove = true;
2818 [removing addObject:name];
2819 } else continue;
2820
2821 substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
2822 substrate_ |= DepSubstrate(iterator.CurrentVer());
2823 }
2824
2825 if (!remove)
2826 essential_ = nil;
2827 else if (Advanced_ || true) {
2828 essential_ = [[UIActionSheet alloc]
2829 initWithTitle:@"Removing Essentials"
2830 buttons:[NSArray arrayWithObjects:
2831 @"Cancel Operation (Safe)",
2832 @"Force Removal (Unsafe)",
2833 nil]
2834 defaultButtonIndex:0
2835 delegate:self
2836 context:@"remove"
2837 ];
2838
2839 #ifndef __OBJC2__
2840 [essential_ setDestructiveButton:[[essential_ buttons] objectAtIndex:0]];
2841 #endif
2842 [essential_ setBodyText:@"This operation involves the removal of one or more packages that are required for the continued operation of either Cydia or iPhoneOS. If you continue, you may not be able to use Cydia to repair any damage."];
2843 } else {
2844 essential_ = [[UIActionSheet alloc]
2845 initWithTitle:@"Unable to Comply"
2846 buttons:[NSArray arrayWithObjects:@"Okay", nil]
2847 defaultButtonIndex:0
2848 delegate:self
2849 context:@"unable"
2850 ];
2851
2852 [essential_ setBodyText:@"This operation requires the removal of one or more packages that are required for the continued operation of either Cydia or iPhoneOS. In order to continue and force this operation you will need to be activate the Advanced mode under to continue and force this operation you will need to be activate the Advanced mode under Settings."];
2853 }
2854
2855 changes_ = [[NSArray alloc] initWithObjects:
2856 installing,
2857 reinstalling,
2858 upgrading,
2859 downgrading,
2860 removing,
2861 nil];
2862
2863 issues_ = [database_ issues];
2864 if (issues_ != nil)
2865 issues_ = [issues_ retain];
2866
2867 sizes_ = [[NSArray alloc] initWithObjects:
2868 SizeString([database_ fetcher].FetchNeeded()),
2869 SizeString([database_ fetcher].PartialPresent()),
2870 SizeString([database_ cache]->UsrSize()),
2871 nil];
2872
2873 [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"confirm" ofType:@"html"]]];
2874 } return self;
2875 }
2876
2877 - (NSString *) backButtonTitle {
2878 return @"Confirm";
2879 }
2880
2881 - (NSString *) leftButtonTitle {
2882 return @"Cancel";
2883 }
2884
2885 - (id) _rightButtonTitle {
2886 #if AlwaysReload || IgnoreInstall
2887 return @"Reload";
2888 #else
2889 return issues_ == nil ? @"Confirm" : nil;
2890 #endif
2891 }
2892
2893 - (void) _leftButtonClicked {
2894 [self cancel];
2895 }
2896
2897 #if !AlwaysReload
2898 - (void) _rightButtonClicked {
2899 #if IgnoreInstall
2900 return [super _rightButtonClicked];
2901 #endif
2902 if (essential_ != nil)
2903 [essential_ popupAlertAnimated:YES];
2904 else {
2905 if (substrate_)
2906 Finish_ = 2;
2907 [delegate_ confirm];
2908 }
2909 }
2910 #endif
2911
2912 @end
2913 /* }}} */
2914
2915 /* Progress Data {{{ */
2916 @interface ProgressData : NSObject {
2917 SEL selector_;
2918 id target_;
2919 id object_;
2920 }
2921
2922 - (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object;
2923
2924 - (SEL) selector;
2925 - (id) target;
2926 - (id) object;
2927 @end
2928
2929 @implementation ProgressData
2930
2931 - (ProgressData *) initWithSelector:(SEL)selector target:(id)target object:(id)object {
2932 if ((self = [super init]) != nil) {
2933 selector_ = selector;
2934 target_ = target;
2935 object_ = object;
2936 } return self;
2937 }
2938
2939 - (SEL) selector {
2940 return selector_;
2941 }
2942
2943 - (id) target {
2944 return target_;
2945 }
2946
2947 - (id) object {
2948 return object_;
2949 }
2950
2951 @end
2952 /* }}} */
2953 /* Progress View {{{ */
2954 @interface ProgressView : UIView <
2955 ConfigurationDelegate,
2956 ProgressDelegate
2957 > {
2958 _transient Database *database_;
2959 UIView *view_;
2960 UIView *background_;
2961 UITransitionView *transition_;
2962 UIView *overlay_;
2963 UINavigationBar *navbar_;
2964 UIProgressBar *progress_;
2965 UITextView *output_;
2966 UITextLabel *status_;
2967 UIPushButton *close_;
2968 id delegate_;
2969 BOOL running_;
2970 SHA1SumValue springlist_;
2971 SHA1SumValue notifyconf_;
2972 SHA1SumValue sandplate_;
2973 size_t received_;
2974 NSTimeInterval last_;
2975 }
2976
2977 - (void) transitionViewDidComplete:(UITransitionView*)view fromView:(UIView*)from toView:(UIView*)to;
2978
2979 - (id) initWithFrame:(struct CGRect)frame database:(Database *)database delegate:(id)delegate;
2980 - (void) setContentView:(UIView *)view;
2981 - (void) resetView;
2982
2983 - (void) _retachThread;
2984 - (void) _detachNewThreadData:(ProgressData *)data;
2985 - (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title;
2986
2987 - (BOOL) isRunning;
2988
2989 @end
2990
2991 @protocol ProgressViewDelegate
2992 - (void) progressViewIsComplete:(ProgressView *)sender;
2993 @end
2994
2995 @implementation ProgressView
2996
2997 - (void) dealloc {
2998 [transition_ setDelegate:nil];
2999 [navbar_ setDelegate:nil];
3000
3001 [view_ release];
3002 if (background_ != nil)
3003 [background_ release];
3004 [transition_ release];
3005 [overlay_ release];
3006 [navbar_ release];
3007 [progress_ release];
3008 [output_ release];
3009 [status_ release];
3010 [close_ release];
3011 [super dealloc];
3012 }
3013
3014 - (void) transitionViewDidComplete:(UITransitionView*)view fromView:(UIView*)from toView:(UIView*)to {
3015 if (bootstrap_ && from == overlay_ && to == view_)
3016 exit(0);
3017 }
3018
3019 - (id) initWithFrame:(struct CGRect)frame database:(Database *)database delegate:(id)delegate {
3020 if ((self = [super initWithFrame:frame]) != nil) {
3021 database_ = database;
3022 delegate_ = delegate;
3023
3024 transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
3025 [transition_ setDelegate:self];
3026
3027 overlay_ = [[UIView alloc] initWithFrame:[transition_ bounds]];
3028
3029 if (bootstrap_)
3030 [overlay_ setBackgroundColor:[UIColor blackColor]];
3031 else {
3032 background_ = [[UIView alloc] initWithFrame:[self bounds]];
3033 [background_ setBackgroundColor:[UIColor blackColor]];
3034 [self addSubview:background_];
3035 }
3036
3037 [self addSubview:transition_];
3038
3039 CGSize navsize = [UINavigationBar defaultSize];
3040 CGRect navrect = {{0, 0}, navsize};
3041
3042 navbar_ = [[UINavigationBar alloc] initWithFrame:navrect];
3043 [overlay_ addSubview:navbar_];
3044
3045 [navbar_ setBarStyle:1];
3046 [navbar_ setDelegate:self];
3047
3048 UINavigationItem *navitem = [[[UINavigationItem alloc] initWithTitle:nil] autorelease];
3049 [navbar_ pushNavigationItem:navitem];
3050
3051 CGRect bounds = [overlay_ bounds];
3052 CGSize prgsize = [UIProgressBar defaultSize];
3053
3054 CGRect prgrect = {{
3055 (bounds.size.width - prgsize.width) / 2,
3056 bounds.size.height - prgsize.height - 20
3057 }, prgsize};
3058
3059 progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
3060 [progress_ setStyle:0];
3061
3062 status_ = [[UITextLabel alloc] initWithFrame:CGRectMake(
3063 10,
3064 bounds.size.height - prgsize.height - 50,
3065 bounds.size.width - 20,
3066 24
3067 )];
3068
3069 [status_ setColor:[UIColor whiteColor]];
3070 [status_ setBackgroundColor:[UIColor clearColor]];
3071
3072 [status_ setCentersHorizontally:YES];
3073 //[status_ setFont:font];
3074
3075 output_ = [[UITextView alloc] initWithFrame:CGRectMake(
3076 10,
3077 navrect.size.height + 20,
3078 bounds.size.width - 20,
3079 bounds.size.height - navsize.height - 62 - navrect.size.height
3080 )];
3081
3082 //[output_ setTextFont:@"Courier New"];
3083 [output_ setTextSize:12];
3084
3085 [output_ setTextColor:[UIColor whiteColor]];
3086 [output_ setBackgroundColor:[UIColor clearColor]];
3087
3088 [output_ setMarginTop:0];
3089 [output_ setAllowsRubberBanding:YES];
3090 [output_ setEditable:NO];
3091
3092 [overlay_ addSubview:output_];
3093
3094 close_ = [[UIPushButton alloc] initWithFrame:CGRectMake(
3095 10,
3096 bounds.size.height - prgsize.height - 50,
3097 bounds.size.width - 20,
3098 32 + prgsize.height
3099 )];
3100
3101 [close_ setAutosizesToFit:NO];
3102 [close_ setDrawsShadow:YES];
3103 [close_ setStretchBackground:YES];
3104 [close_ setEnabled:YES];
3105
3106 UIFont *bold = [UIFont boldSystemFontOfSize:22];
3107 [close_ setTitleFont:bold];
3108
3109 [close_ addTarget:self action:@selector(closeButtonPushed) forEvents:kUIControlEventMouseUpInside];
3110 [close_ setBackground:[UIImage applicationImageNamed:@"green-up.png"] forState:0];
3111 [close_ setBackground:[UIImage applicationImageNamed:@"green-dn.png"] forState:1];
3112 } return self;
3113 }
3114
3115 - (void) setContentView:(UIView *)view {
3116 view_ = [view retain];
3117 }
3118
3119 - (void) resetView {
3120 [transition_ transition:6 toView:view_];
3121 }
3122
3123 - (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
3124 NSString *context([sheet context]);
3125
3126 if ([context isEqualToString:@"error"])
3127 [sheet dismiss];
3128 else if ([context isEqualToString:@"conffile"]) {
3129 FILE *input = [database_ input];
3130
3131 switch (button) {
3132 case 1:
3133 fprintf(input, "N\n");
3134 fflush(input);
3135 break;
3136 case 2:
3137 fprintf(input, "Y\n");
3138 fflush(input);
3139 break;
3140 default:
3141 _assert(false);
3142 }
3143
3144 [sheet dismiss];
3145 }
3146 }
3147
3148 - (void) closeButtonPushed {
3149 running_ = NO;
3150
3151 switch (Finish_) {
3152 case 0:
3153 [self resetView];
3154 break;
3155
3156 case 1:
3157 [delegate_ suspendWithAnimation:YES];
3158 break;
3159
3160 case 2:
3161 system("launchctl stop com.apple.SpringBoard");
3162 break;
3163
3164 case 3:
3165 system("launchctl unload "SpringBoard_"; launchctl load "SpringBoard_);
3166 break;
3167
3168 case 4:
3169 system("reboot");
3170 break;
3171 }
3172 }
3173
3174 - (void) _retachThread {
3175 UINavigationItem *item = [navbar_ topItem];
3176 [item setTitle:@"Complete"];
3177
3178 [overlay_ addSubview:close_];
3179 [progress_ removeFromSuperview];
3180 [status_ removeFromSuperview];
3181
3182 [delegate_ progressViewIsComplete:self];
3183
3184 if (Finish_ < 4) {
3185 FileFd file(SandboxTemplate_, FileFd::ReadOnly);
3186 MMap mmap(file, MMap::ReadOnly);
3187 SHA1Summation sha1;
3188 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
3189 if (!(sandplate_ == sha1.Result()))
3190 Finish_ = 4;
3191 }
3192
3193 if (Finish_ < 4) {
3194 FileFd file(NotifyConfig_, FileFd::ReadOnly);
3195 MMap mmap(file, MMap::ReadOnly);
3196 SHA1Summation sha1;
3197 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
3198 if (!(notifyconf_ == sha1.Result()))
3199 Finish_ = 4;
3200 }
3201
3202 if (Finish_ < 3) {
3203 FileFd file(SpringBoard_, FileFd::ReadOnly);
3204 MMap mmap(file, MMap::ReadOnly);
3205 SHA1Summation sha1;
3206 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
3207 if (!(springlist_ == sha1.Result()))
3208 Finish_ = 3;
3209 }
3210
3211 switch (Finish_) {
3212 case 0: [close_ setTitle:@"Return to Cydia"]; break;
3213 case 1: [close_ setTitle:@"Close Cydia (Restart)"]; break;
3214 case 2: [close_ setTitle:@"Restart SpringBoard"]; break;
3215 case 3: [close_ setTitle:@"Reload SpringBoard"]; break;
3216 case 4: [close_ setTitle:@"Reboot Device"]; break;
3217 }
3218
3219 #define Cache_ "/User/Library/Caches/com.apple.mobile.installation.plist"
3220
3221 if (NSMutableDictionary *cache = [[NSMutableDictionary alloc] initWithContentsOfFile:@ Cache_]) {
3222 [cache autorelease];
3223
3224 NSFileManager *manager = [NSFileManager defaultManager];
3225 NSError *error = nil;
3226
3227 id system = [cache objectForKey:@"System"];
3228 if (system == nil)
3229 goto error;
3230
3231 struct stat info;
3232 if (stat(Cache_, &info) == -1)
3233 goto error;
3234
3235 [system removeAllObjects];
3236
3237 if (NSArray *apps = [manager contentsOfDirectoryAtPath:@"/Applications" error:&error]) {
3238 for (NSString *app in apps)
3239 if ([app hasSuffix:@".app"]) {
3240 NSString *path = [@"/Applications" stringByAppendingPathComponent:app];
3241 NSString *plist = [path stringByAppendingPathComponent:@"Info.plist"];
3242 if (NSMutableDictionary *info = [[NSMutableDictionary alloc] initWithContentsOfFile:plist]) {
3243 [info autorelease];
3244 if ([info objectForKey:@"CFBundleIdentifier"] != nil) {
3245 [info setObject:path forKey:@"Path"];
3246 [info setObject:@"System" forKey:@"ApplicationType"];
3247 [system addInfoDictionary:info];
3248 }
3249 }
3250 }
3251 } else goto error;
3252
3253 [cache writeToFile:@Cache_ atomically:YES];
3254
3255 if (chown(Cache_, info.st_uid, info.st_gid) == -1)
3256 goto error;
3257 if (chmod(Cache_, info.st_mode) == -1)
3258 goto error;
3259
3260 if (false) error:
3261 lprintf("%s\n", error == nil ? strerror(errno) : [[error localizedDescription] UTF8String]);
3262 }
3263
3264 notify_post("com.apple.mobile.application_installed");
3265
3266 [delegate_ setStatusBarShowsProgress:NO];
3267 }
3268
3269 - (void) _detachNewThreadData:(ProgressData *)data { _pooled
3270 [[data target] performSelector:[data selector] withObject:[data object]];
3271 [data release];
3272
3273 [self performSelectorOnMainThread:@selector(_retachThread) withObject:nil waitUntilDone:YES];
3274 }
3275
3276 - (void) detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)object title:(NSString *)title {
3277 UINavigationItem *item = [navbar_ topItem];
3278 [item setTitle:title];
3279
3280 [status_ setText:nil];
3281 [output_ setText:@""];
3282 [progress_ setProgress:0];
3283
3284 received_ = 0;
3285 last_ = 0;//[NSDate timeIntervalSinceReferenceDate];
3286
3287 [close_ removeFromSuperview];
3288 [overlay_ addSubview:progress_];
3289 [overlay_ addSubview:status_];
3290
3291 [delegate_ setStatusBarShowsProgress:YES];
3292 running_ = YES;
3293
3294 {
3295 FileFd file(SandboxTemplate_, FileFd::ReadOnly);
3296 MMap mmap(file, MMap::ReadOnly);
3297 SHA1Summation sha1;
3298 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
3299 sandplate_ = sha1.Result();
3300 }
3301
3302 {
3303 FileFd file(NotifyConfig_, FileFd::ReadOnly);
3304 MMap mmap(file, MMap::ReadOnly);
3305 SHA1Summation sha1;
3306 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
3307 notifyconf_ = sha1.Result();
3308 }
3309
3310 {
3311 FileFd file(SpringBoard_, FileFd::ReadOnly);
3312 MMap mmap(file, MMap::ReadOnly);
3313 SHA1Summation sha1;
3314 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
3315 springlist_ = sha1.Result();
3316 }
3317
3318 [transition_ transition:6 toView:overlay_];
3319
3320 [NSThread
3321 detachNewThreadSelector:@selector(_detachNewThreadData:)
3322 toTarget:self
3323 withObject:[[ProgressData alloc]
3324 initWithSelector:selector
3325 target:target
3326 object:object
3327 ]
3328 ];
3329 }
3330
3331 - (void) repairWithSelector:(SEL)selector {
3332 [self
3333 detachNewThreadSelector:selector
3334 toTarget:database_
3335 withObject:nil
3336 title:@"Repairing"
3337 ];
3338 }
3339
3340 - (void) setConfigurationData:(NSString *)data {
3341 [self
3342 performSelectorOnMainThread:@selector(_setConfigurationData:)
3343 withObject:data
3344 waitUntilDone:YES
3345 ];
3346 }
3347
3348 - (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
3349 Package *package = id == nil ? nil : [database_ packageWithName:id];
3350
3351 UIActionSheet *sheet = [[[UIActionSheet alloc]
3352 initWithTitle:(package == nil ? id : [package name])
3353 buttons:[NSArray arrayWithObjects:@"Okay", nil]
3354 defaultButtonIndex:0
3355 delegate:self
3356 context:@"error"
3357 ] autorelease];
3358
3359 [sheet setBodyText:error];
3360 [sheet popupAlertAnimated:YES];
3361 }
3362
3363 - (void) setProgressTitle:(NSString *)title {
3364 [self
3365 performSelectorOnMainThread:@selector(_setProgressTitle:)
3366 withObject:title
3367 waitUntilDone:YES
3368 ];
3369 }
3370
3371 - (void) setProgressPercent:(float)percent {
3372 [self
3373 performSelectorOnMainThread:@selector(_setProgressPercent:)
3374 withObject:[NSNumber numberWithFloat:percent]
3375 waitUntilDone:YES
3376 ];
3377 }
3378
3379 - (void) startProgress {
3380 last_ = [NSDate timeIntervalSinceReferenceDate];
3381 }
3382
3383 - (void) addProgressOutput:(NSString *)output {
3384 [self
3385 performSelectorOnMainThread:@selector(_addProgressOutput:)
3386 withObject:output
3387 waitUntilDone:YES
3388 ];
3389 }
3390
3391 - (bool) isCancelling:(size_t)received {
3392 if (last_ != 0) {
3393 NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
3394 if (received_ != received) {
3395 received_ = received;
3396 last_ = now;
3397 } else if (now - last_ > 30)
3398 return true;
3399 }
3400
3401 return false;
3402 }
3403
3404 - (void) _setConfigurationData:(NSString *)data {
3405 static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
3406
3407 _assert(conffile_r(data));
3408
3409 NSString *ofile = conffile_r[1];
3410 //NSString *nfile = conffile_r[2];
3411
3412 UIActionSheet *sheet = [[[UIActionSheet alloc]
3413 initWithTitle:@"Configuration Upgrade"
3414 buttons:[NSArray arrayWithObjects:
3415 @"Keep My Old Copy",
3416 @"Accept The New Copy",
3417 // XXX: @"See What Changed",
3418 nil]
3419 defaultButtonIndex:0
3420 delegate:self
3421 context:@"conffile"
3422 ] autorelease];
3423
3424 [sheet setBodyText:[NSString stringWithFormat:
3425 @"The following file has been changed by both the package maintainer and by you (or for you by a script).\n\n%@"
3426 , ofile]];
3427
3428 [sheet popupAlertAnimated:YES];
3429 }
3430
3431 - (void) _setProgressTitle:(NSString *)title {
3432 NSMutableArray *words([[title componentsSeparatedByString:@" "] mutableCopy]);
3433 for (size_t i(0), e([words count]); i != e; ++i) {
3434 NSString *word([words objectAtIndex:i]);
3435 if (Package *package = [database_ packageWithName:word])
3436 [words replaceObjectAtIndex:i withObject:[package name]];
3437 }
3438
3439 [status_ setText:[words componentsJoinedByString:@" "]];
3440 }
3441
3442 - (void) _setProgressPercent:(NSNumber *)percent {
3443 [progress_ setProgress:[percent floatValue]];
3444 }
3445
3446 - (void) _addProgressOutput:(NSString *)output {
3447 [output_ setText:[NSString stringWithFormat:@"%@\n%@", [output_ text], output]];
3448 CGSize size = [output_ contentSize];
3449 CGRect rect = {{0, size.height}, {size.width, 0}};
3450 [output_ scrollRectToVisible:rect animated:YES];
3451 }
3452
3453 - (BOOL) isRunning {
3454 return running_;
3455 }
3456
3457 @end
3458 /* }}} */
3459
3460 /* Package Cell {{{ */
3461 @interface PackageCell : UISimpleTableCell {
3462 UIImage *icon_;
3463 NSString *name_;
3464 NSString *description_;
3465 NSString *source_;
3466 UIImage *badge_;
3467 #ifdef USE_BADGES
3468 UITextLabel *status_;
3469 #endif
3470 }
3471
3472 - (PackageCell *) init;
3473 - (void) setPackage:(Package *)package;
3474
3475 + (int) heightForPackage:(Package *)package;
3476
3477 @end
3478
3479 @implementation PackageCell
3480
3481 - (void) clearPackage {
3482 if (icon_ != nil) {
3483 [icon_ release];
3484 icon_ = nil;
3485 }
3486
3487 if (name_ != nil) {
3488 [name_ release];
3489 name_ = nil;
3490 }
3491
3492 if (description_ != nil) {
3493 [description_ release];
3494 description_ = nil;
3495 }
3496
3497 if (source_ != nil) {
3498 [source_ release];
3499 source_ = nil;
3500 }
3501
3502 if (badge_ != nil) {
3503 [badge_ release];
3504 badge_ = nil;
3505 }
3506 }
3507
3508 - (void) dealloc {
3509 [self clearPackage];
3510 #ifdef USE_BADGES
3511 [status_ release];
3512 #endif
3513 [super dealloc];
3514 }
3515
3516 - (PackageCell *) init {
3517 if ((self = [super init]) != nil) {
3518 #ifdef USE_BADGES
3519 status_ = [[UITextLabel alloc] initWithFrame:CGRectMake(48, 68, 280, 20)];
3520 [status_ setBackgroundColor:[UIColor clearColor]];
3521 [status_ setFont:small];
3522 #endif
3523 } return self;
3524 }
3525
3526 - (void) setPackage:(Package *)package {
3527 [self clearPackage];
3528
3529 Source *source = [package source];
3530 NSString *section = [package simpleSection];
3531
3532 icon_ = [[package icon] retain];
3533
3534 name_ = [[package name] retain];
3535 description_ = [[package tagline] retain];
3536
3537 NSString *label = nil;
3538 bool trusted = false;
3539
3540 if (source != nil) {
3541 label = [source label];
3542 trusted = [source trusted];
3543 } else if ([[package id] isEqualToString:@"firmware"])
3544 label = @"Apple";
3545 else
3546 label = @"Unknown/Local";
3547
3548 NSString *from = [NSString stringWithFormat:@"from %@", label];
3549
3550 if (section != nil && ![section isEqualToString:label])
3551 from = [from stringByAppendingString:[NSString stringWithFormat:@" (%@)", section]];
3552
3553 source_ = [from retain];
3554
3555 if (NSString *purpose = [package primaryPurpose])
3556 if ((badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]]) != nil)
3557 badge_ = [badge_ retain];
3558
3559 #ifdef USE_BADGES
3560 if (NSString *mode = [package mode]) {
3561 [badge_ setImage:[UIImage applicationImageNamed:
3562 [mode isEqualToString:@"Remove"] || [mode isEqualToString:@"Purge"] ? @"removing.png" : @"installing.png"
3563 ]];
3564
3565 [status_ setText:[NSString stringWithFormat:@"Queued for %@", mode]];
3566 [status_ setColor:[UIColor colorWithCGColor:Blueish_]];
3567 } else if ([package half]) {
3568 [badge_ setImage:[UIImage applicationImageNamed:@"damaged.png"]];
3569 [status_ setText:@"Package Damaged"];
3570 [status_ setColor:[UIColor redColor]];
3571 } else {
3572 [badge_ setImage:nil];
3573 [status_ setText:nil];
3574 }
3575 #endif
3576 }
3577
3578 - (void) drawContentInRect:(CGRect)rect selected:(BOOL)selected {
3579 if (icon_ != nil) {
3580 CGRect rect;
3581 rect.size = [icon_ size];
3582
3583 rect.size.width /= 2;
3584 rect.size.height /= 2;
3585
3586 rect.origin.x = 25 - rect.size.width / 2;
3587 rect.origin.y = 25 - rect.size.height / 2;
3588
3589 [icon_ drawInRect:rect];
3590 }
3591
3592 if (badge_ != nil) {
3593 CGSize size = [badge_ size];
3594
3595 [badge_ drawAtPoint:CGPointMake(
3596 36 - size.width / 2,
3597 36 - size.height / 2
3598 )];
3599 }
3600
3601 if (selected)
3602 UISetColor(White_);
3603
3604 if (!selected)
3605 UISetColor(Black_);
3606 [name_ drawAtPoint:CGPointMake(48, 8) forWidth:240 withFont:Font18Bold_ ellipsis:2];
3607 [source_ drawAtPoint:CGPointMake(58, 29) forWidth:225 withFont:Font12_ ellipsis:2];
3608
3609 if (!selected)
3610 UISetColor(Gray_);
3611 [description_ drawAtPoint:CGPointMake(12, 46) forWidth:280 withFont:Font14_ ellipsis:2];
3612
3613 [super drawContentInRect:rect selected:selected];
3614 }
3615
3616 + (int) heightForPackage:(Package *)package {
3617 NSString *tagline([package tagline]);
3618 int height = tagline == nil || [tagline length] == 0 ? -17 : 0;
3619 #ifdef USE_BADGES
3620 if ([package hasMode] || [package half])
3621 return height + 96;
3622 else
3623 #endif
3624 return height + 73;
3625 }
3626
3627 @end
3628 /* }}} */
3629 /* Section Cell {{{ */
3630 @interface SectionCell : UISimpleTableCell {
3631 NSString *section_;
3632 NSString *name_;
3633 NSString *count_;
3634 UIImage *icon_;
3635 _UISwitchSlider *switch_;
3636 BOOL editing_;
3637 }
3638
3639 - (id) init;
3640 - (void) setSection:(Section *)section editing:(BOOL)editing;
3641
3642 @end
3643
3644 @implementation SectionCell
3645
3646 - (void) clearSection {
3647 if (section_ != nil) {
3648 [section_ release];
3649 section_ = nil;
3650 }
3651
3652 if (name_ != nil) {
3653 [name_ release];
3654 name_ = nil;
3655 }
3656
3657 if (count_ != nil) {
3658 [count_ release];
3659 count_ = nil;
3660 }
3661 }
3662
3663 - (void) dealloc {
3664 [self clearSection];
3665 [icon_ release];
3666 [switch_ release];
3667 [super dealloc];
3668 }
3669
3670 - (id) init {
3671 if ((self = [super init]) != nil) {
3672 icon_ = [[UIImage applicationImageNamed:@"folder.png"] retain];
3673
3674 switch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(218, 9, 60, 25)];
3675 [switch_ addTarget:self action:@selector(onSwitch:) forEvents:kUIControlEventMouseUpInside];
3676 } return self;
3677 }
3678
3679 - (void) onSwitch:(id)sender {
3680 NSMutableDictionary *metadata = [Sections_ objectForKey:section_];
3681 if (metadata == nil) {
3682 metadata = [NSMutableDictionary dictionaryWithCapacity:2];
3683 [Sections_ setObject:metadata forKey:section_];
3684 }
3685
3686 Changed_ = true;
3687 [metadata setObject:[NSNumber numberWithBool:([switch_ value] == 0)] forKey:@"Hidden"];
3688 }
3689
3690 - (void) setSection:(Section *)section editing:(BOOL)editing {
3691 if (editing != editing_) {
3692 if (editing_)
3693 [switch_ removeFromSuperview];
3694 else
3695 [self addSubview:switch_];
3696 editing_ = editing;
3697 }
3698
3699 [self clearSection];
3700
3701 if (section == nil) {
3702 name_ = [@"All Packages" retain];
3703 count_ = nil;
3704 } else {
3705 section_ = [section name];
3706 if (section_ != nil)
3707 section_ = [section_ retain];
3708 name_ = [(section_ == nil ? @"(No Section)" : section_) retain];
3709 count_ = [[NSString stringWithFormat:@"%d", [section count]] retain];
3710
3711 if (editing_)
3712 [switch_ setValue:(isSectionVisible(section_) ? 1 : 0) animated:NO];
3713 }
3714 }
3715
3716 - (void) drawContentInRect:(CGRect)rect selected:(BOOL)selected {
3717 [icon_ drawInRect:CGRectMake(8, 7, 32, 32)];
3718
3719 if (selected)
3720 UISetColor(White_);
3721
3722 if (!selected)
3723 UISetColor(Black_);
3724 [name_ drawAtPoint:CGPointMake(48, 9) forWidth:(editing_ ? 164 : 250) withFont:Font22Bold_ ellipsis:2];
3725
3726 CGSize size = [count_ sizeWithFont:Font14_];
3727
3728 UISetColor(White_);
3729 if (count_ != nil)
3730 [count_ drawAtPoint:CGPointMake(13 + (29 - size.width) / 2, 16) withFont:Font12Bold_];
3731
3732 [super drawContentInRect:rect selected:selected];
3733 }
3734
3735 @end
3736 /* }}} */
3737
3738 /* File Table {{{ */
3739 @interface FileTable : RVPage {
3740 _transient Database *database_;
3741 Package *package_;
3742 NSString *name_;
3743 NSMutableArray *files_;
3744 UITable *list_;
3745 }
3746
3747 - (id) initWithBook:(RVBook *)book database:(Database *)database;
3748 - (void) setPackage:(Package *)package;
3749
3750 @end
3751
3752 @implementation FileTable
3753
3754 - (void) dealloc {
3755 if (package_ != nil)
3756 [package_ release];
3757 if (name_ != nil)
3758 [name_ release];
3759 [files_ release];
3760 [list_ release];
3761 [super dealloc];
3762 }
3763
3764 - (int) numberOfRowsInTable:(UITable *)table {
3765 return files_ == nil ? 0 : [files_ count];
3766 }
3767
3768 - (float) table:(UITable *)table heightForRow:(int)row {
3769 return 24;
3770 }
3771
3772 - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
3773 if (reusing == nil) {
3774 reusing = [[[UIImageAndTextTableCell alloc] init] autorelease];
3775 UIFont *font = [UIFont systemFontOfSize:16];
3776 [[(UIImageAndTextTableCell *)reusing titleTextLabel] setFont:font];
3777 }
3778 [(UIImageAndTextTableCell *)reusing setTitle:[files_ objectAtIndex:row]];
3779 return reusing;
3780 }
3781
3782 - (BOOL) table:(UITable *)table canSelectRow:(int)row {
3783 return NO;
3784 }
3785
3786 - (id) initWithBook:(RVBook *)book database:(Database *)database {
3787 if ((self = [super initWithBook:book]) != nil) {
3788 database_ = database;
3789
3790 files_ = [[NSMutableArray arrayWithCapacity:32] retain];
3791
3792 list_ = [[UITable alloc] initWithFrame:[self bounds]];
3793 [self addSubview:list_];
3794
3795 UITableColumn *column = [[[UITableColumn alloc]
3796 initWithTitle:@"Name"
3797 identifier:@"name"
3798 width:[self frame].size.width
3799 ] autorelease];
3800
3801 [list_ setDataSource:self];
3802 [list_ setSeparatorStyle:1];
3803 [list_ addTableColumn:column];
3804 [list_ setDelegate:self];
3805 [list_ setReusesTableCells:YES];
3806 } return self;
3807 }
3808
3809 - (void) setPackage:(Package *)package {
3810 if (package_ != nil) {
3811 [package_ autorelease];
3812 package_ = nil;
3813 }
3814
3815 if (name_ != nil) {
3816 [name_ release];
3817 name_ = nil;
3818 }
3819
3820 [files_ removeAllObjects];
3821
3822 if (package != nil) {
3823 package_ = [package retain];
3824 name_ = [[package id] retain];
3825
3826 if (NSArray *files = [package files])
3827 [files_ addObjectsFromArray:files];
3828
3829 if ([files_ count] != 0) {
3830 if ([[files_ objectAtIndex:0] isEqualToString:@"/."])
3831 [files_ removeObjectAtIndex:0];
3832 [files_ sortUsingSelector:@selector(compareByPath:)];
3833
3834 NSMutableArray *stack = [NSMutableArray arrayWithCapacity:8];
3835 [stack addObject:@"/"];
3836
3837 for (int i(0), e([files_ count]); i != e; ++i) {
3838 NSString *file = [files_ objectAtIndex:i];
3839 while (![file hasPrefix:[stack lastObject]])
3840 [stack removeLastObject];
3841 NSString *directory = [stack lastObject];
3842 [stack addObject:[file stringByAppendingString:@"/"]];
3843 [files_ replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"%*s%@",
3844 ([stack count] - 2) * 3, "",
3845 [file substringFromIndex:[directory length]]
3846 ]];
3847 }
3848 }
3849 }
3850
3851 [list_ reloadData];
3852 }
3853
3854 - (void) resetViewAnimated:(BOOL)animated {
3855 [list_ resetViewAnimated:animated];
3856 }
3857
3858 - (void) reloadData {
3859 [self setPackage:[database_ packageWithName:name_]];
3860 [self reloadButtons];
3861 }
3862
3863 - (NSString *) title {
3864 return @"Installed Files";
3865 }
3866
3867 - (NSString *) backButtonTitle {
3868 return @"Files";
3869 }
3870
3871 @end
3872 /* }}} */
3873 /* Package View {{{ */
3874 @interface PackageView : BrowserView {
3875 _transient Database *database_;
3876 Package *package_;
3877 NSString *name_;
3878 NSMutableArray *buttons_;
3879 }
3880
3881 - (id) initWithBook:(RVBook *)book database:(Database *)database;
3882 - (void) setPackage:(Package *)package;
3883
3884 @end
3885
3886 @implementation PackageView
3887
3888 - (void) dealloc {
3889 if (package_ != nil)
3890 [package_ release];
3891 if (name_ != nil)
3892 [name_ release];
3893 [buttons_ release];
3894 [super dealloc];
3895 }
3896
3897 - (void) _clickButtonWithName:(NSString *)name {
3898 if ([name isEqualToString:@"Install"])
3899 [delegate_ installPackage:package_];
3900 else if ([name isEqualToString:@"Reinstall"])
3901 [delegate_ installPackage:package_];
3902 else if ([name isEqualToString:@"Remove"])
3903 [delegate_ removePackage:package_];
3904 else if ([name isEqualToString:@"Upgrade"])
3905 [delegate_ installPackage:package_];
3906 else _assert(false);
3907 }
3908
3909 - (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
3910 NSString *context([sheet context]);
3911
3912 if ([context isEqualToString:@"modify"]) {
3913 int count = [buttons_ count];
3914 _assert(count != 0);
3915 _assert(button <= count + 1);
3916
3917 if (count != button - 1)
3918 [self _clickButtonWithName:[buttons_ objectAtIndex:(button - 1)]];
3919
3920 [sheet dismiss];
3921 } else
3922 [super alertSheet:sheet buttonClicked:button];
3923 }
3924
3925 - (void) webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
3926 return [super webView:sender didFinishLoadForFrame:frame];
3927 }
3928
3929 - (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
3930 [window setValue:package_ forKey:@"package"];
3931 [super webView:sender didClearWindowObject:window forFrame:frame];
3932 }
3933
3934 #if !AlwaysReload
3935 - (void) _rightButtonClicked {
3936 /*[super _rightButtonClicked];
3937 return;*/
3938
3939 int count = [buttons_ count];
3940 _assert(count != 0);
3941
3942 if (count == 1)
3943 [self _clickButtonWithName:[buttons_ objectAtIndex:0]];
3944 else {
3945 NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:(count + 1)];
3946 [buttons addObjectsFromArray:buttons_];
3947 [buttons addObject:@"Cancel"];
3948
3949 [delegate_ slideUp:[[[UIActionSheet alloc]
3950 initWithTitle:nil
3951 buttons:buttons
3952 defaultButtonIndex:2
3953 delegate:self
3954 context:@"modify"
3955 ] autorelease]];
3956 }
3957 }
3958 #endif
3959
3960 - (id) _rightButtonTitle {
3961 int count = [buttons_ count];
3962 return count == 0 ? nil : count != 1 ? @"Modify" : [buttons_ objectAtIndex:0];
3963 }
3964
3965 - (NSString *) backButtonTitle {
3966 return @"Details";
3967 }
3968
3969 - (id) initWithBook:(RVBook *)book database:(Database *)database {
3970 if ((self = [super initWithBook:book]) != nil) {
3971 database_ = database;
3972 buttons_ = [[NSMutableArray alloc] initWithCapacity:4];
3973 } return self;
3974 }
3975
3976 - (void) setPackage:(Package *)package {
3977 if (package_ != nil) {
3978 [package_ autorelease];
3979 package_ = nil;
3980 }
3981
3982 if (name_ != nil) {
3983 [name_ release];
3984 name_ = nil;
3985 }
3986
3987 [buttons_ removeAllObjects];
3988
3989 if (package != nil) {
3990 package_ = [package retain];
3991 name_ = [[package id] retain];
3992
3993 [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"package" ofType:@"html"]]];
3994
3995 if ([package_ source] == nil);
3996 else if ([package_ upgradableAndEssential:NO])
3997 [buttons_ addObject:@"Upgrade"];
3998 else if ([package_ installed] == nil)
3999 [buttons_ addObject:@"Install"];
4000 else
4001 [buttons_ addObject:@"Reinstall"];
4002 if ([package_ installed] != nil)
4003 [buttons_ addObject:@"Remove"];
4004 }
4005 }
4006
4007 - (bool) _loading {
4008 return false;
4009 }
4010
4011 - (void) reloadData {
4012 [self setPackage:[database_ packageWithName:name_]];
4013 [self reloadButtons];
4014 }
4015
4016 @end
4017 /* }}} */
4018 /* Package Table {{{ */
4019 @interface PackageTable : RVPage {
4020 _transient Database *database_;
4021 NSString *title_;
4022 NSMutableArray *packages_;
4023 NSMutableArray *sections_;
4024 UISectionList *list_;
4025 }
4026
4027 - (id) initWithBook:(RVBook *)book database:(Database *)database title:(NSString *)title;
4028
4029 - (void) setDelegate:(id)delegate;
4030
4031 - (void) reloadData;
4032 - (void) resetCursor;
4033
4034 - (UISectionList *) list;
4035
4036 - (void) setShouldHideHeaderInShortLists:(BOOL)hide;
4037
4038 @end
4039
4040 @implementation PackageTable
4041
4042 - (void) dealloc {
4043 [list_ setDataSource:nil];
4044
4045 [title_ release];
4046 [packages_ release];
4047 [sections_ release];
4048 [list_ release];
4049 [super dealloc];
4050 }
4051
4052 - (int) numberOfSectionsInSectionList:(UISectionList *)list {
4053 return [sections_ count];
4054 }
4055
4056 - (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section {
4057 return [[sections_ objectAtIndex:section] name];
4058 }
4059
4060 - (int) sectionList:(UISectionList *)list rowForSection:(int)section {
4061 return [[sections_ objectAtIndex:section] row];
4062 }
4063
4064 - (int) numberOfRowsInTable:(UITable *)table {
4065 return [packages_ count];
4066 }
4067
4068 - (float) table:(UITable *)table heightForRow:(int)row {
4069 return [PackageCell heightForPackage:[packages_ objectAtIndex:row]];
4070 }
4071
4072 - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
4073 if (reusing == nil)
4074 reusing = [[[PackageCell alloc] init] autorelease];
4075 [(PackageCell *)reusing setPackage:[packages_ objectAtIndex:row]];
4076 return reusing;
4077 }
4078
4079 - (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
4080 return NO;
4081 }
4082
4083 - (void) tableRowSelected:(NSNotification *)notification {
4084 int row = [[notification object] selectedRow];
4085 if (row == INT_MAX)
4086 return;
4087
4088 Package *package = [packages_ objectAtIndex:row];
4089 package = [database_ packageWithName:[package id]];
4090 PackageView *view = [[[PackageView alloc] initWithBook:book_ database:database_] autorelease];
4091 [view setPackage:package];
4092 [view setDelegate:delegate_];
4093 [book_ pushPage:view];
4094 }
4095
4096 - (id) initWithBook:(RVBook *)book database:(Database *)database title:(NSString *)title {
4097 if ((self = [super initWithBook:book]) != nil) {
4098 database_ = database;
4099 title_ = [title retain];
4100
4101 packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
4102 sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
4103
4104 list_ = [[UISectionList alloc] initWithFrame:[self bounds] showSectionIndex:YES];
4105 [list_ setDataSource:self];
4106
4107 UITableColumn *column = [[[UITableColumn alloc]
4108 initWithTitle:@"Name"
4109 identifier:@"name"
4110 width:[self frame].size.width
4111 ] autorelease];
4112
4113 UITable *table = [list_ table];
4114 [table setSeparatorStyle:1];
4115 [table addTableColumn:column];
4116 [table setDelegate:self];
4117 [table setReusesTableCells:YES];
4118
4119 [self addSubview:list_];
4120
4121 [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
4122 [list_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
4123 } return self;
4124 }
4125
4126 - (void) setDelegate:(id)delegate {
4127 delegate_ = delegate;
4128 }
4129
4130 - (bool) hasPackage:(Package *)package {
4131 return true;
4132 }
4133
4134 - (void) reloadData {
4135 NSArray *packages = [database_ packages];
4136
4137 [packages_ removeAllObjects];
4138 [sections_ removeAllObjects];
4139
4140 for (size_t i(0); i != [packages count]; ++i) {
4141 Package *package([packages objectAtIndex:i]);
4142 if ([self hasPackage:package])
4143 [packages_ addObject:package];
4144 }
4145
4146 Section *section = nil;
4147
4148 for (size_t offset(0); offset != [packages_ count]; ++offset) {
4149 Package *package = [packages_ objectAtIndex:offset];
4150 NSString *name = [package index];
4151
4152 if (section == nil || ![[section name] isEqualToString:name]) {
4153 section = [[[Section alloc] initWithName:name row:offset] autorelease];
4154 [sections_ addObject:section];
4155 }
4156
4157 [section addToCount];
4158 }
4159
4160 [list_ reloadData];
4161 }
4162
4163 - (NSString *) title {
4164 return title_;
4165 }
4166
4167 - (void) resetViewAnimated:(BOOL)animated {
4168 [list_ resetViewAnimated:animated];
4169 }
4170
4171 - (void) resetCursor {
4172 [[list_ table] scrollPointVisibleAtTopLeft:CGPointMake(0, 0) animated:NO];
4173 }
4174
4175 - (UISectionList *) list {
4176 return list_;
4177 }
4178
4179 - (void) setShouldHideHeaderInShortLists:(BOOL)hide {
4180 [list_ setShouldHideHeaderInShortLists:hide];
4181 }
4182
4183 @end
4184 /* }}} */
4185 /* Filtered Package Table {{{ */
4186 @interface FilteredPackageTable : PackageTable {
4187 SEL filter_;
4188 id object_;
4189 }
4190
4191 - (void) setObject:(id)object;
4192
4193 - (id) initWithBook:(RVBook *)book database:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object;
4194
4195 @end
4196
4197 @implementation FilteredPackageTable
4198
4199 - (void) dealloc {
4200 if (object_ != nil)
4201 [object_ release];
4202 [super dealloc];
4203 }
4204
4205 - (void) setObject:(id)object {
4206 if (object_ != nil)
4207 [object_ release];
4208 if (object == nil)
4209 object_ = nil;
4210 else
4211 object_ = [object retain];
4212 }
4213
4214 - (bool) hasPackage:(Package *)package {
4215 return [package valid] && [[package performSelector:filter_ withObject:object_] boolValue];
4216 }
4217
4218 - (id) initWithBook:(RVBook *)book database:(Database *)database title:(NSString *)title filter:(SEL)filter with:(id)object {
4219 if ((self = [super initWithBook:book database:database title:title]) != nil) {
4220 filter_ = filter;
4221 object_ = object == nil ? nil : [object retain];
4222
4223 [self reloadData];
4224 } return self;
4225 }
4226
4227 @end
4228 /* }}} */
4229
4230 /* Add Source View {{{ */
4231 @interface AddSourceView : RVPage {
4232 _transient Database *database_;
4233 }
4234
4235 - (id) initWithBook:(RVBook *)book database:(Database *)database;
4236
4237 @end
4238
4239 @implementation AddSourceView
4240
4241 - (id) initWithBook:(RVBook *)book database:(Database *)database {
4242 if ((self = [super initWithBook:book]) != nil) {
4243 database_ = database;
4244 } return self;
4245 }
4246
4247 @end
4248 /* }}} */
4249 /* Source Cell {{{ */
4250 @interface SourceCell : UITableCell {
4251 UIImage *icon_;
4252 NSString *origin_;
4253 NSString *description_;
4254 NSString *label_;
4255 }
4256
4257 - (void) dealloc;
4258
4259 - (SourceCell *) initWithSource:(Source *)source;
4260
4261 @end
4262
4263 @implementation SourceCell
4264
4265 - (void) dealloc {
4266 [icon_ release];
4267 [origin_ release];
4268 [description_ release];
4269 [label_ release];
4270 [super dealloc];
4271 }
4272
4273 - (SourceCell *) initWithSource:(Source *)source {
4274 if ((self = [super init]) != nil) {
4275 if (icon_ == nil)
4276 icon_ = [UIImage applicationImageNamed:[NSString stringWithFormat:@"Sources/%@.png", [source host]]];
4277 if (icon_ == nil)
4278 icon_ = [UIImage applicationImageNamed:@"unknown.png"];
4279 icon_ = [icon_ retain];
4280
4281 origin_ = [[source name] retain];
4282 label_ = [[source uri] retain];
4283 description_ = [[source description] retain];
4284 } return self;
4285 }
4286
4287 - (void) drawContentInRect:(CGRect)rect selected:(BOOL)selected {
4288 if (icon_ != nil)
4289 [icon_ drawInRect:CGRectMake(10, 10, 30, 30)];
4290
4291 if (selected)
4292 UISetColor(White_);
4293
4294 if (!selected)
4295 UISetColor(Black_);
4296 [origin_ drawAtPoint:CGPointMake(48, 8) forWidth:240 withFont:Font18Bold_ ellipsis:2];
4297
4298 if (!selected)
4299 UISetColor(Blue_);
4300 [label_ drawAtPoint:CGPointMake(58, 29) forWidth:225 withFont:Font12_ ellipsis:2];
4301
4302 if (!selected)
4303 UISetColor(Gray_);
4304 [description_ drawAtPoint:CGPointMake(12, 46) forWidth:280 withFont:Font14_ ellipsis:2];
4305
4306 [super drawContentInRect:rect selected:selected];
4307 }
4308
4309 @end
4310 /* }}} */
4311 /* Source Table {{{ */
4312 @interface SourceTable : RVPage {
4313 _transient Database *database_;
4314 UISectionList *list_;
4315 NSMutableArray *sources_;
4316 UIActionSheet *alert_;
4317 int offset_;
4318
4319 NSString *href_;
4320 UIProgressHUD *hud_;
4321 NSError *error_;
4322
4323 //NSURLConnection *installer_;
4324 NSURLConnection *trivial_bz2_;
4325 NSURLConnection *trivial_gz_;
4326 //NSURLConnection *automatic_;
4327
4328 BOOL trivial_;
4329 }
4330
4331 - (id) initWithBook:(RVBook *)book database:(Database *)database;
4332
4333 @end
4334
4335 @implementation SourceTable
4336
4337 - (void) _deallocConnection:(NSURLConnection *)connection {
4338 if (connection != nil) {
4339 [connection cancel];
4340 //[connection setDelegate:nil];
4341 [connection release];
4342 }
4343 }
4344
4345 - (void) dealloc {
4346 [[list_ table] setDelegate:nil];
4347 [list_ setDataSource:nil];
4348
4349 if (href_ != nil)
4350 [href_ release];
4351 if (hud_ != nil)
4352 [hud_ release];
4353 if (error_ != nil)
4354 [error_ release];
4355
4356 //[self _deallocConnection:installer_];
4357 [self _deallocConnection:trivial_gz_];
4358 [self _deallocConnection:trivial_bz2_];
4359 //[self _deallocConnection:automatic_];
4360
4361 [sources_ release];
4362 [list_ release];
4363 [super dealloc];
4364 }
4365
4366 - (int) numberOfSectionsInSectionList:(UISectionList *)list {
4367 return offset_ == 0 ? 1 : 2;
4368 }
4369
4370 - (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section {
4371 switch (section + (offset_ == 0 ? 1 : 0)) {
4372 case 0: return @"Entered by User";
4373 case 1: return @"Installed by Packages";
4374
4375 default:
4376 _assert(false);
4377 return nil;
4378 }
4379 }
4380
4381 - (int) sectionList:(UISectionList *)list rowForSection:(int)section {
4382 switch (section + (offset_ == 0 ? 1 : 0)) {
4383 case 0: return 0;
4384 case 1: return offset_;
4385
4386 default:
4387 _assert(false);
4388 return -1;
4389 }
4390 }
4391
4392 - (int) numberOfRowsInTable:(UITable *)table {
4393 return [sources_ count];
4394 }
4395
4396 - (float) table:(UITable *)table heightForRow:(int)row {
4397 Source *source = [sources_ objectAtIndex:row];
4398 return [source description] == nil ? 56 : 73;
4399 }
4400
4401 - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col {
4402 Source *source = [sources_ objectAtIndex:row];
4403 // XXX: weird warning, stupid selectors ;P
4404 return [[[SourceCell alloc] initWithSource:(id)source] autorelease];
4405 }
4406
4407 - (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
4408 return YES;
4409 }
4410
4411 - (BOOL) table:(UITable *)table canSelectRow:(int)row {
4412 return YES;
4413 }
4414
4415 - (void) tableRowSelected:(NSNotification*)notification {
4416 UITable *table([list_ table]);
4417 int row([table selectedRow]);
4418 if (row == INT_MAX)
4419 return;
4420
4421 Source *source = [sources_ objectAtIndex:row];
4422
4423 PackageTable *packages = [[[FilteredPackageTable alloc]
4424 initWithBook:book_
4425 database:database_
4426 title:[source label]
4427 filter:@selector(isVisibleInSource:)
4428 with:source
4429 ] autorelease];
4430
4431 [packages setDelegate:delegate_];
4432
4433 [book_ pushPage:packages];
4434 }
4435
4436 - (BOOL) table:(UITable *)table canDeleteRow:(int)row {
4437 Source *source = [sources_ objectAtIndex:row];
4438 return [source record] != nil;
4439 }
4440
4441 - (void) table:(UITable *)table willSwipeToDeleteRow:(int)row {
4442 [[list_ table] setDeleteConfirmationRow:row];
4443 }
4444
4445 - (void) table:(UITable *)table deleteRow:(int)row {
4446 Source *source = [sources_ objectAtIndex:row];
4447 [Sources_ removeObjectForKey:[source key]];
4448 [delegate_ syncData];
4449 }
4450
4451 - (void) _endConnection:(NSURLConnection *)connection {
4452 NSURLConnection **field = NULL;
4453 if (connection == trivial_bz2_)
4454 field = &trivial_bz2_;
4455 else if (connection == trivial_gz_)
4456 field = &trivial_gz_;
4457 _assert(field != NULL);
4458 [connection release];
4459 *field = nil;
4460
4461 if (
4462 trivial_bz2_ == nil &&
4463 trivial_gz_ == nil
4464 ) {
4465 [delegate_ setStatusBarShowsProgress:NO];
4466
4467 [hud_ show:NO];
4468 [hud_ removeFromSuperview];
4469 [hud_ autorelease];
4470 hud_ = nil;
4471
4472 if (trivial_) {
4473 [Sources_ setObject:[NSDictionary dictionaryWithObjectsAndKeys:
4474 @"deb", @"Type",
4475 href_, @"URI",
4476 @"./", @"Distribution",
4477 nil] forKey:[NSString stringWithFormat:@"deb:%@:./", href_]];
4478
4479 [delegate_ syncData];
4480 } else if (error_ != nil) {
4481 UIActionSheet *sheet = [[[UIActionSheet alloc]
4482 initWithTitle:@"Verification Error"
4483 buttons:[NSArray arrayWithObjects:@"OK", nil]
4484 defaultButtonIndex:0
4485 delegate:self
4486 context:@"urlerror"
4487 ] autorelease];
4488
4489 [sheet setBodyText:[error_ localizedDescription]];
4490 [sheet popupAlertAnimated:YES];
4491 } else {
4492 UIActionSheet *sheet = [[[UIActionSheet alloc]
4493 initWithTitle:@"Did not Find Repository"
4494 buttons:[NSArray arrayWithObjects:@"OK", nil]
4495 defaultButtonIndex:0
4496 delegate:self
4497 context:@"trivial"
4498 ] autorelease];
4499
4500 [sheet setBodyText:@"The indicated repository could not be found. This could be because you are trying to add a legacy Installer repository (these are not supported). Also, this interface is only capable of working with exact repository URLs. If you host a repository and are having issues please contact the author of Cydia with any questions you have."];
4501 [sheet popupAlertAnimated:YES];
4502 }
4503
4504 [href_ release];
4505 href_ = nil;
4506
4507 if (error_ != nil) {
4508 [error_ release];
4509 error_ = nil;
4510 }
4511 }
4512 }
4513
4514 - (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
4515 switch ([response statusCode]) {
4516 case 200:
4517 trivial_ = YES;
4518 }
4519 }
4520
4521 - (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
4522 lprintf("connection:\"%s\" didFailWithError:\"%s\"", [href_ UTF8String], [[error localizedDescription] UTF8String]);
4523 if (error_ != nil)
4524 error_ = [error retain];
4525 [self _endConnection:connection];
4526 }
4527
4528 - (void) connectionDidFinishLoading:(NSURLConnection *)connection {
4529 [self _endConnection:connection];
4530 }
4531
4532 - (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
4533 NSMutableURLRequest *request = [NSMutableURLRequest
4534 requestWithURL:[NSURL URLWithString:href]
4535 cachePolicy:NSURLRequestUseProtocolCachePolicy
4536 timeoutInterval:20.0
4537 ];
4538
4539 [request setHTTPMethod:method];
4540
4541 return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
4542 }
4543
4544 - (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
4545 NSString *context([sheet context]);
4546
4547 if ([context isEqualToString:@"source"]) {
4548 switch (button) {
4549 case 1: {
4550 NSString *href = [[sheet textField] text];
4551
4552 //installer_ = [[self _requestHRef:href method:@"GET"] retain];
4553
4554 if (![href hasSuffix:@"/"])
4555 href_ = [href stringByAppendingString:@"/"];
4556 else
4557 href_ = href;
4558 href_ = [href_ retain];
4559
4560 trivial_bz2_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.bz2"] method:@"HEAD"] retain];
4561 trivial_gz_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.gz"] method:@"HEAD"] retain];
4562 //trivial_bz2_ = [[self _requestHRef:[href stringByAppendingString:@"dists/Release"] method:@"HEAD"] retain];
4563
4564 trivial_ = false;
4565
4566 hud_ = [delegate_ addProgressHUD];
4567 [hud_ setText:@"Verifying URL"];
4568 } break;
4569
4570 case 2:
4571 break;
4572
4573 default:
4574 _assert(false);
4575 }
4576
4577 [sheet dismiss];
4578 } else if ([context isEqualToString:@"trivial"])
4579 [sheet dismiss];
4580 else if ([context isEqualToString:@"urlerror"])
4581 [sheet dismiss];
4582 }
4583
4584 - (id) initWithBook:(RVBook *)book database:(Database *)database {
4585 if ((self = [super initWithBook:book]) != nil) {
4586 database_ = database;
4587 sources_ = [[NSMutableArray arrayWithCapacity:16] retain];
4588
4589 //list_ = [[UITable alloc] initWithFrame:[self bounds]];
4590 list_ = [[UISectionList alloc] initWithFrame:[self bounds] showSectionIndex:NO];
4591 [list_ setShouldHideHeaderInShortLists:NO];
4592
4593 [self addSubview:list_];
4594 [list_ setDataSource:self];
4595
4596 UITableColumn *column = [[UITableColumn alloc]
4597 initWithTitle:@"Name"
4598 identifier:@"name"
4599 width:[self frame].size.width
4600 ];
4601
4602 UITable *table = [list_ table];
4603 [table setSeparatorStyle:1];
4604 [table addTableColumn:column];
4605 [table setDelegate:self];
4606
4607 [self reloadData];
4608
4609 [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
4610 [list_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
4611 } return self;
4612 }
4613
4614 - (void) reloadData {
4615 pkgSourceList list;
4616 _assert(list.ReadMainList());
4617
4618 [sources_ removeAllObjects];
4619 [sources_ addObjectsFromArray:[database_ sources]];
4620 _trace();
4621 [sources_ sortUsingSelector:@selector(compareByNameAndType:)];
4622 _trace();
4623
4624 int count = [sources_ count];
4625 for (offset_ = 0; offset_ != count; ++offset_) {
4626 Source *source = [sources_ objectAtIndex:offset_];
4627 if ([source record] == nil)
4628 break;
4629 }
4630
4631 [list_ reloadData];
4632 }
4633
4634 - (void) resetViewAnimated:(BOOL)animated {
4635 [list_ resetViewAnimated:animated];
4636 }
4637
4638 - (void) _leftButtonClicked {
4639 /*[book_ pushPage:[[[AddSourceView alloc]
4640 initWithBook:book_
4641 database:database_
4642 ] autorelease]];*/
4643
4644 UIActionSheet *sheet = [[[UIActionSheet alloc]
4645 initWithTitle:@"Enter Cydia/APT URL"
4646 buttons:[NSArray arrayWithObjects:@"Add Source", @"Cancel", nil]
4647 defaultButtonIndex:0
4648 delegate:self
4649 context:@"source"
4650 ] autorelease];
4651
4652 [sheet setNumberOfRows:1];
4653
4654 [sheet addTextFieldWithValue:@"http://" label:@""];
4655
4656 UITextInputTraits *traits = [[sheet textField] textInputTraits];
4657 [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
4658 [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
4659 [traits setKeyboardType:UIKeyboardTypeURL];
4660 // XXX: UIReturnKeyDone
4661 [traits setReturnKeyType:UIReturnKeyNext];
4662
4663 [sheet popupAlertAnimated:YES];
4664 }
4665
4666 - (void) _rightButtonClicked {
4667 UITable *table = [list_ table];
4668 BOOL editing = [table isRowDeletionEnabled];
4669 [table enableRowDeletion:!editing animated:YES];
4670 [book_ reloadButtonsForPage:self];
4671 }
4672
4673 - (NSString *) title {
4674 return @"Sources";
4675 }
4676
4677 - (NSString *) leftButtonTitle {
4678 return [[list_ table] isRowDeletionEnabled] ? @"Add" : nil;
4679 }
4680
4681 - (id) rightButtonTitle {
4682 return [[list_ table] isRowDeletionEnabled] ? @"Done" : @"Edit";
4683 }
4684
4685 - (UINavigationButtonStyle) rightButtonStyle {
4686 return [[list_ table] isRowDeletionEnabled] ? UINavigationButtonStyleHighlighted : UINavigationButtonStyleNormal;
4687 }
4688
4689 @end
4690 /* }}} */
4691
4692 /* Installed View {{{ */
4693 @interface InstalledView : RVPage {
4694 _transient Database *database_;
4695 FilteredPackageTable *packages_;
4696 BOOL expert_;
4697 }
4698
4699 - (id) initWithBook:(RVBook *)book database:(Database *)database;
4700
4701 @end
4702
4703 @implementation InstalledView
4704
4705 - (void) dealloc {
4706 [packages_ release];
4707 [super dealloc];
4708 }
4709
4710 - (id) initWithBook:(RVBook *)book database:(Database *)database {
4711 if ((self = [super initWithBook:book]) != nil) {
4712 database_ = database;
4713
4714 packages_ = [[FilteredPackageTable alloc]
4715 initWithBook:book
4716 database:database
4717 title:nil
4718 filter:@selector(isInstalledAndVisible:)
4719 with:[NSNumber numberWithBool:YES]
4720 ];
4721
4722 [self addSubview:packages_];
4723
4724 [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
4725 [packages_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
4726 } return self;
4727 }
4728
4729 - (void) resetViewAnimated:(BOOL)animated {
4730 [packages_ resetViewAnimated:animated];
4731 }
4732
4733 - (void) reloadData {
4734 [packages_ reloadData];
4735 }
4736
4737 - (void) _rightButtonClicked {
4738 [packages_ setObject:[NSNumber numberWithBool:expert_]];
4739 [packages_ reloadData];
4740 expert_ = !expert_;
4741 [book_ reloadButtonsForPage:self];
4742 }
4743
4744 - (NSString *) title {
4745 return @"Installed";
4746 }
4747
4748 - (NSString *) backButtonTitle {
4749 return @"Packages";
4750 }
4751
4752 - (id) rightButtonTitle {
4753 return Role_ != nil && [Role_ isEqualToString:@"Developer"] ? nil : expert_ ? @"Expert" : @"Simple";
4754 }
4755
4756 - (UINavigationButtonStyle) rightButtonStyle {
4757 return expert_ ? UINavigationButtonStyleHighlighted : UINavigationButtonStyleNormal;
4758 }
4759
4760 - (void) setDelegate:(id)delegate {
4761 [super setDelegate:delegate];
4762 [packages_ setDelegate:delegate];
4763 }
4764
4765 @end
4766 /* }}} */
4767
4768 /* Home View {{{ */
4769 @interface HomeView : BrowserView {
4770 }
4771
4772 @end
4773
4774 @implementation HomeView
4775
4776 - (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
4777 NSString *context([sheet context]);
4778
4779 if ([context isEqualToString:@"about"])
4780 [sheet dismiss];
4781 else
4782 [super alertSheet:sheet buttonClicked:button];
4783 }
4784
4785 - (void) _leftButtonClicked {
4786 UIActionSheet *sheet = [[[UIActionSheet alloc]
4787 initWithTitle:@"About Cydia Installer"
4788 buttons:[NSArray arrayWithObjects:@"Close", nil]
4789 defaultButtonIndex:0
4790 delegate:self
4791 context:@"about"
4792 ] autorelease];
4793
4794 [sheet setBodyText:
4795 @"Copyright (C) 2008\n"
4796 "Jay Freeman (saurik)\n"
4797 "saurik@saurik.com\n"
4798 "http://www.saurik.com/\n"
4799 "\n"
4800 "The Okori Group\n"
4801 "http://www.theokorigroup.com/\n"
4802 "\n"
4803 "College of Creative Studies,\n"
4804 "University of California,\n"
4805 "Santa Barbara\n"
4806 "http://www.ccs.ucsb.edu/"
4807 ];
4808
4809 [sheet popupAlertAnimated:YES];
4810 }
4811
4812 - (NSString *) leftButtonTitle {
4813 return @"About";
4814 }
4815
4816 @end
4817 /* }}} */
4818 /* Manage View {{{ */
4819 @interface ManageView : BrowserView {
4820 }
4821
4822 @end
4823
4824 @implementation ManageView
4825
4826 - (NSString *) title {
4827 return @"Manage";
4828 }
4829
4830 - (void) _leftButtonClicked {
4831 [delegate_ askForSettings];
4832 }
4833
4834 - (NSString *) leftButtonTitle {
4835 return @"Settings";
4836 }
4837
4838 #if !AlwaysReload
4839 - (id) _rightButtonTitle {
4840 return nil;
4841 }
4842 #endif
4843
4844 - (bool) _loading {
4845 return false;
4846 }
4847
4848 @end
4849 /* }}} */
4850
4851 #include <BrowserView.m>
4852
4853 /* Cydia Book {{{ */
4854 @interface CYBook : RVBook <
4855 ProgressDelegate
4856 > {
4857 _transient Database *database_;
4858 UINavigationBar *overlay_;
4859 UINavigationBar *underlay_;
4860 UIProgressIndicator *indicator_;
4861 UITextLabel *prompt_;
4862 UIProgressBar *progress_;
4863 UINavigationButton *cancel_;
4864 bool updating_;
4865 size_t received_;
4866 NSTimeInterval last_;
4867 }
4868
4869 - (id) initWithFrame:(CGRect)frame database:(Database *)database;
4870 - (void) update;
4871 - (BOOL) updating;
4872
4873 @end
4874
4875 @implementation CYBook
4876
4877 - (void) dealloc {
4878 [overlay_ release];
4879 [indicator_ release];
4880 [prompt_ release];
4881 [progress_ release];
4882 [cancel_ release];
4883 [super dealloc];
4884 }
4885
4886 - (NSString *) getTitleForPage:(RVPage *)page {
4887 return Simplify([super getTitleForPage:page]);
4888 }
4889
4890 - (BOOL) updating {
4891 return updating_;
4892 }
4893
4894 - (void) update {
4895 [UIView beginAnimations:nil context:NULL];
4896
4897 CGRect ovrframe = [overlay_ frame];
4898 ovrframe.origin.y = 0;
4899 [overlay_ setFrame:ovrframe];
4900
4901 CGRect barframe = [navbar_ frame];
4902 barframe.origin.y += ovrframe.size.height;
4903 [navbar_ setFrame:barframe];
4904
4905 CGRect trnframe = [transition_ frame];
4906 trnframe.origin.y += ovrframe.size.height;
4907 trnframe.size.height -= ovrframe.size.height;
4908 [transition_ setFrame:trnframe];
4909
4910 [UIView endAnimations];
4911
4912 [indicator_ startAnimation];
4913 [prompt_ setText:@"Updating Database"];
4914 [progress_ setProgress:0];
4915
4916 received_ = 0;
4917 last_ = [NSDate timeIntervalSinceReferenceDate];
4918 updating_ = true;
4919 [overlay_ addSubview:cancel_];
4920
4921 [NSThread
4922 detachNewThreadSelector:@selector(_update)
4923 toTarget:self
4924 withObject:nil
4925 ];
4926 }
4927
4928 - (void) _update_ {
4929 updating_ = false;
4930
4931 [indicator_ stopAnimation];
4932
4933 [UIView beginAnimations:nil context:NULL];
4934
4935 CGRect ovrframe = [overlay_ frame];
4936 ovrframe.origin.y = -ovrframe.size.height;
4937 [overlay_ setFrame:ovrframe];
4938
4939 CGRect barframe = [navbar_ frame];
4940 barframe.origin.y -= ovrframe.size.height;
4941 [navbar_ setFrame:barframe];
4942
4943 CGRect trnframe = [transition_ frame];
4944 trnframe.origin.y -= ovrframe.size.height;
4945 trnframe.size.height += ovrframe.size.height;
4946 [transition_ setFrame:trnframe];
4947
4948 [UIView commitAnimations];
4949
4950 [delegate_ performSelector:@selector(reloadData) withObject:nil afterDelay:0];
4951 }
4952
4953 - (id) initWithFrame:(CGRect)frame database:(Database *)database {
4954 if ((self = [super initWithFrame:frame]) != nil) {
4955 database_ = database;
4956
4957 CGRect ovrrect = [navbar_ bounds];
4958 ovrrect.size.height = [UINavigationBar defaultSize].height;
4959 ovrrect.origin.y = -ovrrect.size.height;
4960
4961 overlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
4962 [self addSubview:overlay_];
4963
4964 ovrrect.origin.y = frame.size.height;
4965 underlay_ = [[UINavigationBar alloc] initWithFrame:ovrrect];
4966 [underlay_ setTintColor:[UIColor colorWithRed:0.23 green:0.23 blue:0.23 alpha:1]];
4967 [self addSubview:underlay_];
4968
4969 [overlay_ setBarStyle:1];
4970 [underlay_ setBarStyle:1];
4971
4972 int barstyle = [overlay_ _barStyle:NO];
4973 bool ugly = barstyle == 0;
4974
4975 UIProgressIndicatorStyle style = ugly ?
4976 UIProgressIndicatorStyleMediumBrown :
4977 UIProgressIndicatorStyleMediumWhite;
4978
4979 CGSize indsize = [UIProgressIndicator defaultSizeForStyle:style];
4980 unsigned indoffset = (ovrrect.size.height - indsize.height) / 2;
4981 CGRect indrect = {{indoffset, indoffset}, indsize};
4982
4983 indicator_ = [[UIProgressIndicator alloc] initWithFrame:indrect];
4984 [indicator_ setStyle:style];
4985 [overlay_ addSubview:indicator_];
4986
4987 CGSize prmsize = {215, indsize.height + 4};
4988
4989 CGRect prmrect = {{
4990 indoffset * 2 + indsize.width,
4991 #ifdef __OBJC2__
4992 -1 +
4993 #endif
4994 unsigned(ovrrect.size.height - prmsize.height) / 2
4995 }, prmsize};
4996
4997 UIFont *font = [UIFont systemFontOfSize:15];
4998
4999 prompt_ = [[UITextLabel alloc] initWithFrame:prmrect];
5000
5001 [prompt_ setColor:[UIColor colorWithCGColor:(ugly ? Blueish_ : Off_)]];
5002 [prompt_ setBackgroundColor:[UIColor clearColor]];
5003 [prompt_ setFont:font];
5004
5005 [overlay_ addSubview:prompt_];
5006
5007 CGSize prgsize = {75, 100};
5008
5009 CGRect prgrect = {{
5010 ovrrect.size.width - prgsize.width - 10,
5011 (ovrrect.size.height - prgsize.height) / 2
5012 } , prgsize};
5013
5014 progress_ = [[UIProgressBar alloc] initWithFrame:prgrect];
5015 [progress_ setStyle:0];
5016 [overlay_ addSubview:progress_];
5017
5018 cancel_ = [[UINavigationButton alloc] initWithTitle:@"Cancel" style:UINavigationButtonStyleHighlighted];
5019 [cancel_ addTarget:self action:@selector(_onCancel) forControlEvents:UIControlEventTouchUpInside];
5020
5021 CGRect frame = [cancel_ frame];
5022 frame.size.width = 65;
5023 frame.origin.x = ovrrect.size.width - frame.size.width - 5;
5024 frame.origin.y = (ovrrect.size.height - frame.size.height) / 2;
5025 [cancel_ setFrame:frame];
5026
5027 [cancel_ setBarStyle:barstyle];
5028 } return self;
5029 }
5030
5031 - (void) _onCancel {
5032 updating_ = false;
5033 [cancel_ removeFromSuperview];
5034 }
5035
5036 - (void) _update { _pooled
5037 Status status;
5038 status.setDelegate(self);
5039
5040 [database_ updateWithStatus:status];
5041
5042 [self
5043 performSelectorOnMainThread:@selector(_update_)
5044 withObject:nil
5045 waitUntilDone:NO
5046 ];
5047 }
5048
5049 - (void) setProgressError:(NSString *)error forPackage:(NSString *)id {
5050 [prompt_ setText:[NSString stringWithFormat:@"Error: %@", error]];
5051 }
5052
5053 - (void) setProgressTitle:(NSString *)title {
5054 [self
5055 performSelectorOnMainThread:@selector(_setProgressTitle:)
5056 withObject:title
5057 waitUntilDone:YES
5058 ];
5059 }
5060
5061 - (void) setProgressPercent:(float)percent {
5062 [self
5063 performSelectorOnMainThread:@selector(_setProgressPercent:)
5064 withObject:[NSNumber numberWithFloat:percent]
5065 waitUntilDone:YES
5066 ];
5067 }
5068
5069 - (void) startProgress {
5070 }
5071
5072 - (void) addProgressOutput:(NSString *)output {
5073 [self
5074 performSelectorOnMainThread:@selector(_addProgressOutput:)
5075 withObject:output
5076 waitUntilDone:YES
5077 ];
5078 }
5079
5080 - (bool) isCancelling:(size_t)received {
5081 NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
5082 if (received_ != received) {
5083 received_ = received;
5084 last_ = now;
5085 } else if (now - last_ > 15)
5086 return true;
5087 return !updating_;
5088 }
5089
5090 - (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
5091 [sheet dismiss];
5092 }
5093
5094 - (void) _setProgressTitle:(NSString *)title {
5095 [prompt_ setText:title];
5096 }
5097
5098 - (void) _setProgressPercent:(NSNumber *)percent {
5099 [progress_ setProgress:[percent floatValue]];
5100 }
5101
5102 - (void) _addProgressOutput:(NSString *)output {
5103 }
5104
5105 @end
5106 /* }}} */
5107 /* Cydia:// Protocol {{{ */
5108 @interface CydiaURLProtocol : NSURLProtocol {
5109 }
5110
5111 @end
5112
5113 @implementation CydiaURLProtocol
5114
5115 + (BOOL) canInitWithRequest:(NSURLRequest *)request {
5116 NSURL *url([request URL]);
5117 if (url == nil)
5118 return NO;
5119 NSString *scheme([[url scheme] lowercaseString]);
5120 if (scheme == nil || ![scheme isEqualToString:@"cydia"])
5121 return NO;
5122 return YES;
5123 }
5124
5125 + (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
5126 return request;
5127 }
5128
5129 - (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request {
5130 id<NSURLProtocolClient> client([self client]);
5131 if (icon == nil)
5132 [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
5133 else {
5134 NSData *data(UIImagePNGRepresentation(icon));
5135
5136 NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
5137 [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
5138 [client URLProtocol:self didLoadData:data];
5139 [client URLProtocolDidFinishLoading:self];
5140 }
5141 }
5142
5143 - (void) startLoading {
5144 id<NSURLProtocolClient> client([self client]);
5145 NSURLRequest *request([self request]);
5146
5147 NSURL *url([request URL]);
5148 NSString *href([url absoluteString]);
5149
5150 NSString *path([href substringFromIndex:8]);
5151 NSRange slash([path rangeOfString:@"/"]);
5152
5153 NSString *command;
5154 if (slash.location == NSNotFound) {
5155 command = path;
5156 path = nil;
5157 } else {
5158 command = [path substringToIndex:slash.location];
5159 path = [path substringFromIndex:(slash.location + 1)];
5160 }
5161
5162 Database *database([Database sharedInstance]);
5163
5164 if ([command isEqualToString:@"package-icon"]) {
5165 if (path == nil)
5166 goto fail;
5167 path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
5168 Package *package([database packageWithName:path]);
5169 if (package == nil)
5170 goto fail;
5171 UIImage *icon([package icon]);
5172 [self _returnPNGWithImage:icon forRequest:request];
5173 } else if ([command isEqualToString:@"source-icon"]) {
5174 if (path == nil)
5175 goto fail;
5176 path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
5177 NSString *source(Simplify(path));
5178 UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sources/%@.png", App_, source]]);
5179 if (icon == nil)
5180 icon = [UIImage applicationImageNamed:@"unknown.png"];
5181 [self _returnPNGWithImage:icon forRequest:request];
5182 } else if ([command isEqualToString:@"uikit-image"]) {
5183 if (path == nil)
5184 goto fail;
5185 path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
5186 UIImage *icon(_UIImageWithName(path));
5187 [self _returnPNGWithImage:icon forRequest:request];
5188 } else if ([command isEqualToString:@"section-icon"]) {
5189 if (path == nil)
5190 goto fail;
5191 path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
5192 NSString *section(Simplify(path));
5193 UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, section]]);
5194 if (icon == nil)
5195 icon = [UIImage applicationImageNamed:@"unknown.png"];
5196 [self _returnPNGWithImage:icon forRequest:request];
5197 } else fail: {
5198 [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
5199 }
5200 }
5201
5202 - (void) stopLoading {
5203 }
5204
5205 @end
5206 /* }}} */
5207
5208 /* Sections View {{{ */
5209 @interface SectionsView : RVPage {
5210 _transient Database *database_;
5211 NSMutableArray *sections_;
5212 NSMutableArray *filtered_;
5213 UITransitionView *transition_;
5214 UITable *list_;
5215 UIView *accessory_;
5216 BOOL editing_;
5217 }
5218
5219 - (id) initWithBook:(RVBook *)book database:(Database *)database;
5220 - (void) reloadData;
5221 - (void) resetView;
5222
5223 @end
5224
5225 @implementation SectionsView
5226
5227 - (void) dealloc {
5228 [list_ setDataSource:nil];
5229 [list_ setDelegate:nil];
5230
5231 [sections_ release];
5232 [filtered_ release];
5233 [transition_ release];
5234 [list_ release];
5235 [accessory_ release];
5236 [super dealloc];
5237 }
5238
5239 - (int) numberOfRowsInTable:(UITable *)table {
5240 return editing_ ? [sections_ count] : [filtered_ count] + 1;
5241 }
5242
5243 - (float) table:(UITable *)table heightForRow:(int)row {
5244 return 45;
5245 }
5246
5247 - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
5248 if (reusing == nil)
5249 reusing = [[[SectionCell alloc] init] autorelease];
5250 [(SectionCell *)reusing setSection:(editing_ ?
5251 [sections_ objectAtIndex:row] :
5252 (row == 0 ? nil : [filtered_ objectAtIndex:(row - 1)])
5253 ) editing:editing_];
5254 return reusing;
5255 }
5256
5257 - (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
5258 return !editing_;
5259 }
5260
5261 - (BOOL) table:(UITable *)table canSelectRow:(int)row {
5262 return !editing_;
5263 }
5264
5265 - (void) tableRowSelected:(NSNotification *)notification {
5266 int row = [[notification object] selectedRow];
5267 if (row == INT_MAX)
5268 return;
5269
5270 Section *section;
5271 NSString *name;
5272 NSString *title;
5273
5274 if (row == 0) {
5275 section = nil;
5276 name = nil;
5277 title = @"All Packages";
5278 } else {
5279 section = [filtered_ objectAtIndex:(row - 1)];
5280 name = [section name];
5281
5282 if (name != nil)
5283 title = name;
5284 else {
5285 name = @"";
5286 title = @"(No Section)";
5287 }
5288 }
5289
5290 PackageTable *table = [[[FilteredPackageTable alloc]
5291 initWithBook:book_
5292 database:database_
5293 title:title
5294 filter:@selector(isVisiblyUninstalledInSection:)
5295 with:name
5296 ] autorelease];
5297
5298 [table setDelegate:delegate_];
5299
5300 [book_ pushPage:table];
5301 }
5302
5303 - (id) initWithBook:(RVBook *)book database:(Database *)database {
5304 if ((self = [super initWithBook:book]) != nil) {
5305 database_ = database;
5306
5307 sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
5308 filtered_ = [[NSMutableArray arrayWithCapacity:16] retain];
5309
5310 transition_ = [[UITransitionView alloc] initWithFrame:[self bounds]];
5311 [self addSubview:transition_];
5312
5313 list_ = [[UITable alloc] initWithFrame:[transition_ bounds]];
5314 [transition_ transition:0 toView:list_];
5315
5316 UITableColumn *column = [[[UITableColumn alloc]
5317 initWithTitle:@"Name"
5318 identifier:@"name"
5319 width:[self frame].size.width
5320 ] autorelease];
5321
5322 [list_ setDataSource:self];
5323 [list_ setSeparatorStyle:1];
5324 [list_ addTableColumn:column];
5325 [list_ setDelegate:self];
5326 [list_ setReusesTableCells:YES];
5327
5328 [self reloadData];
5329
5330 [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
5331 [list_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
5332 } return self;
5333 }
5334
5335 - (void) reloadData {
5336 NSArray *packages = [database_ packages];
5337
5338 [sections_ removeAllObjects];
5339 [filtered_ removeAllObjects];
5340
5341 NSMutableArray *filtered = [NSMutableArray arrayWithCapacity:[packages count]];
5342 NSMutableDictionary *sections = [NSMutableDictionary dictionaryWithCapacity:32];
5343
5344 _trace();
5345 for (size_t i(0); i != [packages count]; ++i) {
5346 Package *package([packages objectAtIndex:i]);
5347 NSString *name([package section]);
5348
5349 if (name != nil) {
5350 Section *section([sections objectForKey:name]);
5351 if (section == nil) {
5352 section = [[[Section alloc] initWithName:name] autorelease];
5353 [sections setObject:section forKey:name];
5354 }
5355 }
5356
5357 if ([package valid] && [package installed] == nil && [package visible])
5358 [filtered addObject:package];
5359 }
5360 _trace();
5361
5362 [sections_ addObjectsFromArray:[sections allValues]];
5363 [sections_ sortUsingSelector:@selector(compareByName:)];
5364
5365 _trace();
5366 [filtered sortUsingSelector:@selector(compareBySection:)];
5367 _trace();
5368
5369 Section *section = nil;
5370 for (size_t offset = 0, count = [filtered count]; offset != count; ++offset) {
5371 Package *package = [filtered objectAtIndex:offset];
5372 NSString *name = [package section];
5373
5374 if (section == nil || name != nil && ![[section name] isEqualToString:name]) {
5375 section = name == nil ?
5376 [[[Section alloc] initWithName:nil] autorelease] :
5377 [sections objectForKey:name];
5378 [filtered_ addObject:section];
5379 }
5380
5381 [section addToCount];
5382 }
5383 _trace();
5384
5385 [list_ reloadData];
5386 _trace();
5387 }
5388
5389 - (void) resetView {
5390 if (editing_)
5391 [self _rightButtonClicked];
5392 }
5393
5394 - (void) resetViewAnimated:(BOOL)animated {
5395 [list_ resetViewAnimated:animated];
5396 }
5397
5398 - (void) _rightButtonClicked {
5399 if ((editing_ = !editing_))
5400 [list_ reloadData];
5401 else
5402 [delegate_ updateData];
5403 [book_ reloadTitleForPage:self];
5404 [book_ reloadButtonsForPage:self];
5405 }
5406
5407 - (NSString *) title {
5408 return editing_ ? @"Section Visibility" : @"Install by Section";
5409 }
5410
5411 - (NSString *) backButtonTitle {
5412 return @"Sections";
5413 }
5414
5415 - (id) rightButtonTitle {
5416 return [sections_ count] == 0 ? nil : editing_ ? @"Done" : @"Edit";
5417 }
5418
5419 - (UINavigationButtonStyle) rightButtonStyle {
5420 return editing_ ? UINavigationButtonStyleHighlighted : UINavigationButtonStyleNormal;
5421 }
5422
5423 - (UIView *) accessoryView {
5424 return accessory_;
5425 }
5426
5427 @end
5428 /* }}} */
5429 /* Changes View {{{ */
5430 @interface ChangesView : RVPage {
5431 _transient Database *database_;
5432 NSMutableArray *packages_;
5433 NSMutableArray *sections_;
5434 UISectionList *list_;
5435 unsigned upgrades_;
5436 }
5437
5438 - (id) initWithBook:(RVBook *)book database:(Database *)database;
5439 - (void) reloadData;
5440
5441 @end
5442
5443 @implementation ChangesView
5444
5445 - (void) dealloc {
5446 [[list_ table] setDelegate:nil];
5447 [list_ setDataSource:nil];
5448
5449 [packages_ release];
5450 [sections_ release];
5451 [list_ release];
5452 [super dealloc];
5453 }
5454
5455 - (int) numberOfSectionsInSectionList:(UISectionList *)list {
5456 return [sections_ count];
5457 }
5458
5459 - (NSString *) sectionList:(UISectionList *)list titleForSection:(int)section {
5460 return [[sections_ objectAtIndex:section] name];
5461 }
5462
5463 - (int) sectionList:(UISectionList *)list rowForSection:(int)section {
5464 return [[sections_ objectAtIndex:section] row];
5465 }
5466
5467 - (int) numberOfRowsInTable:(UITable *)table {
5468 return [packages_ count];
5469 }
5470
5471 - (float) table:(UITable *)table heightForRow:(int)row {
5472 return [PackageCell heightForPackage:[packages_ objectAtIndex:row]];
5473 }
5474
5475 - (UITableCell *) table:(UITable *)table cellForRow:(int)row column:(UITableColumn *)col reusing:(UITableCell *)reusing {
5476 if (reusing == nil)
5477 reusing = [[[PackageCell alloc] init] autorelease];
5478 [(PackageCell *)reusing setPackage:[packages_ objectAtIndex:row]];
5479 return reusing;
5480 }
5481
5482 - (BOOL) table:(UITable *)table showDisclosureForRow:(int)row {
5483 return NO;
5484 }
5485
5486 - (void) tableRowSelected:(NSNotification *)notification {
5487 int row = [[notification object] selectedRow];
5488 if (row == INT_MAX)
5489 return;
5490 Package *package = [packages_ objectAtIndex:row];
5491 PackageView *view = [[[PackageView alloc] initWithBook:book_ database:database_] autorelease];
5492 [view setDelegate:delegate_];
5493 [view setPackage:package];
5494 [book_ pushPage:view];
5495 }
5496
5497 - (void) _leftButtonClicked {
5498 [(CYBook *)book_ update];
5499 [self reloadButtons];
5500 }
5501
5502 - (void) _rightButtonClicked {
5503 [delegate_ distUpgrade];
5504 }
5505
5506 - (id) initWithBook:(RVBook *)book database:(Database *)database {
5507 if ((self = [super initWithBook:book]) != nil) {
5508 database_ = database;
5509
5510 packages_ = [[NSMutableArray arrayWithCapacity:16] retain];
5511 sections_ = [[NSMutableArray arrayWithCapacity:16] retain];
5512
5513 list_ = [[UISectionList alloc] initWithFrame:[self bounds] showSectionIndex:NO];
5514 [self addSubview:list_];
5515
5516 [list_ setShouldHideHeaderInShortLists:NO];
5517 [list_ setDataSource:self];
5518 //[list_ setSectionListStyle:1];
5519
5520 UITableColumn *column = [[[UITableColumn alloc]
5521 initWithTitle:@"Name"
5522 identifier:@"name"
5523 width:[self frame].size.width
5524 ] autorelease];
5525
5526 UITable *table = [list_ table];
5527 [table setSeparatorStyle:1];
5528 [table addTableColumn:column];
5529 [table setDelegate:self];
5530 [table setReusesTableCells:YES];
5531
5532 [self reloadData];
5533
5534 [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
5535 [list_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
5536 } return self;
5537 }
5538
5539 - (void) reloadData {
5540 NSArray *packages = [database_ packages];
5541
5542 [packages_ removeAllObjects];
5543 [sections_ removeAllObjects];
5544
5545 _trace();
5546 for (size_t i(0); i != [packages count]; ++i) {
5547 Package *package([packages objectAtIndex:i]);
5548
5549 if (
5550 [package installed] == nil && [package valid] && [package visible] ||
5551 [package upgradableAndEssential:YES]
5552 )
5553 [packages_ addObject:package];
5554 }
5555
5556 _trace();
5557 [packages_ radixSortUsingSelector:@selector(compareForChanges) withObject:nil];
5558 _trace();
5559
5560 Section *upgradable = [[[Section alloc] initWithName:@"Available Upgrades"] autorelease];
5561 Section *ignored = [[[Section alloc] initWithName:@"Ignored Upgrades"] autorelease];
5562 Section *section = nil;
5563 NSDate *last = nil;
5564
5565 upgrades_ = 0;
5566 bool unseens = false;
5567
5568 CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
5569
5570 _trace();
5571 for (size_t offset = 0, count = [packages_ count]; offset != count; ++offset) {
5572 Package *package = [packages_ objectAtIndex:offset];
5573
5574 if (![package upgradableAndEssential:YES]) {
5575 unseens = true;
5576 NSDate *seen = [package seen];
5577
5578 if (section == nil || last != seen && (seen == nil || [seen compare:last] != NSOrderedSame)) {
5579 last = seen;
5580
5581 NSString *name(seen == nil ? [@"n/a ?" retain] : (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) seen));
5582 section = [[[Section alloc] initWithName:name row:offset] autorelease];
5583 [sections_ addObject:section];
5584 [name release];
5585 }
5586
5587 [section addToCount];
5588 } else if ([package ignored])
5589 [ignored addToCount];
5590 else {
5591 ++upgrades_;
5592 [upgradable addToCount];
5593 }
5594 }
5595 _trace();
5596
5597 CFRelease(formatter);
5598
5599 if (unseens) {
5600 Section *last = [sections_ lastObject];
5601 size_t count = [last count];
5602 [packages_ removeObjectsInRange:NSMakeRange([packages_ count] - count, count)];
5603 [sections_ removeLastObject];
5604 }
5605
5606 if ([ignored count] != 0)
5607 [sections_ insertObject:ignored atIndex:0];
5608 if (upgrades_ != 0)
5609 [sections_ insertObject:upgradable atIndex:0];
5610
5611 [list_ reloadData];
5612 [self reloadButtons];
5613 }
5614
5615 - (void) resetViewAnimated:(BOOL)animated {
5616 [list_ resetViewAnimated:animated];
5617 }
5618
5619 - (NSString *) leftButtonTitle {
5620 return [(CYBook *)book_ updating] ? nil : @"Refresh";
5621 }
5622
5623 - (id) rightButtonTitle {
5624 return upgrades_ == 0 ? nil : [NSString stringWithFormat:@"Upgrade (%u)", upgrades_];
5625 }
5626
5627 - (NSString *) title {
5628 return @"Changes";
5629 }
5630
5631 @end
5632 /* }}} */
5633 /* Search View {{{ */
5634 @protocol SearchViewDelegate
5635 - (void) showKeyboard:(BOOL)show;
5636 @end
5637
5638 @interface SearchView : RVPage {
5639 UIView *accessory_;
5640 UISearchField *field_;
5641 UITransitionView *transition_;
5642 FilteredPackageTable *table_;
5643 UIPreferencesTable *advanced_;
5644 UIView *dimmed_;
5645 bool flipped_;
5646 bool reload_;
5647 }
5648
5649 - (id) initWithBook:(RVBook *)book database:(Database *)database;
5650 - (void) reloadData;
5651
5652 @end
5653
5654 @implementation SearchView
5655
5656 - (void) dealloc {
5657 [field_ setDelegate:nil];
5658
5659 [accessory_ release];
5660 [field_ release];
5661 [transition_ release];
5662 [table_ release];
5663 [advanced_ release];
5664 [dimmed_ release];
5665 [super dealloc];
5666 }
5667
5668 - (int) numberOfGroupsInPreferencesTable:(UIPreferencesTable *)table {
5669 return 1;
5670 }
5671
5672 - (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group {
5673 switch (group) {
5674 case 0: return @"Advanced Search (Coming Soon!)";
5675
5676 default: _assert(false);
5677 }
5678 }
5679
5680 - (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group {
5681 switch (group) {
5682 case 0: return 0;
5683
5684 default: _assert(false);
5685 }
5686 }
5687
5688 - (void) _showKeyboard:(BOOL)show {
5689 CGSize keysize = [UIKeyboard defaultSize];
5690 CGRect keydown = [book_ pageBounds];
5691 CGRect keyup = keydown;
5692 keyup.size.height -= keysize.height - ButtonBarHeight_;
5693
5694 float delay = KeyboardTime_ * ButtonBarHeight_ / keysize.height;
5695
5696 UIFrameAnimation *animation = [[[UIFrameAnimation alloc] initWithTarget:[table_ list]] autorelease];
5697 [animation setSignificantRectFields:8];
5698
5699 if (show) {
5700 [animation setStartFrame:keydown];
5701 [animation setEndFrame:keyup];
5702 } else {
5703 [animation setStartFrame:keyup];
5704 [animation setEndFrame:keydown];
5705 }
5706
5707 UIAnimator *animator = [UIAnimator sharedAnimator];
5708
5709 [animator
5710 addAnimations:[NSArray arrayWithObjects:animation, nil]
5711 withDuration:(KeyboardTime_ - delay)
5712 start:!show
5713 ];
5714
5715 if (show)
5716 [animator performSelector:@selector(startAnimation:) withObject:animation afterDelay:delay];
5717
5718 [delegate_ showKeyboard:show];
5719 }
5720
5721 - (void) textFieldDidBecomeFirstResponder:(UITextField *)field {
5722 [self _showKeyboard:YES];
5723 }
5724
5725 - (void) textFieldDidResignFirstResponder:(UITextField *)field {
5726 [self _showKeyboard:NO];
5727 }
5728
5729 - (void) keyboardInputChanged:(UIFieldEditor *)editor {
5730 if (reload_) {
5731 NSString *text([field_ text]);
5732 [field_ setClearButtonStyle:(text == nil || [text length] == 0 ? 0 : 2)];
5733 [self reloadData];
5734 reload_ = false;
5735 }
5736 }
5737
5738 - (void) textFieldClearButtonPressed:(UITextField *)field {
5739 reload_ = true;
5740 }
5741
5742 - (void) keyboardInputShouldDelete:(id)input {
5743 reload_ = true;
5744 }
5745
5746 - (BOOL) keyboardInput:(id)input shouldInsertText:(NSString *)text isMarkedText:(int)marked {
5747 if ([text length] != 1 || [text characterAtIndex:0] != '\n') {
5748 reload_ = true;
5749 return YES;
5750 } else {
5751 [field_ resignFirstResponder];
5752 return NO;
5753 }
5754 }
5755
5756 - (id) initWithBook:(RVBook *)book database:(Database *)database {
5757 if ((self = [super initWithBook:book]) != nil) {
5758 CGRect pageBounds = [book_ pageBounds];
5759
5760 transition_ = [[UITransitionView alloc] initWithFrame:pageBounds];
5761 [self addSubview:transition_];
5762
5763 advanced_ = [[UIPreferencesTable alloc] initWithFrame:pageBounds];
5764
5765 [advanced_ setReusesTableCells:YES];
5766 [advanced_ setDataSource:self];
5767 [advanced_ reloadData];
5768
5769 dimmed_ = [[UIView alloc] initWithFrame:pageBounds];
5770 CGColor dimmed(space_, 0, 0, 0, 0.5);
5771 [dimmed_ setBackgroundColor:[UIColor colorWithCGColor:dimmed]];
5772
5773 table_ = [[FilteredPackageTable alloc]
5774 initWithBook:book
5775 database:database
5776 title:nil
5777 filter:@selector(isUnfilteredAndSearchedForBy:)
5778 with:nil
5779 ];
5780
5781 [table_ setShouldHideHeaderInShortLists:NO];
5782 [transition_ transition:0 toView:table_];
5783
5784 CGRect cnfrect = {{
5785 #ifdef __OBJC2__
5786 6 +
5787 #endif
5788 1, 38}, {17, 18}};
5789
5790 CGRect area;
5791 area.origin.x = /*cnfrect.origin.x + cnfrect.size.width + 4 +*/ 10;
5792 area.origin.y = 1;
5793
5794 area.size.width =
5795 #ifdef __OBJC2__
5796 8 +
5797 #endif
5798 [self bounds].size.width - area.origin.x - 18;
5799
5800 area.size.height = [UISearchField defaultHeight];
5801
5802 field_ = [[UISearchField alloc] initWithFrame:area];
5803
5804 UIFont *font = [UIFont systemFontOfSize:16];
5805 [field_ setFont:font];
5806
5807 [field_ setPlaceholder:@"Package Names & Descriptions"];
5808 [field_ setDelegate:self];
5809
5810 [field_ setPaddingTop:5];
5811
5812 UITextInputTraits *traits([field_ textInputTraits]);
5813 [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
5814 [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
5815 [traits setReturnKeyType:UIReturnKeySearch];
5816
5817 CGRect accrect = {{0, 6}, {6 + cnfrect.size.width + 6 + area.size.width + 6, area.size.height}};
5818
5819 accessory_ = [[UIView alloc] initWithFrame:accrect];
5820 [accessory_ addSubview:field_];
5821
5822 /*UIPushButton *configure = [[[UIPushButton alloc] initWithFrame:cnfrect] autorelease];
5823 [configure setShowPressFeedback:YES];
5824 [configure setImage:[UIImage applicationImageNamed:@"advanced.png"]];
5825 [configure addTarget:self action:@selector(configurePushed) forEvents:1];
5826 [accessory_ addSubview:configure];*/
5827
5828 [self setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
5829 [table_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
5830 } return self;
5831 }
5832
5833 - (void) flipPage {
5834 #ifndef __OBJC2__
5835 LKAnimation *animation = [LKTransition animation];
5836 [animation setType:@"oglFlip"];
5837 [animation setTimingFunction:[LKTimingFunction functionWithName:@"easeInEaseOut"]];
5838 [animation setFillMode:@"extended"];
5839 [animation setTransitionFlags:3];
5840 [animation setDuration:10];
5841 [animation setSpeed:0.35];
5842 [animation setSubtype:(flipped_ ? @"fromLeft" : @"fromRight")];
5843 [[transition_ _layer] addAnimation:animation forKey:0];
5844 [transition_ transition:0 toView:(flipped_ ? (UIView *) table_ : (UIView *) advanced_)];
5845 flipped_ = !flipped_;
5846 #endif
5847 }
5848
5849 - (void) configurePushed {
5850 [field_ resignFirstResponder];
5851 [self flipPage];
5852 }
5853
5854 - (void) resetViewAnimated:(BOOL)animated {
5855 if (flipped_)
5856 [self flipPage];
5857 [table_ resetViewAnimated:animated];
5858 }
5859
5860 - (void) _reloadData {
5861 }
5862
5863 - (void) reloadData {
5864 if (flipped_)
5865 [self flipPage];
5866 [table_ setObject:[field_ text]];
5867 [table_ reloadData];
5868 [table_ resetCursor];
5869 }
5870
5871 - (UIView *) accessoryView {
5872 return accessory_;
5873 }
5874
5875 - (NSString *) title {
5876 return nil;
5877 }
5878
5879 - (NSString *) backButtonTitle {
5880 return @"Search";
5881 }
5882
5883 - (void) setDelegate:(id)delegate {
5884 [table_ setDelegate:delegate];
5885 [super setDelegate:delegate];
5886 }
5887
5888 @end
5889 /* }}} */
5890
5891 @interface SettingsView : RVPage {
5892 _transient Database *database_;
5893 NSString *name_;
5894 Package *package_;
5895 UIPreferencesTable *table_;
5896 _UISwitchSlider *subscribedSwitch_;
5897 _UISwitchSlider *ignoredSwitch_;
5898 UIPreferencesControlTableCell *subscribedCell_;
5899 UIPreferencesControlTableCell *ignoredCell_;
5900 }
5901
5902 - (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package;
5903
5904 @end
5905
5906 @implementation SettingsView
5907
5908 - (void) dealloc {
5909 [table_ setDataSource:nil];
5910
5911 [name_ release];
5912 if (package_ != nil)
5913 [package_ release];
5914 [table_ release];
5915 [subscribedSwitch_ release];
5916 [ignoredSwitch_ release];
5917 [subscribedCell_ release];
5918 [ignoredCell_ release];
5919 [super dealloc];
5920 }
5921
5922 - (int) numberOfGroupsInPreferencesTable:(UIPreferencesTable *)table {
5923 if (package_ == nil)
5924 return 0;
5925
5926 return 2;
5927 }
5928
5929 - (NSString *) preferencesTable:(UIPreferencesTable *)table titleForGroup:(int)group {
5930 if (package_ == nil)
5931 return nil;
5932
5933 switch (group) {
5934 case 0: return nil;
5935 case 1: return nil;
5936
5937 default: _assert(false);
5938 }
5939
5940 return nil;
5941 }
5942
5943 - (BOOL) preferencesTable:(UIPreferencesTable *)table isLabelGroup:(int)group {
5944 if (package_ == nil)
5945 return NO;
5946
5947 switch (group) {
5948 case 0: return NO;
5949 case 1: return YES;
5950
5951 default: _assert(false);
5952 }
5953
5954 return NO;
5955 }
5956
5957 - (int) preferencesTable:(UIPreferencesTable *)table numberOfRowsInGroup:(int)group {
5958 if (package_ == nil)
5959 return 0;
5960
5961 switch (group) {
5962 case 0: return 1;
5963 case 1: return 1;
5964
5965 default: _assert(false);
5966 }
5967
5968 return 0;
5969 }
5970
5971 - (void) onSomething:(UIPreferencesControlTableCell *)cell withKey:(NSString *)key {
5972 if (package_ == nil)
5973 return;
5974
5975 _UISwitchSlider *slider([cell control]);
5976 BOOL value([slider value] != 0);
5977 NSMutableDictionary *metadata([package_ metadata]);
5978
5979 BOOL before;
5980 if (NSNumber *number = [metadata objectForKey:key])
5981 before = [number boolValue];
5982 else
5983 before = NO;
5984
5985 if (value != before) {
5986 [metadata setObject:[NSNumber numberWithBool:value] forKey:key];
5987 Changed_ = true;
5988 [delegate_ updateData];
5989 }
5990 }
5991
5992 - (void) onSubscribed:(UIPreferencesControlTableCell *)cell {
5993 [self onSomething:cell withKey:@"IsSubscribed"];
5994 }
5995
5996 - (void) onIgnored:(UIPreferencesControlTableCell *)cell {
5997 [self onSomething:cell withKey:@"IsIgnored"];
5998 }
5999
6000 - (id) preferencesTable:(UIPreferencesTable *)table cellForRow:(int)row inGroup:(int)group {
6001 if (package_ == nil)
6002 return nil;
6003
6004 switch (group) {
6005 case 0: switch (row) {
6006 case 0:
6007 return subscribedCell_;
6008 case 1:
6009 return ignoredCell_;
6010 default: _assert(false);
6011 } break;
6012
6013 case 1: switch (row) {
6014 case 0: {
6015 UIPreferencesControlTableCell *cell([[[UIPreferencesControlTableCell alloc] init] autorelease]);
6016 [cell setShowSelection:NO];
6017 [cell setTitle:@"Changes only shows upgrades to installed packages so as to minimize spam from packagers. Activate this to see upgrades to this package even when it is not installed."];
6018 return cell;
6019 }
6020
6021 default: _assert(false);
6022 } break;
6023
6024 default: _assert(false);
6025 }
6026
6027 return nil;
6028 }
6029
6030 - (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package {
6031 if ((self = [super initWithBook:book])) {
6032 database_ = database;
6033 name_ = [package retain];
6034
6035 table_ = [[UIPreferencesTable alloc] initWithFrame:[self bounds]];
6036 [self addSubview:table_];
6037
6038 subscribedSwitch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(200, 10, 50, 20)];
6039 [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:kUIControlEventMouseUpInside];
6040
6041 ignoredSwitch_ = [[_UISwitchSlider alloc] initWithFrame:CGRectMake(200, 10, 50, 20)];
6042 [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:kUIControlEventMouseUpInside];
6043
6044 subscribedCell_ = [[UIPreferencesControlTableCell alloc] init];
6045 [subscribedCell_ setShowSelection:NO];
6046 [subscribedCell_ setTitle:@"Show All Changes"];
6047 [subscribedCell_ setControl:subscribedSwitch_];
6048
6049 ignoredCell_ = [[UIPreferencesControlTableCell alloc] init];
6050 [ignoredCell_ setShowSelection:NO];
6051 [ignoredCell_ setTitle:@"Ignore Upgrades"];
6052 [ignoredCell_ setControl:ignoredSwitch_];
6053
6054 [table_ setDataSource:self];
6055 [self reloadData];
6056 } return self;
6057 }
6058
6059 - (void) resetViewAnimated:(BOOL)animated {
6060 [table_ resetViewAnimated:animated];
6061 }
6062
6063 - (void) reloadData {
6064 if (package_ != nil)
6065 [package_ autorelease];
6066 package_ = [database_ packageWithName:name_];
6067 if (package_ != nil) {
6068 [package_ retain];
6069 [subscribedSwitch_ setValue:([package_ subscribed] ? 1 : 0) animated:NO];
6070 [ignoredSwitch_ setValue:([package_ ignored] ? 1 : 0) animated:NO];
6071 }
6072
6073 [table_ reloadData];
6074 }
6075
6076 - (NSString *) title {
6077 return @"Settings";
6078 }
6079
6080 @end
6081
6082 /* Signature View {{{ */
6083 @interface SignatureView : BrowserView {
6084 _transient Database *database_;
6085 NSString *package_;
6086 }
6087
6088 - (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package;
6089
6090 @end
6091
6092 @implementation SignatureView
6093
6094 - (void) dealloc {
6095 [package_ release];
6096 [super dealloc];
6097 }
6098
6099 - (void) webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
6100 // XXX: dude!
6101 [super webView:sender didClearWindowObject:window forFrame:frame];
6102 }
6103
6104 - (id) initWithBook:(RVBook *)book database:(Database *)database package:(NSString *)package {
6105 if ((self = [super initWithBook:book]) != nil) {
6106 database_ = database;
6107 package_ = [package retain];
6108 [self reloadData];
6109 } return self;
6110 }
6111
6112 - (void) resetViewAnimated:(BOOL)animated {
6113 }
6114
6115 - (void) reloadData {
6116 [self loadURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"signature" ofType:@"html"]]];
6117 }
6118
6119 @end
6120 /* }}} */
6121
6122 @interface Cydia : UIApplication <
6123 ConfirmationViewDelegate,
6124 ProgressViewDelegate,
6125 SearchViewDelegate,
6126 CydiaDelegate
6127 > {
6128 UIWindow *window_;
6129
6130 UIView *underlay_;
6131 UIView *overlay_;
6132 CYBook *book_;
6133 UIToolbar *buttonbar_;
6134
6135 RVBook *confirm_;
6136
6137 NSMutableArray *essential_;
6138 NSMutableArray *broken_;
6139
6140 Database *database_;
6141 ProgressView *progress_;
6142
6143 unsigned tag_;
6144
6145 UIKeyboard *keyboard_;
6146 UIProgressHUD *hud_;
6147
6148 SectionsView *sections_;
6149 ChangesView *changes_;
6150 ManageView *manage_;
6151 SearchView *search_;
6152 }
6153
6154 @end
6155
6156 @implementation Cydia
6157
6158 - (void) _loaded {
6159 if ([broken_ count] != 0) {
6160 int count = [broken_ count];
6161
6162 UIActionSheet *sheet = [[[UIActionSheet alloc]
6163 initWithTitle:[NSString stringWithFormat:@"%d Half-Installed Package%@", count, (count == 1 ? @"" : @"s")]
6164 buttons:[NSArray arrayWithObjects:
6165 @"Forcibly Clear",
6166 @"Ignore (Temporary)",
6167 nil]
6168 defaultButtonIndex:0
6169 delegate:self
6170 context:@"fixhalf"
6171 ] autorelease];
6172
6173 [sheet setBodyText:@"When the shell scripts associated with packages fail, they are left in a bad state known as either half-configured or half-installed. These errors don't go away and instead continue to cause issues. These scripts can be deleted and the packages forcibly removed."];
6174 [sheet popupAlertAnimated:YES];
6175 } else if (!Ignored_ && [essential_ count] != 0) {
6176 int count = [essential_ count];
6177
6178 UIActionSheet *sheet = [[[UIActionSheet alloc]
6179 initWithTitle:[NSString stringWithFormat:@"%d Essential Upgrade%@", count, (count == 1 ? @"" : @"s")]
6180 buttons:[NSArray arrayWithObjects:
6181 @"Upgrade Essential",
6182 @"Complete Upgrade",
6183 @"Ignore (Temporary)",
6184 nil]
6185 defaultButtonIndex:0
6186 delegate:self
6187 context:@"upgrade"
6188 ] autorelease];
6189
6190 [sheet setBodyText:@"One or more essential packages are currently out of date. If these upgrades are not performed you are likely to encounter errors."];
6191 [sheet popupAlertAnimated:YES];
6192 }
6193 }
6194
6195 - (void) _reloadData {
6196 /*UIProgressHUD *hud = [[UIProgressHUD alloc] initWithWindow:window_];
6197 [hud setText:@"Reloading Data"];
6198 [overlay_ addSubview:hud];
6199 [hud show:YES];*/
6200
6201 [database_ reloadData];
6202
6203 size_t changes(0);
6204
6205 [essential_ removeAllObjects];
6206 [broken_ removeAllObjects];
6207
6208 NSArray *packages = [database_ packages];
6209 for (Package *package in packages) {
6210 if ([package half])
6211 [broken_ addObject:package];
6212 if ([package upgradableAndEssential:NO]) {
6213 if ([package essential])
6214 [essential_ addObject:package];
6215 ++changes;
6216 }
6217 }
6218
6219 if (changes != 0) {
6220 NSString *badge([[NSNumber numberWithInt:changes] stringValue]);
6221 [buttonbar_ setBadgeValue:badge forButton:3];
6222 if ([buttonbar_ respondsToSelector:@selector(setBadgeAnimated:forButton:)])
6223 [buttonbar_ setBadgeAnimated:YES forButton:3];
6224 [self setApplicationBadge:badge];
6225 } else {
6226 [buttonbar_ setBadgeValue:nil forButton:3];
6227 if ([buttonbar_ respondsToSelector:@selector(setBadgeAnimated:forButton:)])
6228 [buttonbar_ setBadgeAnimated:NO forButton:3];
6229 [self removeApplicationBadge];
6230 }
6231
6232 [self updateData];
6233
6234 // XXX: what is this line of code for?
6235 if ([packages count] == 0);
6236 else if (Loaded_) loaded:
6237 [self _loaded];
6238 else {
6239 Loaded_ = YES;
6240
6241 if (NSDate *update = [Metadata_ objectForKey:@"LastUpdate"]) {
6242 NSTimeInterval interval([update timeIntervalSinceNow]);
6243 if (interval <= 0 && interval > -600)
6244 goto loaded;
6245 }
6246
6247 [book_ update];
6248 }
6249
6250 /*[hud show:NO];
6251 [hud removeFromSuperview];*/
6252 }
6253
6254 - (void) _saveConfig {
6255 if (Changed_) {
6256 _trace();
6257 _assert([Metadata_ writeToFile:@"/var/lib/cydia/metadata.plist" atomically:YES] == YES);
6258 _trace();
6259 Changed_ = false;
6260 }
6261 }
6262
6263 - (void) updateData {
6264 [self _saveConfig];
6265
6266 /* XXX: this is just stupid */
6267 if (tag_ != 2 && sections_ != nil)
6268 [sections_ reloadData];
6269 if (tag_ != 3 && changes_ != nil)
6270 [changes_ reloadData];
6271 if (tag_ != 5 && search_ != nil)
6272 [search_ reloadData];
6273
6274 [book_ reloadData];
6275 }
6276
6277 - (void) update_ {
6278 [database_ update];
6279 }
6280
6281 - (void) syncData {
6282 FILE *file = fopen("/etc/apt/sources.list.d/cydia.list", "w");
6283 _assert(file != NULL);
6284
6285 NSArray *keys = [Sources_ allKeys];
6286
6287 for (int i(0), e([keys count]); i != e; ++i) {
6288 NSString *key = [keys objectAtIndex:i];
6289 NSDictionary *source = [Sources_ objectForKey:key];
6290
6291 fprintf(file, "%s %s %s\n",
6292 [[source objectForKey:@"Type"] UTF8String],
6293 [[source objectForKey:@"URI"] UTF8String],
6294 [[source objectForKey:@"Distribution"] UTF8String]
6295 );
6296 }
6297
6298 fclose(file);
6299
6300 [self _saveConfig];
6301
6302 [progress_
6303 detachNewThreadSelector:@selector(update_)
6304 toTarget:self
6305 withObject:nil
6306 title:@"Updating Sources"
6307 ];
6308 }
6309
6310 - (void) reloadData {
6311 @synchronized (self) {
6312 if (confirm_ == nil)
6313 [self _reloadData];
6314 }
6315 }
6316
6317 - (void) resolve {
6318 pkgProblemResolver *resolver = [database_ resolver];
6319
6320 resolver->InstallProtect();
6321 if (!resolver->Resolve(true))
6322 _error->Discard();
6323 }
6324
6325 - (void) popUpBook:(RVBook *)book {
6326 [underlay_ popSubview:book];
6327 }
6328
6329 - (CGRect) popUpBounds {
6330 return [underlay_ bounds];
6331 }
6332
6333 - (void) perform {
6334 [database_ prepare];
6335
6336 confirm_ = [[RVBook alloc] initWithFrame:[self popUpBounds]];
6337 [confirm_ setDelegate:self];
6338
6339 ConfirmationView *page([[[ConfirmationView alloc] initWithBook:confirm_ database:database_] autorelease]);
6340 [page setDelegate:self];
6341
6342 [confirm_ setPage:page];
6343 [self popUpBook:confirm_];
6344 }
6345
6346 - (void) installPackage:(Package *)package {
6347 @synchronized (self) {
6348 [package install];
6349 [self resolve];
6350 [self perform];
6351 }
6352 }
6353
6354 - (void) removePackage:(Package *)package {
6355 @synchronized (self) {
6356 [package remove];
6357 [self resolve];
6358 [self perform];
6359 }
6360 }
6361
6362 - (void) distUpgrade {
6363 @synchronized (self) {
6364 [database_ upgrade];
6365 [self perform];
6366 }
6367 }
6368
6369 - (void) cancel {
6370 @synchronized (self) {
6371 [self _reloadData];
6372 if (confirm_ != nil) {
6373 [confirm_ release];
6374 confirm_ = nil;
6375 }
6376 }
6377 }
6378
6379 - (void) confirm {
6380 [overlay_ removeFromSuperview];
6381 reload_ = true;
6382
6383 [progress_
6384 detachNewThreadSelector:@selector(perform)
6385 toTarget:database_
6386 withObject:nil
6387 title:@"Running"
6388 ];
6389 }
6390
6391 - (void) bootstrap_ {
6392 [database_ update];
6393 [database_ upgrade];
6394 [database_ prepare];
6395 [database_ perform];
6396 }
6397
6398 - (void) bootstrap {
6399 [progress_
6400 detachNewThreadSelector:@selector(bootstrap_)
6401 toTarget:self
6402 withObject:nil
6403 title:@"Bootstrap Install"
6404 ];
6405 }
6406
6407 - (void) progressViewIsComplete:(ProgressView *)progress {
6408 if (confirm_ != nil) {
6409 [underlay_ addSubview:overlay_];
6410 [confirm_ popFromSuperviewAnimated:NO];
6411 }
6412
6413 [self cancel];
6414 }
6415
6416 - (void) setPage:(RVPage *)page {
6417 [page resetViewAnimated:NO];
6418 [page setDelegate:self];
6419 [book_ setPage:page];
6420 }
6421
6422 - (RVPage *) _pageForURL:(NSURL *)url withClass:(Class)_class {
6423 BrowserView *browser = [[[_class alloc] initWithBook:book_] autorelease];
6424 [browser loadURL:url];
6425 return browser;
6426 }
6427
6428 - (void) _setHomePage {
6429 [self setPage:[self _pageForURL:[NSURL URLWithString:@"http://cydia.saurik.com/"] withClass:[HomeView class]]];
6430 }
6431
6432 - (void) buttonBarItemTapped:(id)sender {
6433 unsigned tag = [sender tag];
6434 if (tag == tag_) {
6435 [book_ resetViewAnimated:YES];
6436 return;
6437 } else if (tag_ == 2 && tag != 2)
6438 [sections_ resetView];
6439
6440 switch (tag) {
6441 case 1: [self _setHomePage]; break;
6442
6443 case 2: [self setPage:sections_]; break;
6444 case 3: [self setPage:changes_]; break;
6445 case 4: [self setPage:manage_]; break;
6446 case 5: [self setPage:search_]; break;
6447
6448 default: _assert(false);
6449 }
6450
6451 tag_ = tag;
6452 }
6453
6454 - (void) applicationWillSuspend {
6455 [database_ clean];
6456 [super applicationWillSuspend];
6457 }
6458
6459 - (void) askForSettings {
6460 UIActionSheet *role = [[[UIActionSheet alloc]
6461 initWithTitle:@"Who Are You?"
6462 buttons:[NSArray arrayWithObjects:
6463 @"User (Graphical Only)",
6464 @"Hacker (+ Command Line)",
6465 @"Developer (No Filters)",
6466 nil]
6467 defaultButtonIndex:-1
6468 delegate:self
6469 context:@"role"
6470 ] autorelease];
6471
6472 [role setBodyText:@"Not all of the packages available via Cydia are designed to be used by all users. Please categorize yourself so that Cydia can apply helpful filters.\n\nThis choice can be changed from \"Settings\" under the \"Manage\" tab."];
6473 [role popupAlertAnimated:YES];
6474 }
6475
6476 - (void) finish {
6477 if (hud_ != nil) {
6478 [self setStatusBarShowsProgress:NO];
6479
6480 [hud_ show:NO];
6481 [hud_ removeFromSuperview];
6482 [hud_ autorelease];
6483 hud_ = nil;
6484
6485 pid_t pid = ExecFork();
6486 if (pid == 0) {
6487 execlp("launchctl", "launchctl", "stop", "com.apple.SpringBoard", NULL);
6488 perror("launchctl stop");
6489 }
6490
6491 return;
6492 }
6493
6494 if (Role_ == nil) {
6495 [self askForSettings];
6496 return;
6497 }
6498
6499 overlay_ = [[UIView alloc] initWithFrame:[underlay_ bounds]];
6500
6501 CGRect screenrect = [UIHardware fullScreenApplicationContentRect];
6502 book_ = [[CYBook alloc] initWithFrame:CGRectMake(
6503 0, 0, screenrect.size.width, screenrect.size.height - 48
6504 ) database:database_];
6505
6506 [book_ setDelegate:self];
6507
6508 [overlay_ addSubview:book_];
6509
6510 NSArray *buttonitems = [NSArray arrayWithObjects:
6511 [NSDictionary dictionaryWithObjectsAndKeys:
6512 @"buttonBarItemTapped:", kUIButtonBarButtonAction,
6513 @"home-up.png", kUIButtonBarButtonInfo,
6514 @"home-dn.png", kUIButtonBarButtonSelectedInfo,
6515 [NSNumber numberWithInt:1], kUIButtonBarButtonTag,
6516 self, kUIButtonBarButtonTarget,
6517 @"Home", kUIButtonBarButtonTitle,
6518 @"0", kUIButtonBarButtonType,
6519 nil],
6520
6521 [NSDictionary dictionaryWithObjectsAndKeys:
6522 @"buttonBarItemTapped:", kUIButtonBarButtonAction,
6523 @"install-up.png", kUIButtonBarButtonInfo,
6524 @"install-dn.png", kUIButtonBarButtonSelectedInfo,
6525 [NSNumber numberWithInt:2], kUIButtonBarButtonTag,
6526 self, kUIButtonBarButtonTarget,
6527 @"Sections", kUIButtonBarButtonTitle,
6528 @"0", kUIButtonBarButtonType,
6529 nil],
6530
6531 [NSDictionary dictionaryWithObjectsAndKeys:
6532 @"buttonBarItemTapped:", kUIButtonBarButtonAction,
6533 @"changes-up.png", kUIButtonBarButtonInfo,
6534 @"changes-dn.png", kUIButtonBarButtonSelectedInfo,
6535 [NSNumber numberWithInt:3], kUIButtonBarButtonTag,
6536 self, kUIButtonBarButtonTarget,
6537 @"Changes", kUIButtonBarButtonTitle,
6538 @"0", kUIButtonBarButtonType,
6539 nil],
6540
6541 [NSDictionary dictionaryWithObjectsAndKeys:
6542 @"buttonBarItemTapped:", kUIButtonBarButtonAction,
6543 @"manage-up.png", kUIButtonBarButtonInfo,
6544 @"manage-dn.png", kUIButtonBarButtonSelectedInfo,
6545 [NSNumber numberWithInt:4], kUIButtonBarButtonTag,
6546 self, kUIButtonBarButtonTarget,
6547 @"Manage", kUIButtonBarButtonTitle,
6548 @"0", kUIButtonBarButtonType,
6549 nil],
6550
6551 [NSDictionary dictionaryWithObjectsAndKeys:
6552 @"buttonBarItemTapped:", kUIButtonBarButtonAction,
6553 @"search-up.png", kUIButtonBarButtonInfo,
6554 @"search-dn.png", kUIButtonBarButtonSelectedInfo,
6555 [NSNumber numberWithInt:5], kUIButtonBarButtonTag,
6556 self, kUIButtonBarButtonTarget,
6557 @"Search", kUIButtonBarButtonTitle,
6558 @"0", kUIButtonBarButtonType,
6559 nil],
6560 nil];
6561
6562 buttonbar_ = [[UIToolbar alloc]
6563 initInView:overlay_
6564 withFrame:CGRectMake(
6565 0, screenrect.size.height - ButtonBarHeight_,
6566 screenrect.size.width, ButtonBarHeight_
6567 )
6568 withItemList:buttonitems
6569 ];
6570
6571 [buttonbar_ setDelegate:self];
6572 [buttonbar_ setBarStyle:1];
6573 [buttonbar_ setButtonBarTrackingMode:2];
6574
6575 int buttons[5] = {1, 2, 3, 4, 5};
6576 [buttonbar_ registerButtonGroup:0 withButtons:buttons withCount:5];
6577 [buttonbar_ showButtonGroup:0 withDuration:0];
6578
6579 for (int i = 0; i != 5; ++i)
6580 [[buttonbar_ viewWithTag:(i + 1)] setFrame:CGRectMake(
6581 i * 64 + 2, 1, 60, ButtonBarHeight_
6582 )];
6583
6584 [buttonbar_ showSelectionForButton:1];
6585 [overlay_ addSubview:buttonbar_];
6586
6587 [UIKeyboard initImplementationNow];
6588 CGSize keysize = [UIKeyboard defaultSize];
6589 CGRect keyrect = {{0, [overlay_ bounds].size.height}, keysize};
6590 keyboard_ = [[UIKeyboard alloc] initWithFrame:keyrect];
6591 //[[UIKeyboardImpl sharedInstance] setSoundsEnabled:(Sounds_Keyboard_ ? YES : NO)];
6592 [overlay_ addSubview:keyboard_];
6593
6594 if (!bootstrap_)
6595 [underlay_ addSubview:overlay_];
6596
6597 [self reloadData];
6598
6599 sections_ = [[SectionsView alloc] initWithBook:book_ database:database_];
6600 changes_ = [[ChangesView alloc] initWithBook:book_ database:database_];
6601 search_ = [[SearchView alloc] initWithBook:book_ database:database_];
6602
6603 manage_ = (ManageView *) [[self
6604 _pageForURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"manage" ofType:@"html"]]
6605 withClass:[ManageView class]
6606 ] retain];
6607
6608 if (bootstrap_)
6609 [self bootstrap];
6610 else
6611 [self _setHomePage];
6612 }
6613
6614 - (void) alertSheet:(UIActionSheet *)sheet buttonClicked:(int)button {
6615 NSString *context([sheet context]);
6616
6617 if ([context isEqualToString:@"missing"])
6618 [sheet dismiss];
6619 else if ([context isEqualToString:@"fixhalf"]) {
6620 switch (button) {
6621 case 1:
6622 @synchronized (self) {
6623 for (int i = 0, e = [broken_ count]; i != e; ++i) {
6624 Package *broken = [broken_ objectAtIndex:i];
6625 [broken remove];
6626
6627 NSString *id = [broken id];
6628 unlink([[NSString stringWithFormat:@"/var/lib/dpkg/info/%@.prerm", id] UTF8String]);
6629 unlink([[NSString stringWithFormat:@"/var/lib/dpkg/info/%@.postrm", id] UTF8String]);
6630 unlink([[NSString stringWithFormat:@"/var/lib/dpkg/info/%@.preinst", id] UTF8String]);
6631 unlink([[NSString stringWithFormat:@"/var/lib/dpkg/info/%@.postinst", id] UTF8String]);
6632 }
6633
6634 [self resolve];
6635 [self perform];
6636 }
6637 break;
6638
6639 case 2:
6640 [broken_ removeAllObjects];
6641 [self _loaded];
6642 break;
6643
6644 default:
6645 _assert(false);
6646 }
6647
6648 [sheet dismiss];
6649 } else if ([context isEqualToString:@"role"]) {
6650 switch (button) {
6651 case 1: Role_ = @"User"; break;
6652 case 2: Role_ = @"Hacker"; break;
6653 case 3: Role_ = @"Developer"; break;
6654
6655 default:
6656 Role_ = nil;
6657 _assert(false);
6658 }
6659
6660 bool reset = Settings_ != nil;
6661
6662 Settings_ = [NSMutableDictionary dictionaryWithObjectsAndKeys:
6663 Role_, @"Role",
6664 nil];
6665
6666 [Metadata_ setObject:Settings_ forKey:@"Settings"];
6667
6668 Changed_ = true;
6669
6670 if (reset)
6671 [self updateData];
6672 else
6673 [self finish];
6674
6675 [sheet dismiss];
6676 } else if ([context isEqualToString:@"upgrade"]) {
6677 switch (button) {
6678 case 1:
6679 @synchronized (self) {
6680 for (int i = 0, e = [essential_ count]; i != e; ++i) {
6681 Package *essential = [essential_ objectAtIndex:i];
6682 [essential install];
6683 }
6684
6685 [self resolve];
6686 [self perform];
6687 }
6688 break;
6689
6690 case 2:
6691 [self distUpgrade];
6692 break;
6693
6694 case 3:
6695 Ignored_ = YES;
6696 break;
6697
6698 default:
6699 _assert(false);
6700 }
6701
6702 [sheet dismiss];
6703 }
6704 }
6705
6706 - (void) reorganize { _pooled
6707 system("/usr/libexec/cydia/free.sh");
6708 [self performSelectorOnMainThread:@selector(finish) withObject:nil waitUntilDone:NO];
6709 }
6710
6711 - (void) applicationSuspend:(__GSEvent *)event {
6712 if (hud_ == nil && ![progress_ isRunning])
6713 [super applicationSuspend:event];
6714 }
6715
6716 - (void) _animateSuspension:(BOOL)arg0 duration:(double)arg1 startTime:(double)arg2 scale:(float)arg3 {
6717 if (hud_ == nil)
6718 [super _animateSuspension:arg0 duration:arg1 startTime:arg2 scale:arg3];
6719 }
6720
6721 - (void) _setSuspended:(BOOL)value {
6722 if (hud_ == nil)
6723 [super _setSuspended:value];
6724 }
6725
6726 - (UIProgressHUD *) addProgressHUD {
6727 UIProgressHUD *hud = [[UIProgressHUD alloc] initWithWindow:window_];
6728 [hud show:YES];
6729 [underlay_ addSubview:hud];
6730 return hud;
6731 }
6732
6733 - (void) openMailToURL:(NSURL *)url {
6734 // XXX: this makes me sad
6735 #if 0
6736 [[[MailToView alloc] initWithView:underlay_ delegate:self url:url] autorelease];
6737 #else
6738 [UIApp openURL:url];// asPanel:YES];
6739 #endif
6740 }
6741
6742 - (void) clearFirstResponder {
6743 if (id responder = [window_ firstResponder])
6744 [responder resignFirstResponder];
6745 }
6746
6747 - (RVPage *) pageForPackage:(NSString *)name {
6748 if (Package *package = [database_ packageWithName:name]) {
6749 PackageView *view = [[[PackageView alloc] initWithBook:book_ database:database_] autorelease];
6750 [view setPackage:package];
6751 return view;
6752 } else {
6753 UIActionSheet *sheet = [[[UIActionSheet alloc]
6754 initWithTitle:@"Cannot Locate Package"
6755 buttons:[NSArray arrayWithObjects:@"Close", nil]
6756 defaultButtonIndex:0
6757 delegate:self
6758 context:@"missing"
6759 ] autorelease];
6760
6761 [sheet setBodyText:[NSString stringWithFormat:
6762 @"The package %@ cannot be found in your current sources. I might recommend installing more sources."
6763 , name]];
6764
6765 [sheet popupAlertAnimated:YES];
6766 return nil;
6767 }
6768 }
6769
6770 - (RVPage *) pageForURL:(NSURL *)url hasTag:(int *)tag {
6771 if (tag != NULL)
6772 tag = 0;
6773
6774 NSString *scheme([[url scheme] lowercaseString]);
6775 if (![scheme isEqualToString:@"cydia"])
6776 return nil;
6777 NSString *path([url absoluteString]);
6778 if ([path length] < 8)
6779 return nil;
6780 path = [path substringFromIndex:8];
6781 if (![path hasPrefix:@"/"])
6782 path = [@"/" stringByAppendingString:path];
6783
6784 if ([path isEqualToString:@"/add-source"])
6785 return [[[AddSourceView alloc] initWithBook:book_ database:database_] autorelease];
6786 else if ([path isEqualToString:@"/storage"])
6787 return [self _pageForURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"storage" ofType:@"html"]] withClass:[BrowserView class]];
6788 else if ([path isEqualToString:@"/sources"])
6789 return [[[SourceTable alloc] initWithBook:book_ database:database_] autorelease];
6790 else if ([path isEqualToString:@"/packages"])
6791 return [[[InstalledView alloc] initWithBook:book_ database:database_] autorelease];
6792 else if ([path hasPrefix:@"/url/"])
6793 return [self _pageForURL:[NSURL URLWithString:[path substringFromIndex:5]] withClass:[BrowserView class]];
6794 else if ([path hasPrefix:@"/launch/"])
6795 [self launchApplicationWithIdentifier:[path substringFromIndex:8] suspended:NO];
6796 else if ([path hasPrefix:@"/package-settings/"])
6797 return [[[SettingsView alloc] initWithBook:book_ database:database_ package:[path substringFromIndex:18]] autorelease];
6798 else if ([path hasPrefix:@"/package-signature/"])
6799 return [[[SignatureView alloc] initWithBook:book_ database:database_ package:[path substringFromIndex:19]] autorelease];
6800 else if ([path hasPrefix:@"/package/"])
6801 return [self pageForPackage:[path substringFromIndex:9]];
6802 else if ([path hasPrefix:@"/files/"]) {
6803 NSString *name = [path substringFromIndex:7];
6804
6805 if (Package *package = [database_ packageWithName:name]) {
6806 FileTable *files = [[[FileTable alloc] initWithBook:book_ database:database_] autorelease];
6807 [files setPackage:package];
6808 return files;
6809 }
6810 }
6811
6812 return nil;
6813 }
6814
6815 - (void) applicationOpenURL:(NSURL *)url {
6816 [super applicationOpenURL:url];
6817 int tag;
6818 if (RVPage *page = [self pageForURL:url hasTag:&tag]) {
6819 [self setPage:page];
6820 [buttonbar_ showSelectionForButton:tag];
6821 tag_ = tag;
6822 }
6823 }
6824
6825 - (void) applicationDidFinishLaunching:(id)unused {
6826 Font12_ = [[UIFont systemFontOfSize:12] retain];
6827 Font12Bold_ = [[UIFont boldSystemFontOfSize:12] retain];
6828 Font14_ = [[UIFont systemFontOfSize:14] retain];
6829 Font18Bold_ = [[UIFont boldSystemFontOfSize:18] retain];
6830 Font22Bold_ = [[UIFont boldSystemFontOfSize:22] retain];
6831
6832 _assert(pkgInitConfig(*_config));
6833 _assert(pkgInitSystem(*_config, _system));
6834
6835 tag_ = 1;
6836
6837 essential_ = [[NSMutableArray alloc] initWithCapacity:4];
6838 broken_ = [[NSMutableArray alloc] initWithCapacity:4];
6839
6840 [NSURLProtocol registerClass:[CydiaURLProtocol class]];
6841
6842 CGRect screenrect = [UIHardware fullScreenApplicationContentRect];
6843 window_ = [[UIWindow alloc] initWithContentRect:screenrect];
6844
6845 [window_ orderFront:self];
6846 [window_ makeKey:self];
6847 [window_ setHidden:NO];
6848
6849 database_ = [Database sharedInstance];
6850 progress_ = [[ProgressView alloc] initWithFrame:[window_ bounds] database:database_ delegate:self];
6851 [database_ setDelegate:progress_];
6852 [window_ setContentView:progress_];
6853
6854 underlay_ = [[UIView alloc] initWithFrame:[progress_ bounds]];
6855 [progress_ setContentView:underlay_];
6856
6857 [progress_ resetView];
6858
6859 if (
6860 readlink("/Applications", NULL, 0) == -1 && errno == EINVAL ||
6861 readlink("/Library/Ringtones", NULL, 0) == -1 && errno == EINVAL ||
6862 readlink("/Library/Wallpaper", NULL, 0) == -1 && errno == EINVAL ||
6863 readlink("/usr/include", NULL, 0) == -1 && errno == EINVAL ||
6864 readlink("/usr/libexec", NULL, 0) == -1 && errno == EINVAL ||
6865 readlink("/usr/share", NULL, 0) == -1 && errno == EINVAL /*||
6866 readlink("/var/lib", NULL, 0) == -1 && errno == EINVAL*/
6867 ) {
6868 [self setIdleTimerDisabled:YES];
6869
6870 hud_ = [self addProgressHUD];
6871 [hud_ setText:@"Reorganizing\n\nWill Automatically\nClose When Done"];
6872
6873 [self setStatusBarShowsProgress:YES];
6874
6875 [NSThread
6876 detachNewThreadSelector:@selector(reorganize)
6877 toTarget:self
6878 withObject:nil
6879 ];
6880 } else
6881 [self finish];
6882 }
6883
6884 - (void) showKeyboard:(BOOL)show {
6885 CGSize keysize = [UIKeyboard defaultSize];
6886 CGRect keydown = {{0, [overlay_ bounds].size.height}, keysize};
6887 CGRect keyup = keydown;
6888 keyup.origin.y -= keysize.height;
6889
6890 UIFrameAnimation *animation = [[[UIFrameAnimation alloc] initWithTarget:keyboard_] autorelease];
6891 [animation setSignificantRectFields:2];
6892
6893 if (show) {
6894 [animation setStartFrame:keydown];
6895 [animation setEndFrame:keyup];
6896 [keyboard_ activate];
6897 } else {
6898 [animation setStartFrame:keyup];
6899 [animation setEndFrame:keydown];
6900 [keyboard_ deactivate];
6901 }
6902
6903 [[UIAnimator sharedAnimator]
6904 addAnimations:[NSArray arrayWithObjects:animation, nil]
6905 withDuration:KeyboardTime_
6906 start:YES
6907 ];
6908 }
6909
6910 - (void) slideUp:(UIActionSheet *)alert {
6911 if (Advanced_)
6912 [alert presentSheetFromButtonBar:buttonbar_];
6913 else
6914 [alert presentSheetInView:overlay_];
6915 }
6916
6917 @end
6918
6919 void AddPreferences(NSString *plist) { _pooled
6920 NSMutableDictionary *settings = [[[NSMutableDictionary alloc] initWithContentsOfFile:plist] autorelease];
6921 _assert(settings != NULL);
6922 NSMutableArray *items = [settings objectForKey:@"items"];
6923
6924 bool cydia(false);
6925
6926 for (size_t i(0); i != [items count]; ++i) {
6927 NSMutableDictionary *item([items objectAtIndex:i]);
6928 NSString *label = [item objectForKey:@"label"];
6929 if (label != nil && [label isEqualToString:@"Cydia"]) {
6930 cydia = true;
6931 break;
6932 }
6933 }
6934
6935 if (!cydia) {
6936 for (size_t i(0); i != [items count]; ++i) {
6937 NSDictionary *item([items objectAtIndex:i]);
6938 NSString *label = [item objectForKey:@"label"];
6939 if (label != nil && [label isEqualToString:@"General"]) {
6940 [items insertObject:[NSDictionary dictionaryWithObjectsAndKeys:
6941 @"CydiaSettings", @"bundle",
6942 @"PSLinkCell", @"cell",
6943 [NSNumber numberWithBool:YES], @"hasIcon",
6944 [NSNumber numberWithBool:YES], @"isController",
6945 @"Cydia", @"label",
6946 nil] atIndex:(i + 1)];
6947
6948 break;
6949 }
6950 }
6951
6952 _assert([settings writeToFile:plist atomically:YES] == YES);
6953 }
6954 }
6955
6956 /*IMP alloc_;
6957 id Alloc_(id self, SEL selector) {
6958 id object = alloc_(self, selector);
6959 lprintf("[%s]A-%p\n", self->isa->name, object);
6960 return object;
6961 }*/
6962
6963 /*IMP dealloc_;
6964 id Dealloc_(id self, SEL selector) {
6965 id object = dealloc_(self, selector);
6966 lprintf("[%s]D-%p\n", self->isa->name, object);
6967 return object;
6968 }*/
6969
6970 int main(int argc, char *argv[]) { _pooled
6971 _trace();
6972 class_addMethod(objc_getClass("DOMNodeList"), @selector(countByEnumeratingWithState:objects:count:), (IMP) &DOMNodeList$countByEnumeratingWithState$objects$count$, "I20@0:4^{NSFastEnumerationState}8^@12I16");
6973
6974 bool substrate(false);
6975
6976 if (argc != 0) {
6977 char **args(argv);
6978 int arge(1);
6979
6980 for (int argi(1); argi != argc; ++argi)
6981 if (strcmp(argv[argi], "--") == 0) {
6982 arge = argi;
6983 argv[argi] = argv[0];
6984 argv += argi;
6985 argc -= argi;
6986 break;
6987 }
6988
6989 for (int argi(1); argi != arge; ++argi)
6990 if (strcmp(args[argi], "--bootstrap") == 0)
6991 bootstrap_ = true;
6992 else if (strcmp(args[argi], "--substrate") == 0)
6993 substrate = true;
6994 else
6995 fprintf(stderr, "unknown argument: %s\n", args[argi]);
6996 }
6997
6998 App_ = [[NSBundle mainBundle] bundlePath];
6999 Home_ = NSHomeDirectory();
7000 Locale_ = CFLocaleCopyCurrent();
7001
7002 {
7003 NSString *plist = [Home_ stringByAppendingString:@"/Library/Preferences/com.apple.preferences.sounds.plist"];
7004 if (NSDictionary *sounds = [NSDictionary dictionaryWithContentsOfFile:plist])
7005 if (NSNumber *keyboard = [sounds objectForKey:@"keyboard"])
7006 Sounds_Keyboard_ = [keyboard boolValue];
7007 }
7008
7009 setuid(0);
7010 setgid(0);
7011
7012 #if 1 /* XXX: this costs 1.4s of startup performance */
7013 if (unlink("/var/cache/apt/pkgcache.bin") == -1)
7014 _assert(errno == ENOENT);
7015 if (unlink("/var/cache/apt/srcpkgcache.bin") == -1)
7016 _assert(errno == ENOENT);
7017 #endif
7018
7019 /*Method alloc = class_getClassMethod([NSObject class], @selector(alloc));
7020 alloc_ = alloc->method_imp;
7021 alloc->method_imp = (IMP) &Alloc_;*/
7022
7023 /*Method dealloc = class_getClassMethod([NSObject class], @selector(dealloc));
7024 dealloc_ = dealloc->method_imp;
7025 dealloc->method_imp = (IMP) &Dealloc_;*/
7026
7027 size_t size;
7028
7029 int maxproc;
7030 size = sizeof(maxproc);
7031 if (sysctlbyname("kern.maxproc", &maxproc, &size, NULL, 0) == -1)
7032 perror("sysctlbyname(\"kern.maxproc\", ?)");
7033 else if (maxproc < 64) {
7034 maxproc = 64;
7035 if (sysctlbyname("kern.maxproc", NULL, NULL, &maxproc, sizeof(maxproc)) == -1)
7036 perror("sysctlbyname(\"kern.maxproc\", #)");
7037 }
7038
7039 sysctlbyname("hw.machine", NULL, &size, NULL, 0);
7040 char *machine = new char[size];
7041 if (sysctlbyname("hw.machine", machine, &size, NULL, 0) == -1)
7042 perror("sysctlbyname(\"hw.machine\", ?)");
7043 else
7044 Machine_ = machine;
7045
7046 UniqueID_ = [[UIDevice currentDevice] uniqueIdentifier];
7047
7048 if (NSDictionary *system = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"])
7049 Build_ = [system objectForKey:@"ProductBuildVersion"];
7050
7051 /*AddPreferences(@"/Applications/Preferences.app/Settings-iPhone.plist");
7052 AddPreferences(@"/Applications/Preferences.app/Settings-iPod.plist");*/
7053
7054 _trace();
7055 Metadata_ = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/lib/cydia/metadata.plist"];
7056 _trace();
7057
7058 if (Metadata_ == NULL)
7059 Metadata_ = [[NSMutableDictionary alloc] initWithCapacity:2];
7060 else {
7061 Settings_ = [Metadata_ objectForKey:@"Settings"];
7062
7063 Packages_ = [Metadata_ objectForKey:@"Packages"];
7064 Sections_ = [Metadata_ objectForKey:@"Sections"];
7065 Sources_ = [Metadata_ objectForKey:@"Sources"];
7066 }
7067
7068 if (Settings_ != nil)
7069 Role_ = [Settings_ objectForKey:@"Role"];
7070
7071 if (Packages_ == nil) {
7072 Packages_ = [[[NSMutableDictionary alloc] initWithCapacity:128] autorelease];
7073 [Metadata_ setObject:Packages_ forKey:@"Packages"];
7074 }
7075
7076 if (Sections_ == nil) {
7077 Sections_ = [[[NSMutableDictionary alloc] initWithCapacity:32] autorelease];
7078 [Metadata_ setObject:Sections_ forKey:@"Sections"];
7079 }
7080
7081 if (Sources_ == nil) {
7082 Sources_ = [[[NSMutableDictionary alloc] initWithCapacity:0] autorelease];
7083 [Metadata_ setObject:Sources_ forKey:@"Sources"];
7084 }
7085
7086 #if RecycleWebViews
7087 Documents_ = [[[NSMutableArray alloc] initWithCapacity:4] autorelease];
7088 #endif
7089
7090 if (substrate && access("/Applications/WinterBoard.app/WinterBoard.dylib", F_OK) == 0)
7091 dlopen("/Applications/WinterBoard.app/WinterBoard.dylib", RTLD_LAZY | RTLD_GLOBAL);
7092 /*if (substrate && access("/Library/MobileSubstrate/MobileSubstrate.dylib", F_OK) == 0)
7093 dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib", RTLD_LAZY | RTLD_GLOBAL);*/
7094
7095 if (access("/User", F_OK) != 0) {
7096 _trace();
7097 system("/usr/libexec/cydia/firmware.sh");
7098 _trace();
7099 }
7100
7101 _assert([[NSFileManager defaultManager]
7102 createDirectoryAtPath:@"/var/cache/apt/archives/partial"
7103 withIntermediateDirectories:YES
7104 attributes:nil
7105 error:NULL
7106 ]);
7107
7108 space_ = CGColorSpaceCreateDeviceRGB();
7109
7110 Blue_.Set(space_, 0.2, 0.2, 1.0, 1.0);
7111 Blueish_.Set(space_, 0x19/255.f, 0x32/255.f, 0x50/255.f, 1.0);
7112 Black_.Set(space_, 0.0, 0.0, 0.0, 1.0);
7113 Off_.Set(space_, 0.9, 0.9, 0.9, 1.0);
7114 White_.Set(space_, 1.0, 1.0, 1.0, 1.0);
7115 Gray_.Set(space_, 0.4, 0.4, 0.4, 1.0);
7116
7117 Finishes_ = [NSArray arrayWithObjects:@"return", @"reopen", @"restart", @"reload", @"reboot", nil];
7118
7119 SectionMap_ = [[[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Sections" ofType:@"plist"]] autorelease];
7120
7121 UIApplicationUseLegacyEvents(YES);
7122 UIKeyboardDisableAutomaticAppearance();
7123
7124 _trace();
7125 int value = UIApplicationMain(argc, argv, @"Cydia", @"Cydia");
7126
7127 CGColorSpaceRelease(space_);
7128 CFRelease(Locale_);
7129
7130 return value;
7131 }