]> git.saurik.com Git - cydia.git/blame - MobileCydia.mm
Sorting packages also requires the database lock.
[cydia.git] / MobileCydia.mm
CommitLineData
36bb2ca2 1/* Cydia - iPhone UIKit Front-End for Debian APT
323746d3 2 * Copyright (C) 2008-2013 Jay Freeman (saurik)
36bb2ca2
JF
3*/
4
6d9696a5 5/* GNU General Public License, Version 3 {{{ */
36bb2ca2 6/*
6d9696a5
JF
7 * Cydia is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published
9 * by the Free Software Foundation, either version 3 of the License,
10 * or (at your option) any later version.
36bb2ca2 11 *
6d9696a5
JF
12 * Cydia is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
36bb2ca2 16 *
6d9696a5
JF
17 * You should have received a copy of the GNU General Public License
18 * along with Cydia. If not, see <http://www.gnu.org/licenses/>.
19**/
bfc87a4d 20/* }}} */
36bb2ca2 21
b0ad8dee
JF
22// XXX: wtf/FastMalloc.h... wtf?
23#define USE_SYSTEM_MALLOC 1
24
dc5812ec 25/* #include Directives {{{ */
819a0ab1 26#include "CyteKit/UCPlatform.h"
d458596e 27#include "CyteKit/Localize.h"
807ae6d7 28
ab398adf
JF
29#include <objc/objc.h>
30#include <objc/runtime.h>
31
5f6bff8c 32#include <CoreGraphics/CoreGraphics.h>
5f6bff8c 33#include <Foundation/Foundation.h>
7d2ac47f 34
eef4ccaf
JF
35#if 0
36#define DEPLOYMENT_TARGET_MACOSX 1
37#define CF_BUILDING_CF 1
38#include <CoreFoundation/CFInternal.h>
39#endif
40
677b8415
JF
41#include <CoreFoundation/CFUniChar.h>
42
afb5333a
JF
43#include <SystemConfiguration/SystemConfiguration.h>
44
c21004b9
JF
45#include <UIKit/UIKit.h>
46#include "iPhonePrivate.h"
47
48#include <IOKit/IOKitLib.h>
a9543575 49
53db9999
JF
50#include <QuartzCore/CALayer.h>
51
f79a4512 52#include <WebCore/WebCoreThread.h>
aa1e1906 53#include <WebKit/DOMHTMLIFrameElement.h>
7e9a36b6 54
677b8415 55#include <algorithm>
808c6eb6 56#include <iomanip>
21ac0ce2 57#include <set>
2367a917 58#include <sstream>
a0be02eb
JF
59#include <string>
60
2367a917
JF
61#include <ext/stdio_filebuf.h>
62
e59669fd
JF
63#undef ABS
64
dc5812ec
JF
65#include <apt-pkg/acquire.h>
66#include <apt-pkg/acquire-item.h>
67#include <apt-pkg/algorithms.h>
68#include <apt-pkg/cachefile.h>
77fcccaf 69#include <apt-pkg/clean.h>
dc5812ec 70#include <apt-pkg/configuration.h>
7376b55c 71#include <apt-pkg/debindexfile.h>
3178d79b 72#include <apt-pkg/debmetaindex.h>
dc5812ec
JF
73#include <apt-pkg/error.h>
74#include <apt-pkg/init.h>
dddbc481 75#include <apt-pkg/mmap.h>
dc5812ec 76#include <apt-pkg/pkgrecords.h>
dddbc481 77#include <apt-pkg/sha1.h>
dc5812ec 78#include <apt-pkg/sourcelist.h>
2367a917 79#include <apt-pkg/sptr.h>
affeffc7 80#include <apt-pkg/strutl.h>
f9f6d9e8 81#include <apt-pkg/tagfile.h>
dc5812ec 82
f79a4512
JF
83#include <apr-1/apr_pools.h>
84
87f46a96
JF
85#include <sys/types.h>
86#include <sys/stat.h>
3178d79b 87#include <sys/sysctl.h>
59c011d8
JF
88#include <sys/param.h>
89#include <sys/mount.h>
bb0fe3c9 90#include <sys/reboot.h>
87f46a96 91
c21004b9 92#include <fcntl.h>
2a8d9add 93#include <notify.h>
dddbc481 94#include <dlfcn.h>
3178d79b 95
b4d89997
JF
96extern "C" {
97#include <mach-o/nlist.h>
98}
99
a0be02eb
JF
100#include <cstdio>
101#include <cstdlib>
102#include <cstring>
b4d89997 103
2367a917 104#include <errno.h>
a9543575 105
94b0b3e5 106#include <Cytore.hpp>
25c1dafb 107#include "Sources.h"
94b0b3e5 108
86cf87e2 109#include <CydiaSubstrate/CydiaSubstrate.h>
cb218676
JF
110#include "Menes/Menes.h"
111
8731fdb0 112#include "CyteKit/IndirectDelegate.h"
819a0ab1 113#include "CyteKit/PerlCompatibleRegEx.hpp"
b97fcfc6 114#include "CyteKit/TableViewCell.h"
5fe2bcc6 115#include "CyteKit/TabBarController.h"
f172aa8f 116#include "CyteKit/WebScriptObject-Cyte.h"
d458596e 117#include "CyteKit/WebViewController.h"
8731fdb0 118#include "CyteKit/WebViewTableViewCell.h"
d458596e 119#include "CyteKit/stringWithUTF8Bytes.h"
449ef9d5 120
7aa82ca2 121#include "Cydia/MIMEAddress.h"
70750ab3 122#include "Cydia/LoadingViewController.h"
cb218676 123#include "Cydia/ProgressEvent.h"
28b8b687 124
71cc7be1 125#include "SDURLCache/SDURLCache.h"
b5e7eebb
GP
126/* }}} */
127
bfc87a4d 128/* Profiler {{{ */
807ae6d7
JF
129struct timeval _ltv;
130bool _itv;
131
2083b866
JF
132#define _timestamp ({ \
133 struct timeval tv; \
134 gettimeofday(&tv, NULL); \
135 tv.tv_sec * 1000000 + tv.tv_usec; \
136})
137
808c6eb6
JF
138typedef std::vector<class ProfileTime *> TimeList;
139TimeList times_;
140
141class ProfileTime {
142 private:
143 const char *name_;
144 uint64_t total_;
76933519 145 uint64_t count_;
808c6eb6
JF
146
147 public:
148 ProfileTime(const char *name) :
149 name_(name),
150 total_(0)
151 {
152 times_.push_back(this);
153 }
154
155 void AddTime(uint64_t time) {
156 total_ += time;
76933519 157 ++count_;
808c6eb6
JF
158 }
159
160 void Print() {
161 if (total_ != 0)
76933519 162 std::cerr << std::setw(5) << count_ << ", " << std::setw(7) << total_ << " : " << name_ << std::endl;
808c6eb6 163 total_ = 0;
76933519 164 count_ = 0;
808c6eb6
JF
165 }
166};
167
168class ProfileTimer {
169 private:
170 ProfileTime &time_;
171 uint64_t start_;
172
173 public:
174 ProfileTimer(ProfileTime &time) :
175 time_(time),
176 start_(_timestamp)
177 {
178 }
179
180 ~ProfileTimer() {
181 time_.AddTime(_timestamp - start_);
182 }
183};
184
185void PrintTimes() {
186 for (TimeList::const_iterator i(times_.begin()); i != times_.end(); ++i)
187 (*i)->Print();
188 std::cerr << "========" << std::endl;
189}
190
191#define _profile(name) { \
192 static ProfileTime name(#name); \
193 ProfileTimer _ ## name(name);
194
195#define _end }
f79a4512 196/* }}} */
affeffc7 197
4cc9e99a
JF
198// XXX: I hate clang. Apple: please get over your petty hatred of GPL and fix your gcc fork
199#define synchronized(lock) \
200 synchronized(static_cast<NSObject *>(lock))
201
3d45bad1 202extern NSString *Cydia_;
f9ba524a 203
43c5d1cb
JF
204#define lprintf(args...) fprintf(stderr, args)
205
206#define ForRelease 1
207#define TraceLogging (1 && !ForRelease)
208#define HistogramInsertionSort (!ForRelease ? 0 : 0)
209#define ProfileTimes (0 && !ForRelease)
210#define ForSaurik (0 && !ForRelease)
211#define LogBrowser (0 && !ForRelease)
212#define TrackResize (0 && !ForRelease)
213#define ManualRefresh (1 && !ForRelease)
214#define ShowInternals (0 && !ForRelease)
215#define AlwaysReload (0 && !ForRelease)
216#define TryIndexedCollation (0 && !ForRelease)
217
218#if !TraceLogging
219#undef _trace
220#define _trace(args...)
221#endif
222
223#if !ProfileTimes
224#undef _profile
225#define _profile(name) {
226#undef _end
227#define _end }
228#define PrintTimes() do {} while (false)
229#endif
230
94b0b3e5
JF
231// Hash Functions/Structures {{{
232extern "C" uint32_t hashlittle(const void *key, size_t length, uint32_t initval = 0);
233
234union SplitHash {
235 uint32_t u32;
236 uint16_t u16[2];
237};
238// }}}
239
5db5891a
JF
240static NSString *Colon_;
241NSString *Elision_;
242static NSString *Error_;
243static NSString *Warning_;
244
d1c7f1fd
JF
245static NSString *Cache_;
246
8a3b565c
JF
247static void (*$SBSSetInterceptsMenuButtonForever)(bool);
248
7c80833f
JF
249static CFStringRef (*$MGCopyAnswer)(CFStringRef);
250
251static NSString *UniqueIdentifier(UIDevice *device = nil) {
252 if (kCFCoreFoundationVersionNumber < 800) // iOS 7.x
253 return [device ?: [UIDevice currentDevice] uniqueIdentifier];
254 else
255 return [(id)$MGCopyAnswer(CFSTR("UniqueDeviceID")) autorelease];
256}
257
6cbfbe28
JF
258static bool IsReachable(const char *name) {
259 SCNetworkReachabilityFlags flags; {
37f1fb03 260 SCNetworkReachabilityRef reachability(SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, name));
6cbfbe28
JF
261 SCNetworkReachabilityGetFlags(reachability, &flags);
262 CFRelease(reachability);
263 }
264
265 // XXX: this elaborate mess is what Apple is using to determine this? :(
266 // XXX: do we care if the user has to intervene? maybe that's ok?
267 return
268 (flags & kSCNetworkReachabilityFlagsReachable) != 0 && (
269 (flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0 || (
270 (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0 ||
271 (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0
272 ) && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0 ||
273 (flags & kSCNetworkReachabilityFlagsIsWWAN) != 0
274 )
275 ;
276}
277
04fe1349
JF
278static const NSUInteger UIViewAutoresizingFlexibleBoth(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
279
670a0494 280static _finline NSString *CydiaURL(NSString *path) {
e3d2a2f5
JF
281 char page[26];
282 page[0] = 'h'; page[1] = 't'; page[2] = 't'; page[3] = 'p'; page[4] = 's';
283 page[5] = ':'; page[6] = '/'; page[7] = '/'; page[8] = 'c'; page[9] = 'y';
284 page[10] = 'd'; page[11] = 'i'; page[12] = 'a'; page[13] = '.'; page[14] = 's';
285 page[15] = 'a'; page[16] = 'u'; page[17] = 'r'; page[18] = 'i'; page[19] = 'k';
286 page[20] = '.'; page[21] = 'c'; page[22] = 'o'; page[23] = 'm'; page[24] = '/';
287 page[25] = '\0';
670a0494
JF
288 return [[NSString stringWithUTF8String:page] stringByAppendingString:path];
289}
290
d99e3659
JF
291static void ReapZombie(pid_t pid) {
292 int status;
293 wait:
294 if (waitpid(pid, &status, 0) == -1)
295 if (errno == EINTR)
296 goto wait;
297 else _assert(false);
298}
299
ef494bd8
RP
300static _finline void UpdateExternalStatus(uint64_t newStatus) {
301 int notify_token;
302 if (notify_register_check("com.saurik.Cydia.status", &notify_token) == NOTIFY_STATUS_OK) {
303 notify_set_state(notify_token, newStatus);
304 notify_cancel(notify_token);
305 }
306 notify_post("com.saurik.Cydia.status");
307}
308
0b7516cf 309static CGFloat CYStatusBarHeight() {
57daa971 310 CGSize size([[UIApplication sharedApplication] statusBarFrame].size);
0b7516cf 311 return UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) ? size.height : size.width;
57daa971
JF
312}
313
68f1828e 314/* NSForcedOrderingSearch doesn't work on the iPhone */
808c6eb6 315static const NSStringCompareOptions MatchCompareOptions_ = NSLiteralSearch | NSCaseInsensitiveSearch;
677b8415
JF
316static const NSStringCompareOptions LaxCompareOptions_ = NSNumericSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch | NSCaseInsensitiveSearch;
317static const CFStringCompareFlags LaxCompareFlags_ = kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareNumerically | kCFCompareWidthInsensitive | kCFCompareForcedOrdering;
1e7a90f5 318
eef4ccaf
JF
319/* Insertion Sort {{{ */
320
df213583
JF
321CFIndex SKBSearch_(const void *element, CFIndex elementSize, const void *list, CFIndex count, CFComparatorFunction comparator, void *context) {
322 const char *ptr = (const char *)list;
323 while (0 < count) {
324 CFIndex half = count / 2;
325 const char *probe = ptr + elementSize * half;
326 CFComparisonResult cr = comparator(element, probe, context);
b5e7eebb 327 if (0 == cr) return (probe - (const char *)list) / elementSize;
df213583
JF
328 ptr = (cr < 0) ? ptr : probe + elementSize;
329 count = (cr < 0) ? half : (half + (count & 1) - 1);
330 }
331 return (ptr - (const char *)list) / elementSize;
332}
333
eef4ccaf
JF
334CFIndex CFBSearch_(const void *element, CFIndex elementSize, const void *list, CFIndex count, CFComparatorFunction comparator, void *context) {
335 const char *ptr = (const char *)list;
336 while (0 < count) {
337 CFIndex half = count / 2;
338 const char *probe = ptr + elementSize * half;
339 CFComparisonResult cr = comparator(element, probe, context);
b5e7eebb 340 if (0 == cr) return (probe - (const char *)list) / elementSize;
eef4ccaf
JF
341 ptr = (cr < 0) ? ptr : probe + elementSize;
342 count = (cr < 0) ? half : (half + (count & 1) - 1);
343 }
344 return (ptr - (const char *)list) / elementSize;
345}
346
347void CFArrayInsertionSortValues(CFMutableArrayRef array, CFRange range, CFComparatorFunction comparator, void *context) {
348 if (range.length == 0)
349 return;
350 const void **values(new const void *[range.length]);
351 CFArrayGetValues(array, range, values);
352
1539387a 353#if HistogramInsertionSort > 0
df213583
JF
354 uint32_t total(0), *offsets(new uint32_t[range.length]);
355#endif
356
eef4ccaf
JF
357 for (CFIndex index(1); index != range.length; ++index) {
358 const void *value(values[index]);
df213583
JF
359 //CFIndex correct(SKBSearch_(&value, sizeof(const void *), values, index, comparator, context));
360 CFIndex correct(index);
1539387a
JF
361 while (comparator(value, values[correct - 1], context) == kCFCompareLessThan) {
362#if HistogramInsertionSort > 1
363 NSLog(@"%@ < %@", value, values[correct - 1]);
364#endif
df213583
JF
365 if (--correct == 0)
366 break;
1539387a 367 }
eef4ccaf 368 if (correct != index) {
df213583
JF
369 size_t offset(index - correct);
370#if HistogramInsertionSort
371 total += offset;
372 ++offsets[offset];
373 if (offset > 10)
374 NSLog(@"Heavy Insertion Displacement: %u = %@", offset, value);
375#endif
376 memmove(values + correct + 1, values + correct, sizeof(const void *) * offset);
eef4ccaf
JF
377 values[correct] = value;
378 }
379 }
380
381 CFArrayReplaceValues(array, range, values, range.length);
382 delete [] values;
df213583 383
1539387a 384#if HistogramInsertionSort > 0
df213583
JF
385 for (CFIndex index(0); index != range.length; ++index)
386 if (offsets[index] != 0)
387 NSLog(@"Insertion Displacement [%u]: %u", index, offsets[index]);
388 NSLog(@"Average Insertion Displacement: %f", double(total) / range.length);
389 delete [] offsets;
390#endif
eef4ccaf
JF
391}
392
807ae6d7
JF
393/* }}} */
394
bf8476c8
JF
395/* Apple Bug Fixes {{{ */
396@implementation UIWebDocumentView (Cydia)
397
398- (void) _setScrollerOffset:(CGPoint)offset {
399 UIScroller *scroller([self _scroller]);
400
401 CGSize size([scroller contentSize]);
402 CGSize bounds([scroller bounds].size);
403
404 CGPoint max;
405 max.x = size.width - bounds.width;
406 max.y = size.height - bounds.height;
407
408 // wtf Apple?!
409 if (max.x < 0)
410 max.x = 0;
411 if (max.y < 0)
412 max.y = 0;
413
414 offset.x = offset.x < 0 ? 0 : offset.x > max.x ? max.x : offset.x;
415 offset.y = offset.y < 0 ? 0 : offset.y > max.y ? max.y : offset.y;
416
417 [scroller setOffset:offset];
418}
419
420@end
421/* }}} */
422
680eb135
JF
423NSUInteger DOMNodeList$countByEnumeratingWithState$objects$count$(DOMNodeList *self, SEL sel, NSFastEnumerationState *state, id *objects, NSUInteger count) {
424 size_t length([self length] - state->state);
425 if (length <= 0)
426 return 0;
427 else if (length > count)
428 length = count;
429 for (size_t i(0); i != length; ++i)
430 objects[i] = [self item:state->state++];
431 state->itemsPtr = objects;
432 state->mutationsPtr = (unsigned long *) self;
433 return length;
434}
435
bfc87a4d 436/* Cydia NSString Additions {{{ */
2388b078 437@interface NSString (Cydia)
a54b1c10 438- (NSComparisonResult) compareByPath:(NSString *)other;
2fc76a2d 439- (NSString *) stringByAddingPercentEscapesIncludingReserved;
2388b078
JF
440@end
441
449ef9d5
JF
442@implementation NSString (Cydia)
443
a54b1c10
JF
444- (NSComparisonResult) compareByPath:(NSString *)other {
445 NSString *prefix = [self commonPrefixWithString:other options:0];
446 size_t length = [prefix length];
447
448 NSRange lrange = NSMakeRange(length, [self length] - length);
449 NSRange rrange = NSMakeRange(length, [other length] - length);
450
451 lrange = [self rangeOfString:@"/" options:0 range:lrange];
452 rrange = [other rangeOfString:@"/" options:0 range:rrange];
453
454 NSComparisonResult value;
455
456 if (lrange.location == NSNotFound && rrange.location == NSNotFound)
457 value = NSOrderedSame;
458 else if (lrange.location == NSNotFound)
459 value = NSOrderedAscending;
460 else if (rrange.location == NSNotFound)
461 value = NSOrderedDescending;
462 else
463 value = NSOrderedSame;
464
465 NSString *lpath = lrange.location == NSNotFound ? [self substringFromIndex:length] :
466 [self substringWithRange:NSMakeRange(length, lrange.location - length)];
467 NSString *rpath = rrange.location == NSNotFound ? [other substringFromIndex:length] :
468 [other substringWithRange:NSMakeRange(length, rrange.location - length)];
469
470 NSComparisonResult result = [lpath compare:rpath];
471 return result == NSOrderedSame ? value : result;
472}
473
2fc76a2d
JF
474- (NSString *) stringByAddingPercentEscapesIncludingReserved {
475 return [(id)CFURLCreateStringByAddingPercentEscapes(
bc11cf5b 476 kCFAllocatorDefault,
2fc76a2d
JF
477 (CFStringRef) self,
478 NULL,
479 CFSTR(";/?:@&=+$,"),
480 kCFStringEncodingUTF8
481 ) autorelease];
482}
483
2388b078 484@end
bfc87a4d 485/* }}} */
2388b078 486
bfc87a4d 487/* C++ NSString Wrapper Cache {{{ */
8e8fca7f
JF
488static _finline CFStringRef CYStringCreate(const char *data, size_t size) {
489 return size == 0 ? NULL :
490 CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const uint8_t *>(data), size, kCFStringEncodingUTF8, NO, kCFAllocatorNull) ?:
491 CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const uint8_t *>(data), size, kCFStringEncodingISOLatin1, NO, kCFAllocatorNull);
492}
493
494static _finline CFStringRef CYStringCreate(const char *data) {
495 return CYStringCreate(data, strlen(data));
496}
497
f79a4512
JF
498class CYString {
499 private:
500 char *data_;
501 size_t size_;
502 CFStringRef cache_;
503
504 _finline void clear_() {
18873623 505 if (cache_ != NULL) {
f79a4512 506 CFRelease(cache_);
18873623
JF
507 cache_ = NULL;
508 }
f79a4512
JF
509 }
510
511 public:
512 _finline bool empty() const {
513 return size_ == 0;
514 }
515
516 _finline size_t size() const {
517 return size_;
518 }
519
520 _finline char *data() const {
521 return data_;
522 }
523
524 _finline void clear() {
525 size_ = 0;
526 clear_();
527 }
528
529 _finline CYString() :
530 data_(0),
531 size_(0),
18873623 532 cache_(NULL)
f79a4512
JF
533 {
534 }
535
536 _finline ~CYString() {
537 clear_();
538 }
539
540 void operator =(const CYString &rhs) {
541 data_ = rhs.data_;
542 size_ = rhs.size_;
543
544 if (rhs.cache_ == nil)
545 cache_ = NULL;
546 else
547 cache_ = reinterpret_cast<CFStringRef>(CFRetain(rhs.cache_));
548 }
549
97814287
JF
550 void copy(apr_pool_t *pool) {
551 char *temp(reinterpret_cast<char *>(apr_palloc(pool, size_ + 1)));
552 memcpy(temp, data_, size_);
553 temp[size_] = '\0';
554 data_ = temp;
555 }
556
f79a4512
JF
557 void set(apr_pool_t *pool, const char *data, size_t size) {
558 if (size == 0)
559 clear();
560 else {
561 clear_();
562
97814287 563 data_ = const_cast<char *>(data);
f79a4512 564 size_ = size;
97814287
JF
565
566 if (pool != NULL)
567 copy(pool);
f79a4512
JF
568 }
569 }
570
571 _finline void set(apr_pool_t *pool, const char *data) {
572 set(pool, data, data == NULL ? 0 : strlen(data));
573 }
574
575 _finline void set(apr_pool_t *pool, const std::string &rhs) {
576 set(pool, rhs.data(), rhs.size());
577 }
578
579 bool operator ==(const CYString &rhs) const {
580 return size_ == rhs.size_ && memcmp(data_, rhs.data_, size_) == 0;
581 }
582
8e8fca7f
JF
583 _finline operator CFStringRef() {
584 if (cache_ == NULL)
585 cache_ = CYStringCreate(data_, size_);
586 return cache_;
df213583
JF
587 }
588
589 _finline operator id() {
590 return (NSString *) static_cast<CFStringRef>(*this);
f79a4512 591 }
4c0ed943
JF
592
593 _finline operator const char *() {
594 return reinterpret_cast<const char *>(data_);
595 }
f79a4512 596};
bfc87a4d
JF
597/* }}} */
598/* C++ NSString Algorithm Adapters {{{ */
f79a4512
JF
599extern "C" {
600 CF_EXPORT CFHashCode CFStringHashNSString(CFStringRef str);
601}
602
603struct NSStringMapHash :
604 std::unary_function<NSString *, size_t>
605{
606 _finline size_t operator ()(NSString *value) const {
607 return CFStringHashNSString((CFStringRef) value);
608 }
609};
610
611struct NSStringMapLess :
612 std::binary_function<NSString *, NSString *, bool>
613{
614 _finline bool operator ()(NSString *lhs, NSString *rhs) const {
615 return [lhs compare:rhs] == NSOrderedAscending;
616 }
617};
618
619struct NSStringMapEqual :
620 std::binary_function<NSString *, NSString *, bool>
621{
622 _finline bool operator ()(NSString *lhs, NSString *rhs) const {
623 return CFStringCompare((CFStringRef) lhs, (CFStringRef) rhs, 0) == kCFCompareEqualTo;
624 //CFEqual((CFTypeRef) lhs, (CFTypeRef) rhs);
625 //[lhs isEqualToString:rhs];
626 }
627};
bfc87a4d 628/* }}} */
f79a4512 629
5f6bff8c 630/* CoreGraphics Primitives {{{ */
02012733 631class CYColor {
b4d89997
JF
632 private:
633 CGColorRef color_;
634
6a575b5e
JF
635 static CGColorRef Create_(CGColorSpaceRef space, float red, float green, float blue, float alpha) {
636 CGFloat color[] = {red, green, blue, alpha};
637 return CGColorCreate(space, color);
638 }
639
b4d89997 640 public:
02012733 641 CYColor() :
36bb2ca2
JF
642 color_(NULL)
643 {
644 }
645
02012733 646 CYColor(CGColorSpaceRef space, float red, float green, float blue, float alpha) :
6a575b5e 647 color_(Create_(space, red, green, blue, alpha))
36bb2ca2
JF
648 {
649 Set(space, red, green, blue, alpha);
650 }
651
652 void Clear() {
653 if (color_ != NULL)
654 CGColorRelease(color_);
b4d89997
JF
655 }
656
02012733 657 ~CYColor() {
36bb2ca2
JF
658 Clear();
659 }
660
661 void Set(CGColorSpaceRef space, float red, float green, float blue, float alpha) {
662 Clear();
6a575b5e 663 color_ = Create_(space, red, green, blue, alpha);
b4d89997
JF
664 }
665
666 operator CGColorRef() {
667 return color_;
668 }
669};
b4d89997
JF
670/* }}} */
671
36bb2ca2 672/* Random Global Variables {{{ */
c73d524b 673static int PulseInterval_ = 500000;
affeffc7 674
57e8b225
JF
675static const NSString *UI_;
676
d791dce4 677static int Finish_;
be860cc8 678static bool RestartSubstrate_;
d791dce4
JF
679static NSArray *Finishes_;
680
affeffc7 681#define SpringBoard_ "/System/Library/LaunchDaemons/com.apple.SpringBoard.plist"
22f8bed9 682#define NotifyConfig_ "/etc/notify.conf"
2a8d9add 683
dc63e78f
JF
684static bool Queuing_;
685
02012733
JF
686static CYColor Blue_;
687static CYColor Blueish_;
688static CYColor Black_;
86a333c6 689static CYColor Folder_;
02012733
JF
690static CYColor Off_;
691static CYColor White_;
692static CYColor Gray_;
693static CYColor Green_;
694static CYColor Purple_;
695static CYColor Purplish_;
3bd1c2a2 696
dc63e78f
JF
697static UIColor *InstallingColor_;
698static UIColor *RemovingColor_;
36bb2ca2 699
7d2ac47f 700static NSString *App_;
d73cede2 701
2388b078 702static BOOL Advanced_;
a54b1c10 703static BOOL Ignored_;
2388b078 704
7b33d201
JF
705static _H<UIFont> Font12_;
706static _H<UIFont> Font12Bold_;
707static _H<UIFont> Font14_;
2cdc6e57 708static _H<UIFont> Font18_;
7b33d201
JF
709static _H<UIFont> Font18Bold_;
710static _H<UIFont> Font22Bold_;
f641a0e5 711
87f46a96 712static const char *Machine_ = NULL;
e967efd5 713static _H<NSString> System_;
56127854
JF
714static NSString *SerialNumber_ = nil;
715static NSString *ChipID_ = nil;
6ffdaae3 716static NSString *BBSNum_ = nil;
9737d93e 717static _H<NSString> Token_;
7c80833f 718static _H<NSString> UniqueID_;
e967efd5
JF
719static _H<NSString> UserAgent_;
720static _H<NSString> Product_;
721static _H<NSString> Safari_;
2a8d9add 722
d791dce4
JF
723static CFLocaleRef Locale_;
724static NSArray *Languages_;
725static CGColorSpaceRef space_;
36bb2ca2 726
46dbfd32 727static NSDictionary *SectionMap_;
b4d89997 728static NSMutableDictionary *Metadata_;
7b0ce2da 729static _transient NSMutableDictionary *Settings_;
7b0ce2da 730static _transient NSMutableDictionary *Packages_;
b3c8e69c 731static _transient NSMutableDictionary *Values_;
7b0ce2da 732static _transient NSMutableDictionary *Sections_;
25c1dafb 733_H<NSMutableDictionary> Sources_;
33e30380 734static _transient NSNumber *Version_;
25c1dafb 735bool Changed_;
31bc18a7 736static time_t now_;
8da60fb7 737
f333f6c5 738bool IsWildcat_;
57e8b225 739static CGFloat ScreenScale_;
c138614d 740static NSString *Idiom_;
407564b5 741static _H<NSString> Firmware_;
fd825a2d 742static NSString *Major_;
5df7ecfb 743
7b33d201
JF
744static _H<NSMutableDictionary> SessionData_;
745static _H<NSObject> HostConfig_;
746static _H<NSMutableSet> BridgedHosts_;
247bedb6 747static _H<NSMutableSet> TokenHosts_;
2e1652a9 748static _H<NSMutableSet> InsecureHosts_;
7b33d201 749static _H<NSMutableSet> PipelinedHosts_;
5e845121 750static _H<NSMutableSet> CachedURLs_;
389133be 751
e4123ce0
JF
752static NSString *kCydiaProgressEventTypeError = @"Error";
753static NSString *kCydiaProgressEventTypeInformation = @"Information";
754static NSString *kCydiaProgressEventTypeStatus = @"Status";
755static NSString *kCydiaProgressEventTypeWarning = @"Warning";
36bb2ca2 756/* }}} */
d791dce4 757
36bb2ca2
JF
758/* Display Helpers {{{ */
759inline float Interpolate(float begin, float end, float fraction) {
760 return (end - begin) * fraction + begin;
761}
2367a917 762
1c1dfc2d 763static _finline const char *StripVersion_(const char *version) {
6a155117 764 const char *colon(strchr(version, ':'));
673ad3c3 765 return colon == NULL ? version : colon + 1;
6a155117
JF
766}
767
f79a4512 768NSString *LocalizeSection(NSString *section) {
b1ce61ec 769 static Pcre title_r("^(.*?) \\((.*)\\)$");
9fcbca29
JF
770 if (title_r(section)) {
771 NSString *parent(title_r[1]);
772 NSString *child(title_r[2]);
773
43f3d7f6 774 return [NSString stringWithFormat:UCLocalize("PARENTHETICAL"),
9fcbca29
JF
775 LocalizeSection(parent),
776 LocalizeSection(child)
b1ce61ec 777 ];
9fcbca29 778 }
b1ce61ec
JF
779
780 return [[NSBundle mainBundle] localizedStringForKey:section value:nil table:@"Sections"];
f79a4512
JF
781}
782
4cf4165e
JF
783NSString *Simplify(NSString *title) {
784 const char *data = [title UTF8String];
785 size_t size = [title length];
786
7b0ce2da
JF
787 static Pcre square_r("^\\[(.*)\\]$");
788 if (square_r(data, size))
789 return Simplify(square_r[1]);
790
791 static Pcre paren_r("^\\((.*)\\)$");
792 if (paren_r(data, size))
793 return Simplify(paren_r[1]);
794
b1ce61ec 795 static Pcre title_r("^(.*?) \\((.*)\\)$");
4cf4165e 796 if (title_r(data, size))
7b0ce2da
JF
797 return Simplify(title_r[1]);
798
799 return title;
4cf4165e 800}
36bb2ca2
JF
801/* }}} */
802
d791dce4
JF
803NSString *GetLastUpdate() {
804 NSDate *update = [Metadata_ objectForKey:@"LastUpdate"];
805
806 if (update == nil)
807 return UCLocalize("NEVER_OR_UNKNOWN");
808
809 CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
810 CFStringRef formatted = CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) update);
811
812 CFRelease(formatter);
813
814 return [(NSString *) formatted autorelease];
815}
816
6d9712c4 817bool isSectionVisible(NSString *section) {
45447dc3 818 NSDictionary *metadata([Sections_ objectForKey:(section ?: @"")]);
677b8415 819 NSNumber *hidden(metadata == nil ? nil : [metadata objectForKey:@"Hidden"]);
6d9712c4
JF
820 return hidden == nil || ![hidden boolValue];
821}
822
c31d7cdc 823static NSObject *CYIOGetValue(const char *path, NSString *property) {
947a8eef
JF
824 io_registry_entry_t entry(IORegistryEntryFromPath(kIOMasterPortDefault, path));
825 if (entry == MACH_PORT_NULL)
826 return nil;
827
828 CFTypeRef value(IORegistryEntryCreateCFProperty(entry, (CFStringRef) property, kCFAllocatorDefault, 0));
829 IOObjectRelease(entry);
830
831 if (value == NULL)
832 return nil;
833 return [(id) value autorelease];
834}
835
c31d7cdc 836static NSString *CYHex(NSData *data, bool reverse = false) {
947a8eef
JF
837 if (data == nil)
838 return nil;
839
840 size_t length([data length]);
841 uint8_t bytes[length];
842 [data getBytes:bytes];
843
844 char string[length * 2 + 1];
845 for (size_t i(0); i != length; ++i)
be45a862 846 sprintf(string + i * 2, "%.2x", bytes[reverse ? length - i - 1 : i]);
947a8eef
JF
847
848 return [NSString stringWithUTF8String:string];
849}
850
9cb0bff2
GP
851@class Cydia;
852
d36e83a3 853/* Delegate Prototypes {{{ */
36bb2ca2
JF
854@class Package;
855@class Source;
6915b806 856@class CydiaProgressEvent;
36bb2ca2 857
6915b806 858@protocol DatabaseDelegate
5a09ae08 859- (void) repairWithSelector:(SEL)selector;
8b29f8e6 860- (void) setConfigurationData:(NSString *)data;
6915b806 861- (void) addProgressEventOnMainThread:(CydiaProgressEvent *)event forTask:(NSString *)task;
8b29f8e6
JF
862@end
863
f6e13561 864@class CYPackageController;
f79a4512 865
21ac0ce2
JF
866@protocol SourceDelegate
867- (void) setFetch:(NSNumber *)fetch;
868@end
869
870@protocol FetchDelegate
871- (bool) isSourceCancelled;
872- (void) startSourceFetch:(NSString *)uri;
873- (void) stopSourceFetch:(NSString *)uri;
874@end
875
36bb2ca2 876@protocol CydiaDelegate
2925cbba 877- (void) returnToCydia;
9dd3045d 878- (void) saveState;
54043703
JF
879- (void) retainNetworkActivityIndicator;
880- (void) releaseNetworkActivityIndicator;
dc63e78f 881- (void) clearPackage:(Package *)package;
36bb2ca2 882- (void) installPackage:(Package *)package;
77801ff1 883- (void) installPackages:(NSArray *)packages;
36bb2ca2 884- (void) removePackage:(Package *)package;
c21004b9
JF
885- (void) beginUpdate;
886- (BOOL) updating;
e67ebdad 887- (bool) requestUpdate;
36bb2ca2 888- (void) distUpgrade;
fed0d010 889- (void) loadData;
6d9712c4 890- (void) updateData;
392ff7e4 891- (void) _saveConfig;
7b0ce2da 892- (void) syncData;
33e30380 893- (void) addSource:(NSDictionary *)source;
93460555 894- (void) addTrivialSource:(NSString *)href;
7b0ce2da 895- (UIProgressHUD *) addProgressHUD;
d061f4ba 896- (void) removeProgressHUD:(UIProgressHUD *)hud;
9daa7f25 897- (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item;
4ba8f30a 898- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
36bb2ca2 899@end
d36e83a3 900/* }}} */
b4d89997 901
21ac0ce2
JF
902/* CancelStatus {{{ */
903class CancelStatus :
dc5812ec
JF
904 public pkgAcquireStatus
905{
906 private:
fc470c15 907 bool cancelled_;
dc5812ec
JF
908
909 public:
21ac0ce2 910 CancelStatus() :
fc470c15 911 cancelled_(false)
dc5812ec
JF
912 {
913 }
914
dc5812ec
JF
915 virtual bool MediaChange(std::string media, std::string drive) {
916 return false;
917 }
918
919 virtual void IMSHit(pkgAcquire::ItemDesc &item) {
d35bcbbf 920 Done(item);
dc5812ec
JF
921 }
922
21ac0ce2
JF
923 virtual bool Pulse_(pkgAcquire *Owner) = 0;
924
925 virtual bool Pulse(pkgAcquire *Owner) {
926 if (pkgAcquireStatus::Pulse(Owner) && Pulse_(Owner))
927 return true;
928 else {
929 cancelled_ = true;
930 return false;
931 }
932 }
933
934 _finline bool WasCancelled() const {
935 return cancelled_;
936 }
937};
938/* }}} */
939/* DelegateStatus {{{ */
940class CydiaStatus :
941 public CancelStatus
942{
943 private:
944 _transient NSObject<ProgressDelegate> *delegate_;
945
946 public:
947 CydiaStatus() :
948 delegate_(nil)
949 {
950 }
951
952 void setDelegate(NSObject<ProgressDelegate> *delegate) {
953 delegate_ = delegate;
954 }
955
dc5812ec 956 virtual void Fetch(pkgAcquire::ItemDesc &item) {
6915b806 957 NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
389133be 958 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithFormat:UCLocalize("DOWNLOADING_"), name] ofType:kCydiaProgressEventTypeStatus forItem:item]);
6915b806 959 [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
dc5812ec
JF
960 }
961
962 virtual void Done(pkgAcquire::ItemDesc &item) {
d35bcbbf
JF
963 NSString *name([NSString stringWithUTF8String:item.ShortDesc.c_str()]);
964 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithFormat:Colon_, UCLocalize("DONE"), name] ofType:kCydiaProgressEventTypeStatus forItem:item]);
965 [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
dc5812ec
JF
966 }
967
968 virtual void Fail(pkgAcquire::ItemDesc &item) {
1d80f6b9
JF
969 if (
970 item.Owner->Status == pkgAcquire::Item::StatIdle ||
971 item.Owner->Status == pkgAcquire::Item::StatDone
972 )
973 return;
974
affeffc7
JF
975 std::string &error(item.Owner->ErrorText);
976 if (error.empty())
977 return;
978
389133be 979 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:kCydiaProgressEventTypeError forItem:item]);
6915b806 980 [delegate_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
dc5812ec
JF
981 }
982
21ac0ce2 983 virtual bool Pulse_(pkgAcquire *Owner) {
bcbac8f7 984 double percent(
2367a917
JF
985 double(CurrentBytes + CurrentItems) /
986 double(TotalBytes + TotalItems)
987 );
988
bcbac8f7
JF
989 [delegate_ performSelectorOnMainThread:@selector(setProgressStatus:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:
990 [NSNumber numberWithDouble:percent], @"Percent",
991
992 [NSNumber numberWithDouble:CurrentBytes], @"Current",
993 [NSNumber numberWithDouble:TotalBytes], @"Total",
994 [NSNumber numberWithDouble:CurrentCPS], @"Speed",
995 nil] waitUntilDone:YES];
996
21ac0ce2 997 return ![delegate_ isProgressCancelled];
dc5812ec
JF
998 }
999
1000 virtual void Start() {
0210c2b5 1001 pkgAcquireStatus::Start();
aaae308d 1002 [delegate_ performSelectorOnMainThread:@selector(setProgressCancellable:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
dc5812ec
JF
1003 }
1004
1005 virtual void Stop() {
0210c2b5 1006 pkgAcquireStatus::Stop();
aaae308d 1007 [delegate_ performSelectorOnMainThread:@selector(setProgressCancellable:) withObject:[NSNumber numberWithBool:NO] waitUntilDone:YES];
bcbac8f7 1008 [delegate_ performSelectorOnMainThread:@selector(setProgressStatus:) withObject:nil waitUntilDone:YES];
dc5812ec
JF
1009 }
1010};
1011/* }}} */
36bb2ca2 1012/* Database Interface {{{ */
68d927e2
JF
1013typedef std::map< unsigned long, _H<Source> > SourceMap;
1014
36bb2ca2 1015@interface Database : NSObject {
f79a4512
JF
1016 NSZone *zone_;
1017 apr_pool_t *pool_;
1018
a1440b10
JF
1019 unsigned era_;
1020
36bb2ca2 1021 pkgCacheFile cache_;
5a09ae08 1022 pkgDepCache::Policy *policy_;
36bb2ca2
JF
1023 pkgRecords *records_;
1024 pkgProblemResolver *resolver_;
1025 pkgAcquire *fetcher_;
1026 FileFd *lock_;
1027 SPtr<pkgPackageManager> manager_;
1028 pkgSourceList *list_;
5f6bff8c 1029
34f70f5d 1030 SourceMap sourceMap_;
7b33d201 1031 _H<NSMutableArray> sourceList_;
34f70f5d 1032
077e9d90 1033 CFMutableArrayRef packages_;
b4d89997 1034
6915b806
JF
1035 _transient NSObject<DatabaseDelegate> *delegate_;
1036 _transient NSObject<ProgressDelegate> *progress_;
1037
21ac0ce2 1038 CydiaStatus status_;
8b29f8e6 1039
77fcccaf 1040 int cydiafd_;
36bb2ca2 1041 int statusfd_;
8b29f8e6 1042 FILE *input_;
fe33a23e
JF
1043
1044 std::map<const char *, _H<NSString> > sections_;
dc5812ec
JF
1045}
1046
770f2a8e 1047+ (Database *) sharedInstance;
a1440b10 1048- (unsigned) era;
770f2a8e 1049
77fcccaf 1050- (void) _readCydia:(NSNumber *)fd;
36bb2ca2
JF
1051- (void) _readStatus:(NSNumber *)fd;
1052- (void) _readOutput:(NSNumber *)fd;
2367a917 1053
8b29f8e6
JF
1054- (FILE *) input;
1055
36bb2ca2 1056- (Package *) packageWithName:(NSString *)name;
dc5812ec 1057
36bb2ca2 1058- (pkgCacheFile &) cache;
5a09ae08 1059- (pkgDepCache::Policy *) policy;
36bb2ca2
JF
1060- (pkgRecords *) records;
1061- (pkgProblemResolver *) resolver;
1062- (pkgAcquire &) fetcher;
a3328c28 1063- (pkgSourceList &) list;
36bb2ca2 1064- (NSArray *) packages;
7b0ce2da 1065- (NSArray *) sources;
d669236d 1066- (Source *) sourceWithKey:(NSString *)key;
4ba8f30a 1067- (void) reloadDataWithInvocation:(NSInvocation *)invocation;
36bb2ca2 1068
5a09ae08 1069- (void) configure;
670a0494 1070- (bool) prepare;
36bb2ca2 1071- (void) perform;
670a0494 1072- (bool) upgrade;
36bb2ca2
JF
1073- (void) update;
1074
21ac0ce2 1075- (void) updateWithStatus:(CancelStatus &)status;
36bb2ca2 1076
6915b806
JF
1077- (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate;
1078
1079- (void) setProgressDelegate:(NSObject<ProgressDelegate> *)delegate;
1080- (NSObject<ProgressDelegate> *) progressDelegate;
1081
7376b55c 1082- (Source *) getSource:(pkgCache::PkgFileIterator)file;
21ac0ce2 1083- (void) setFetch:(bool)fetch forURI:(const char *)uri;
9ed626f1 1084- (void) resetFetch;
fe33a23e
JF
1085
1086- (NSString *) mappedSectionForPointer:(const char *)pointer;
1087
670a0494
JF
1088@end
1089/* }}} */
21ac0ce2
JF
1090/* SourceStatus {{{ */
1091class SourceStatus :
1092 public CancelStatus
1093{
1094 private:
1095 _transient NSObject<FetchDelegate> *delegate_;
1096 _transient Database *database_;
1097
1098 public:
1099 SourceStatus(NSObject<FetchDelegate> *delegate, Database *database) :
1100 delegate_(delegate),
1101 database_(database)
1102 {
1103 }
1104
1105 void Set(bool fetch, pkgAcquire::ItemDesc &desc) {
1106 desc.Owner->ID = 0;
1107 [database_ setFetch:fetch forURI:desc.Owner->DescURI().c_str()];
1108 }
1109
1110 virtual void Fetch(pkgAcquire::ItemDesc &desc) {
1111 Set(true, desc);
1112 }
1113
1114 virtual void Done(pkgAcquire::ItemDesc &desc) {
1115 Set(false, desc);
1116 }
1117
1118 virtual void Fail(pkgAcquire::ItemDesc &desc) {
1119 Set(false, desc);
1120 }
1121
1122 virtual bool Pulse_(pkgAcquire *Owner) {
1123 for (pkgAcquire::ItemCIterator item = Owner->ItemsBegin(); item != Owner->ItemsEnd(); ++item)
1124 if ((*item)->ID != 0);
1125 else if ((*item)->Status == pkgAcquire::Item::StatIdle) {
1126 (*item)->ID = 1;
1127 [database_ setFetch:true forURI:(*item)->DescURI().c_str()];
1128 } else (*item)->ID = 0;
1129 return ![delegate_ isSourceCancelled];
1130 }
9ed626f1
JF
1131
1132 virtual void Stop() {
1133 pkgAcquireStatus::Stop();
1134 [database_ resetFetch];
1135 }
21ac0ce2
JF
1136};
1137/* }}} */
6915b806
JF
1138/* ProgressEvent Implementation {{{ */
1139@implementation CydiaProgressEvent
670a0494 1140
6915b806
JF
1141+ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type {
1142 return [[[CydiaProgressEvent alloc] initWithMessage:message ofType:type] autorelease];
670a0494
JF
1143}
1144
6915b806
JF
1145+ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forPackage:(NSString *)package {
1146 CydiaProgressEvent *event([self eventWithMessage:message ofType:type]);
1147 [event setPackage:package];
1148 return event;
670a0494
JF
1149}
1150
c57867ea
JF
1151+ (CydiaProgressEvent *) eventWithMessage:(NSString *)message ofType:(NSString *)type forItem:(pkgAcquire::ItemDesc &)item {
1152 CydiaProgressEvent *event([self eventWithMessage:message ofType:type]);
1153
1154 NSString *description([NSString stringWithUTF8String:item.Description.c_str()]);
1155 NSArray *fields([description componentsSeparatedByString:@" "]);
1156 [event setItem:fields];
1157
1158 if ([fields count] > 3) {
1159 [event setPackage:[fields objectAtIndex:2]];
1160 [event setVersion:[fields objectAtIndex:3]];
1161 }
1162
1163 [event setURL:[NSString stringWithUTF8String:item.URI.c_str()]];
1164
1165 return event;
1166}
1167
4ede7a3f
JF
1168+ (NSArray *) _attributeKeys {
1169 return [NSArray arrayWithObjects:
c57867ea 1170 @"item",
4ede7a3f
JF
1171 @"message",
1172 @"package",
1173 @"type",
c57867ea
JF
1174 @"url",
1175 @"version",
4ede7a3f
JF
1176 nil];
1177}
1178
1179- (NSArray *) attributeKeys {
1180 return [[self class] _attributeKeys];
1181}
1182
1183+ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1184 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1185}
1186
6915b806
JF
1187- (id) initWithMessage:(NSString *)message ofType:(NSString *)type {
1188 if ((self = [super init]) != nil) {
1189 message_ = message;
1190 type_ = type;
1191 } return self;
670a0494
JF
1192}
1193
6915b806
JF
1194- (NSString *) message {
1195 return message_;
1196}
6067f1b8 1197
6915b806
JF
1198- (NSString *) type {
1199 return type_;
1200}
1201
c57867ea
JF
1202- (NSArray *) item {
1203 return (id) item_ ?: [NSNull null];
1204}
1205
1206- (void) setItem:(NSArray *)item {
1207 item_ = item;
6915b806
JF
1208}
1209
c57867ea
JF
1210- (NSString *) package {
1211 return (id) package_ ?: [NSNull null];
6915b806
JF
1212}
1213
1214- (void) setPackage:(NSString *)package {
1215 package_ = package;
1216}
1217
c57867ea
JF
1218- (NSString *) url {
1219 return (id) url_ ?: [NSNull null];
1220}
1221
1222- (void) setURL:(NSString *)url {
1223 url_ = url;
1224}
1225
1226- (void) setVersion:(NSString *)version {
1227 version_ = version;
1228}
1229
1230- (NSString *) version {
1231 return (id) version_ ?: [NSNull null];
1232}
1233
6915b806
JF
1234- (NSString *) compound:(NSString *)value {
1235 if (value != nil) {
1236 NSString *mode(nil); {
1237 NSString *type([self type]);
389133be 1238 if ([type isEqualToString:kCydiaProgressEventTypeError])
6915b806 1239 mode = UCLocalize("ERROR");
389133be 1240 else if ([type isEqualToString:kCydiaProgressEventTypeWarning])
6915b806
JF
1241 mode = UCLocalize("WARNING");
1242 }
1243
1244 if (mode != nil)
1245 value = [NSString stringWithFormat:UCLocalize("COLON_DELIMITED"), mode, value];
1246 }
1247
1248 return value;
1249}
1250
1251- (NSString *) compoundMessage {
1252 return [self compound:[self message]];
1253}
1254
1255- (NSString *) compoundTitle {
1256 NSString *title;
1257
83b78e5f 1258 if (package_ == nil)
6915b806 1259 title = nil;
83b78e5f
JF
1260 else if (Package *package = [[Database sharedInstance] packageWithName:package_])
1261 title = [package name];
1262 else
1263 title = package_;
6915b806
JF
1264
1265 return [self compound:title];
670a0494
JF
1266}
1267
dc5812ec 1268@end
36bb2ca2 1269/* }}} */
dc5812ec 1270
94b0b3e5
JF
1271// Cytore Definitions {{{
1272struct PackageValue :
1273 Cytore::Block
1274{
94b0b3e5
JF
1275 Cytore::Offset<PackageValue> next_;
1276
1277 uint32_t index_ : 23;
1278 uint32_t subscribed_ : 1;
1279 uint32_t : 8;
1280
1281 int32_t first_;
1282 int32_t last_;
1283
1284 uint16_t vhash_;
1285 uint16_t nhash_;
1286
1287 char version_[8];
1288 char name_[];
1289};
1290
1291struct MetaValue :
1292 Cytore::Block
1293{
9f357d11 1294 uint32_t active_;
94b0b3e5
JF
1295 Cytore::Offset<PackageValue> packages_[1 << 16];
1296};
1297
1298static Cytore::File<MetaValue> MetaFile_;
1299// }}}
1300// Cytore Helper Functions {{{
c65611b9 1301static PackageValue *PackageFind(const char *name, size_t length, bool *fail = NULL) {
94b0b3e5
JF
1302 SplitHash nhash = { hashlittle(name, length) };
1303
1304 PackageValue *metadata;
1305
1306 Cytore::Offset<PackageValue> *offset(&MetaFile_->packages_[nhash.u16[0]]);
ac5f7cb3 1307 for (;; offset = &metadata->next_) { if (offset->IsNull()) {
94b0b3e5
JF
1308 *offset = MetaFile_.New<PackageValue>(length + 1);
1309 metadata = &MetaFile_.Get(*offset);
1310
c65611b9
JF
1311 if (metadata == NULL) {
1312 if (fail != NULL)
1313 *fail = true;
1314
1315 metadata = new PackageValue();
1316 memset(metadata, 0, sizeof(*metadata));
1317 }
1318
ac5f7cb3
JF
1319 memcpy(metadata->name_, name, length);
1320 metadata->name_[length] = '\0';
94b0b3e5
JF
1321 metadata->nhash_ = nhash.u16[1];
1322 } else {
1323 metadata = &MetaFile_.Get(*offset);
ac5f7cb3
JF
1324 if (metadata->nhash_ != nhash.u16[1])
1325 continue;
1326 if (strncmp(metadata->name_, name, length) != 0)
1327 continue;
1328 if (metadata->name_[length] != '\0')
1329 continue;
1330 } break; }
94b0b3e5 1331
94b0b3e5
JF
1332 return metadata;
1333}
1334
1335static void PackageImport(const void *key, const void *value, void *context) {
c65611b9
JF
1336 bool &fail(*reinterpret_cast<bool *>(context));
1337
94b0b3e5
JF
1338 char buffer[1024];
1339 if (!CFStringGetCString((CFStringRef) key, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
1340 NSLog(@"failed to import package %@", key);
1341 return;
1342 }
1343
c65611b9 1344 PackageValue *metadata(PackageFind(buffer, strlen(buffer), &fail));
94b0b3e5
JF
1345 NSDictionary *package((NSDictionary *) value);
1346
1347 if (NSNumber *subscribed = [package objectForKey:@"IsSubscribed"])
2f856365 1348 if ([subscribed boolValue] && !metadata->subscribed_)
94b0b3e5
JF
1349 metadata->subscribed_ = true;
1350
1351 if (NSDate *date = [package objectForKey:@"FirstSeen"]) {
1352 time_t time([date timeIntervalSince1970]);
1353 if (metadata->first_ > time || metadata->first_ == 0)
1354 metadata->first_ = time;
1355 }
1356
2f856365
JF
1357 NSDate *date([package objectForKey:@"LastSeen"]);
1358 NSString *version([package objectForKey:@"LastVersion"]);
5a933937 1359
2f856365 1360 if (date != nil && version != nil) {
94b0b3e5 1361 time_t time([date timeIntervalSince1970]);
2f856365 1362 if (metadata->last_ < time || metadata->last_ == 0)
5a933937
JF
1363 if (CFStringGetCString((CFStringRef) version, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
1364 size_t length(strlen(buffer));
1365 uint16_t vhash(hashlittle(buffer, length));
94b0b3e5 1366
5a933937
JF
1367 size_t capped(std::min<size_t>(8, length));
1368 char *latest(buffer + length - capped);
1369
1370 strncpy(metadata->version_, latest, sizeof(metadata->version_));
1371 metadata->vhash_ = vhash;
2f856365
JF
1372
1373 metadata->last_ = time;
5a933937 1374 }
2f856365 1375 }
94b0b3e5
JF
1376}
1377// }}}
1378
36bb2ca2
JF
1379/* Source Class {{{ */
1380@interface Source : NSObject {
aec64d46
JF
1381 unsigned era_;
1382 Database *database_;
1383 metaIndex *index_;
1384
6204f56a 1385 CYString depiction_;
f9f6d9e8
JF
1386 CYString description_;
1387 CYString label_;
1388 CYString origin_;
1389 CYString support_;
dc5812ec 1390
f9f6d9e8
JF
1391 CYString uri_;
1392 CYString distribution_;
1393 CYString type_;
8252b666 1394 CYString base_;
f9f6d9e8 1395 CYString version_;
2367a917 1396
c08c8943
JF
1397 _H<NSString> host_;
1398 _H<NSString> authority_;
f9f6d9e8
JF
1399
1400 CYString defaultIcon_;
dc5812ec 1401
4cc9e99a 1402 _H<NSMutableDictionary> record_;
36bb2ca2 1403 BOOL trusted_;
21ac0ce2
JF
1404
1405 std::set<std::string> fetches_;
1406 std::set<std::string> files_;
1407 _transient NSObject<SourceDelegate> *delegate_;
dc5812ec
JF
1408}
1409
aec64d46 1410- (Source *) initWithMetaIndex:(metaIndex *)index forDatabase:(Database *)database inPool:(apr_pool_t *)pool;
dc5812ec 1411
90ba4f86 1412- (NSComparisonResult) compareByName:(Source *)source;
7b0ce2da 1413
6204f56a 1414- (NSString *) depictionForPackage:(NSString *)package;
dc63e78f
JF
1415- (NSString *) supportForPackage:(NSString *)package;
1416
0c28a403 1417- (metaIndex *) metaIndex;
7b0ce2da 1418- (NSDictionary *) record;
36bb2ca2 1419- (BOOL) trusted;
dc5812ec 1420
7bd76e97 1421- (NSString *) rooturi;
36bb2ca2
JF
1422- (NSString *) distribution;
1423- (NSString *) type;
8252b666 1424
7b0ce2da
JF
1425- (NSString *) key;
1426- (NSString *) host;
36bb2ca2 1427
7b0ce2da 1428- (NSString *) name;
8d262908 1429- (NSString *) shortDescription;
36bb2ca2
JF
1430- (NSString *) label;
1431- (NSString *) origin;
1432- (NSString *) version;
dc5812ec 1433
36bb2ca2 1434- (NSString *) defaultIcon;
7bd76e97 1435- (NSURL *) iconURL;
7b0ce2da 1436
21ac0ce2 1437- (void) setFetch:(bool)fetch forURI:(const char *)uri;
9ed626f1 1438- (void) resetFetch;
21ac0ce2 1439
dc5812ec 1440@end
dc5812ec 1441
36bb2ca2 1442@implementation Source
dc5812ec 1443
33e30380
JF
1444+ (NSString *) webScriptNameForSelector:(SEL)selector {
1445 if (false);
1446 else if (selector == @selector(addSection:))
1447 return @"addSection";
aec64d46
JF
1448 else if (selector == @selector(getField:))
1449 return @"getField";
33e30380
JF
1450 else if (selector == @selector(removeSection:))
1451 return @"removeSection";
1452 else if (selector == @selector(remove))
1453 return @"remove";
1454 else
1455 return nil;
1456}
1457
1458+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
1459 return [self webScriptNameForSelector:selector] == nil;
1460}
1461
6f1a15d9 1462+ (NSArray *) _attributeKeys {
e58ff941 1463 return [NSArray arrayWithObjects:
7bd76e97 1464 @"baseuri",
e58ff941
JF
1465 @"distribution",
1466 @"host",
1467 @"key",
7bd76e97 1468 @"iconuri",
e58ff941
JF
1469 @"label",
1470 @"name",
1471 @"origin",
7bd76e97 1472 @"rooturi",
33e30380 1473 @"sections",
8d262908 1474 @"shortDescription",
e58ff941
JF
1475 @"trusted",
1476 @"type",
e58ff941
JF
1477 @"version",
1478 nil];
6f1a15d9
JF
1479}
1480
1481- (NSArray *) attributeKeys {
1482 return [[self class] _attributeKeys];
1483}
1484
1485+ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1486 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1487}
1488
0c28a403
JF
1489- (metaIndex *) metaIndex {
1490 return index_;
1491}
1492
f9f6d9e8 1493- (void) setMetaIndex:(metaIndex *)index inPool:(apr_pool_t *)pool {
a3328c28
JF
1494 trusted_ = index->IsTrusted();
1495
f9f6d9e8
JF
1496 uri_.set(pool, index->GetURI());
1497 distribution_.set(pool, index->GetDist());
1498 type_.set(pool, index->GetType());
a3328c28
JF
1499
1500 debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index));
1501 if (dindex != NULL) {
21ac0ce2 1502 std::string file(dindex->MetaIndexURI(""));
21ac0ce2
JF
1503 base_.set(pool, file);
1504
3f88f205
JF
1505 pkgAcquire acquire;
1506 dindex->GetIndexes(&acquire, true);
1507 for (pkgAcquire::ItemIterator item(acquire.ItemsBegin()); item != acquire.ItemsEnd(); item++) {
1508 std::string file((*item)->DescURI());
1509 files_.insert(file);
1510 if (file.length() < sizeof("Packages.bz2") || file.substr(file.length() - sizeof("Packages.bz2")) != "/Packages.bz2")
1511 continue;
1512 file = file.substr(0, file.length() - 4);
21ac0ce2
JF
1513 files_.insert(file);
1514 files_.insert(file + ".gz");
21ac0ce2 1515 files_.insert(file + "Index");
3f88f205 1516 }
8252b666 1517
18873623
JF
1518 FileFd fd;
1519 if (!fd.Open(dindex->MetaIndexFile("Release"), FileFd::ReadOnly))
1520 _error->Discard();
1521 else {
1522 pkgTagFile tags(&fd);
f9f6d9e8 1523
18873623
JF
1524 pkgTagSection section;
1525 tags.Step(section);
a3328c28 1526
18873623
JF
1527 struct {
1528 const char *name_;
1529 CYString *value_;
1530 } names[] = {
1531 {"default-icon", &defaultIcon_},
6204f56a 1532 {"depiction", &depiction_},
18873623
JF
1533 {"description", &description_},
1534 {"label", &label_},
1535 {"origin", &origin_},
1536 {"support", &support_},
1537 {"version", &version_},
1538 };
f9f6d9e8 1539
18873623
JF
1540 for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i) {
1541 const char *start, *end;
1542
1543 if (section.Find(names[i].name_, start, end)) {
1544 CYString &value(*names[i].value_);
1545 value.set(pool, start, end - start);
1546 }
f9f6d9e8 1547 }
36bb2ca2 1548 }
a3328c28 1549 }
7b0ce2da 1550
a3328c28 1551 record_ = [Sources_ objectForKey:[self key]];
f9f6d9e8 1552
7623f855
JF
1553 NSURL *url([NSURL URLWithString:uri_]);
1554
1555 host_ = [url host];
1556 if (host_ != nil)
c08c8943 1557 host_ = [host_ lowercaseString];
7623f855
JF
1558
1559 if (host_ != nil)
dfdb9ae0 1560 authority_ = host_;
7623f855
JF
1561 else
1562 authority_ = [url path];
a3328c28
JF
1563}
1564
aec64d46 1565- (Source *) initWithMetaIndex:(metaIndex *)index forDatabase:(Database *)database inPool:(apr_pool_t *)pool {
a3328c28 1566 if ((self = [super init]) != nil) {
aec64d46
JF
1567 era_ = [database era];
1568 database_ = database;
1569 index_ = index;
1570
f9f6d9e8 1571 [self setMetaIndex:index inPool:pool];
36bb2ca2 1572 } return self;
2367a917 1573}
dc5812ec 1574
aec64d46
JF
1575- (NSString *) getField:(NSString *)name {
1576@synchronized (database_) {
1577 if ([database_ era] != era_ || index_ == NULL)
1578 return nil;
1579
1580 debReleaseIndex *dindex(dynamic_cast<debReleaseIndex *>(index_));
1581 if (dindex == NULL)
1582 return nil;
1583
1584 FileFd fd;
1585 if (!fd.Open(dindex->MetaIndexFile("Release"), FileFd::ReadOnly)) {
1586 _error->Discard();
1587 return nil;
1588 }
1589
1590 pkgTagFile tags(&fd);
1591
1592 pkgTagSection section;
1593 tags.Step(section);
1594
1595 const char *start, *end;
1596 if (!section.Find([name UTF8String], start, end))
1597 return (NSString *) [NSNull null];
1598
1599 return [NSString stringWithString:[(NSString *) CYStringCreate(start, end - start) autorelease]];
1600} }
1601
90ba4f86 1602- (NSComparisonResult) compareByName:(Source *)source {
7b0ce2da
JF
1603 NSString *lhs = [self name];
1604 NSString *rhs = [source name];
1605
1606 if ([lhs length] != 0 && [rhs length] != 0) {
1607 unichar lhc = [lhs characterAtIndex:0];
1608 unichar rhc = [rhs characterAtIndex:0];
1609
1610 if (isalpha(lhc) && !isalpha(rhc))
1611 return NSOrderedAscending;
1612 else if (!isalpha(lhc) && isalpha(rhc))
1613 return NSOrderedDescending;
1614 }
1615
68f1828e 1616 return [lhs compare:rhs options:LaxCompareOptions_];
7b0ce2da
JF
1617}
1618
6204f56a 1619- (NSString *) depictionForPackage:(NSString *)package {
89b0ea4a 1620 return depiction_.empty() ? nil : [static_cast<id>(depiction_) stringByReplacingOccurrencesOfString:@"*" withString:package];
6204f56a
JF
1621}
1622
dc63e78f 1623- (NSString *) supportForPackage:(NSString *)package {
89b0ea4a 1624 return support_.empty() ? nil : [static_cast<id>(support_) stringByReplacingOccurrencesOfString:@"*" withString:package];
dc63e78f
JF
1625}
1626
33e30380
JF
1627- (NSArray *) sections {
1628 return record_ == nil ? (id) [NSNull null] : [record_ objectForKey:@"Sections"] ?: [NSArray array];
1629}
1630
1631- (void) _addSection:(NSString *)section {
1632 if (record_ == nil)
1633 return;
1634 else if (NSMutableArray *sections = [record_ objectForKey:@"Sections"]) {
1635 if (![sections containsObject:section]) {
1636 [sections addObject:section];
1637 Changed_ = true;
1638 }
1639 } else {
1640 [record_ setObject:[NSMutableArray arrayWithObject:section] forKey:@"Sections"];
1641 Changed_ = true;
1642 }
1643}
1644
1645- (bool) addSection:(NSString *)section {
1646 if (record_ == nil)
1647 return false;
1648
1649 [self performSelectorOnMainThread:@selector(_addSection:) withObject:section waitUntilDone:NO];
1650 return true;
1651}
1652
1653- (void) _removeSection:(NSString *)section {
1654 if (record_ == nil)
1655 return;
1656
1657 if (NSMutableArray *sections = [record_ objectForKey:@"Sections"])
1658 if ([sections containsObject:section]) {
1659 [sections removeObject:section];
1660 Changed_ = true;
1661 }
1662}
1663
1664- (bool) removeSection:(NSString *)section {
1665 if (record_ == nil)
1666 return false;
1667
1668 [self performSelectorOnMainThread:@selector(_removeSection:) withObject:section waitUntilDone:NO];
1669 return true;
1670}
1671
1672- (void) _remove {
1673 [Sources_ removeObjectForKey:[self key]];
1674 Changed_ = true;
1675}
1676
1677- (bool) remove {
1678 bool value(record_ != nil);
1679 [self performSelectorOnMainThread:@selector(_remove) withObject:nil waitUntilDone:NO];
1680 return value;
1681}
1682
7b0ce2da
JF
1683- (NSDictionary *) record {
1684 return record_;
1685}
1686
36bb2ca2
JF
1687- (BOOL) trusted {
1688 return trusted_;
1689}
3178d79b 1690
7bd76e97 1691- (NSString *) rooturi {
36bb2ca2
JF
1692 return uri_;
1693}
ec97ef06 1694
36bb2ca2
JF
1695- (NSString *) distribution {
1696 return distribution_;
1697}
dc5812ec 1698
36bb2ca2
JF
1699- (NSString *) type {
1700 return type_;
dc5812ec
JF
1701}
1702
7bd76e97
JF
1703- (NSString *) baseuri {
1704 return base_.empty() ? nil : (id) base_;
1705}
1706
1707- (NSString *) iconuri {
1708 if (NSString *base = [self baseuri])
1709 return [base stringByAppendingString:@"CydiaIcon.png"];
1710
1711 return nil;
1712}
1713
1714- (NSURL *) iconURL {
1715 if (NSString *uri = [self iconuri])
1716 return [NSURL URLWithString:uri];
1717 return nil;
8252b666
JF
1718}
1719
7b0ce2da 1720- (NSString *) key {
f9f6d9e8 1721 return [NSString stringWithFormat:@"%@:%@:%@", (NSString *) type_, (NSString *) uri_, (NSString *) distribution_];
7b0ce2da
JF
1722}
1723
1724- (NSString *) host {
f9f6d9e8 1725 return host_;
7b0ce2da
JF
1726}
1727
1728- (NSString *) name {
c08c8943 1729 return origin_.empty() ? (id) authority_ : origin_;
7b0ce2da
JF
1730}
1731
8d262908 1732- (NSString *) shortDescription {
36bb2ca2
JF
1733 return description_;
1734}
b4d89997 1735
36bb2ca2 1736- (NSString *) label {
c08c8943 1737 return label_.empty() ? (id) authority_ : label_;
36bb2ca2 1738}
3178d79b 1739
36bb2ca2
JF
1740- (NSString *) origin {
1741 return origin_;
1742}
3178d79b 1743
36bb2ca2
JF
1744- (NSString *) version {
1745 return version_;
1746}
2367a917 1747
36bb2ca2
JF
1748- (NSString *) defaultIcon {
1749 return defaultIcon_;
1750}
2367a917 1751
21ac0ce2
JF
1752- (void) setDelegate:(NSObject<SourceDelegate> *)delegate {
1753 delegate_ = delegate;
1754}
1755
1756- (bool) fetch {
1757 return !fetches_.empty();
1758}
1759
1760- (void) setFetch:(bool)fetch forURI:(const char *)uri {
1761 if (!fetch) {
1762 if (fetches_.erase(uri) == 0)
1763 return;
1764 } else if (files_.find(uri) == files_.end())
1765 return;
1766 else if (!fetches_.insert(uri).second)
1767 return;
1768
1769 [delegate_ performSelectorOnMainThread:@selector(setFetch:) withObject:[NSNumber numberWithBool:[self fetch]] waitUntilDone:NO];
1770}
1771
9ed626f1
JF
1772- (void) resetFetch {
1773 fetches_.clear();
1774 [delegate_ performSelectorOnMainThread:@selector(setFetch:) withObject:[NSNumber numberWithBool:NO] waitUntilDone:NO];
1775}
1776
2388b078
JF
1777@end
1778/* }}} */
83682c75
JF
1779/* CydiaOperation Class {{{ */
1780@interface CydiaOperation : NSObject {
7b33d201
JF
1781 _H<NSString> operator_;
1782 _H<NSString> value_;
2388b078
JF
1783}
1784
83682c75
JF
1785- (NSString *) operator;
1786- (NSString *) value;
2388b078
JF
1787
1788@end
1789
83682c75 1790@implementation CydiaOperation
2388b078 1791
83682c75
JF
1792- (id) initWithOperator:(const char *)_operator value:(const char *)value {
1793 if ((self = [super init]) != nil) {
7b33d201
JF
1794 operator_ = [NSString stringWithUTF8String:_operator];
1795 value_ = [NSString stringWithUTF8String:value];
83682c75
JF
1796 } return self;
1797}
1798
1799+ (NSArray *) _attributeKeys {
1800 return [NSArray arrayWithObjects:
1801 @"operator",
1802 @"value",
1803 nil];
2388b078
JF
1804}
1805
83682c75
JF
1806- (NSArray *) attributeKeys {
1807 return [[self class] _attributeKeys];
2388b078
JF
1808}
1809
83682c75
JF
1810+ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1811 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1812}
1813
1814- (NSString *) operator {
1815 return operator_;
1816}
1817
1818- (NSString *) value {
1819 return value_;
1820}
1821
1822@end
1823/* }}} */
810c9763
JF
1824/* CydiaClause Class {{{ */
1825@interface CydiaClause : NSObject {
7b33d201
JF
1826 _H<NSString> package_;
1827 _H<CydiaOperation> version_;
83682c75
JF
1828}
1829
83682c75
JF
1830- (NSString *) package;
1831- (CydiaOperation *) version;
1832
1833@end
1834
810c9763 1835@implementation CydiaClause
83682c75 1836
83682c75
JF
1837- (id) initWithIterator:(pkgCache::DepIterator &)dep {
1838 if ((self = [super init]) != nil) {
7b33d201 1839 package_ = [NSString stringWithUTF8String:dep.TargetPkg().Name()];
83682c75
JF
1840
1841 if (const char *version = dep.TargetVer())
7b33d201 1842 version_ = [[[CydiaOperation alloc] initWithOperator:dep.CompType() value:version] autorelease];
83682c75 1843 else
7b33d201 1844 version_ = (id) [NSNull null];
83682c75
JF
1845 } return self;
1846}
1847
1848+ (NSArray *) _attributeKeys {
1849 return [NSArray arrayWithObjects:
1850 @"package",
83682c75
JF
1851 @"version",
1852 nil];
1853}
1854
1855- (NSArray *) attributeKeys {
1856 return [[self class] _attributeKeys];
1857}
1858
1859+ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1860 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1861}
1862
83682c75
JF
1863- (NSString *) package {
1864 return package_;
1865}
1866
1867- (CydiaOperation *) version {
1868 return version_;
2388b078
JF
1869}
1870
810c9763
JF
1871@end
1872/* }}} */
1873/* CydiaRelation Class {{{ */
1874@interface CydiaRelation : NSObject {
7b33d201
JF
1875 _H<NSString> relationship_;
1876 _H<NSMutableArray> clauses_;
810c9763
JF
1877}
1878
1879- (NSString *) relationship;
1880- (NSArray *) clauses;
1881
1882@end
1883
1884@implementation CydiaRelation
1885
810c9763
JF
1886- (id) initWithIterator:(pkgCache::DepIterator &)dep {
1887 if ((self = [super init]) != nil) {
7b33d201
JF
1888 relationship_ = [NSString stringWithUTF8String:dep.DepType()];
1889 clauses_ = [NSMutableArray arrayWithCapacity:8];
810c9763
JF
1890
1891 pkgCache::DepIterator start;
1892 pkgCache::DepIterator end;
1893 dep.GlobOr(start, end); // ++dep
1894
1895 _forever {
1896 [clauses_ addObject:[[[CydiaClause alloc] initWithIterator:start] autorelease]];
1897
1898 // yes, seriously. (wtf?)
1899 if (start == end)
1900 break;
1901 ++start;
1902 }
1903 } return self;
1904}
1905
1906+ (NSArray *) _attributeKeys {
1907 return [NSArray arrayWithObjects:
1908 @"clauses",
1909 @"relationship",
1910 nil];
1911}
1912
1913- (NSArray *) attributeKeys {
1914 return [[self class] _attributeKeys];
1915}
1916
1917+ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
1918 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
1919}
1920
1921- (NSString *) relationship {
1922 return relationship_;
1923}
1924
1925- (NSArray *) clauses {
1926 return clauses_;
1927}
1928
1929- (void) addClause:(CydiaClause *)clause {
1930 [clauses_ addObject:clause];
1931}
1932
dc5812ec 1933@end
b4d89997 1934/* }}} */
36bb2ca2 1935/* Package Class {{{ */
12016ee5 1936struct ParsedPackage {
974742b2 1937 CYString md5sum_;
12016ee5
JF
1938 CYString tagline_;
1939
a412f357 1940 CYString architecture_;
12016ee5
JF
1941 CYString icon_;
1942
1943 CYString depiction_;
1944 CYString homepage_;
12016ee5
JF
1945 CYString author_;
1946
12016ee5
JF
1947 CYString support_;
1948};
1949
36bb2ca2 1950@interface Package : NSObject {
13902177 1951 uint32_t era_ : 25;
89bdef78 1952 @public uint32_t role_ : 3;
aab28f8b
JF
1953 uint32_t essential_ : 1;
1954 uint32_t obsolete_ : 1;
1955 uint32_t ignored_ : 1;
13902177 1956 uint32_t pooled_ : 1;
aab28f8b 1957
68d927e2 1958 apr_pool_t *pool_;
a1440b10 1959
9c5737d5
JF
1960 uint32_t rank_;
1961
aab28f8b
JF
1962 _transient Database *database_;
1963
7376b55c 1964 pkgCache::VerIterator version_;
36bb2ca2 1965 pkgCache::PkgIterator iterator_;
36bb2ca2 1966 pkgCache::VerFileIterator file_;
bbb879fb 1967
aab28f8b
JF
1968 CYString id_;
1969 CYString name_;
807ae6d7 1970
4c0ed943 1971 CYString latest_;
6a155117 1972 CYString installed_;
81bf4684 1973 time_t updated_;
dc5812ec 1974
fe33a23e 1975 const char *section_;
aab28f8b 1976 _transient NSString *section$_;
f79a4512 1977
7b33d201 1978 _H<Source> source_;
2388b078 1979
bb6bb6d6 1980 PackageValue *metadata_;
aab28f8b 1981 ParsedPackage *parsed_;
94b0b3e5 1982
7b33d201 1983 _H<NSMutableArray> tags_;
b4d89997
JF
1984}
1985
7376b55c 1986- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
f79a4512 1987+ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database;
b4d89997 1988
2388b078 1989- (pkgCache::PkgIterator) iterator;
68d927e2 1990- (void) parse;
2388b078 1991
36bb2ca2 1992- (NSString *) section;
dec6029f
JF
1993- (NSString *) simpleSection;
1994
f79a4512
JF
1995- (NSString *) longSection;
1996- (NSString *) shortSection;
1997
a3328c28
JF
1998- (NSString *) uri;
1999
7aa82ca2 2000- (MIMEAddress *) maintainer;
36bb2ca2 2001- (size_t) size;
eef4ccaf
JF
2002- (NSString *) longDescription;
2003- (NSString *) shortDescription;
808c6eb6 2004- (unichar) index;
dc5812ec 2005
94b0b3e5 2006- (PackageValue *) metadata;
31bc18a7 2007- (time_t) seen;
94b0b3e5
JF
2008
2009- (bool) subscribed;
2010- (bool) setSubscribed:(bool)subscribed;
2011
807ae6d7 2012- (BOOL) ignored;
9e98e020 2013
36bb2ca2
JF
2014- (NSString *) latest;
2015- (NSString *) installed;
6a155117 2016- (BOOL) uninstalled;
5a09ae08
JF
2017
2018- (BOOL) valid;
31f3cfff 2019- (BOOL) upgradableAndEssential:(BOOL)essential;
36bb2ca2
JF
2020- (BOOL) essential;
2021- (BOOL) broken;
7d2ac47f 2022- (BOOL) unfiltered;
6d9712c4 2023- (BOOL) visible;
b4d89997 2024
9bedffaa
JF
2025- (BOOL) half;
2026- (BOOL) halfConfigured;
2027- (BOOL) halfInstalled;
2028- (BOOL) hasMode;
2029- (NSString *) mode;
2030
36bb2ca2
JF
2031- (NSString *) id;
2032- (NSString *) name;
770f2a8e 2033- (UIImage *) icon;
6f1a15d9 2034- (NSString *) homepage;
25a2158d 2035- (NSString *) depiction;
7aa82ca2 2036- (MIMEAddress *) author;
b4d89997 2037
dc63e78f
JF
2038- (NSString *) support;
2039
affeffc7 2040- (NSArray *) files;
affeffc7
JF
2041- (NSArray *) warnings;
2042- (NSArray *) applications;
2388b078 2043
36bb2ca2 2044- (Source *) source;
b4d89997 2045
9c5737d5 2046- (uint32_t) rank;
d84597fe 2047- (BOOL) matches:(NSArray *)query;
b4d89997 2048
6d9712c4 2049- (BOOL) hasTag:(NSString *)tag;
c390d3ab 2050- (NSString *) primaryPurpose;
770f2a8e 2051- (NSArray *) purposes;
3bd1c2a2 2052- (bool) isCommercial;
6d9712c4 2053
cd95e390
JF
2054- (void) setIndex:(size_t)index;
2055
df213583
JF
2056- (CYString &) cyname;
2057
f79a4512 2058- (uint32_t) compareBySection:(NSArray *)sections;
807ae6d7 2059
36bb2ca2
JF
2060- (void) install;
2061- (void) remove;
06aa974d 2062
36bb2ca2 2063@end
b4d89997 2064
f79a4512
JF
2065uint32_t PackageChangesRadix(Package *self, void *) {
2066 union {
2067 uint32_t key;
2068
2069 struct {
2070 uint32_t timestamp : 30;
2071 uint32_t ignored : 1;
2072 uint32_t upgradable : 1;
2073 } bits;
2074 } value;
2075
2076 bool upgradable([self upgradableAndEssential:YES]);
2077 value.bits.upgradable = upgradable ? 1 : 0;
2078
2079 if (upgradable) {
2080 value.bits.timestamp = 0;
2081 value.bits.ignored = [self ignored] ? 0 : 1;
2082 value.bits.upgradable = 1;
2083 } else {
36047c66 2084 value.bits.timestamp = [self seen] >> 2;
f79a4512
JF
2085 value.bits.ignored = 0;
2086 value.bits.upgradable = 0;
2087 }
2088
2089 return _not(uint32_t) - value.key;
2090}
2091
df213583
JF
2092uint32_t PackagePrefixRadix(Package *self, void *context) {
2093 size_t offset(reinterpret_cast<size_t>(context));
2094 CYString &name([self cyname]);
677b8415 2095
df213583
JF
2096 size_t size(name.size());
2097 if (size == 0)
2098 return 0;
2099 char *text(name.data());
677b8415 2100
df213583
JF
2101 size_t zeros;
2102 if (!isdigit(text[0]))
2103 zeros = 0;
2104 else {
2105 size_t digits(1);
2106 while (size != digits && isdigit(text[digits]))
2107 if (++digits == 4)
2108 break;
2109 zeros = 4 - digits;
2110 }
677b8415 2111
df213583
JF
2112 uint8_t data[4];
2113
df213583
JF
2114 if (offset == 0 && zeros != 0) {
2115 memset(data, '0', zeros);
2116 memcpy(data + zeros, text, 4 - zeros);
2117 } else {
2118 /* XXX: there's some danger here if you request a non-zero offset < 4 and it gets zero padded */
2119 if (size <= offset - zeros)
2120 return 0;
2121
2122 text += offset - zeros;
2123 size -= offset - zeros;
2124
2125 if (size >= 4)
2126 memcpy(data, text, 4);
2127 else {
2128 memcpy(data, text, size);
2129 memset(data + size, 0, 4 - size);
2130 }
2131
2132 for (size_t i(0); i != 4; ++i)
2133 if (isalpha(data[i]))
a7a59ee1 2134 data[i] |= 0x20;
df213583
JF
2135 }
2136
2137 if (offset == 0)
a7a59ee1
JF
2138 if (data[0] == '@')
2139 data[0] = 0x7f;
2140 else
2141 data[0] = (data[0] & 0x1f) | "\x80\x00\xc0\x40"[data[0] >> 6];
df213583
JF
2142
2143 /* XXX: ntohl may be more honest */
2144 return OSSwapInt32(*reinterpret_cast<uint32_t *>(data));
2145}
2146
2147CYString &(*PackageName)(Package *self, SEL sel);
2148
2149CFComparisonResult PackageNameCompare(Package *lhs, Package *rhs, void *arg) {
2150 _profile(PackageNameCompare)
2151 CYString &lhi(PackageName(lhs, @selector(cyname)));
2152 CYString &rhi(PackageName(rhs, @selector(cyname)));
2153 CFStringRef lhn(lhi), rhn(rhi);
677b8415 2154
5358f56f 2155 if (lhn == NULL)
b129e6d9 2156 return rhn == NULL ? kCFCompareEqualTo : kCFCompareLessThan;
5358f56f 2157 else if (rhn == NULL)
b129e6d9 2158 return kCFCompareGreaterThan;
5358f56f 2159
677b8415 2160 _profile(PackageNameCompare$NumbersLast)
df213583 2161 if (!lhi.empty() && !rhi.empty()) {
677b8415
JF
2162 UniChar lhc(CFStringGetCharacterAtIndex(lhn, 0));
2163 UniChar rhc(CFStringGetCharacterAtIndex(rhn, 0));
2164 bool lha(CFUniCharIsMemberOf(lhc, kCFUniCharLetterCharacterSet));
2165 if (lha != CFUniCharIsMemberOf(rhc, kCFUniCharLetterCharacterSet))
b129e6d9 2166 return lha ? kCFCompareLessThan : kCFCompareGreaterThan;
677b8415
JF
2167 }
2168 _end
2169
df213583
JF
2170 CFIndex length = CFStringGetLength(lhn);
2171
677b8415
JF
2172 _profile(PackageNameCompare$Compare)
2173 return CFStringCompareWithOptionsAndLocale(lhn, rhn, CFRangeMake(0, length), LaxCompareFlags_, Locale_);
2174 _end
2175 _end
2176}
2177
eef4ccaf
JF
2178CFComparisonResult PackageNameCompare_(Package **lhs, Package **rhs, void *context) {
2179 return PackageNameCompare(*lhs, *rhs, context);
677b8415
JF
2180}
2181
2182struct PackageNameOrdering :
2183 std::binary_function<Package *, Package *, bool>
2184{
2185 _finline bool operator ()(Package *lhs, Package *rhs) const {
b129e6d9 2186 return PackageNameCompare(lhs, rhs, NULL) == kCFCompareLessThan;
677b8415
JF
2187 }
2188};
2189
36bb2ca2 2190@implementation Package
b4d89997 2191
df213583
JF
2192- (NSString *) description {
2193 return [NSString stringWithFormat:@"<Package:%@>", static_cast<NSString *>(name_)];
2194}
2195
36bb2ca2 2196- (void) dealloc {
13902177
JF
2197 if (!pooled_)
2198 apr_pool_destroy(pool_);
12016ee5
JF
2199 if (parsed_ != NULL)
2200 delete parsed_;
36bb2ca2 2201 [super dealloc];
b4d89997
JF
2202}
2203
3bd1c2a2 2204+ (NSString *) webScriptNameForSelector:(SEL)selector {
e58ff941 2205 if (false);
10d63f9e
JF
2206 else if (selector == @selector(clear))
2207 return @"clear";
2cd1afd9
JF
2208 else if (selector == @selector(getField:))
2209 return @"getField";
8fb7c7a1
JF
2210 else if (selector == @selector(getRecord))
2211 return @"getRecord";
e58ff941 2212 else if (selector == @selector(hasTag:))
3bd1c2a2 2213 return @"hasTag";
10d63f9e
JF
2214 else if (selector == @selector(install))
2215 return @"install";
2216 else if (selector == @selector(remove))
2217 return @"remove";
3bd1c2a2
JF
2218 else
2219 return nil;
2220}
2221
2222+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
2223 return [self webScriptNameForSelector:selector] == nil;
2224}
2225
6f1a15d9 2226+ (NSArray *) _attributeKeys {
e58ff941
JF
2227 return [NSArray arrayWithObjects:
2228 @"applications",
a412f357 2229 @"architecture",
e58ff941
JF
2230 @"author",
2231 @"depiction",
2232 @"essential",
2233 @"homepage",
2234 @"icon",
2235 @"id",
2236 @"installed",
2237 @"latest",
2238 @"longDescription",
2239 @"longSection",
2240 @"maintainer",
974742b2 2241 @"md5sum",
e58ff941
JF
2242 @"mode",
2243 @"name",
2244 @"purposes",
83682c75 2245 @"relations",
e58ff941 2246 @"section",
de1ace71 2247 @"selection",
e58ff941
JF
2248 @"shortDescription",
2249 @"shortSection",
2250 @"simpleSection",
2251 @"size",
2252 @"source",
5959b596 2253 @"state",
e58ff941 2254 @"support",
82aa2434 2255 @"tags",
e58ff941
JF
2256 @"warnings",
2257 nil];
6f1a15d9
JF
2258}
2259
2260- (NSArray *) attributeKeys {
2261 return [[self class] _attributeKeys];
2262}
2263
2264+ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
2265 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
2266}
2267
83682c75
JF
2268- (NSArray *) relations {
2269@synchronized (database_) {
2270 NSMutableArray *relations([NSMutableArray arrayWithCapacity:16]);
810c9763
JF
2271 for (pkgCache::DepIterator dep(version_.DependsList()); !dep.end(); ++dep)
2272 [relations addObject:[[[CydiaRelation alloc] initWithIterator:dep] autorelease]];
83682c75
JF
2273 return relations;
2274} }
2275
a412f357
JF
2276- (NSString *) architecture {
2277 [self parse];
2278@synchronized (database_) {
2279 return parsed_->architecture_.empty() ? [NSNull null] : (id) parsed_->architecture_;
2280} }
2281
2cd1afd9
JF
2282- (NSString *) getField:(NSString *)name {
2283@synchronized (database_) {
2284 if ([database_ era] != era_ || file_.end())
2285 return nil;
2286
2287 pkgRecords::Parser &parser([database_ records]->Lookup(file_));
2288
2289 const char *start, *end;
2290 if (!parser.Find([name UTF8String], start, end))
2291 return (NSString *) [NSNull null];
2292
b6f9e52a 2293 return [NSString stringWithString:[(NSString *) CYStringCreate(start, end - start) autorelease]];
2cd1afd9
JF
2294} }
2295
8fb7c7a1
JF
2296- (NSString *) getRecord {
2297@synchronized (database_) {
2298 if ([database_ era] != era_ || file_.end())
2299 return nil;
2300
2301 pkgRecords::Parser &parser([database_ records]->Lookup(file_));
2302
2303 const char *start, *end;
2304 parser.GetRec(start, end);
2305
2306 return [NSString stringWithString:[(NSString *) CYStringCreate(start, end - start) autorelease]];
2307} }
2308
68d927e2 2309- (void) parse {
12016ee5 2310 if (parsed_ != NULL)
68d927e2 2311 return;
12016ee5
JF
2312@synchronized (database_) {
2313 if ([database_ era] != era_ || file_.end())
68d927e2
JF
2314 return;
2315
12016ee5
JF
2316 ParsedPackage *parsed(new ParsedPackage);
2317 parsed_ = parsed;
2318
68d927e2
JF
2319 _profile(Package$parse)
2320 pkgRecords::Parser *parser;
2321
2322 _profile(Package$parse$Lookup)
2323 parser = &[database_ records]->Lookup(file_);
2324 _end
2325
00067a67 2326 CYString bugs;
68d927e2
JF
2327 CYString website;
2328
2329 _profile(Package$parse$Find)
2330 struct {
2331 const char *name_;
2332 CYString *value_;
2333 } names[] = {
a412f357 2334 {"architecture", &parsed->architecture_},
12016ee5
JF
2335 {"icon", &parsed->icon_},
2336 {"depiction", &parsed->depiction_},
2337 {"homepage", &parsed->homepage_},
68d927e2 2338 {"website", &website},
00067a67 2339 {"bugs", &bugs},
12016ee5 2340 {"support", &parsed->support_},
12016ee5 2341 {"author", &parsed->author_},
974742b2 2342 {"md5sum", &parsed->md5sum_},
68d927e2
JF
2343 };
2344
2345 for (size_t i(0); i != sizeof(names) / sizeof(names[0]); ++i) {
2346 const char *start, *end;
2347
2348 if (parser->Find(names[i].name_, start, end)) {
2349 CYString &value(*names[i].value_);
2350 _profile(Package$parse$Value)
2351 value.set(pool_, start, end - start);
2352 _end
2353 }
2354 }
2355 _end
2356
2357 _profile(Package$parse$Tagline)
2358 const char *start, *end;
0dd0c302 2359 if (parser->ShortDesc(start, end)) {
68d927e2
JF
2360 const char *stop(reinterpret_cast<const char *>(memchr(start, '\n', end - start)));
2361 if (stop == NULL)
2362 stop = end;
2363 while (stop != start && stop[-1] == '\r')
2364 --stop;
12016ee5 2365 parsed->tagline_.set(pool_, start, stop - start);
68d927e2
JF
2366 }
2367 _end
2368
2369 _profile(Package$parse$Retain)
12016ee5
JF
2370 if (parsed->homepage_.empty())
2371 parsed->homepage_ = website;
2372 if (parsed->homepage_ == parsed->depiction_)
2373 parsed->homepage_.clear();
00067a67
JF
2374 if (parsed->support_.empty())
2375 parsed->support_ = bugs;
68d927e2
JF
2376 _end
2377 _end
12016ee5 2378} }
68d927e2 2379
7376b55c 2380- (Package *) initWithVersion:(pkgCache::VerIterator)version withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database {
a1440b10 2381 if ((self = [super init]) != nil) {
7376b55c 2382 _profile(Package$initWithVersion)
13902177
JF
2383 if (pool == NULL)
2384 apr_pool_create(&pool_, NULL);
2385 else {
2386 pool_ = pool;
2387 pooled_ = true;
2388 }
a1440b10 2389
36bb2ca2 2390 database_ = database;
aab28f8b 2391 era_ = [database era];
b4d89997 2392
aab28f8b 2393 version_ = version;
2083b866 2394
aab28f8b
JF
2395 pkgCache::PkgIterator iterator(version.ParentPkg());
2396 iterator_ = iterator;
06aa974d 2397
aab28f8b 2398 _profile(Package$initWithVersion$Version)
68d927e2
JF
2399 if (!version_.end())
2400 file_ = version_.FileList();
2401 else {
2402 pkgCache &cache([database_ cache]);
2403 file_ = pkgCache::VerFileIterator(cache, cache.VerFileP);
2404 }
2405 _end
808c6eb6 2406
aab28f8b 2407 _profile(Package$initWithVersion$Cache)
aab28f8b
JF
2408 name_.set(NULL, iterator.Display());
2409
2410 latest_.set(NULL, StripVersion_(version_.VerStr()));
2411
2412 pkgCache::VerIterator current(iterator.CurrentVer());
2413 if (!current.end())
2414 installed_.set(NULL, StripVersion_(current.VerStr()));
808c6eb6
JF
2415 _end
2416
7376b55c 2417 _profile(Package$initWithVersion$Tags)
aab28f8b 2418 pkgCache::TagIterator tag(iterator.TagList());
0dd0c302 2419 if (!tag.end()) {
7b33d201 2420 tags_ = [NSMutableArray arrayWithCapacity:8];
5b625a2e
JF
2421
2422 goto tag; for (; !tag.end(); ++tag) tag: {
0dd0c302 2423 const char *name(tag.Name());
5b625a2e
JF
2424 NSString *string((NSString *) CYStringCreate(name));
2425 if (string == nil)
2426 continue;
2427
2428 [tags_ addObject:[string autorelease]];
0a377825 2429
97a3d89e
JF
2430 if (role_ == 0 && strncmp(name, "role::", 6) == 0 /*&& strcmp(name, "role::leaper") != 0*/) {
2431 if (strcmp(name + 6, "enduser") == 0)
2432 role_ = 1;
2433 else if (strcmp(name + 6, "hacker") == 0)
2434 role_ = 2;
2435 else if (strcmp(name + 6, "developer") == 0)
2436 role_ = 3;
2437 else if (strcmp(name + 6, "cydia") == 0)
2438 role_ = 7;
2439 else
2440 role_ = 4;
2441 }
0a377825
JF
2442
2443 if (strncmp(name, "cydia::", 7) == 0) {
2444 if (strcmp(name + 7, "essential") == 0)
2445 essential_ = true;
2446 else if (strcmp(name + 7, "obsolete") == 0)
2447 obsolete_ = true;
2448 }
5b625a2e 2449 }
0dd0c302 2450 }
808c6eb6 2451 _end
7b0ce2da 2452
7376b55c 2453 _profile(Package$initWithVersion$Metadata)
029c8b74
JF
2454 const char *mixed(iterator.Name());
2455 size_t size(strlen(mixed));
81bf4684
JF
2456 static const size_t prefix(sizeof("/var/lib/dpkg/info/") - 1);
2457 char lower[prefix + size + 5 + 1];
029c8b74
JF
2458
2459 for (size_t i(0); i != size; ++i)
81bf4684
JF
2460 lower[prefix + i] = mixed[i] | 0x20;
2461
2462 if (!installed_.empty()) {
2463 memcpy(lower, "/var/lib/dpkg/info/", prefix);
2464 memcpy(lower + prefix + size, ".list", 6);
2465 struct stat info;
2466 if (stat(lower, &info) != -1)
2467 updated_ = info.st_birthtime;
2468 }
029c8b74 2469
81bf4684 2470 PackageValue *metadata(PackageFind(lower + prefix, size));
bb6bb6d6 2471 metadata_ = metadata;
677b8415 2472
029c8b74
JF
2473 id_.set(NULL, metadata->name_, size);
2474
94b0b3e5
JF
2475 const char *latest(version_.VerStr());
2476 size_t length(strlen(latest));
a4b0ec52 2477
94b0b3e5 2478 uint16_t vhash(hashlittle(latest, length));
677b8415 2479
94b0b3e5
JF
2480 size_t capped(std::min<size_t>(8, length));
2481 latest = latest + length - capped;
677b8415 2482
94b0b3e5
JF
2483 if (metadata->first_ == 0)
2484 metadata->first_ = now_;
808c6eb6 2485
94b0b3e5 2486 if (metadata->vhash_ != vhash || strncmp(metadata->version_, latest, sizeof(metadata->version_)) != 0) {
94b0b3e5
JF
2487 strncpy(metadata->version_, latest, sizeof(metadata->version_));
2488 metadata->vhash_ = vhash;
2f856365
JF
2489 metadata->last_ = now_;
2490 } else if (metadata->last_ == 0)
2491 metadata->last_ = metadata->first_;
808c6eb6 2492 _end
a1440b10 2493
7376b55c 2494 _profile(Package$initWithVersion$Section)
0bc841de 2495 section_ = version_.Section();
f79a4512 2496 _end
a1440b10 2497
aab28f8b
JF
2498 _profile(Package$initWithVersion$Flags)
2499 essential_ |= ((iterator->Flags & pkgCache::Flag::Essential) == 0 ? NO : YES);
2500 ignored_ = iterator->SelectedState == pkgCache::State::Hold;
4fa77608 2501 _end
3dd53516 2502 _end } return self;
dc5812ec
JF
2503}
2504
f79a4512 2505+ (Package *) packageWithIterator:(pkgCache::PkgIterator)iterator withZone:(NSZone *)zone inPool:(apr_pool_t *)pool database:(Database *)database {
b1ce61ec
JF
2506 pkgCache::VerIterator version;
2507
2508 _profile(Package$packageWithIterator$GetCandidateVer)
2509 version = [database policy]->GetCandidateVer(iterator);
2510 _end
2511
7376b55c
JF
2512 if (version.end())
2513 return nil;
b1ce61ec 2514
8564efc1
JF
2515 Package *package;
2516
2517 _profile(Package$packageWithIterator$Allocate)
2518 package = [Package allocWithZone:zone];
2519 _end
2520
2521 _profile(Package$packageWithIterator$Initialize)
2522 package = [package
2523 initWithVersion:version
2524 withZone:zone
2525 inPool:pool
2526 database:database
2527 ];
2528 _end
2529
2530 _profile(Package$packageWithIterator$Autorelease)
2531 package = [package autorelease];
2532 _end
2533
2534 return package;
3dd53516 2535}
dc5812ec 2536
2388b078
JF
2537- (pkgCache::PkgIterator) iterator {
2538 return iterator_;
2539}
2540
36bb2ca2 2541- (NSString *) section {
f79a4512 2542 if (section$_ == nil) {
fe33a23e 2543 if (section_ == NULL)
f79a4512
JF
2544 return nil;
2545
fe33a23e
JF
2546 _profile(Package$section$mappedSectionForPointer)
2547 section$_ = [database_ mappedSectionForPointer:section_];
e4f3d6e9 2548 _end
f79a4512 2549 } return section$_;
dc5812ec
JF
2550}
2551
dec6029f
JF
2552- (NSString *) simpleSection {
2553 if (NSString *section = [self section])
2554 return Simplify(section);
2555 else
2556 return nil;
a3328c28 2557}
dec6029f 2558
f79a4512 2559- (NSString *) longSection {
18873623 2560 return LocalizeSection([self section]);
f79a4512
JF
2561}
2562
2563- (NSString *) shortSection {
2564 return [[NSBundle mainBundle] localizedStringForKey:[self simpleSection] value:nil table:@"Sections"];
2565}
2566
a3328c28
JF
2567- (NSString *) uri {
2568 return nil;
2569#if 0
2570 pkgIndexFile *index;
2571 pkgCache::PkgFileIterator file(file_.File());
2572 if (![database_ list].FindIndex(file, index))
2573 return nil;
2574 return [NSString stringWithUTF8String:iterator_->Path];
2575 //return [NSString stringWithUTF8String:file.Site()];
2576 //return [NSString stringWithUTF8String:index->ArchiveURI(file.FileName()).c_str()];
2577#endif
dec6029f
JF
2578}
2579
7aa82ca2 2580- (MIMEAddress *) maintainer {
884171d6
JF
2581@synchronized (database_) {
2582 if ([database_ era] != era_ || file_.end())
5a09ae08 2583 return nil;
884171d6 2584
36bb2ca2 2585 pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
00e2109e 2586 const std::string &maintainer(parser->Maintainer());
7aa82ca2 2587 return maintainer.empty() ? nil : [MIMEAddress addressWithString:[NSString stringWithUTF8String:maintainer.c_str()]];
884171d6 2588} }
8fe19fc1 2589
974742b2
JF
2590- (NSString *) md5sum {
2591 return parsed_ == NULL ? nil : (id) parsed_->md5sum_;
2592}
2593
36bb2ca2 2594- (size_t) size {
884171d6
JF
2595@synchronized (database_) {
2596 if ([database_ era] != era_ || version_.end())
2597 return 0;
2598
2599 return version_->InstalledSize;
2600} }
dc5812ec 2601
eef4ccaf 2602- (NSString *) longDescription {
3dd53516
JF
2603@synchronized (database_) {
2604 if ([database_ era] != era_ || file_.end())
5a09ae08 2605 return nil;
3dd53516 2606
36bb2ca2 2607 pkgRecords::Parser *parser = &[database_ records]->Lookup(file_);
2388b078 2608 NSString *description([NSString stringWithUTF8String:parser->LongDesc().c_str()]);
8fe19fc1 2609
36bb2ca2
JF
2610 NSArray *lines = [description componentsSeparatedByString:@"\n"];
2611 NSMutableArray *trimmed = [NSMutableArray arrayWithCapacity:([lines count] - 1)];
2612 if ([lines count] < 2)
2613 return nil;
3178d79b 2614
36bb2ca2 2615 NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
c4dcf2c2 2616 for (size_t i(1), e([lines count]); i != e; ++i) {
36bb2ca2
JF
2617 NSString *trim = [[lines objectAtIndex:i] stringByTrimmingCharactersInSet:whitespace];
2618 [trimmed addObject:trim];
2619 }
3178d79b 2620
36bb2ca2 2621 return [trimmed componentsJoinedByString:@"\n"];
3dd53516 2622} }
dc5812ec 2623
eef4ccaf 2624- (NSString *) shortDescription {
60bef540
JF
2625 if (parsed_ != NULL)
2626 return static_cast<NSString *>(parsed_->tagline_);
2627
2628@synchronized (database_) {
2629 pkgRecords::Parser &parser([database_ records]->Lookup(file_));
2630
2631 const char *start, *end;
2632 if (!parser.ShortDesc(start, end))
2633 return nil;
2634
abd93900
JF
2635 if (end - start > 200)
2636 end = start + 200;
60bef540
JF
2637
2638 /*
2639 if (const char *stop = reinterpret_cast<const char *>(memchr(start, '\n', end - start)))
2640 end = stop;
2641
2642 while (end != start && end[-1] == '\r')
2643 --end;
2644 */
2645
2646 return [(id) CYStringCreate(start, end - start) autorelease];
2647} }
eef4ccaf 2648
808c6eb6
JF
2649- (unichar) index {
2650 _profile(Package$index)
677b8415
JF
2651 CFStringRef name((CFStringRef) [self name]);
2652 if (CFStringGetLength(name) == 0)
808c6eb6 2653 return '#';
677b8415
JF
2654 UniChar character(CFStringGetCharacterAtIndex(name, 0));
2655 if (!CFUniCharIsMemberOf(character, kCFUniCharLetterCharacterSet))
808c6eb6 2656 return '#';
447db19d 2657 return toupper(character);
808c6eb6 2658 _end
36bb2ca2 2659}
3178d79b 2660
94b0b3e5 2661- (PackageValue *) metadata {
bb6bb6d6 2662 return metadata_;
807ae6d7
JF
2663}
2664
31bc18a7 2665- (time_t) seen {
94b0b3e5
JF
2666 PackageValue *metadata([self metadata]);
2667 return metadata->subscribed_ ? metadata->last_ : metadata->first_;
3178d79b
JF
2668}
2669
94b0b3e5
JF
2670- (bool) subscribed {
2671 return [self metadata]->subscribed_;
2672}
2673
2674- (bool) setSubscribed:(bool)subscribed {
2675 PackageValue *metadata([self metadata]);
2676 if (metadata->subscribed_ == subscribed)
2677 return false;
2678 metadata->subscribed_ = subscribed;
2679 return true;
807ae6d7
JF
2680}
2681
2682- (BOOL) ignored {
1b18f026 2683 return ignored_;
807ae6d7
JF
2684}
2685
36bb2ca2
JF
2686- (NSString *) latest {
2687 return latest_;
8fe19fc1
JF
2688}
2689
36bb2ca2
JF
2690- (NSString *) installed {
2691 return installed_;
3178d79b
JF
2692}
2693
6a155117
JF
2694- (BOOL) uninstalled {
2695 return installed_.empty();
2696}
2697
5a09ae08
JF
2698- (BOOL) valid {
2699 return !version_.end();
2700}
2701
31f3cfff 2702- (BOOL) upgradableAndEssential:(BOOL)essential {
677b8415
JF
2703 _profile(Package$upgradableAndEssential)
2704 pkgCache::VerIterator current(iterator_.CurrentVer());
2705 if (current.end())
e4f3d6e9 2706 return essential && essential_;
677b8415 2707 else
e4f3d6e9 2708 return !version_.end() && version_ != current;
677b8415 2709 _end
36bb2ca2 2710}
3178d79b 2711
36bb2ca2 2712- (BOOL) essential {
a1440b10 2713 return essential_;
3178d79b
JF
2714}
2715
36bb2ca2 2716- (BOOL) broken {
9bedffaa
JF
2717 return [database_ cache][iterator_].InstBroken();
2718}
2719
7d2ac47f 2720- (BOOL) unfiltered {
4fa77608 2721 _profile(Package$unfiltered$obsolete)
cf48f656 2722 if (_unlikely(obsolete_))
4fa77608
JF
2723 return false;
2724 _end
2725
4121c5e0
JF
2726 _profile(Package$unfiltered$role)
2727 if (_unlikely(role_ > 3))
4fa77608
JF
2728 return false;
2729 _end
2730
e4f3d6e9
JF
2731 return true;
2732}
4fa77608 2733
e4f3d6e9
JF
2734- (BOOL) visible {
2735 if (![self unfiltered])
2736 return false;
2737
fe33a23e
JF
2738 NSString *section;
2739
2740 _profile(Package$visible$section)
2741 section = [self section];
2742 _end
4fa77608 2743
e4f3d6e9 2744 _profile(Package$visible$isSectionVisible)
45447dc3 2745 if (!isSectionVisible(section))
4fa77608
JF
2746 return false;
2747 _end
2748
2749 return true;
7d2ac47f
JF
2750}
2751
9bedffaa 2752- (BOOL) half {
677b8415 2753 unsigned char current(iterator_->CurrentState);
9bedffaa
JF
2754 return current == pkgCache::State::HalfConfigured || current == pkgCache::State::HalfInstalled;
2755}
2756
2757- (BOOL) halfConfigured {
2758 return iterator_->CurrentState == pkgCache::State::HalfConfigured;
2759}
2760
2761- (BOOL) halfInstalled {
2762 return iterator_->CurrentState == pkgCache::State::HalfInstalled;
2763}
2764
2765- (BOOL) hasMode {
f18b4a97
JF
2766@synchronized (database_) {
2767 if ([database_ era] != era_ || iterator_.end())
b129e6d9 2768 return NO;
f18b4a97 2769
9bedffaa
JF
2770 pkgDepCache::StateCache &state([database_ cache][iterator_]);
2771 return state.Mode != pkgDepCache::ModeKeep;
f18b4a97 2772} }
9bedffaa
JF
2773
2774- (NSString *) mode {
f18b4a97
JF
2775@synchronized (database_) {
2776 if ([database_ era] != era_ || iterator_.end())
2777 return nil;
2778
9bedffaa
JF
2779 pkgDepCache::StateCache &state([database_ cache][iterator_]);
2780
2781 switch (state.Mode) {
2782 case pkgDepCache::ModeDelete:
2783 if ((state.iFlags & pkgDepCache::Purge) != 0)
f79a4512 2784 return @"PURGE";
9bedffaa 2785 else
f79a4512 2786 return @"REMOVE";
9bedffaa 2787 case pkgDepCache::ModeKeep:
dc63e78f 2788 if ((state.iFlags & pkgDepCache::ReInstall) != 0)
f79a4512 2789 return @"REINSTALL";
dc63e78f
JF
2790 /*else if ((state.iFlags & pkgDepCache::AutoKept) != 0)
2791 return nil;*/
9bedffaa
JF
2792 else
2793 return nil;
9bedffaa 2794 case pkgDepCache::ModeInstall:
dc63e78f 2795 /*if ((state.iFlags & pkgDepCache::ReInstall) != 0)
f79a4512 2796 return @"REINSTALL";
dc63e78f 2797 else*/ switch (state.Status) {
9bedffaa 2798 case -1:
f79a4512 2799 return @"DOWNGRADE";
9bedffaa 2800 case 0:
f79a4512 2801 return @"INSTALL";
9bedffaa 2802 case 1:
f79a4512 2803 return @"UPGRADE";
9bedffaa 2804 case 2:
f79a4512 2805 return @"NEW_INSTALL";
670a0494 2806 _nodefault
9bedffaa 2807 }
670a0494 2808 _nodefault
9bedffaa 2809 }
f18b4a97 2810} }
8fe19fc1 2811
36bb2ca2
JF
2812- (NSString *) id {
2813 return id_;
8fe19fc1
JF
2814}
2815
36bb2ca2 2816- (NSString *) name {
9fcbca29 2817 return name_.empty() ? id_ : name_;
36bb2ca2 2818}
8fe19fc1 2819
770f2a8e 2820- (UIImage *) icon {
dec6029f 2821 NSString *section = [self simpleSection];
770f2a8e
JF
2822
2823 UIImage *icon(nil);
12016ee5
JF
2824 if (parsed_ != NULL)
2825 if (NSString *href = parsed_->icon_)
2826 if ([href hasPrefix:@"file:///"])
84851d87 2827 icon = [UIImage imageAtPath:[[href substringFromIndex:7] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
770f2a8e 2828 if (icon == nil) if (section != nil)
84851d87 2829 icon = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, [section stringByReplacingOccurrencesOfString:@" " withString:@"_"]]];
b9956841 2830 if (icon == nil) if (Source *source = [self source]) if (NSString *dicon = [source defaultIcon])
189a73d0 2831 if ([dicon hasPrefix:@"file:///"])
84851d87 2832 icon = [UIImage imageAtPath:[[dicon substringFromIndex:7] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
770f2a8e
JF
2833 if (icon == nil)
2834 icon = [UIImage applicationImageNamed:@"unknown.png"];
2835 return icon;
36bb2ca2 2836}
8fe19fc1 2837
6f1a15d9 2838- (NSString *) homepage {
12016ee5 2839 return parsed_ == NULL ? nil : static_cast<NSString *>(parsed_->homepage_);
8fe19fc1
JF
2840}
2841
25a2158d 2842- (NSString *) depiction {
12016ee5 2843 return parsed_ != NULL && !parsed_->depiction_.empty() ? parsed_->depiction_ : [[self source] depictionForPackage:id_];
25a2158d
JF
2844}
2845
7aa82ca2
JF
2846- (MIMEAddress *) author {
2847 return parsed_ == NULL || parsed_->author_.empty() ? nil : [MIMEAddress addressWithString:parsed_->author_];
77fcccaf
JF
2848}
2849
dc63e78f 2850- (NSString *) support {
00067a67 2851 return parsed_ != NULL && !parsed_->support_.empty() ? parsed_->support_ : [[self source] supportForPackage:id_];
dc63e78f
JF
2852}
2853
affeffc7 2854- (NSArray *) files {
9fcbca29 2855 NSString *path = [NSString stringWithFormat:@"/var/lib/dpkg/info/%@.list", static_cast<NSString *>(id_)];
affeffc7
JF
2856 NSMutableArray *files = [NSMutableArray arrayWithCapacity:128];
2857
2858 std::ifstream fin;
2859 fin.open([path UTF8String]);
2860 if (!fin.is_open())
2861 return nil;
2862
2863 std::string line;
2864 while (std::getline(fin, line))
2865 [files addObject:[NSString stringWithUTF8String:line.c_str()]];
2866
2867 return files;
2868}
2869
5959b596
JF
2870- (NSString *) state {
2871@synchronized (database_) {
2872 if ([database_ era] != era_ || file_.end())
2873 return nil;
2874
2875 switch (iterator_->CurrentState) {
2876 case pkgCache::State::NotInstalled:
2877 return @"NotInstalled";
2878 case pkgCache::State::UnPacked:
2879 return @"UnPacked";
2880 case pkgCache::State::HalfConfigured:
2881 return @"HalfConfigured";
2882 case pkgCache::State::HalfInstalled:
2883 return @"HalfInstalled";
2884 case pkgCache::State::ConfigFiles:
2885 return @"ConfigFiles";
2886 case pkgCache::State::Installed:
2887 return @"Installed";
2888 case pkgCache::State::TriggersAwaited:
2889 return @"TriggersAwaited";
2890 case pkgCache::State::TriggersPending:
2891 return @"TriggersPending";
2892 }
2893
2894 return (NSString *) [NSNull null];
2895} }
2896
de1ace71
JF
2897- (NSString *) selection {
2898@synchronized (database_) {
2899 if ([database_ era] != era_ || file_.end())
2900 return nil;
2901
2902 switch (iterator_->SelectedState) {
2903 case pkgCache::State::Unknown:
2904 return @"Unknown";
2905 case pkgCache::State::Install:
2906 return @"Install";
2907 case pkgCache::State::Hold:
2908 return @"Hold";
2909 case pkgCache::State::DeInstall:
2910 return @"DeInstall";
2911 case pkgCache::State::Purge:
2912 return @"Purge";
2913 }
2914
2915 return (NSString *) [NSNull null];
2916} }
2917
affeffc7
JF
2918- (NSArray *) warnings {
2919 NSMutableArray *warnings([NSMutableArray arrayWithCapacity:4]);
2920 const char *name(iterator_.Name());
2921
2922 size_t length(strlen(name));
2923 if (length < 2) invalid:
43f3d7f6 2924 [warnings addObject:UCLocalize("ILLEGAL_PACKAGE_IDENTIFIER")];
affeffc7
JF
2925 else for (size_t i(0); i != length; ++i)
2926 if (
9dd60d81
JF
2927 /* XXX: technically this is not allowed */
2928 (name[i] < 'A' || name[i] > 'Z') &&
affeffc7
JF
2929 (name[i] < 'a' || name[i] > 'z') &&
2930 (name[i] < '0' || name[i] > '9') &&
2931 (i == 0 || name[i] != '+' && name[i] != '-' && name[i] != '.')
2932 ) goto invalid;
2933
2934 if (strcmp(name, "cydia") != 0) {
2935 bool cydia = false;
7623f855 2936 bool user = false;
9dd60d81 2937 bool _private = false;
affeffc7
JF
2938 bool stash = false;
2939
9dd60d81
JF
2940 bool repository = [[self section] isEqualToString:@"Repositories"];
2941
affeffc7
JF
2942 if (NSArray *files = [self files])
2943 for (NSString *file in files)
2944 if (!cydia && [file isEqualToString:@"/Applications/Cydia.app"])
2945 cydia = true;
7623f855
JF
2946 else if (!user && [file isEqualToString:@"/User"])
2947 user = true;
9dd60d81
JF
2948 else if (!_private && [file isEqualToString:@"/private"])
2949 _private = true;
affeffc7
JF
2950 else if (!stash && [file isEqualToString:@"/var/stash"])
2951 stash = true;
2952
9dd60d81
JF
2953 /* XXX: this is not sensitive enough. only some folders are valid. */
2954 if (cydia && !repository)
43f3d7f6 2955 [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"Cydia.app"]];
7623f855
JF
2956 if (user)
2957 [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"/User"]];
9dd60d81 2958 if (_private)
43f3d7f6 2959 [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"/private"]];
affeffc7 2960 if (stash)
43f3d7f6 2961 [warnings addObject:[NSString stringWithFormat:UCLocalize("FILES_INSTALLED_TO"), @"/var/stash"]];
affeffc7
JF
2962 }
2963
2964 return [warnings count] == 0 ? nil : warnings;
2965}
2966
2967- (NSArray *) applications {
2968 NSString *me([[NSBundle mainBundle] bundleIdentifier]);
2969
2970 NSMutableArray *applications([NSMutableArray arrayWithCapacity:2]);
2971
2972 static Pcre application_r("^/Applications/(.*)\\.app/Info.plist$");
2973 if (NSArray *files = [self files])
2974 for (NSString *file in files)
2975 if (application_r(file)) {
2976 NSDictionary *info([NSDictionary dictionaryWithContentsOfFile:file]);
2977 NSString *id([info objectForKey:@"CFBundleIdentifier"]);
2978 if ([id isEqualToString:me])
2979 continue;
2980
2981 NSString *display([info objectForKey:@"CFBundleDisplayName"]);
2982 if (display == nil)
2983 display = application_r[1];
2984
2985 NSString *bundle([file stringByDeletingLastPathComponent]);
2986 NSString *icon([info objectForKey:@"CFBundleIconFile"]);
5e17a734
JF
2987 // XXX: maybe this should check if this is really a string, not just for length
2988 if (icon == nil || ![icon respondsToSelector:@selector(length)] || [icon length] == 0)
affeffc7
JF
2989 icon = @"icon.png";
2990 NSURL *url([NSURL fileURLWithPath:[bundle stringByAppendingPathComponent:icon]]);
2991
2992 NSMutableArray *application([NSMutableArray arrayWithCapacity:2]);
2993 [applications addObject:application];
2994
2995 [application addObject:id];
2996 [application addObject:display];
2997 [application addObject:url];
2998 }
2999
3000 return [applications count] == 0 ? nil : applications;
3001}
3002
36bb2ca2 3003- (Source *) source {
5699667a 3004 if (source_ == nil) {
a1440b10
JF
3005 @synchronized (database_) {
3006 if ([database_ era] != era_ || file_.end())
5699667a
JF
3007 source_ = (Source *) [NSNull null];
3008 else
7b33d201 3009 source_ = [database_ getSource:file_.File()] ?: (Source *) [NSNull null];
a1440b10 3010 }
bbb879fb
JF
3011 }
3012
5699667a 3013 return source_ == (Source *) [NSNull null] ? nil : source_;
8fe19fc1
JF
3014}
3015
9c5737d5
JF
3016- (uint32_t) rank {
3017 return rank_;
3018}
3019
d84597fe
JF
3020- (BOOL) matches:(NSArray *)query {
3021 if (query == nil || [query count] == 0)
36bb2ca2 3022 return NO;
8fe19fc1 3023
9c5737d5
JF
3024 rank_ = 0;
3025
3026 NSString *string;
36bb2ca2 3027 NSRange range;
9c5737d5 3028 NSUInteger length;
8fe19fc1 3029
718c267c 3030 string = [self name];
d84597fe
JF
3031 length = [string length];
3032
3033 for (NSString *term in query) {
3034 range = [string rangeOfString:term options:MatchCompareOptions_];
3035 if (range.location != NSNotFound)
718c267c 3036 rank_ -= 6 * 1000000 / length;
d84597fe 3037 }
8fe19fc1 3038
718c267c
JF
3039 if (rank_ == 0) {
3040 string = [self id];
3041 length = [string length];
8fe19fc1 3042
718c267c
JF
3043 for (NSString *term in query) {
3044 range = [string rangeOfString:term options:MatchCompareOptions_];
3045 if (range.location != NSNotFound)
3046 rank_ -= 6 * 1000000 / length;
3047 }
d84597fe 3048 }
c4b530a7 3049
9c5737d5
JF
3050 string = [self shortDescription];
3051 length = [string length];
abd93900 3052 NSUInteger stop(std::min<NSUInteger>(length, 200));
df289c5a 3053
d84597fe
JF
3054 for (NSString *term in query) {
3055 range = [string rangeOfString:term options:MatchCompareOptions_ range:NSMakeRange(0, stop)];
3056 if (range.location != NSNotFound)
718c267c 3057 rank_ -= 2 * 100000;
d84597fe 3058 }
8fe19fc1 3059
9c5737d5 3060 return rank_ != 0;
36bb2ca2 3061}
8fe19fc1 3062
82aa2434
JF
3063- (NSArray *) tags {
3064 return tags_;
3065}
3066
6d9712c4
JF
3067- (BOOL) hasTag:(NSString *)tag {
3068 return tags_ == nil ? NO : [tags_ containsObject:tag];
3069}
3070
c390d3ab 3071- (NSString *) primaryPurpose {
7b33d201 3072 for (NSString *tag in (NSArray *) tags_)
c390d3ab
JF
3073 if ([tag hasPrefix:@"purpose::"])
3074 return [tag substringFromIndex:9];
3075 return nil;
3076}
3077
770f2a8e
JF
3078- (NSArray *) purposes {
3079 NSMutableArray *purposes([NSMutableArray arrayWithCapacity:2]);
7b33d201 3080 for (NSString *tag in (NSArray *) tags_)
770f2a8e
JF
3081 if ([tag hasPrefix:@"purpose::"])
3082 [purposes addObject:[tag substringFromIndex:9]];
3083 return [purposes count] == 0 ? nil : purposes;
3084}
3085
3bd1c2a2
JF
3086- (bool) isCommercial {
3087 return [self hasTag:@"cydia::commercial"];
3088}
3089
cd95e390
JF
3090- (void) setIndex:(size_t)index {
3091 if (metadata_->index_ != index)
3092 metadata_->index_ = index;
3093}
3094
df213583
JF
3095- (CYString &) cyname {
3096 return name_.empty() ? id_ : name_;
f79a4512
JF
3097}
3098
f79a4512
JF
3099- (uint32_t) compareBySection:(NSArray *)sections {
3100 NSString *section([self section]);
3101 for (size_t i(0), e([sections count]); i != e; ++i) {
3102 if ([section isEqualToString:[[sections objectAtIndex:i] name]])
3103 return i;
36bb2ca2 3104 }
3178d79b 3105
f79a4512 3106 return _not(uint32_t);
36bb2ca2 3107}
3178d79b 3108
dc63e78f 3109- (void) clear {
c6ca67ba 3110@synchronized (database_) {
dc63e78f
JF
3111 pkgProblemResolver *resolver = [database_ resolver];
3112 resolver->Clear(iterator_);
c6ca67ba
JF
3113
3114 pkgCacheFile &cache([database_ cache]);
3115 cache->SetReInstall(iterator_, false);
3116 cache->MarkKeep(iterator_, false);
3117} }
dc63e78f 3118
36bb2ca2 3119- (void) install {
c6ca67ba 3120@synchronized (database_) {
36bb2ca2
JF
3121 pkgProblemResolver *resolver = [database_ resolver];
3122 resolver->Clear(iterator_);
3123 resolver->Protect(iterator_);
c6ca67ba 3124
36bb2ca2 3125 pkgCacheFile &cache([database_ cache]);
c6ca67ba 3126 cache->SetReInstall(iterator_, false);
36bb2ca2 3127 cache->MarkInstall(iterator_, false);
c6ca67ba 3128
36bb2ca2
JF
3129 pkgDepCache::StateCache &state((*cache)[iterator_]);
3130 if (!state.Install())
3131 cache->SetReInstall(iterator_, true);
c6ca67ba 3132} }
68a238ec 3133
36bb2ca2 3134- (void) remove {
c6ca67ba 3135@synchronized (database_) {
36bb2ca2
JF
3136 pkgProblemResolver *resolver = [database_ resolver];
3137 resolver->Clear(iterator_);
36bb2ca2 3138 resolver->Remove(iterator_);
c6ca67ba
JF
3139 resolver->Protect(iterator_);
3140
3141 pkgCacheFile &cache([database_ cache]);
3142 cache->SetReInstall(iterator_, false);
3143 cache->MarkDelete(iterator_, true);
3144} }
8fe19fc1 3145
36bb2ca2
JF
3146@end
3147/* }}} */
3148/* Section Class {{{ */
3149@interface Section : NSObject {
7b33d201 3150 _H<NSString> name_;
808c6eb6 3151 unichar index_;
36bb2ca2
JF
3152 size_t row_;
3153 size_t count_;
7b33d201 3154 _H<NSString> localized_;
36bb2ca2 3155}
3178d79b 3156
677b8415 3157- (NSComparisonResult) compareByLocalized:(Section *)section;
9fcbca29
JF
3158- (Section *) initWithName:(NSString *)name localized:(NSString *)localized;
3159- (Section *) initWithName:(NSString *)name localize:(BOOL)localize;
3160- (Section *) initWithName:(NSString *)name row:(size_t)row localize:(BOOL)localize;
808c6eb6 3161- (Section *) initWithIndex:(unichar)index row:(size_t)row;
36bb2ca2 3162- (NSString *) name;
808c6eb6 3163- (unichar) index;
f79a4512 3164
36bb2ca2
JF
3165- (size_t) row;
3166- (size_t) count;
f79a4512
JF
3167
3168- (void) addToRow;
36bb2ca2 3169- (void) addToCount;
b19871dd 3170
f79a4512 3171- (void) setCount:(size_t)count;
677b8415 3172- (NSString *) localized;
f79a4512 3173
36bb2ca2 3174@end
b19871dd 3175
36bb2ca2 3176@implementation Section
b19871dd 3177
677b8415 3178- (NSComparisonResult) compareByLocalized:(Section *)section {
9fcbca29
JF
3179 NSString *lhs(localized_);
3180 NSString *rhs([section localized]);
6d9712c4 3181
9fcbca29 3182 /*if ([lhs length] != 0 && [rhs length] != 0) {
6d9712c4
JF
3183 unichar lhc = [lhs characterAtIndex:0];
3184 unichar rhc = [rhs characterAtIndex:0];
3185
3186 if (isalpha(lhc) && !isalpha(rhc))
3187 return NSOrderedAscending;
3188 else if (!isalpha(lhc) && isalpha(rhc))
3189 return NSOrderedDescending;
9fcbca29 3190 }*/
6d9712c4 3191
68f1828e 3192 return [lhs compare:rhs options:LaxCompareOptions_];
6d9712c4
JF
3193}
3194
9fcbca29
JF
3195- (Section *) initWithName:(NSString *)name localized:(NSString *)localized {
3196 if ((self = [self initWithName:name localize:NO]) != nil) {
3197 if (localized != nil)
7b33d201 3198 localized_ = localized;
9fcbca29
JF
3199 } return self;
3200}
3201
3202- (Section *) initWithName:(NSString *)name localize:(BOOL)localize {
3203 return [self initWithName:name row:0 localize:localize];
6d9712c4
JF
3204}
3205
9fcbca29 3206- (Section *) initWithName:(NSString *)name row:(size_t)row localize:(BOOL)localize {
36bb2ca2 3207 if ((self = [super init]) != nil) {
7b33d201 3208 name_ = name;
808c6eb6
JF
3209 index_ = '\0';
3210 row_ = row;
9fcbca29 3211 if (localize)
7b33d201 3212 localized_ = LocalizeSection(name_);
808c6eb6
JF
3213 } return self;
3214}
3215
f79a4512 3216/* XXX: localize the index thingees */
808c6eb6
JF
3217- (Section *) initWithIndex:(unichar)index row:(size_t)row {
3218 if ((self = [super init]) != nil) {
7b33d201 3219 name_ = [NSString stringWithCharacters:&index length:1];
808c6eb6 3220 index_ = index;
36bb2ca2 3221 row_ = row;
b19871dd
JF
3222 } return self;
3223}
3224
36bb2ca2
JF
3225- (NSString *) name {
3226 return name_;
3227}
3228
808c6eb6
JF
3229- (unichar) index {
3230 return index_;
3231}
3232
36bb2ca2
JF
3233- (size_t) row {
3234 return row_;
3235}
3236
3237- (size_t) count {
3238 return count_;
3239}
3240
f79a4512
JF
3241- (void) addToRow {
3242 ++row_;
3243}
3244
36bb2ca2
JF
3245- (void) addToCount {
3246 ++count_;
3247}
3248
f79a4512
JF
3249- (void) setCount:(size_t)count {
3250 count_ = count;
3251}
3252
3253- (NSString *) localized {
3254 return localized_;
3255}
3256
b19871dd
JF
3257@end
3258/* }}} */
3259
9f9ae81c
JF
3260class CydiaLogCleaner :
3261 public pkgArchiveCleaner
3262{
3263 protected:
3264 virtual void Erase(const char *File, std::string Pkg, std::string Ver, struct stat &St) {
3265 unlink(File);
3266 }
3267};
3268
36bb2ca2
JF
3269/* Database Implementation {{{ */
3270@implementation Database
ec97ef06 3271
770f2a8e 3272+ (Database *) sharedInstance {
7b33d201 3273 static _H<Database> instance;
770f2a8e 3274 if (instance == nil)
7b33d201 3275 instance = [[[Database alloc] init] autorelease];
770f2a8e
JF
3276 return instance;
3277}
3278
a1440b10
JF
3279- (unsigned) era {
3280 return era_;
3281}
3282
0944377b
JF
3283- (void) releasePackages {
3284 CFArrayApplyFunction(packages_, CFRangeMake(0, CFArrayGetCount(packages_)), reinterpret_cast<CFArrayApplierFunction>(&CFRelease), NULL);
3285 CFArrayRemoveAllValues(packages_);
3286}
3287
36bb2ca2 3288- (void) dealloc {
3931b718 3289 // XXX: actually implement this thing
36bb2ca2 3290 _assert(false);
0944377b 3291 [self releasePackages];
f79a4512 3292 apr_pool_destroy(pool_);
0944377b 3293 NSRecycleZone(zone_);
36bb2ca2
JF
3294 [super dealloc];
3295}
ec97ef06 3296
d13edf44 3297- (void) _readCydia:(NSNumber *)fd {
77fcccaf
JF
3298 __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
3299 std::istream is(&ib);
3300 std::string line;
3301
bc8cd583
JF
3302 static Pcre finish_r("^finish:([^:]*)$");
3303
77fcccaf 3304 while (std::getline(is, line)) {
d13edf44
JF
3305 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
3306
77fcccaf 3307 const char *data(line.c_str());
bc8cd583 3308 size_t size = line.size();
c390d3ab 3309 lprintf("C:%s\n", data);
bc8cd583
JF
3310
3311 if (finish_r(data, size)) {
3312 NSString *finish = finish_r[1];
3313 int index = [Finishes_ indexOfObject:finish];
3314 if (index != INT_MAX && index > Finish_)
3315 Finish_ = index;
3316 }
d13edf44
JF
3317
3318 [pool release];
77fcccaf
JF
3319 }
3320
670a0494 3321 _assume(false);
77fcccaf
JF
3322}
3323
d13edf44 3324- (void) _readStatus:(NSNumber *)fd {
36bb2ca2
JF
3325 __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
3326 std::istream is(&ib);
3327 std::string line;
ec97ef06 3328
7b0ce2da
JF
3329 static Pcre conffile_r("^status: [^ ]* : conffile-prompt : (.*?) *$");
3330 static Pcre pmstatus_r("^([^:]*):([^:]*):([^:]*):(.*)$");
ec97ef06 3331
36bb2ca2 3332 while (std::getline(is, line)) {
d13edf44
JF
3333 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
3334
36bb2ca2 3335 const char *data(line.c_str());
670a0494 3336 size_t size(line.size());
c390d3ab 3337 lprintf("S:%s\n", data);
ec97ef06 3338
a08145a8
JF
3339 if (conffile_r(data, size)) {
3340 // status: /fail : conffile-prompt : '/fail' '/fail.dpkg-new' 1 1
4187453f 3341 [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:conffile_r[1] waitUntilDone:YES];
a08145a8
JF
3342 } else if (strncmp(data, "status: ", 8) == 0) {
3343 // status: <package>: {unpacked,half-configured,installed}
389133be 3344 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 8)] ofType:kCydiaProgressEventTypeStatus]);
a08145a8
JF
3345 [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
3346 } else if (strncmp(data, "processing: ", 12) == 0) {
3347 // processing: configure: config-test
389133be 3348 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:(data + 12)] ofType:kCydiaProgressEventTypeStatus]);
a08145a8 3349 [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
6915b806 3350 } else if (pmstatus_r(data, size)) {
31f3cfff 3351 std::string type([pmstatus_r[1] UTF8String]);
4a2dc82e 3352
6915b806 3353 NSString *package = pmstatus_r[2];
4a2dc82e
JF
3354 if ([package isEqualToString:@"dpkg-exec"])
3355 package = nil;
31f3cfff 3356
5a09ae08 3357 float percent([pmstatus_r[3] floatValue]);
6915b806 3358 [progress_ performSelectorOnMainThread:@selector(setProgressPercent:) withObject:[NSNumber numberWithFloat:(percent / 100)] waitUntilDone:YES];
5a09ae08
JF
3359
3360 NSString *string = pmstatus_r[4];
5a09ae08 3361
6915b806 3362 if (type == "pmerror") {
389133be 3363 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:kCydiaProgressEventTypeError forPackage:package]);
6915b806
JF
3364 [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
3365 } else if (type == "pmstatus") {
389133be 3366 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:string ofType:kCydiaProgressEventTypeStatus forPackage:package]);
6915b806
JF
3367 [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
3368 } else if (type == "pmconffile")
4187453f 3369 [delegate_ performSelectorOnMainThread:@selector(setConfigurationData:) withObject:string waitUntilDone:YES];
670a0494
JF
3370 else
3371 lprintf("E:unknown pmstatus\n");
3372 } else
3373 lprintf("E:unknown status\n");
d13edf44
JF
3374
3375 [pool release];
36bb2ca2 3376 }
ec97ef06 3377
670a0494 3378 _assume(false);
36bb2ca2 3379}
ec97ef06 3380
d13edf44 3381- (void) _readOutput:(NSNumber *)fd {
36bb2ca2
JF
3382 __gnu_cxx::stdio_filebuf<char> ib([fd intValue], std::ios::in);
3383 std::istream is(&ib);
3384 std::string line;
3385
5a09ae08 3386 while (std::getline(is, line)) {
d13edf44
JF
3387 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
3388
c390d3ab 3389 lprintf("O:%s\n", line.c_str());
27024935 3390
389133be 3391 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:line.c_str()] ofType:kCydiaProgressEventTypeInformation]);
6915b806 3392 [progress_ performSelectorOnMainThread:@selector(addProgressEvent:) withObject:event waitUntilDone:YES];
d13edf44
JF
3393
3394 [pool release];
5a09ae08 3395 }
36bb2ca2 3396
670a0494 3397 _assume(false);
ec97ef06
JF
3398}
3399
8b29f8e6
JF
3400- (FILE *) input {
3401 return input_;
3402}
3403
36bb2ca2 3404- (Package *) packageWithName:(NSString *)name {
c5f1a937
JF
3405 if (name == nil)
3406 return nil;
3dd53516 3407@synchronized (self) {
5a09ae08
JF
3408 if (static_cast<pkgDepCache *>(cache_) == NULL)
3409 return nil;
36bb2ca2 3410 pkgCache::PkgIterator iterator(cache_->FindPkg([name UTF8String]));
13902177 3411 return iterator.end() ? nil : [Package packageWithIterator:iterator withZone:NULL inPool:NULL database:self];
28ce8704 3412} }
36bb2ca2 3413
eb30da80 3414- (id) init {
ec97ef06 3415 if ((self = [super init]) != nil) {
5a09ae08 3416 policy_ = NULL;
36bb2ca2
JF
3417 records_ = NULL;
3418 resolver_ = NULL;
3419 fetcher_ = NULL;
3420 lock_ = NULL;
ec97ef06 3421
f79a4512
JF
3422 zone_ = NSCreateZone(1024 * 1024, 256 * 1024, NO);
3423 apr_pool_create(&pool_, NULL);
3424
9f357d11
JF
3425 size_t capacity(MetaFile_->active_);
3426 if (capacity == 0)
3427 capacity = 16384;
3428 else
3429 capacity += 1024;
3430
3431 packages_ = CFArrayCreateMutable(kCFAllocatorDefault, capacity, NULL);
7b33d201 3432 sourceList_ = [NSMutableArray arrayWithCapacity:16];
ec97ef06 3433
36bb2ca2 3434 int fds[2];
ec97ef06 3435
77fcccaf
JF
3436 _assert(pipe(fds) != -1);
3437 cydiafd_ = fds[1];
3438
3439 _config->Set("APT::Keep-Fds::", cydiafd_);
bc8cd583 3440 setenv("CYDIA", [[[[NSNumber numberWithInt:cydiafd_] stringValue] stringByAppendingString:@" 1"] UTF8String], _not(int));
77fcccaf
JF
3441
3442 [NSThread
3443 detachNewThreadSelector:@selector(_readCydia:)
3444 toTarget:self
3931b718 3445 withObject:[NSNumber numberWithInt:fds[0]]
77fcccaf
JF
3446 ];
3447
36bb2ca2
JF
3448 _assert(pipe(fds) != -1);
3449 statusfd_ = fds[1];
ec97ef06 3450
36bb2ca2
JF
3451 [NSThread
3452 detachNewThreadSelector:@selector(_readStatus:)
3453 toTarget:self
3931b718 3454 withObject:[NSNumber numberWithInt:fds[0]]
36bb2ca2 3455 ];
ec97ef06 3456
8b29f8e6
JF
3457 _assert(pipe(fds) != -1);
3458 _assert(dup2(fds[0], 0) != -1);
3459 _assert(close(fds[0]) != -1);
3460
3461 input_ = fdopen(fds[1], "a");
3462
36bb2ca2
JF
3463 _assert(pipe(fds) != -1);
3464 _assert(dup2(fds[1], 1) != -1);
3465 _assert(close(fds[1]) != -1);
3466
3467 [NSThread
3468 detachNewThreadSelector:@selector(_readOutput:)
3469 toTarget:self
3931b718 3470 withObject:[NSNumber numberWithInt:fds[0]]
36bb2ca2 3471 ];
ec97ef06
JF
3472 } return self;
3473}
3474
36bb2ca2
JF
3475- (pkgCacheFile &) cache {
3476 return cache_;
ec97ef06
JF
3477}
3478
5a09ae08
JF
3479- (pkgDepCache::Policy *) policy {
3480 return policy_;
3481}
3482
36bb2ca2
JF
3483- (pkgRecords *) records {
3484 return records_;
ec97ef06
JF
3485}
3486
36bb2ca2
JF
3487- (pkgProblemResolver *) resolver {
3488 return resolver_;
ec97ef06
JF
3489}
3490
36bb2ca2
JF
3491- (pkgAcquire &) fetcher {
3492 return *fetcher_;
ec97ef06
JF
3493}
3494
a3328c28
JF
3495- (pkgSourceList &) list {
3496 return *list_;
3497}
3498
36bb2ca2 3499- (NSArray *) packages {
077e9d90 3500 return (NSArray *) packages_;
ec97ef06
JF
3501}
3502
7b0ce2da 3503- (NSArray *) sources {
34f70f5d 3504 return sourceList_;
7b0ce2da
JF
3505}
3506
d669236d
GP
3507- (Source *) sourceWithKey:(NSString *)key {
3508 for (Source *source in [self sources]) {
3509 if ([[source key] isEqualToString:key])
3510 return source;
3511 } return nil;
3512}
3513
670a0494
JF
3514- (bool) popErrorWithTitle:(NSString *)title {
3515 bool fatal(false);
670a0494
JF
3516
3517 while (!_error->empty()) {
3518 std::string error;
3519 bool warning(!_error->PopMessage(error));
3520 if (!warning)
3521 fatal = true;
cb6e2ccf 3522
670a0494
JF
3523 for (;;) {
3524 size_t size(error.size());
3525 if (size == 0 || error[size - 1] != '\n')
3526 break;
3527 error.resize(size - 1);
3528 }
cb6e2ccf 3529
670a0494
JF
3530 lprintf("%c:[%s]\n", warning ? 'W' : 'E', error.c_str());
3531
c8ce71c7
JF
3532 static Pcre no_pubkey("^GPG error:.* NO_PUBKEY .*$");
3533 if (warning && no_pubkey(error.c_str()))
3534 continue;
3535
389133be 3536 [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? kCydiaProgressEventTypeWarning : kCydiaProgressEventTypeError)] forTask:title];
670a0494
JF
3537 }
3538
670a0494
JF
3539 return fatal;
3540}
3541
3542- (bool) popErrorWithTitle:(NSString *)title forOperation:(bool)success {
3543 return [self popErrorWithTitle:title] || !success;
3544}
3545
d13edf44 3546- (void) reloadDataWithInvocation:(NSInvocation *)invocation {
3dd53516
JF
3547@synchronized (self) {
3548 ++era_;
a1440b10 3549
0944377b 3550 [self releasePackages];
ab3f6a01 3551
34f70f5d
JF
3552 sourceMap_.clear();
3553 [sourceList_ removeAllObjects];
843e75b8 3554
36bb2ca2 3555 _error->Discard();
5a09ae08 3556
36bb2ca2 3557 delete list_;
5a09ae08 3558 list_ = NULL;
36bb2ca2
JF
3559 manager_ = NULL;
3560 delete lock_;
5a09ae08 3561 lock_ = NULL;
36bb2ca2 3562 delete fetcher_;
5a09ae08 3563 fetcher_ = NULL;
36bb2ca2 3564 delete resolver_;
5a09ae08 3565 resolver_ = NULL;
36bb2ca2 3566 delete records_;
5a09ae08
JF
3567 records_ = NULL;
3568 delete policy_;
3569 policy_ = NULL;
36bb2ca2 3570
5a09ae08 3571 cache_.Close();
8b29f8e6 3572
f79a4512 3573 apr_pool_clear(pool_);
a020a50e 3574
f79a4512 3575 NSRecycleZone(zone_);
a020a50e 3576 zone_ = NSCreateZone(1024 * 1024, 256 * 1024, NO);
f79a4512 3577
7376b55c
JF
3578 int chk(creat("/tmp/cydia.chk", 0644));
3579 if (chk != -1)
3580 close(chk);
3581
4ba8f30a
JF
3582 if (invocation != nil)
3583 [invocation invoke];
3584
670a0494
JF
3585 NSString *title(UCLocalize("DATABASE"));
3586
0c28a403
JF
3587 list_ = new pkgSourceList();
3588 if ([self popErrorWithTitle:title forOperation:list_->ReadMainList()])
3589 return;
3590
3591 for (pkgSourceList::const_iterator source = list_->begin(); source != list_->end(); ++source) {
3592 Source *object([[[Source alloc] initWithMetaIndex:*source forDatabase:self inPool:pool_] autorelease]);
3593 [sourceList_ addObject:object];
3594 }
3595
9487f027 3596 _trace();
124cea03 3597 OpProgress progress;
2b49cff9
JF
3598 open:
3599 if (!cache_.Open(progress, true)) {
3600 // XXX: what if there are errors, but Open() == true? this should be merged with popError:
3601 while (!_error->empty()) {
3602 std::string error;
3603 bool warning(!_error->PopMessage(error));
3604
3605 lprintf("cache_.Open():[%s]\n", error.c_str());
3606
389133be 3607 [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:(warning ? kCydiaProgressEventTypeWarning : kCydiaProgressEventTypeError)] forTask:title];
2b49cff9
JF
3608
3609 SEL repair(NULL);
3610 if (false);
3611 else if (error == "dpkg was interrupted, you must manually run 'dpkg --configure -a' to correct the problem. ")
3612 repair = @selector(configure);
3613 //else if (error == "The package lists or status file could not be parsed or opened.")
3614 // repair = @selector(update);
3615 // else if (error == "Could not get lock /var/lib/dpkg/lock - open (35 Resource temporarily unavailable)")
3616 // else if (error == "Could not open lock file /var/lib/dpkg/lock - open (13 Permission denied)")
3617 // else if (error == "Malformed Status line")
3618 // else if (error == "The list of sources could not be read.")
3619
3620 if (repair != NULL) {
3621 _error->Discard();
3622 [delegate_ repairWithSelector:repair];
3623 goto open;
3624 }
efa53fa9 3625 }
5a09ae08 3626
2b49cff9 3627 return;
36bb2ca2 3628 }
9487f027 3629 _trace();
36bb2ca2 3630
7376b55c
JF
3631 unlink("/tmp/cydia.chk");
3632
31bc18a7 3633 now_ = [[NSDate date] timeIntervalSince1970];
36bb2ca2 3634
5a09ae08 3635 policy_ = new pkgDepCache::Policy();
36bb2ca2
JF
3636 records_ = new pkgRecords(cache_);
3637 resolver_ = new pkgProblemResolver(cache_);
3638 fetcher_ = new pkgAcquire(&status_);
3639 lock_ = NULL;
3640
670a0494 3641 if (cache_->DelCount() != 0 || cache_->InstCount() != 0) {
389133be 3642 [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("COUNTS_NONZERO_EX") ofType:kCydiaProgressEventTypeError] forTask:title];
670a0494
JF
3643 return;
3644 }
36bb2ca2 3645
670a0494
JF
3646 if ([self popErrorWithTitle:title forOperation:pkgApplyStatus(cache_)])
3647 return;
9bedffaa
JF
3648
3649 if (cache_->BrokenCount() != 0) {
670a0494
JF
3650 if ([self popErrorWithTitle:title forOperation:pkgFixBroken(cache_)])
3651 return;
3652
3653 if (cache_->BrokenCount() != 0) {
389133be 3654 [delegate_ addProgressEventOnMainThread:[CydiaProgressEvent eventWithMessage:UCLocalize("STILL_BROKEN_EX") ofType:kCydiaProgressEventTypeError] forTask:title];
670a0494
JF
3655 return;
3656 }
3657
3658 if ([self popErrorWithTitle:title forOperation:pkgMinimizeUpgrade(cache_)])
3659 return;
9bedffaa
JF
3660 }
3661
0c28a403
JF
3662 for (Source *object in (id) sourceList_) {
3663 metaIndex *source([object metaIndex]);
3664 std::vector<pkgIndexFile *> *indices = source->GetIndexFiles();
68d927e2
JF
3665 for (std::vector<pkgIndexFile *>::const_iterator index = indices->begin(); index != indices->end(); ++index)
3666 // XXX: this could be more intelligent
3667 if (dynamic_cast<debPackagesIndex *>(*index) != NULL) {
3668 pkgCache::PkgFileIterator cached((*index)->FindInCache(cache_));
34f70f5d
JF
3669 if (!cached.end())
3670 sourceMap_[cached->ID] = object;
68d927e2 3671 }
36bb2ca2 3672 }
68d927e2 3673
677b8415 3674 {
9fcbca29 3675 /*std::vector<Package *> packages;
677b8415 3676 packages.reserve(std::max(10000U, [packages_ count] + 1000));
9fcbca29
JF
3677 packages_ = nil;*/
3678
677b8415
JF
3679 _trace();
3680
3681 for (pkgCache::PkgIterator iterator = cache_->PkgBegin(); !iterator.end(); ++iterator)
3682 if (Package *package = [Package packageWithIterator:iterator withZone:zone_ inPool:pool_ database:self])
9fcbca29 3683 //packages.push_back(package);
7b33d201 3684 CFArrayAppendValue(packages_, CFRetain(package));
677b8415
JF
3685
3686 _trace();
3687
9fcbca29 3688 /*if (packages.empty())
677b8415
JF
3689 packages_ = [[NSArray alloc] init];
3690 else
3691 packages_ = [[NSArray alloc] initWithObjects:&packages.front() count:packages.size()];
9fcbca29
JF
3692 _trace();*/
3693
de42680b
JF
3694 [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(16)];
3695 [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(4)];
3696 [(NSMutableArray *) packages_ radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackagePrefixRadix) withContext:reinterpret_cast<void *>(0)];
9fcbca29
JF
3697
3698 /*_trace();
3699 PrintTimes();
3700 _trace();*/
3701
3702 _trace();
3703
3704 /*if (!packages.empty())
3705 CFQSortArray(&packages.front(), packages.size(), sizeof(packages.front()), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare_), NULL);*/
3706 //std::sort(packages.begin(), packages.end(), PackageNameOrdering());
3707
eef4ccaf
JF
3708 //CFArraySortValues((CFMutableArrayRef) packages_, CFRangeMake(0, [packages_ count]), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
3709
077e9d90 3710 CFArrayInsertionSortValues(packages_, CFRangeMake(0, CFArrayGetCount(packages_)), reinterpret_cast<CFComparatorFunction>(&PackageNameCompare), NULL);
9fcbca29
JF
3711
3712 //[packages_ sortUsingFunction:reinterpret_cast<NSComparisonResult (*)(id, id, void *)>(&PackageNameCompare) context:NULL];
677b8415
JF
3713
3714 _trace();
cd95e390
JF
3715
3716 size_t count(CFArrayGetCount(packages_));
9f357d11
JF
3717 MetaFile_->active_ = count;
3718
cd95e390
JF
3719 for (size_t index(0); index != count; ++index)
3720 [(Package *) CFArrayGetValueAtIndex(packages_, index) setIndex:index];
3721
3722 _trace();
677b8415 3723 }
d13edf44 3724} }
ec97ef06 3725
c6ca67ba
JF
3726- (void) clear {
3727@synchronized (self) {
3728 delete resolver_;
3729 resolver_ = new pkgProblemResolver(cache_);
3730
50c1653e
JF
3731 for (pkgCache::PkgIterator iterator(cache_->PkgBegin()); !iterator.end(); ++iterator)
3732 if (!cache_[iterator].Keep())
c6ca67ba 3733 cache_->MarkKeep(iterator, false);
50c1653e 3734 else if ((cache_[iterator].iFlags & pkgDepCache::ReInstall) != 0)
c6ca67ba 3735 cache_->SetReInstall(iterator, false);
c6ca67ba
JF
3736} }
3737
5a09ae08
JF
3738- (void) configure {
3739 NSString *dpkg = [NSString stringWithFormat:@"dpkg --configure -a --status-fd %u", statusfd_];
985d2dff 3740 _trace();
5a09ae08 3741 system([dpkg UTF8String]);
985d2dff 3742 _trace();
5a09ae08
JF
3743}
3744
670a0494 3745- (bool) clean {
0517651f 3746@synchronized (self) {
670a0494 3747 // XXX: I don't remember this condition
77fcccaf 3748 if (lock_ != NULL)
670a0494 3749 return false;
77fcccaf
JF
3750
3751 FileFd Lock;
3752 Lock.Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
670a0494
JF
3753
3754 NSString *title(UCLocalize("CLEAN_ARCHIVES"));
3755
3756 if ([self popErrorWithTitle:title])
3757 return false;
77fcccaf
JF
3758
3759 pkgAcquire fetcher;
3760 fetcher.Clean(_config->FindDir("Dir::Cache::Archives"));
3761
9f9ae81c 3762 CydiaLogCleaner cleaner;
670a0494
JF
3763 if ([self popErrorWithTitle:title forOperation:cleaner.Go(_config->FindDir("Dir::Cache::Archives") + "partial/", cache_)])
3764 return false;
3765
3766 return true;
0517651f 3767} }
77fcccaf 3768
670a0494 3769- (bool) prepare {
744f398e
JF
3770 fetcher_->Shutdown();
3771
36bb2ca2
JF
3772 pkgRecords records(cache_);
3773
3774 lock_ = new FileFd();
3775 lock_->Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
670a0494
JF
3776
3777 NSString *title(UCLocalize("PREPARE_ARCHIVES"));
3778
3779 if ([self popErrorWithTitle:title])
3780 return false;
36bb2ca2
JF
3781
3782 pkgSourceList list;
670a0494
JF
3783 if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
3784 return false;
36bb2ca2
JF
3785
3786 manager_ = (_system->CreatePM(cache_));
670a0494
JF
3787 if ([self popErrorWithTitle:title forOperation:manager_->GetArchives(fetcher_, &list, &records)])
3788 return false;
3789
3790 return true;
ec97ef06
JF
3791}
3792
36bb2ca2 3793- (void) perform {
be860cc8
JF
3794 bool substrate(RestartSubstrate_);
3795 RestartSubstrate_ = false;
3796
670a0494
JF
3797 NSString *title(UCLocalize("PERFORM_SELECTIONS"));
3798
a72074b2
JF
3799 NSMutableArray *before = [NSMutableArray arrayWithCapacity:16]; {
3800 pkgSourceList list;
670a0494
JF
3801 if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
3802 return;
a72074b2
JF
3803 for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
3804 [before addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
3805 }
a0be02eb 3806
dcaecde2 3807 [delegate_ performSelectorOnMainThread:@selector(retainNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
7ffd70fd 3808
eeb9b112
JF
3809 if (fetcher_->Run(PulseInterval_) != pkgAcquire::Continue) {
3810 _trace();
6e90508f 3811 [self popErrorWithTitle:title];
36bb2ca2 3812 return;
eeb9b112
JF
3813 }
3814
3815 bool failed = false;
3816 for (pkgAcquire::ItemIterator item = fetcher_->ItemsBegin(); item != fetcher_->ItemsEnd(); item++) {
3817 if ((*item)->Status == pkgAcquire::Item::StatDone && (*item)->Complete)
3818 continue;
6204f56a
JF
3819 if ((*item)->Status == pkgAcquire::Item::StatIdle)
3820 continue;
eeb9b112 3821
680a3c3c
JF
3822 std::string uri = (*item)->DescURI();
3823 std::string error = (*item)->ErrorText;
3824
3825 lprintf("pAf:%s:%s\n", uri.c_str(), error.c_str());
eeb9b112 3826 failed = true;
680a3c3c
JF
3827
3828 CydiaProgressEvent *event([CydiaProgressEvent eventWithMessage:[NSString stringWithUTF8String:error.c_str()] ofType:kCydiaProgressEventTypeError]);
3829 [delegate_ addProgressEventOnMainThread:event forTask:title];
eeb9b112
JF
3830 }
3831
dcaecde2 3832 [delegate_ performSelectorOnMainThread:@selector(releaseNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
54043703 3833
eeb9b112
JF
3834 if (failed) {
3835 _trace();
3836 return;
3837 }
36bb2ca2 3838
be860cc8
JF
3839 if (substrate)
3840 RestartSubstrate_ = true;
3841
36bb2ca2
JF
3842 _system->UnLock();
3843 pkgPackageManager::OrderResult result = manager_->DoInstall(statusfd_);
471683a3 3844 if ([self popErrorWithTitle:title])
36bb2ca2 3845 return;
eeb9b112
JF
3846
3847 if (result == pkgPackageManager::Failed) {
3848 _trace();
36bb2ca2 3849 return;
eeb9b112
JF
3850 }
3851
3852 if (result != pkgPackageManager::Completed) {
3853 _trace();
36bb2ca2 3854 return;
eeb9b112 3855 }
a0be02eb 3856
a72074b2
JF
3857 NSMutableArray *after = [NSMutableArray arrayWithCapacity:16]; {
3858 pkgSourceList list;
670a0494
JF
3859 if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
3860 return;
a72074b2
JF
3861 for (pkgSourceList::const_iterator source = list.begin(); source != list.end(); ++source)
3862 [after addObject:[NSString stringWithUTF8String:(*source)->GetURI().c_str()]];
3863 }
3864
3865 if (![before isEqualToArray:after])
3866 [self update];
ec97ef06
JF
3867}
3868
670a0494
JF
3869- (bool) upgrade {
3870 NSString *title(UCLocalize("UPGRADE"));
3871 if ([self popErrorWithTitle:title forOperation:pkgDistUpgrade(cache_)])
3872 return false;
3873 return true;
c7c6384e
JF
3874}
3875
36bb2ca2
JF
3876- (void) update {
3877 [self updateWithStatus:status_];
3878}
b4d89997 3879
21ac0ce2 3880- (void) updateWithStatus:(CancelStatus &)status {
670a0494
JF
3881 NSString *title(UCLocalize("REFRESHING_DATA"));
3882
36bb2ca2 3883 pkgSourceList list;
53ca7fdd
JF
3884 if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
3885 return;
b4d89997 3886
36bb2ca2
JF
3887 FileFd lock;
3888 lock.Fd(GetLock(_config->FindDir("Dir::State::Lists") + "lock"));
670a0494
JF
3889 if ([self popErrorWithTitle:title])
3890 return;
18873623 3891
aaae308d
JF
3892 [delegate_ performSelectorOnMainThread:@selector(retainNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
3893
fc470c15
JF
3894 bool success(ListUpdate(status, list, PulseInterval_));
3895 if (status.WasCancelled())
3896 _error->Discard();
aa42c612 3897 else {
fc470c15 3898 [self popErrorWithTitle:title forOperation:success];
aa42c612
JF
3899 [Metadata_ setObject:[NSDate date] forKey:@"LastUpdate"];
3900 Changed_ = true;
3901 }
36bb2ca2 3902
aaae308d 3903 [delegate_ performSelectorOnMainThread:@selector(releaseNetworkActivityIndicator) withObject:nil waitUntilDone:YES];
b4d89997
JF
3904}
3905
6915b806 3906- (void) setDelegate:(NSObject<DatabaseDelegate> *)delegate {
36bb2ca2 3907 delegate_ = delegate;
6915b806
JF
3908}
3909
3910- (void) setProgressDelegate:(NSObject<ProgressDelegate> *)delegate {
3911 progress_ = delegate;
36bb2ca2 3912 status_.setDelegate(delegate);
36bb2ca2 3913}
b4d89997 3914
6915b806
JF
3915- (NSObject<ProgressDelegate> *) progressDelegate {
3916 return progress_;
3917}
3918
7376b55c 3919- (Source *) getSource:(pkgCache::PkgFileIterator)file {
34f70f5d
JF
3920 SourceMap::const_iterator i(sourceMap_.find(file->ID));
3921 return i == sourceMap_.end() ? nil : i->second;
b19871dd
JF
3922}
3923
21ac0ce2
JF
3924- (void) setFetch:(bool)fetch forURI:(const char *)uri {
3925 for (Source *source in (id) sourceList_)
3926 [source setFetch:fetch forURI:uri];
3927}
3928
9ed626f1
JF
3929- (void) resetFetch {
3930 for (Source *source in (id) sourceList_)
3931 [source resetFetch];
3932}
3933
fe33a23e 3934- (NSString *) mappedSectionForPointer:(const char *)section {
4905df00 3935 _H<NSString> *mapped;
fe33a23e 3936
4905df00
JF
3937 _profile(Database$mappedSectionForPointer$Cache)
3938 mapped = &sections_[section];
3939 _end
3940
3941 if (*mapped == NULL) {
fe33a23e
JF
3942 size_t length(strlen(section));
3943 char spaced[length + 1];
3944
3945 _profile(Database$mappedSectionForPointer$Replace)
3946 for (size_t index(0); index != length; ++index)
3947 spaced[index] = section[index] == '_' ? ' ' : section[index];
3948 spaced[length] = '\0';
3949 _end
3950
3951 NSString *string;
3952
3953 _profile(Database$mappedSectionForPointer$stringWithUTF8String)
3954 string = [NSString stringWithUTF8String:spaced];
3955 _end
3956
3957 _profile(Database$mappedSectionForPointer$Map)
4905df00 3958 string = [SectionMap_ objectForKey:string] ?: string;
fe33a23e 3959 _end
4905df00
JF
3960
3961 *mapped = string;
3962 } return *mapped;
fe33a23e
JF
3963}
3964
36bb2ca2
JF
3965@end
3966/* }}} */
b4d89997 3967
7b33d201 3968static _H<NSMutableSet> Diversions_;
7256476b 3969
775deead
JF
3970@interface Diversion : NSObject {
3971 Pcre pattern_;
3972 _H<NSString> key_;
3973 _H<NSString> format_;
3974}
3975
3976@end
3977
3978@implementation Diversion
3979
3980- (id) initWithFrom:(NSString *)from to:(NSString *)to {
3981 if ((self = [super init]) != nil) {
3982 pattern_ = [from UTF8String];
3983 key_ = from;
3984 format_ = to;
3985 } return self;
3986}
3987
3988- (NSString *) divert:(NSString *)url {
8ea598c0 3989 return !pattern_(url) ? nil : pattern_->*format_;
775deead
JF
3990}
3991
7256476b
JF
3992+ (NSURL *) divertURL:(NSURL *)url {
3993 divert:
3994 NSString *href([url absoluteString]);
3995
7b33d201 3996 for (Diversion *diversion in (id) Diversions_)
7256476b 3997 if (NSString *diverted = [diversion divert:href]) {
85d5d452
JF
3998#if !ForRelease
3999 NSLog(@"div: %@", diverted);
4000#endif
7256476b
JF
4001 url = [NSURL URLWithString:diverted];
4002 goto divert;
4003 }
4004
4005 return url;
4006}
4007
775deead
JF
4008- (NSString *) key {
4009 return key_;
4010}
4011
4012- (NSUInteger) hash {
4013 return [key_ hash];
4014}
4015
4016- (BOOL) isEqual:(Diversion *)object {
4017 return self == object || [self class] == [object class] && [key_ isEqual:[object key]];
4018}
4019
4020@end
4021
43f3d7f6 4022@interface CydiaObject : NSObject {
4cc9e99a 4023 _H<CyteWebViewController> indirect_;
3931b718 4024 _transient id delegate_;
43f3d7f6 4025}
c390d3ab 4026
43f3d7f6 4027- (id) initWithDelegate:(IndirectDelegate *)indirect;
6840bff3 4028
c390d3ab
JF
4029@end
4030
9e130bc2
JF
4031@class CydiaObject;
4032
09e89a8a 4033@interface CydiaWebViewController : CyteWebViewController {
7b33d201 4034 _H<CydiaObject> cydia_;
775deead
JF
4035}
4036
4037+ (void) addDiversion:(Diversion *)diversion;
85ae5f42 4038+ (NSURLRequest *) requestWithHeaders:(NSURLRequest *)request;
9e130bc2
JF
4039+ (void) didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame withCydia:(CydiaObject *)cydia;
4040- (void) setDelegate:(id)delegate;
775deead
JF
4041
4042@end
4043
775deead 4044/* Web Scripting {{{ */
43f3d7f6 4045@implementation CydiaObject
c390d3ab 4046
43f3d7f6
JF
4047- (id) initWithDelegate:(IndirectDelegate *)indirect {
4048 if ((self = [super init]) != nil) {
4cc9e99a 4049 indirect_ = (CyteWebViewController *) indirect;
43f3d7f6
JF
4050 } return self;
4051}
4052
77801ff1
JF
4053- (void) setDelegate:(id)delegate {
4054 delegate_ = delegate;
4055}
4056
43f3d7f6 4057+ (NSArray *) _attributeKeys {
e58ff941 4058 return [NSArray arrayWithObjects:
6ffdaae3 4059 @"bbsnum",
e967efd5
JF
4060 @"build",
4061 @"coreFoundationVersionNumber",
e58ff941 4062 @"device",
56296da0 4063 @"ecid",
93d6d318
JF
4064 @"firmware",
4065 @"hostname",
4066 @"idiom",
f87cac81
JF
4067 @"mcc",
4068 @"mnc",
56296da0 4069 @"model",
2656c992 4070 @"operator",
56296da0 4071 @"role",
e58ff941 4072 @"serial",
3ea82d91 4073 @"token",
bf1d5e69 4074 @"version",
e58ff941 4075 nil];
43f3d7f6
JF
4076}
4077
4078- (NSArray *) attributeKeys {
4079 return [[self class] _attributeKeys];
c390d3ab
JF
4080}
4081
43f3d7f6
JF
4082+ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
4083 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
c390d3ab 4084}
43f3d7f6 4085
bf1d5e69 4086- (NSString *) version {
3d45bad1 4087 return Cydia_;
bf1d5e69
JF
4088}
4089
e967efd5
JF
4090- (NSString *) build {
4091 return System_;
4092}
4093
4094- (NSString *) coreFoundationVersionNumber {
4095 return [NSString stringWithFormat:@"%.2f", kCFCoreFoundationVersionNumber];
4096}
4097
43f3d7f6 4098- (NSString *) device {
7c80833f 4099 return UniqueIdentifier();
c390d3ab
JF
4100}
4101
93d6d318
JF
4102- (NSString *) firmware {
4103 return [[UIDevice currentDevice] systemVersion];
4104}
4105
4106- (NSString *) hostname {
4107 return [[UIDevice currentDevice] name];
4108}
4109
4110- (NSString *) idiom {
c138614d 4111 return (id) Idiom_ ?: [NSNull null];
93d6d318
JF
4112}
4113
f87cac81
JF
4114- (NSString *) mcc {
4115 if (CFStringRef (*$CTSIMSupportCopyMobileSubscriberCountryCode)(CFAllocatorRef) = reinterpret_cast<CFStringRef (*)(CFAllocatorRef)>(dlsym(RTLD_DEFAULT, "CTSIMSupportCopyMobileSubscriberCountryCode")))
4116 return [(NSString *) (*$CTSIMSupportCopyMobileSubscriberCountryCode)(kCFAllocatorDefault) autorelease];
4117 return nil;
4118}
4119
4120- (NSString *) mnc {
4121 if (CFStringRef (*$CTSIMSupportCopyMobileSubscriberNetworkCode)(CFAllocatorRef) = reinterpret_cast<CFStringRef (*)(CFAllocatorRef)>(dlsym(RTLD_DEFAULT, "CTSIMSupportCopyMobileSubscriberNetworkCode")))
4122 return [(NSString *) (*$CTSIMSupportCopyMobileSubscriberNetworkCode)(kCFAllocatorDefault) autorelease];
4123 return nil;
56296da0
JF
4124}
4125
2656c992
JF
4126- (NSString *) operator {
4127 if (CFStringRef (*$CTRegistrationCopyOperatorName)(CFAllocatorRef) = reinterpret_cast<CFStringRef (*)(CFAllocatorRef)>(dlsym(RTLD_DEFAULT, "CTRegistrationCopyOperatorName")))
4128 return [(NSString *) (*$CTRegistrationCopyOperatorName)(kCFAllocatorDefault) autorelease];
4129 return nil;
4130}
4131
6ffdaae3
JF
4132- (NSString *) bbsnum {
4133 return (id) BBSNum_ ?: [NSNull null];
4134}
4135
56296da0 4136- (NSString *) ecid {
849cd6bf 4137 return (id) ChipID_ ?: [NSNull null];
affeffc7
JF
4138}
4139
43f3d7f6 4140- (NSString *) serial {
56296da0 4141 return SerialNumber_;
43f3d7f6 4142}
86316a91 4143
56296da0 4144- (NSString *) role {
4121c5e0 4145 return (id) [NSNull null];
affeffc7
JF
4146}
4147
56296da0
JF
4148- (NSString *) model {
4149 return [NSString stringWithUTF8String:Machine_];
43f3d7f6 4150}
43f3d7f6 4151
3ea82d91
JF
4152- (NSString *) token {
4153 return (id) Token_ ?: [NSNull null];
4154}
4155
43f3d7f6 4156+ (NSString *) webScriptNameForSelector:(SEL)selector {
e58ff941 4157 if (false);
cfc7b442
JF
4158 else if (selector == @selector(addBridgedHost:))
4159 return @"addBridgedHost";
2e1652a9
JF
4160 else if (selector == @selector(addInsecureHost:))
4161 return @"addInsecureHost";
3f428e4e
JF
4162 else if (selector == @selector(addInternalRedirect::))
4163 return @"addInternalRedirect";
e4b48f2f 4164 else if (selector == @selector(addPipelinedHost:scheme:))
834c18a4 4165 return @"addPipelinedHost";
33e30380
JF
4166 else if (selector == @selector(addSource:::))
4167 return @"addSource";
247bedb6
JF
4168 else if (selector == @selector(addTokenHost:))
4169 return @"addTokenHost";
b088c0cd
JF
4170 else if (selector == @selector(addTrivialSource:))
4171 return @"addTrivialSource";
e58ff941 4172 else if (selector == @selector(close))
43f3d7f6 4173 return @"close";
e58ff941
JF
4174 else if (selector == @selector(du:))
4175 return @"du";
4176 else if (selector == @selector(stringWithFormat:arguments:))
4177 return @"format";
5c32f89e 4178 else if (selector == @selector(getAllSources))
caf0475e 4179 return @"getAllSources";
8c5b623f
JF
4180 else if (selector == @selector(getApplicationInfo:value:))
4181 return @"getApplicationInfoValue";
5bc1277a
JF
4182 else if (selector == @selector(getKernelNumber:))
4183 return @"getKernelNumber";
4184 else if (selector == @selector(getKernelString:))
4185 return @"getKernelString";
8cc8eb1c
JF
4186 else if (selector == @selector(getInstalledPackages))
4187 return @"getInstalledPackages";
c31d7cdc
JF
4188 else if (selector == @selector(getIORegistryEntry::))
4189 return @"getIORegistryEntry";
948db680
JF
4190 else if (selector == @selector(getLocaleIdentifier))
4191 return @"getLocaleIdentifier";
4192 else if (selector == @selector(getPreferredLanguages))
4193 return @"getPreferredLanguages";
43f3d7f6
JF
4194 else if (selector == @selector(getPackageById:))
4195 return @"getPackageById";
37fa9338
JF
4196 else if (selector == @selector(getMetadataKeys))
4197 return @"getMetadataKeys";
b3c8e69c
JF
4198 else if (selector == @selector(getMetadataValue:))
4199 return @"getMetadataValue";
ef974f52
JF
4200 else if (selector == @selector(getSessionValue:))
4201 return @"getSessionValue";
77801ff1
JF
4202 else if (selector == @selector(installPackages:))
4203 return @"installPackages";
518a552a
JF
4204 else if (selector == @selector(isReachable:))
4205 return @"isReachable";
e58ff941
JF
4206 else if (selector == @selector(localizedStringForKey:value:table:))
4207 return @"localize";
8d497e2a
JF
4208 else if (selector == @selector(popViewController:))
4209 return @"popViewController";
b088c0cd
JF
4210 else if (selector == @selector(refreshSources))
4211 return @"refreshSources";
aa1e1906
JF
4212 else if (selector == @selector(registerFrame:))
4213 return @"registerFrame";
ed5566c7
JF
4214 else if (selector == @selector(removeButton))
4215 return @"removeButton";
33e30380
JF
4216 else if (selector == @selector(saveConfig))
4217 return @"saveConfig";
b3c8e69c
JF
4218 else if (selector == @selector(setMetadataValue::))
4219 return @"setMetadataValue";
ef974f52
JF
4220 else if (selector == @selector(setSessionValue::))
4221 return @"setSessionValue";
8a126074
JF
4222 else if (selector == @selector(substitutePackageNames:))
4223 return @"substitutePackageNames";
8e3b68d4
JF
4224 else if (selector == @selector(scrollToBottom:))
4225 return @"scrollToBottom";
8366df5e
JF
4226 else if (selector == @selector(setAllowsNavigationAction:))
4227 return @"setAllowsNavigationAction";
c31c825d
JF
4228 else if (selector == @selector(setBadgeValue:))
4229 return @"setBadgeValue";
43f3d7f6
JF
4230 else if (selector == @selector(setButtonImage:withStyle:toFunction:))
4231 return @"setButtonImage";
4232 else if (selector == @selector(setButtonTitle:withStyle:toFunction:))
4233 return @"setButtonTitle";
b8a5d89d
JF
4234 else if (selector == @selector(setHidesBackButton:))
4235 return @"setHidesBackButton";
5cdfcd6f
JF
4236 else if (selector == @selector(setHidesNavigationBar:))
4237 return @"setHidesNavigationBar";
82406217
JF
4238 else if (selector == @selector(setNavigationBarStyle:))
4239 return @"setNavigationBarStyle";
00984204
JF
4240 else if (selector == @selector(setNavigationBarTintRed:green:blue:alpha:))
4241 return @"setNavigationBarTintColor";
36a20e14
JF
4242 else if (selector == @selector(setPasteboardString:))
4243 return @"setPasteboardString";
4244 else if (selector == @selector(setPasteboardURL:))
4245 return @"setPasteboardURL";
db698f42
JF
4246 else if (selector == @selector(setScrollAlwaysBounceVertical:))
4247 return @"setScrollAlwaysBounceVertical";
4886cc81
JF
4248 else if (selector == @selector(setScrollIndicatorStyle:))
4249 return @"setScrollIndicatorStyle";
ef055c6c
JF
4250 else if (selector == @selector(setToken:))
4251 return @"setToken";
43f3d7f6
JF
4252 else if (selector == @selector(setViewportWidth:))
4253 return @"setViewportWidth";
43f3d7f6
JF
4254 else if (selector == @selector(statfs:))
4255 return @"statfs";
e58ff941
JF
4256 else if (selector == @selector(supports:))
4257 return @"supports";
7c218781
JF
4258 else if (selector == @selector(unload))
4259 return @"unload";
c390d3ab 4260 else
43f3d7f6
JF
4261 return nil;
4262}
4263
4264+ (BOOL) isSelectorExcludedFromWebScript:(SEL)selector {
4265 return [self webScriptNameForSelector:selector] == nil;
c390d3ab
JF
4266}
4267
43f3d7f6
JF
4268- (BOOL) supports:(NSString *)feature {
4269 return [feature isEqualToString:@"window.open"];
4270}
c390d3ab 4271
7c218781
JF
4272- (void) unload {
4273 [delegate_ performSelectorOnMainThread:@selector(unloadData) withObject:nil waitUntilDone:NO];
4274}
4275
db698f42
JF
4276- (void) setScrollAlwaysBounceVertical:(NSNumber *)value {
4277 [indirect_ performSelectorOnMainThread:@selector(setScrollAlwaysBounceVerticalNumber:) withObject:value waitUntilDone:NO];
4278}
4279
4886cc81
JF
4280- (void) setScrollIndicatorStyle:(NSString *)style {
4281 [indirect_ performSelectorOnMainThread:@selector(setScrollIndicatorStyleWithName:) withObject:style waitUntilDone:NO];
4282}
4283
3f428e4e 4284- (void) addInternalRedirect:(NSString *)from :(NSString *)to {
a576488f 4285 [CydiaWebViewController performSelectorOnMainThread:@selector(addDiversion:) withObject:[[[Diversion alloc] initWithFrom:from to:to] autorelease] waitUntilDone:NO];
775deead
JF
4286}
4287
8c5b623f
JF
4288- (NSDictionary *) getApplicationInfo:(NSString *)display value:(NSString *)key {
4289 char path[1024];
4290 if (SBBundlePathForDisplayIdentifier(SBSSpringBoardServerPort(), [display UTF8String], path) != 0)
4291 return (id) [NSNull null];
4292 NSDictionary *info([NSDictionary dictionaryWithContentsOfFile:[[NSString stringWithUTF8String:path] stringByAppendingString:@"/Info.plist"]]);
4293 if (info == nil)
4294 return (id) [NSNull null];
4295 return [info objectForKey:key];
4296}
4297
5bc1277a
JF
4298- (NSNumber *) getKernelNumber:(NSString *)name {
4299 const char *string([name UTF8String]);
4300
4301 size_t size;
4302 if (sysctlbyname(string, NULL, &size, NULL, 0) == -1)
4303 return (id) [NSNull null];
4304
4305 if (size != sizeof(int))
4306 return (id) [NSNull null];
4307
4308 int value;
4309 if (sysctlbyname(string, &value, &size, NULL, 0) == -1)
4310 return (id) [NSNull null];
4311
4312 return [NSNumber numberWithInt:value];
4313}
4314
4315- (NSString *) getKernelString:(NSString *)name {
4316 const char *string([name UTF8String]);
4317
4318 size_t size;
4319 if (sysctlbyname(string, NULL, &size, NULL, 0) == -1)
4320 return (id) [NSNull null];
4321
4322 char value[size + 1];
4323 if (sysctlbyname(string, value, &size, NULL, 0) == -1)
4324 return (id) [NSNull null];
4325
4326 // XXX: just in case you request something ludicrous
4327 value[size] = '\0';
4328
4329 return [NSString stringWithCString:value];
4330}
4331
c31d7cdc
JF
4332- (NSObject *) getIORegistryEntry:(NSString *)path :(NSString *)entry {
4333 NSObject *value(CYIOGetValue([path UTF8String], entry));
4334
4335 if (value != nil)
4336 if ([value isKindOfClass:[NSData class]])
4337 value = CYHex((NSData *) value);
4338
4339 return value;
4340}
4341
37fa9338
JF
4342- (NSArray *) getMetadataKeys {
4343@synchronized (Values_) {
4344 return [Values_ allKeys];
4345} }
4346
aa1e1906
JF
4347- (void) registerFrame:(DOMHTMLIFrameElement *)iframe {
4348 WebFrame *frame([iframe contentFrame]);
4349 [indirect_ registerFrame:frame];
4350}
4351
b3c8e69c
JF
4352- (id) getMetadataValue:(NSString *)key {
4353@synchronized (Values_) {
4354 return [Values_ objectForKey:key];
4355} }
4356
4357- (void) setMetadataValue:(NSString *)key :(NSString *)value {
4358@synchronized (Values_) {
33e30380 4359 if (value == nil || value == (id) [WebUndefined undefined] || value == (id) [NSNull null])
b3c8e69c
JF
4360 [Values_ removeObjectForKey:key];
4361 else
4362 [Values_ setObject:value forKey:key];
4363
4364 [delegate_ performSelectorOnMainThread:@selector(updateValues) withObject:nil waitUntilDone:YES];
4365} }
4366
ef974f52
JF
4367- (id) getSessionValue:(NSString *)key {
4368@synchronized (SessionData_) {
4369 return [SessionData_ objectForKey:key];
4370} }
4371
4372- (void) setSessionValue:(NSString *)key :(NSString *)value {
4373@synchronized (SessionData_) {
4374 if (value == (id) [WebUndefined undefined])
4375 [SessionData_ removeObjectForKey:key];
4376 else
4377 [SessionData_ setObject:value forKey:key];
4378} }
4379
cfc7b442 4380- (void) addBridgedHost:(NSString *)host {
48f1762f
JF
4381@synchronized (HostConfig_) {
4382 [BridgedHosts_ addObject:host];
4383} }
5df7ecfb 4384
2e1652a9
JF
4385- (void) addInsecureHost:(NSString *)host {
4386@synchronized (HostConfig_) {
4387 [InsecureHosts_ addObject:host];
4388} }
4389
247bedb6
JF
4390- (void) addTokenHost:(NSString *)host {
4391@synchronized (HostConfig_) {
4392 [TokenHosts_ addObject:host];
4393} }
4394
e4b48f2f 4395- (void) addPipelinedHost:(NSString *)host scheme:(NSString *)scheme {
48f1762f 4396@synchronized (HostConfig_) {
e4b48f2f
JF
4397 if (scheme != (id) [WebUndefined undefined])
4398 host = [NSString stringWithFormat:@"%@:%@", [scheme lowercaseString], host];
4399
48f1762f
JF
4400 [PipelinedHosts_ addObject:host];
4401} }
834c18a4 4402
8d497e2a
JF
4403- (void) popViewController:(NSNumber *)value {
4404 if (value == (id) [WebUndefined undefined])
4405 value = [NSNumber numberWithBool:YES];
4406 [indirect_ performSelectorOnMainThread:@selector(popViewControllerWithNumber:) withObject:value waitUntilDone:NO];
4407}
4408
33e30380
JF
4409- (void) addSource:(NSString *)href :(NSString *)distribution :(WebScriptObject *)sections {
4410 NSMutableArray *array([NSMutableArray arrayWithCapacity:[sections count]]);
4411
4412 for (NSString *section in sections)
4413 [array addObject:section];
4414
4415 [delegate_ performSelectorOnMainThread:@selector(addSource:) withObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
4416 @"deb", @"Type",
4417 href, @"URI",
4418 distribution, @"Distribution",
4419 array, @"Sections",
4420 nil] waitUntilDone:NO];
4421}
4422
b088c0cd 4423- (void) addTrivialSource:(NSString *)href {
a1d85d42 4424 [delegate_ performSelectorOnMainThread:@selector(addTrivialSource:) withObject:href waitUntilDone:NO];
b088c0cd
JF
4425}
4426
4427- (void) refreshSources {
4428 [delegate_ performSelectorOnMainThread:@selector(syncData) withObject:nil waitUntilDone:NO];
4429}
4430
33e30380
JF
4431- (void) saveConfig {
4432 [delegate_ performSelectorOnMainThread:@selector(_saveConfig) withObject:nil waitUntilDone:NO];
4433}
4434
5c32f89e
JF
4435- (NSArray *) getAllSources {
4436 return [[Database sharedInstance] sources];
4437}
4438
8cc8eb1c 4439- (NSArray *) getInstalledPackages {
26e6829b
JF
4440 Database *database([Database sharedInstance]);
4441@synchronized (database) {
4442 NSArray *packages([database packages]);
f4db946e 4443 NSMutableArray *installed([NSMutableArray arrayWithCapacity:1024]);
77801ff1 4444 for (Package *package in packages)
26e6829b 4445 if (![package uninstalled])
8cc8eb1c
JF
4446 [installed addObject:package];
4447 return installed;
26e6829b 4448} }
8cc8eb1c 4449
43f3d7f6 4450- (Package *) getPackageById:(NSString *)id {
62cab237
JF
4451 if (Package *package = [[Database sharedInstance] packageWithName:id]) {
4452 [package parse];
4453 return package;
4454 } else
4455 return (Package *) [NSNull null];
43f3d7f6
JF
4456}
4457
948db680
JF
4458- (NSString *) getLocaleIdentifier {
4459 return Locale_ == NULL ? (NSString *) [NSNull null] : (NSString *) CFLocaleGetIdentifier(Locale_);
4460}
4461
4462- (NSArray *) getPreferredLanguages {
4463 return Languages_;
4464}
4465
43f3d7f6
JF
4466- (NSArray *) statfs:(NSString *)path {
4467 struct statfs stat;
4468
4469 if (path == nil || statfs([path UTF8String], &stat) == -1)
4470 return nil;
4471
4472 return [NSArray arrayWithObjects:
4473 [NSNumber numberWithUnsignedLong:stat.f_bsize],
4474 [NSNumber numberWithUnsignedLong:stat.f_blocks],
4475 [NSNumber numberWithUnsignedLong:stat.f_bfree],
4476 nil];
4477}
4478
4479- (NSNumber *) du:(NSString *)path {
4480 NSNumber *value(nil);
4481
4482 int fds[2];
4483 _assert(pipe(fds) != -1);
4484
4485 pid_t pid(ExecFork());
4486 if (pid == 0) {
4487 _assert(dup2(fds[1], 1) != -1);
4488 _assert(close(fds[0]) != -1);
4489 _assert(close(fds[1]) != -1);
4490 /* XXX: this should probably not use du */
4491 execl("/usr/libexec/cydia/du", "du", "-s", [path UTF8String], NULL);
4492 exit(1);
51c8106a
JF
4493 } else {
4494 _assert(close(fds[1]) != -1);
43f3d7f6 4495
51c8106a
JF
4496 if (FILE *du = fdopen(fds[0], "r")) {
4497 char line[1024];
4498 while (fgets(line, sizeof(line), du) != NULL) {
4499 size_t length(strlen(line));
4500 while (length != 0 && line[length - 1] == '\n')
4501 line[--length] = '\0';
4502 if (char *tab = strchr(line, '\t')) {
4503 *tab = '\0';
4504 value = [NSNumber numberWithUnsignedLong:strtoul(line, NULL, 0)];
4505 }
43f3d7f6 4506 }
43f3d7f6 4507
51c8106a
JF
4508 fclose(du);
4509 } else
4510 _assert(close(fds[0]) != -1);
4511 } ReapZombie(pid);
43f3d7f6
JF
4512
4513 return value;
4514}
4515
4516- (void) close {
e6417cea 4517 [indirect_ performSelectorOnMainThread:@selector(close) withObject:nil waitUntilDone:NO];
43f3d7f6
JF
4518}
4519
518a552a
JF
4520- (NSNumber *) isReachable:(NSString *)name {
4521 return [NSNumber numberWithBool:IsReachable([name UTF8String])];
4522}
4523
77801ff1
JF
4524- (void) installPackages:(NSArray *)packages {
4525 [delegate_ performSelectorOnMainThread:@selector(installPackages:) withObject:packages waitUntilDone:NO];
4526}
4527
8a126074 4528- (NSString *) substitutePackageNames:(NSString *)message {
0c4fe0f4 4529 NSMutableArray *words([[[message componentsSeparatedByString:@" "] mutableCopy] autorelease]);
8a126074
JF
4530 for (size_t i(0), e([words count]); i != e; ++i) {
4531 NSString *word([words objectAtIndex:i]);
4532 if (Package *package = [[Database sharedInstance] packageWithName:word])
4533 [words replaceObjectAtIndex:i withObject:[package name]];
4534 }
4535
4536 return [words componentsJoinedByString:@" "];
4537}
4538
ed5566c7
JF
4539- (void) removeButton {
4540 [indirect_ removeButton];
4541}
4542
43f3d7f6
JF
4543- (void) setButtonImage:(NSString *)button withStyle:(NSString *)style toFunction:(id)function {
4544 [indirect_ setButtonImage:button withStyle:style toFunction:function];
4545}
4546
4547- (void) setButtonTitle:(NSString *)button withStyle:(NSString *)style toFunction:(id)function {
4548 [indirect_ setButtonTitle:button withStyle:style toFunction:function];
4549}
4550
c31c825d
JF
4551- (void) setBadgeValue:(id)value {
4552 [indirect_ performSelectorOnMainThread:@selector(setBadgeValue:) withObject:value waitUntilDone:NO];
4553}
4554
8366df5e
JF
4555- (void) setAllowsNavigationAction:(NSString *)value {
4556 [indirect_ performSelectorOnMainThread:@selector(setAllowsNavigationActionByNumber:) withObject:value waitUntilDone:NO];
4557}
4558
b8a5d89d
JF
4559- (void) setHidesBackButton:(NSString *)value {
4560 [indirect_ performSelectorOnMainThread:@selector(setHidesBackButtonByNumber:) withObject:value waitUntilDone:NO];
4561}
4562
5cdfcd6f
JF
4563- (void) setHidesNavigationBar:(NSString *)value {
4564 [indirect_ performSelectorOnMainThread:@selector(setHidesNavigationBarByNumber:) withObject:value waitUntilDone:NO];
4565}
4566
82406217
JF
4567- (void) setNavigationBarStyle:(NSString *)value {
4568 [indirect_ performSelectorOnMainThread:@selector(setNavigationBarStyle:) withObject:value waitUntilDone:NO];
4569}
4570
00984204
JF
4571- (void) setNavigationBarTintRed:(NSNumber *)red green:(NSNumber *)green blue:(NSNumber *)blue alpha:(NSNumber *)alpha {
4572 float opacity(alpha == (id) [WebUndefined undefined] ? 1 : [alpha floatValue]);
4573 UIColor *color([UIColor colorWithRed:[red floatValue] green:[green floatValue] blue:[blue floatValue] alpha:opacity]);
4574 [indirect_ performSelectorOnMainThread:@selector(setNavigationBarTintColor:) withObject:color waitUntilDone:NO];
4575}
4576
36a20e14
JF
4577- (void) setPasteboardString:(NSString *)value {
4578 [[objc_getClass("UIPasteboard") generalPasteboard] setString:value];
4579}
4580
4581- (void) setPasteboardURL:(NSString *)value {
4582 [[objc_getClass("UIPasteboard") generalPasteboard] setURL:[NSURL URLWithString:value]];
4583}
4584
673a6e1a 4585- (void) _setToken:(NSString *)token {
9737d93e
JF
4586 Token_ = token;
4587
4588 if (token == nil)
4589 [Metadata_ removeObjectForKey:@"Token"];
4590 else
4591 [Metadata_ setObject:Token_ forKey:@"Token"];
ef055c6c 4592
ef055c6c
JF
4593 Changed_ = true;
4594}
4595
673a6e1a
JF
4596- (void) setToken:(NSString *)token {
4597 [self performSelectorOnMainThread:@selector(_setToken:) withObject:token waitUntilDone:NO];
4598}
4599
8e3b68d4
JF
4600- (void) scrollToBottom:(NSNumber *)animated {
4601 [indirect_ performSelectorOnMainThread:@selector(scrollToBottomAnimated:) withObject:animated waitUntilDone:NO];
4602}
4603
43f3d7f6 4604- (void) setViewportWidth:(float)width {
8dbdaafa 4605 [indirect_ setViewportWidthOnMainThread:width];
43f3d7f6
JF
4606}
4607
4608- (NSString *) stringWithFormat:(NSString *)format arguments:(WebScriptObject *)arguments {
4609 //NSLog(@"SWF:\"%@\" A:%@", format, [arguments description]);
4610 unsigned count([arguments count]);
4611 id values[count];
4612 for (unsigned i(0); i != count; ++i)
4613 values[i] = [arguments objectAtIndex:i];
673e8fa3 4614 return [[[NSString alloc] initWithFormat:format arguments:reinterpret_cast<va_list>(values)] autorelease];
43f3d7f6
JF
4615}
4616
4617- (NSString *) localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)table {
a95e0405
JF
4618 if (reinterpret_cast<id>(value) == [WebUndefined undefined])
4619 value = nil;
43f3d7f6
JF
4620 if (reinterpret_cast<id>(table) == [WebUndefined undefined])
4621 table = nil;
4622 return [[NSBundle mainBundle] localizedStringForKey:key value:value table:table];
c390d3ab
JF
4623}
4624
4625@end
4626/* }}} */
f79a4512 4627
2e1652a9
JF
4628@interface NSURL (CydiaSecure)
4629@end
4630
4631@implementation NSURL (CydiaSecure)
4632
4633- (bool) isCydiaSecure {
4634 if ([[[self scheme] lowercaseString] isEqualToString:@"https"])
4635 return true;
4636
4637 @synchronized (HostConfig_) {
4638 if ([InsecureHosts_ containsObject:[self host]])
4639 return true;
4640 }
4641
4642 return false;
4643}
4644
4645@end
dd5f8161 4646
80319240 4647/* Cydia Browser Controller {{{ */
a576488f 4648@implementation CydiaWebViewController
43f3d7f6 4649
fe8e721f 4650- (NSURL *) navigationURL {
d323285e 4651 return request_ == nil ? nil : [NSURL URLWithString:[NSString stringWithFormat:@"cydia://url/%@", [[request_ URL] absoluteString]]];
fe8e721f
GP
4652}
4653
3f9ab807
JF
4654+ (void) _initialize {
4655 [super _initialize];
4656
7b33d201 4657 Diversions_ = [NSMutableSet setWithCapacity:0];
775deead
JF
4658}
4659
4660+ (void) addDiversion:(Diversion *)diversion {
4661 [Diversions_ addObject:diversion];
4662}
4663
2634b249
JF
4664- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
4665 [super webView:view didClearWindowObject:window forFrame:frame];
9e130bc2
JF
4666 [CydiaWebViewController didClearWindowObject:window forFrame:frame withCydia:cydia_];
4667}
01d93940 4668
9e130bc2 4669+ (void) didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame withCydia:(CydiaObject *)cydia {
01d93940
JF
4670 WebDataSource *source([frame dataSource]);
4671 NSURLResponse *response([source response]);
4672 NSURL *url([response URL]);
b8f1a18a
JF
4673 NSString *scheme([[url scheme] lowercaseString]);
4674
4675 bool bridged(false);
8804004f 4676
48f1762f 4677 @synchronized (HostConfig_) {
b8f1a18a
JF
4678 if ([scheme isEqualToString:@"file"])
4679 bridged = true;
4680 else if ([scheme isEqualToString:@"https"])
48f1762f 4681 if ([BridgedHosts_ containsObject:[url host]])
b8f1a18a 4682 bridged = true;
48f1762f 4683 }
b8f1a18a
JF
4684
4685 if (bridged)
9e130bc2 4686 [window setValue:cydia forKey:@"cydia"];
43f3d7f6
JF
4687}
4688
22485d93
JF
4689- (void) _setupMail:(MFMailComposeViewController *)controller {
4690 [controller addAttachmentData:[NSData dataWithContentsOfFile:@"/tmp/cydia.log"] mimeType:@"text/plain" fileName:@"cydia.log"];
4691
4692 system("/usr/bin/dpkg -l >/tmp/dpkgl.log");
4693 [controller addAttachmentData:[NSData dataWithContentsOfFile:@"/tmp/dpkgl.log"] mimeType:@"text/plain" fileName:@"dpkgl.log"];
4694}
4695
f9b36dae
JF
4696- (NSURL *) URLWithURL:(NSURL *)url {
4697 return [Diversion divertURL:url];
4698}
4699
9d1bf666 4700- (NSURLRequest *) webView:(WebView *)view resource:(id)resource willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response fromDataSource:(WebDataSource *)source {
85ae5f42
JF
4701 return [CydiaWebViewController requestWithHeaders:[super webView:view resource:resource willSendRequest:request redirectResponse:response fromDataSource:source]];
4702}
4703
4704+ (NSURLRequest *) requestWithHeaders:(NSURLRequest *)request {
0c4fe0f4 4705 NSMutableURLRequest *copy([[request mutableCopy] autorelease]);
9d1bf666 4706
bc1cffbe 4707 NSURL *url([copy URL]);
daa21f8e 4708 NSString *href([url absoluteString]);
bc1cffbe
JF
4709 NSString *host([url host]);
4710
daa21f8e
JF
4711 if ([href hasPrefix:@"https://cydia.saurik.com/TSS/"]) {
4712 if (NSString *agent = [copy valueForHTTPHeaderField:@"X-User-Agent"]) {
4713 [copy setValue:agent forHTTPHeaderField:@"User-Agent"];
4714 [copy setValue:nil forHTTPHeaderField:@"X-User-Agent"];
4715 }
4716
4717 [copy setValue:nil forHTTPHeaderField:@"Referer"];
4718 [copy setValue:nil forHTTPHeaderField:@"Origin"];
4719
4720 [copy setURL:[NSURL URLWithString:[@"http://gs.apple.com/TSS/" stringByAppendingString:[href substringFromIndex:29]]]];
4721 return copy;
4722 }
4723
6f44d712
JF
4724 if ([copy valueForHTTPHeaderField:@"X-Cydia-Cf"] == nil)
4725 [copy setValue:[NSString stringWithFormat:@"%.2f", kCFCoreFoundationVersionNumber] forHTTPHeaderField:@"X-Cydia-Cf"];
1baae086 4726 if (Machine_ != NULL && [copy valueForHTTPHeaderField:@"X-Machine"] == nil)
9d1bf666 4727 [copy setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"];
247bedb6 4728
e7817a6b 4729 bool bridged;
247bedb6 4730 bool token;
e7817a6b 4731
247bedb6 4732 @synchronized (HostConfig_) {
e7817a6b
JF
4733 bridged = [BridgedHosts_ containsObject:host];
4734 token = [TokenHosts_ containsObject:host];
247bedb6
JF
4735 }
4736
e7817a6b
JF
4737 if ([url isCydiaSecure]) {
4738 if (bridged) {
4739 if (UniqueID_ != nil && [copy valueForHTTPHeaderField:@"X-Cydia-Id"] == nil)
4740 [copy setValue:UniqueID_ forHTTPHeaderField:@"X-Cydia-Id"];
4741 } else if (token) {
4742 if (Token_ != nil && [copy valueForHTTPHeaderField:@"X-Cydia-Token"] == nil)
4743 [copy setValue:Token_ forHTTPHeaderField:@"X-Cydia-Token"];
4744 }
247bedb6 4745 }
43f3d7f6
JF
4746
4747 return copy;
a9a0661e
JF
4748}
4749
77801ff1
JF
4750- (void) setDelegate:(id)delegate {
4751 [super setDelegate:delegate];
4752 [cydia_ setDelegate:delegate];
4753}
4754
c6cf66c7 4755- (NSString *) applicationNameForUserAgent {
e967efd5 4756 return UserAgent_;
c6cf66c7 4757}
43f3d7f6 4758
c6cf66c7
JF
4759- (id) init {
4760 if ((self = [super initWithWidth:0 ofClass:[CydiaWebViewController class]]) != nil) {
4761 cydia_ = [[[CydiaObject alloc] initWithDelegate:indirect_] autorelease];
43f3d7f6
JF
4762 } return self;
4763}
4764
29bb09d7
JF
4765@end
4766
4767@interface AppCacheController : CydiaWebViewController {
4768}
4769
4770@end
4771
4772@implementation AppCacheController
4773
4774- (void) didReceiveMemoryWarning {
6271cb57 4775 // XXX: this doesn't work
29bb09d7
JF
4776}
4777
2713be8e
JF
4778- (bool) retainsNetworkActivityIndicator {
4779 return false;
4780}
4781
43f3d7f6 4782@end
80319240 4783/* }}} */
43f3d7f6 4784
b1ca831d
JF
4785// CydiaScript {{{
4786@interface NSObject (CydiaScript)
4787- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context;
4788@end
4789
4790@implementation NSObject (CydiaScript)
4791
4792- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
4793 return self;
4794}
4795
4796@end
4797
4798@implementation NSArray (CydiaScript)
4799
4800- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
4801 WebScriptObject *object([context evaluateWebScript:@"[]"]);
4802 for (size_t i(0), e([self count]); i != e; ++i)
4803 [object setWebScriptValueAtIndex:i value:[[self objectAtIndex:i] Cydia$webScriptObjectInContext:context]];
4804 return object;
4805}
4806
4807@end
4808
4809@implementation NSDictionary (CydiaScript)
4810
4811- (id) Cydia$webScriptObjectInContext:(WebScriptObject *)context {
4812 WebScriptObject *object([context evaluateWebScript:@"({})"]);
4813 for (id i in self)
4814 [object setValue:[[self objectForKey:i] Cydia$webScriptObjectInContext:context] forKey:i];
4815 return object;
4816}
4817
4818@end
4819// }}}
4820
5829aea2
GP
4821/* Confirmation Controller {{{ */
4822bool DepSubstrate(const pkgCache::VerIterator &iterator) {
4823 if (!iterator.end())
4824 for (pkgCache::DepIterator dep(iterator.DependsList()); !dep.end(); ++dep) {
4825 if (dep->Type != pkgCache::Dep::Depends && dep->Type != pkgCache::Dep::PreDepends)
4826 continue;
4827 pkgCache::PkgIterator package(dep.TargetPkg());
4828 if (package.end())
4829 continue;
4830 if (strcmp(package.Name(), "mobilesubstrate") == 0)
4831 return true;
4832 }
4833
4834 return false;
4835}
4836
adb61bda 4837@protocol ConfirmationControllerDelegate
674dce72 4838- (void) cancelAndClear:(bool)clear;
b5e7eebb 4839- (void) confirmWithNavigationController:(UINavigationController *)navigation;
dc63e78f 4840- (void) queue;
36bb2ca2 4841@end
2367a917 4842
a576488f 4843@interface ConfirmationController : CydiaWebViewController {
770f2a8e 4844 _transient Database *database_;
6ceb0959 4845
7b33d201 4846 _H<UIAlertView> essential_;
6ceb0959 4847
7b33d201
JF
4848 _H<NSDictionary> changes_;
4849 _H<NSMutableArray> issues_;
4850 _H<NSDictionary> sizes_;
6ceb0959 4851
a9a0661e 4852 BOOL substrate_;
36bb2ca2 4853}
dc5812ec 4854
b5e7eebb 4855- (id) initWithDatabase:(Database *)database;
b4d89997 4856
dc5812ec
JF
4857@end
4858
adb61bda 4859@implementation ConfirmationController
dc5812ec 4860
ab2cfc1e
JF
4861- (void) complete {
4862 if (substrate_)
be860cc8 4863 RestartSubstrate_ = true;
ab2cfc1e
JF
4864 [delegate_ confirmWithNavigationController:[self navigationController]];
4865}
4866
b5e7eebb 4867- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
3272e699 4868 NSString *context([alert context]);
9bedffaa 4869
1cedb821 4870 if ([context isEqualToString:@"remove"]) {
ab2cfc1e 4871 if (button == [alert cancelButtonIndex])
b5e7eebb 4872 [self dismissModalViewControllerAnimated:YES];
ab2cfc1e 4873 else if (button == [alert firstOtherButtonIndex]) {
d69dbfc5 4874 [self performSelector:@selector(complete) withObject:nil afterDelay:0];
9bedffaa 4875 }
9bedffaa 4876
3272e699 4877 [alert dismissWithClickedButtonIndex:-1 animated:YES];
1cedb821 4878 } else if ([context isEqualToString:@"unable"]) {
b5e7eebb 4879 [self dismissModalViewControllerAnimated:YES];
3272e699
GP
4880 [alert dismissWithClickedButtonIndex:-1 animated:YES];
4881 } else {
b5e7eebb
GP
4882 [super alertView:alert clickedButtonAtIndex:button];
4883 }
36bb2ca2
JF
4884}
4885
46c46f4f 4886- (void) _doContinue {
21ea11a4 4887 [delegate_ cancelAndClear:NO];
12d3d98d 4888 [self dismissModalViewControllerAnimated:YES];
46c46f4f 4889}
bc11cf5b 4890
46c46f4f
JF
4891- (id) invokeDefaultMethodWithArguments:(NSArray *)args {
4892 [self performSelectorOnMainThread:@selector(_doContinue) withObject:nil waitUntilDone:NO];
21ea11a4
GP
4893 return nil;
4894}
4895
2634b249
JF
4896- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
4897 [super webView:view didClearWindowObject:window forFrame:frame];
6ceb0959 4898
781001d7 4899 [window setValue:[[NSDictionary dictionaryWithObjectsAndKeys:
7b33d201
JF
4900 (id) changes_, @"changes",
4901 (id) issues_, @"issues",
4902 (id) sizes_, @"sizes",
781001d7
JF
4903 self, @"queue",
4904 nil] Cydia$webScriptObjectInContext:window] forKey:@"cydiaConfirm"];
36bb2ca2
JF
4905}
4906
b5e7eebb
GP
4907- (id) initWithDatabase:(Database *)database {
4908 if ((self = [super init]) != nil) {
770f2a8e
JF
4909 database_ = database;
4910
6ceb0959
JF
4911 NSMutableArray *installs([NSMutableArray arrayWithCapacity:16]);
4912 NSMutableArray *reinstalls([NSMutableArray arrayWithCapacity:16]);
4913 NSMutableArray *upgrades([NSMutableArray arrayWithCapacity:16]);
4914 NSMutableArray *downgrades([NSMutableArray arrayWithCapacity:16]);
4915 NSMutableArray *removes([NSMutableArray arrayWithCapacity:16]);
dc5812ec 4916
36bb2ca2 4917 bool remove(false);
dc5812ec 4918
6ceb0959
JF
4919 pkgCacheFile &cache([database_ cache]);
4920 NSArray *packages([database_ packages]);
a9a0661e
JF
4921 pkgDepCache::Policy *policy([database_ policy]);
4922
7b33d201 4923 issues_ = [NSMutableArray arrayWithCapacity:4];
6ceb0959 4924
c4dcf2c2 4925 for (Package *package in packages) {
6ceb0959
JF
4926 pkgCache::PkgIterator iterator([package iterator]);
4927 NSString *name([package id]);
4928
4929 if ([package broken]) {
4930 NSMutableArray *reasons([NSMutableArray arrayWithCapacity:4]);
4931
4932 [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
4933 name, @"package",
4934 reasons, @"reasons",
4935 nil]];
4936
4937 pkgCache::VerIterator ver(cache[iterator].InstVerIter(cache));
4938 if (ver.end())
4939 continue;
4940
4941 for (pkgCache::DepIterator dep(ver.DependsList()); !dep.end(); ) {
4942 pkgCache::DepIterator start;
4943 pkgCache::DepIterator end;
4944 dep.GlobOr(start, end); // ++dep
4945
4946 if (!cache->IsImportantDep(end))
4947 continue;
4948 if ((cache[end] & pkgDepCache::DepGInstall) != 0)
4949 continue;
4950
810c9763
JF
4951 NSMutableArray *clauses([NSMutableArray arrayWithCapacity:4]);
4952
4953 [reasons addObject:[NSDictionary dictionaryWithObjectsAndKeys:
4954 [NSString stringWithUTF8String:start.DepType()], @"relationship",
4955 clauses, @"clauses",
4956 nil]];
4957
6ceb0959
JF
4958 _forever {
4959 NSString *reason, *installed((NSString *) [WebUndefined undefined]);
4960
4961 pkgCache::PkgIterator target(start.TargetPkg());
4962 if (target->ProvidesList != 0)
4963 reason = @"missing";
4964 else {
4965 pkgCache::VerIterator ver(cache[target].InstVerIter(cache));
4966 if (!ver.end()) {
4967 reason = @"installed";
4968 installed = [NSString stringWithUTF8String:ver.VerStr()];
4969 } else if (!cache[target].CandidateVerIter(cache).end())
4970 reason = @"uninstalled";
4971 else if (target->ProvidesList == 0)
4972 reason = @"uninstallable";
4973 else
4974 reason = @"virtual";
4975 }
4976
3e5a9f5d 4977 NSDictionary *version(start.TargetVer() == 0 ? (NSDictionary *) [NSNull null] : [NSDictionary dictionaryWithObjectsAndKeys:
6ceb0959
JF
4978 [NSString stringWithUTF8String:start.CompType()], @"operator",
4979 [NSString stringWithUTF8String:start.TargetVer()], @"value",
4980 nil]);
4981
810c9763 4982 [clauses addObject:[NSDictionary dictionaryWithObjectsAndKeys:
6ceb0959
JF
4983 [NSString stringWithUTF8String:start.TargetPkg().Name()], @"package",
4984 version, @"version",
4985 reason, @"reason",
4986 installed, @"installed",
4987 nil]];
4988
4989 // yes, seriously. (wtf?)
4990 if (start == end)
4991 break;
4992 ++start;
4993 }
4994 }
4995 }
4996
36bb2ca2 4997 pkgDepCache::StateCache &state(cache[iterator]);
dc5812ec 4998
6ceb0959 4999 static Pcre special_r("^(firmware$|gsc\\.|cy\\+)");
2388b078 5000
36bb2ca2 5001 if (state.NewInstall())
6ceb0959 5002 [installs addObject:name];
f8d15be2 5003 // XXX: else if (state.Install())
36bb2ca2 5004 else if (!state.Delete() && (state.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall)
6ceb0959 5005 [reinstalls addObject:name];
f8d15be2 5006 // XXX: move before previous if
36bb2ca2 5007 else if (state.Upgrade())
6ceb0959 5008 [upgrades addObject:name];
36bb2ca2 5009 else if (state.Downgrade())
6ceb0959
JF
5010 [downgrades addObject:name];
5011 else if (!state.Delete())
f8d15be2 5012 // XXX: _assert(state.Keep());
6ceb0959 5013 continue;
1916f316
JF
5014 else if (special_r(name))
5015 [issues_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
5016 [NSNull null], @"package",
5017 [NSArray arrayWithObjects:
5018 [NSDictionary dictionaryWithObjectsAndKeys:
810c9763
JF
5019 @"Conflicts", @"relationship",
5020 [NSArray arrayWithObjects:
5021 [NSDictionary dictionaryWithObjectsAndKeys:
5022 name, @"package",
5023 [NSNull null], @"version",
5024 @"installed", @"reason",
5025 nil],
5026 nil], @"clauses",
1916f316
JF
5027 nil],
5028 nil], @"reasons",
5029 nil]];
5030 else {
2388b078 5031 if ([package essential])
36bb2ca2 5032 remove = true;
6ceb0959
JF
5033 [removes addObject:name];
5034 }
a9a0661e
JF
5035
5036 substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
5037 substrate_ |= DepSubstrate(iterator.CurrentVer());
36bb2ca2 5038 }
ec97ef06 5039
36bb2ca2
JF
5040 if (!remove)
5041 essential_ = nil;
d791dce4 5042 else if (Advanced_) {
43f3d7f6 5043 NSString *parenthetical(UCLocalize("PARENTHETICAL"));
f79a4512 5044
7b33d201 5045 essential_ = [[[UIAlertView alloc]
43f3d7f6 5046 initWithTitle:UCLocalize("REMOVING_ESSENTIALS")
b5e7eebb
GP
5047 message:UCLocalize("REMOVING_ESSENTIALS_EX")
5048 delegate:self
5049 cancelButtonTitle:[NSString stringWithFormat:parenthetical, UCLocalize("CANCEL_OPERATION"), UCLocalize("SAFE")]
1aa29546
JF
5050 otherButtonTitles:
5051 [NSString stringWithFormat:parenthetical, UCLocalize("FORCE_REMOVAL"), UCLocalize("UNSAFE")],
5052 nil
7b33d201 5053 ] autorelease];
04fe1349 5054
3272e699 5055 [essential_ setContext:@"remove"];
9aaebfec 5056 [essential_ setNumberOfRows:2];
9bedffaa 5057 } else {
7b33d201 5058 essential_ = [[[UIAlertView alloc]
43f3d7f6 5059 initWithTitle:UCLocalize("UNABLE_TO_COMPLY")
b5e7eebb
GP
5060 message:UCLocalize("UNABLE_TO_COMPLY_EX")
5061 delegate:self
5062 cancelButtonTitle:UCLocalize("OKAY")
5063 otherButtonTitles:nil
7b33d201 5064 ] autorelease];
ec97ef06 5065
b5e7eebb 5066 [essential_ setContext:@"unable"];
36bb2ca2 5067 }
ec97ef06 5068
7b33d201 5069 changes_ = [NSDictionary dictionaryWithObjectsAndKeys:
6ceb0959
JF
5070 installs, @"installs",
5071 reinstalls, @"reinstalls",
5072 upgrades, @"upgrades",
5073 downgrades, @"downgrades",
5074 removes, @"removes",
affeffc7 5075 nil];
dc5812ec 5076
7b33d201 5077 sizes_ = [NSDictionary dictionaryWithObjectsAndKeys:
6ceb0959
JF
5078 [NSNumber numberWithInteger:[database_ fetcher].FetchNeeded()], @"downloading",
5079 [NSNumber numberWithInteger:[database_ fetcher].PartialPresent()], @"resuming",
affeffc7 5080 nil];
dc5812ec 5081
90351d93 5082 [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/confirm/", UI_]]];
36bb2ca2 5083 } return self;
b4d89997 5084}
8da60fb7 5085
e6124cb6
JF
5086- (UIBarButtonItem *) leftButton {
5087 return [[[UIBarButtonItem alloc]
5088 initWithTitle:UCLocalize("CANCEL")
5089 style:UIBarButtonItemStylePlain
5090 target:self
5091 action:@selector(cancelButtonClicked)
5092 ] autorelease];
5093}
5094
614cd4f1 5095#if !AlwaysReload
bcde1e70 5096- (void) applyRightButton {
6ceb0959 5097 if ([issues_ count] == 0 && ![self isLoading])
dd9de556
JF
5098 [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
5099 initWithTitle:UCLocalize("CONFIRM")
2761d574 5100 style:UIBarButtonItemStyleDone
dd9de556
JF
5101 target:self
5102 action:@selector(confirmButtonClicked)
5103 ] autorelease]];
5104 else
bcde1e70 5105 [[self navigationItem] setRightBarButtonItem:nil];
affeffc7 5106}
bcde1e70 5107#endif
affeffc7 5108
b5e7eebb 5109- (void) cancelButtonClicked {
21ea11a4 5110 [delegate_ cancelAndClear:YES];
05452929 5111 [self dismissModalViewControllerAnimated:YES];
affeffc7
JF
5112}
5113
9487f027 5114#if !AlwaysReload
b5e7eebb 5115- (void) confirmButtonClicked {
affeffc7 5116 if (essential_ != nil)
b5e7eebb 5117 [essential_ show];
ab2cfc1e
JF
5118 else
5119 [self complete];
affeffc7 5120}
9487f027 5121#endif
affeffc7 5122
36bb2ca2
JF
5123@end
5124/* }}} */
8da60fb7 5125
aaae308d
JF
5126/* Progress Data {{{ */
5127@interface CydiaProgressData : NSObject {
5128 _transient id delegate_;
5129
5130 bool running_;
b0b11d99 5131 float percent_;
aaae308d 5132
bcbac8f7
JF
5133 float current_;
5134 float total_;
5135 float speed_;
5136
aaae308d
JF
5137 _H<NSMutableArray> events_;
5138 _H<NSString> title_;
5139
5140 _H<NSString> status_;
5141 _H<NSString> finish_;
5142}
5143
5144@end
5145
5146@implementation CydiaProgressData
5147
5148+ (NSArray *) _attributeKeys {
5149 return [NSArray arrayWithObjects:
bcbac8f7 5150 @"current",
aaae308d
JF
5151 @"events",
5152 @"finish",
b0b11d99 5153 @"percent",
aaae308d 5154 @"running",
bcbac8f7 5155 @"speed",
aaae308d 5156 @"title",
bcbac8f7 5157 @"total",
aaae308d
JF
5158 nil];
5159}
5160
5161- (NSArray *) attributeKeys {
5162 return [[self class] _attributeKeys];
5163}
5164
5165+ (BOOL) isKeyExcludedFromWebScript:(const char *)name {
5166 return ![[self _attributeKeys] containsObject:[NSString stringWithUTF8String:name]] && [super isKeyExcludedFromWebScript:name];
5167}
5168
5169- (id) init {
5170 if ((self = [super init]) != nil) {
5171 events_ = [NSMutableArray arrayWithCapacity:32];
5172 } return self;
5173}
5174
353dda5b
JF
5175- (id) delegate {
5176 return delegate_;
5177}
5178
aaae308d
JF
5179- (void) setDelegate:(id)delegate {
5180 delegate_ = delegate;
5181}
5182
b0b11d99
JF
5183- (void) setPercent:(float)value {
5184 percent_ = value;
aaae308d
JF
5185}
5186
b0b11d99
JF
5187- (NSNumber *) percent {
5188 return [NSNumber numberWithFloat:percent_];
aaae308d
JF
5189}
5190
bcbac8f7
JF
5191- (void) setCurrent:(float)value {
5192 current_ = value;
5193}
5194
5195- (NSNumber *) current {
5196 return [NSNumber numberWithFloat:current_];
5197}
5198
5199- (void) setTotal:(float)value {
5200 total_ = value;
5201}
5202
5203- (NSNumber *) total {
5204 return [NSNumber numberWithFloat:total_];
5205}
5206
5207- (void) setSpeed:(float)value {
5208 speed_ = value;
5209}
5210
5211- (NSNumber *) speed {
5212 return [NSNumber numberWithFloat:speed_];
5213}
5214
aaae308d
JF
5215- (NSArray *) events {
5216 return events_;
5217}
5218
5219- (void) removeAllEvents {
5220 [events_ removeAllObjects];
5221}
5222
5223- (void) addEvent:(CydiaProgressEvent *)event {
5224 [events_ addObject:event];
5225}
5226
5227- (void) setTitle:(NSString *)text {
5228 title_ = text;
5229}
5230
5231- (NSString *) title {
5232 return title_;
5233}
5234
5235- (void) setFinish:(NSString *)text {
5236 finish_ = text;
5237}
5238
5239- (NSString *) finish {
5240 return (id) finish_ ?: [NSNull null];
5241}
5242
5243- (void) setRunning:(bool)running {
5244 running_ = running;
5245}
5246
5247- (NSNumber *) running {
5248 return running_ ? (NSNumber *) kCFBooleanTrue : (NSNumber *) kCFBooleanFalse;
5249}
5250
5251@end
5252/* }}} */
adb61bda 5253/* Progress Controller {{{ */
a576488f 5254@interface ProgressController : CydiaWebViewController <
36bb2ca2
JF
5255 ProgressDelegate
5256> {
8b29f8e6 5257 _transient Database *database_;
bf7c998c 5258 _H<CydiaProgressData, 1> progress_;
aaae308d 5259 unsigned cancel_;
2367a917
JF
5260}
5261
b5e7eebb 5262- (id) initWithDatabase:(Database *)database delegate:(id)delegate;
2367a917 5263
6915b806 5264- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title;
2367a917 5265
6915b806 5266- (void) setTitle:(NSString *)title;
aaae308d 5267- (void) setCancellable:(bool)cancellable;
bd150f54 5268
36bb2ca2
JF
5269@end
5270
adb61bda 5271@implementation ProgressController
2367a917
JF
5272
5273- (void) dealloc {
6915b806 5274 [database_ setProgressDelegate:nil];
2367a917
JF
5275 [super dealloc];
5276}
5277
63ae52be
JF
5278- (UIBarButtonItem *) leftButton {
5279 return cancel_ == 1 ? [[[UIBarButtonItem alloc]
3c62d654
JF
5280 initWithTitle:UCLocalize("CANCEL")
5281 style:UIBarButtonItemStylePlain
5282 target:self
5283 action:@selector(cancel)
63ae52be
JF
5284 ] autorelease] : nil;
5285}
5286
5287- (void) updateCancel {
5288 [super applyLeftButton];
3c62d654
JF
5289}
5290
b5e7eebb
GP
5291- (id) initWithDatabase:(Database *)database delegate:(id)delegate {
5292 if ((self = [super init]) != nil) {
8b29f8e6 5293 database_ = database;
36bb2ca2 5294 delegate_ = delegate;
ec97ef06 5295
6915b806
JF
5296 [database_ setProgressDelegate:self];
5297
aaae308d
JF
5298 progress_ = [[[CydiaProgressData alloc] init] autorelease];
5299 [progress_ setDelegate:self];
3c62d654 5300
90351d93 5301 [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/progress/", UI_]]];
3c62d654
JF
5302
5303 [scroller_ setBackgroundColor:[UIColor blackColor]];
5304
5305 [[self navigationItem] setHidesBackButton:YES];
5306
5307 [self updateCancel];
36bb2ca2 5308 } return self;
2367a917
JF
5309}
5310
aaae308d
JF
5311- (void) webView:(WebView *)view didClearWindowObject:(WebScriptObject *)window forFrame:(WebFrame *)frame {
5312 [super webView:view didClearWindowObject:window forFrame:frame];
5313 [window setValue:progress_ forKey:@"cydiaProgress"];
5314}
bc11cf5b 5315
aaae308d
JF
5316- (void) updateProgress {
5317 [self dispatchEvent:@"CydiaProgressUpdate"];
5318}
b5e7eebb 5319
b5e7eebb 5320- (void) viewWillAppear:(BOOL)animated {
14e4ff09 5321 [[[self navigationController] navigationBar] setBarStyle:UIBarStyleBlack];
3c62d654 5322 [super viewWillAppear:animated];
2367a917
JF
5323}
5324
a3755a1e 5325- (void) reloadSpringBoard {
2b6abb56
JF
5326 if (kCFCoreFoundationVersionNumber > 700) { // XXX: iOS 6.x
5327 system("/bin/launchctl stop com.apple.backboardd");
5328 sleep(15);
5329 system("/usr/bin/killall backboardd SpringBoard sbreload");
5330 return;
5331 }
5332
1b120913
JF
5333 pid_t pid(ExecFork());
5334 if (pid == 0) {
024cdbbf
JF
5335 if (setsid() == -1)
5336 perror("setsid");
5337
1b120913
JF
5338 pid_t pid(ExecFork());
5339 if (pid == 0) {
5340 execl("/usr/bin/sbreload", "sbreload", NULL);
5341 perror("sbreload");
51c8106a 5342
1b120913 5343 exit(0);
51c8106a 5344 } ReapZombie(pid);
1b120913
JF
5345
5346 exit(0);
51c8106a 5347 } ReapZombie(pid);
1b120913
JF
5348
5349 sleep(15);
2b6abb56 5350 system("/usr/bin/killall backboardd SpringBoard sbreload");
a3755a1e
JF
5351}
5352
aaae308d 5353- (void) close {
ef494bd8
RP
5354 UpdateExternalStatus(0);
5355
9dd3045d
JF
5356 if (Finish_ > 1)
5357 [delegate_ saveState];
5358
670a0494
JF
5359 switch (Finish_) {
5360 case 0:
2925cbba 5361 [delegate_ returnToCydia];
670a0494
JF
5362 break;
5363
5364 case 1:
c4899376
JF
5365 [delegate_ terminateWithSuccess];
5366 /*if ([delegate_ respondsToSelector:@selector(suspendWithAnimation:)])
5367 [delegate_ suspendWithAnimation:YES];
5368 else
5369 [delegate_ suspend];*/
670a0494
JF
5370 break;
5371
5372 case 2:
985d2dff 5373 _trace();
ef812071 5374 goto reload;
670a0494
JF
5375
5376 case 3:
985d2dff 5377 _trace();
ef812071
JF
5378 goto reload;
5379
a3755a1e
JF
5380 reload: {
5381 UIProgressHUD *hud([delegate_ addProgressHUD]);
5382 [hud setText:UCLocalize("LOADING")];
4a4dcb68 5383 [self performSelector:@selector(reloadSpringBoard) withObject:nil afterDelay:0.5];
317eb8e3
JF
5384 return;
5385 }
670a0494
JF
5386
5387 case 4:
985d2dff 5388 _trace();
0e371502
JF
5389 if (void (*SBReboot)(mach_port_t) = reinterpret_cast<void (*)(mach_port_t)>(dlsym(RTLD_DEFAULT, "SBReboot")))
5390 SBReboot(SBSSpringBoardServerPort());
5391 else
bb0fe3c9 5392 reboot2(RB_AUTOBOOT);
670a0494 5393 break;
bc8cd583 5394 }
aaae308d
JF
5395
5396 [super close];
670a0494 5397}
bd150f54 5398
6915b806 5399- (void) setTitle:(NSString *)title {
aaae308d
JF
5400 [progress_ setTitle:title];
5401 [self updateProgress];
5402}
5403
5404- (UIBarButtonItem *) rightButton {
d53628b6 5405 return [[progress_ running] boolValue] ? [super rightButton] : [[[UIBarButtonItem alloc]
aaae308d
JF
5406 initWithTitle:UCLocalize("CLOSE")
5407 style:UIBarButtonItemStylePlain
5408 target:self
5409 action:@selector(close)
5410 ] autorelease];
6915b806
JF
5411}
5412
5413- (void) invoke:(NSInvocation *)invocation withTitle:(NSString *)title {
5414 UpdateExternalStatus(1);
5415
aaae308d 5416 [progress_ setRunning:true];
6915b806 5417 [self setTitle:title];
aaae308d 5418 // implicit updateProgress
6915b806 5419
140710ba 5420 SHA1SumValue notifyconf; {
6915b806
JF
5421 FileFd file;
5422 if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
5423 _error->Discard();
5424 else {
5425 MMap mmap(file, MMap::ReadOnly);
5426 SHA1Summation sha1;
5427 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
140710ba 5428 notifyconf = sha1.Result();
6915b806
JF
5429 }
5430 }
5431
140710ba 5432 SHA1SumValue springlist; {
6915b806
JF
5433 FileFd file;
5434 if (!file.Open(SpringBoard_, FileFd::ReadOnly))
5435 _error->Discard();
5436 else {
5437 MMap mmap(file, MMap::ReadOnly);
5438 SHA1Summation sha1;
5439 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
140710ba 5440 springlist = sha1.Result();
6915b806
JF
5441 }
5442 }
5443
5444 if (invocation != nil) {
5445 [invocation yieldToSelector:@selector(invoke)];
aaae308d 5446 [self setTitle:@"COMPLETE"];
6915b806 5447 }
670a0494 5448
22f8bed9 5449 if (Finish_ < 4) {
70d45c1e
JF
5450 FileFd file;
5451 if (!file.Open(NotifyConfig_, FileFd::ReadOnly))
5452 _error->Discard();
5453 else {
5454 MMap mmap(file, MMap::ReadOnly);
5455 SHA1Summation sha1;
5456 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
140710ba 5457 if (!(notifyconf == sha1.Result()))
70d45c1e
JF
5458 Finish_ = 4;
5459 }
22f8bed9
JF
5460 }
5461
affeffc7 5462 if (Finish_ < 3) {
70d45c1e
JF
5463 FileFd file;
5464 if (!file.Open(SpringBoard_, FileFd::ReadOnly))
5465 _error->Discard();
5466 else {
5467 MMap mmap(file, MMap::ReadOnly);
5468 SHA1Summation sha1;
5469 sha1.Add(reinterpret_cast<uint8_t *>(mmap.Data()), mmap.Size());
140710ba 5470 if (!(springlist == sha1.Result()))
70d45c1e
JF
5471 Finish_ = 3;
5472 }
dddbc481
JF
5473 }
5474
be860cc8
JF
5475 if (Finish_ < 2) {
5476 if (RestartSubstrate_)
5477 Finish_ = 2;
5478 }
5479
5480 RestartSubstrate_ = false;
5481
bc8cd583 5482 switch (Finish_) {
aaae308d
JF
5483 case 0: [progress_ setFinish:UCLocalize("RETURN_TO_CYDIA")]; break; /* XXX: Maybe UCLocalize("DONE")? */
5484 case 1: [progress_ setFinish:UCLocalize("CLOSE_CYDIA")]; break;
5485 case 2: [progress_ setFinish:UCLocalize("RESTART_SPRINGBOARD")]; break;
5486 case 3: [progress_ setFinish:UCLocalize("RELOAD_SPRINGBOARD")]; break;
5487 case 4: [progress_ setFinish:UCLocalize("REBOOT_DEVICE")]; break;
bc8cd583
JF
5488 }
5489
eb403f34 5490 UpdateExternalStatus(Finish_ == 0 ? 0 : 2);
ef494bd8 5491
aaae308d
JF
5492 [progress_ setRunning:false];
5493 [self updateProgress];
5494
5495 [self applyRightButton];
31f3cfff
JF
5496}
5497
6915b806 5498- (void) addProgressEvent:(CydiaProgressEvent *)event {
aaae308d
JF
5499 [progress_ addEvent:event];
5500 [self updateProgress];
baf80942
JF
5501}
5502
ff2d5dcd 5503- (bool) isProgressCancelled {
aaae308d
JF
5504 return cancel_ == 2;
5505}
5506
5507- (void) cancel {
5508 cancel_ = 2;
5509 [self updateCancel];
5510}
5511
5512- (void) setCancellable:(bool)cancellable {
5513 unsigned cancel(cancel_);
5514
5515 if (!cancellable)
5516 cancel_ = 0;
5517 else if (cancel_ == 0)
5518 cancel_ = 1;
5519
5520 if (cancel != cancel_)
5521 [self updateCancel];
5522}
5523
5524- (void) setProgressCancellable:(NSNumber *)cancellable {
5525 [self setCancellable:[cancellable boolValue]];
baf80942
JF
5526}
5527
d885343d 5528- (void) setProgressPercent:(NSNumber *)percent {
b0b11d99 5529 [progress_ setPercent:[percent floatValue]];
aaae308d 5530 [self updateProgress];
49048579
JF
5531}
5532
bcbac8f7
JF
5533- (void) setProgressStatus:(NSDictionary *)status {
5534 if (status == nil) {
5535 [progress_ setCurrent:0];
5536 [progress_ setTotal:0];
5537 [progress_ setSpeed:0];
5538 } else {
5539 [progress_ setPercent:[[status objectForKey:@"Percent"] floatValue]];
5540
5541 [progress_ setCurrent:[[status objectForKey:@"Current"] floatValue]];
5542 [progress_ setTotal:[[status objectForKey:@"Total"] floatValue]];
5543 [progress_ setSpeed:[[status objectForKey:@"Speed"] floatValue]];
5544 }
5545
5546 [self updateProgress];
5547}
5548
36bb2ca2
JF
5549@end
5550/* }}} */
dc088e63 5551
46aa9775 5552/* Package Cell {{{ */
a9311516 5553@interface PackageCell : CyteTableViewCell <
b97fcfc6 5554 CyteTableViewCellDelegate
c21004b9 5555> {
7b33d201
JF
5556 _H<UIImage> icon_;
5557 _H<NSString> name_;
5558 _H<NSString> description_;
3bd1c2a2 5559 bool commercial_;
7b33d201
JF
5560 _H<NSString> source_;
5561 _H<UIImage> badge_;
7b33d201 5562 _H<UIImage> placard_;
59f3d290 5563 bool summarized_;
36bb2ca2 5564}
723a0072 5565
36bb2ca2 5566- (PackageCell *) init;
59f3d290 5567- (void) setPackage:(Package *)package asSummary:(bool)summary;
ec97ef06 5568
327624b6
JF
5569- (void) drawContentRect:(CGRect)rect;
5570
5571@end
5572
36bb2ca2
JF
5573@implementation PackageCell
5574
36bb2ca2 5575- (PackageCell *) init {
327624b6
JF
5576 CGRect frame(CGRectMake(0, 0, 320, 74));
5577 if ((self = [super initWithFrame:frame reuseIdentifier:@"Package"]) != nil) {
5578 UIView *content([self contentView]);
5579 CGRect bounds([content bounds]);
04fe1349 5580
b97fcfc6 5581 content_ = [[[CyteTableViewCellContentView alloc] initWithFrame:bounds] autorelease];
04fe1349
JF
5582 [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
5583 [content addSubview:content_];
5584
327624b6 5585 [content_ setDelegate:self];
327624b6 5586 [content_ setOpaque:YES];
36bb2ca2 5587 } return self;
b4d89997 5588}
b19871dd 5589
003fc610 5590- (NSString *) accessibilityLabel {
353dda5b 5591 return name_;
003fc610
GP
5592}
5593
59f3d290
JF
5594- (void) setPackage:(Package *)package asSummary:(bool)summary {
5595 summarized_ = summary;
5596
7b33d201
JF
5597 icon_ = nil;
5598 name_ = nil;
5599 description_ = nil;
5600 source_ = nil;
5601 badge_ = nil;
5602 placard_ = nil;
7b33d201 5603
80132602
JF
5604 if (package == nil)
5605 [content_ setBackgroundColor:[UIColor whiteColor]];
5606 else {
5607 [package parse];
31f3cfff 5608
80132602 5609 Source *source = [package source];
b19871dd 5610
80132602 5611 icon_ = [package icon];
78de2878 5612
80132602
JF
5613 if (NSString *name = [package name])
5614 name_ = [NSString stringWithString:name];
ef055c6c 5615
9374f6b0 5616 if (NSString *description = [package shortDescription])
80132602 5617 description_ = [NSString stringWithString:description];
ef055c6c 5618
80132602 5619 commercial_ = [package isCommercial];
36bb2ca2 5620
80132602
JF
5621 NSString *label = nil;
5622 bool trusted = false;
b19871dd 5623
80132602
JF
5624 if (source != nil) {
5625 label = [source label];
5626 trusted = [source trusted];
5627 } else if ([[package id] isEqualToString:@"firmware"])
5628 label = UCLocalize("APPLE");
5629 else
5630 label = [NSString stringWithFormat:UCLocalize("SLASH_DELIMITED"), UCLocalize("UNKNOWN"), UCLocalize("LOCAL")];
b19871dd 5631
80132602 5632 NSString *from(label);
a54b1c10 5633
80132602
JF
5634 NSString *section = [package simpleSection];
5635 if (section != nil && ![section isEqualToString:label]) {
5636 section = [[NSBundle mainBundle] localizedStringForKey:section value:nil table:@"Sections"];
5637 from = [NSString stringWithFormat:UCLocalize("PARENTHETICAL"), from, section];
5638 }
a54b1c10 5639
80132602 5640 source_ = [NSString stringWithFormat:UCLocalize("FROM"), from];
36bb2ca2 5641
80132602
JF
5642 if (NSString *purpose = [package primaryPurpose])
5643 badge_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/Purposes/%@.png", App_, purpose]];
c390d3ab 5644
80132602
JF
5645 UIColor *color;
5646 NSString *placard;
5647
5648 if (NSString *mode = [package mode]) {
5649 if ([mode isEqualToString:@"REMOVE"] || [mode isEqualToString:@"PURGE"]) {
5650 color = RemovingColor_;
86a333c6 5651 placard = @"removing";
80132602
JF
5652 } else {
5653 color = InstallingColor_;
86a333c6 5654 placard = @"installing";
80132602 5655 }
d832908d 5656 } else {
80132602
JF
5657 color = [UIColor whiteColor];
5658
5659 if ([package installed] != nil)
5660 placard = @"installed";
5661 else
5662 placard = nil;
d832908d
JF
5663 }
5664
80132602 5665 [content_ setBackgroundColor:color];
d832908d 5666
80132602
JF
5667 if (placard != nil)
5668 placard_ = [UIImage imageAtPath:[NSString stringWithFormat:@"%@/%@.png", App_, placard]];
d832908d
JF
5669 }
5670
d832908d 5671 [self setNeedsDisplay];
327624b6 5672 [content_ setNeedsDisplay];
3bd1c2a2
JF
5673}
5674
59f3d290 5675- (void) drawSummaryContentRect:(CGRect)rect {
555aaf71 5676 bool highlighted(highlighted_);
ef055c6c 5677 float width([self bounds].size.width);
dc63e78f 5678
59f3d290
JF
5679 if (icon_ != nil) {
5680 CGRect rect;
5681 rect.size = [(UIImage *) icon_ size];
5682
25c1dafb 5683 while (rect.size.width > 16 || rect.size.height > 16) {
7e1f9f6a
JF
5684 rect.size.width /= 2;
5685 rect.size.height /= 2;
5686 }
59f3d290 5687
86a333c6
JF
5688 rect.origin.x = 19 - rect.size.width / 2;
5689 rect.origin.y = 19 - rect.size.height / 2;
59f3d290
JF
5690
5691 [icon_ drawInRect:rect];
5692 }
5693
5694 if (badge_ != nil) {
5695 CGRect rect;
5696 rect.size = [(UIImage *) badge_ size];
5697
5698 rect.size.width /= 4;
5699 rect.size.height /= 4;
5700
86a333c6
JF
5701 rect.origin.x = 25 - rect.size.width / 2;
5702 rect.origin.y = 25 - rect.size.height / 2;
59f3d290
JF
5703
5704 [badge_ drawInRect:rect];
5705 }
5706
5d0438dc 5707 if (highlighted && kCFCoreFoundationVersionNumber < 800)
59f3d290
JF
5708 UISetColor(White_);
5709
5710 if (!highlighted)
5711 UISetColor(commercial_ ? Purple_ : Black_);
b129e6d9 5712 [name_ drawAtPoint:CGPointMake(36, 8) forWidth:(width - (placard_ == nil ? 68 : 94)) withFont:Font18Bold_ lineBreakMode:NSLineBreakByTruncatingTail];
59f3d290
JF
5713
5714 if (placard_ != nil)
86a333c6 5715 [placard_ drawAtPoint:CGPointMake(width - 52, 11)];
59f3d290
JF
5716}
5717
5718- (void) drawNormalContentRect:(CGRect)rect {
5719 bool highlighted(highlighted_);
5720 float width([self bounds].size.width);
b19871dd 5721
baf80942
JF
5722 if (icon_ != nil) {
5723 CGRect rect;
7b33d201 5724 rect.size = [(UIImage *) icon_ size];
baf80942 5725
25c1dafb 5726 while (rect.size.width > 32 || rect.size.height > 32) {
7e1f9f6a
JF
5727 rect.size.width /= 2;
5728 rect.size.height /= 2;
5729 }
baf80942
JF
5730
5731 rect.origin.x = 25 - rect.size.width / 2;
5732 rect.origin.y = 25 - rect.size.height / 2;
5733
5734 [icon_ drawInRect:rect];
5735 }
b19871dd 5736
c390d3ab 5737 if (badge_ != nil) {
f79c810d 5738 CGRect rect;
7b33d201 5739 rect.size = [(UIImage *) badge_ size];
f79c810d
JF
5740
5741 rect.size.width /= 2;
5742 rect.size.height /= 2;
5743
5744 rect.origin.x = 36 - rect.size.width / 2;
5745 rect.origin.y = 36 - rect.size.height / 2;
c390d3ab 5746
f79c810d 5747 [badge_ drawInRect:rect];
c390d3ab
JF
5748 }
5749
5d0438dc 5750 if (highlighted && kCFCoreFoundationVersionNumber < 800)
f641a0e5 5751 UISetColor(White_);
b19871dd 5752
555aaf71 5753 if (!highlighted)
3bd1c2a2 5754 UISetColor(commercial_ ? Purple_ : Black_);
b129e6d9
JF
5755 [name_ drawAtPoint:CGPointMake(48, 8) forWidth:(width - (placard_ == nil ? 80 : 106)) withFont:Font18Bold_ lineBreakMode:NSLineBreakByTruncatingTail];
5756 [source_ drawAtPoint:CGPointMake(58, 29) forWidth:(width - 95) withFont:Font12_ lineBreakMode:NSLineBreakByTruncatingTail];
b19871dd 5757
555aaf71 5758 if (!highlighted)
3bd1c2a2 5759 UISetColor(commercial_ ? Purplish_ : Gray_);
b129e6d9 5760 [description_ drawAtPoint:CGPointMake(12, 46) forWidth:(width - 46) withFont:Font14_ lineBreakMode:NSLineBreakByTruncatingTail];
670a0494
JF
5761
5762 if (placard_ != nil)
01d93940 5763 [placard_ drawAtPoint:CGPointMake(width - 52, 9)];
ec97ef06
JF
5764}
5765
59f3d290
JF
5766- (void) drawContentRect:(CGRect)rect {
5767 if (summarized_)
5768 [self drawSummaryContentRect:rect];
5769 else
5770 [self drawNormalContentRect:rect];
5771}
5772
8da60fb7
JF
5773@end
5774/* }}} */
36bb2ca2 5775/* Section Cell {{{ */
a9311516 5776@interface SectionCell : CyteTableViewCell <
b97fcfc6 5777 CyteTableViewCellDelegate
c21004b9 5778> {
7b33d201
JF
5779 _H<NSString> basic_;
5780 _H<NSString> section_;
5781 _H<NSString> name_;
5782 _H<NSString> count_;
5783 _H<UIImage> icon_;
5784 _H<UISwitch> switch_;
6d9712c4 5785 BOOL editing_;
b4d89997 5786}
b19871dd 5787
6d9712c4 5788- (void) setSection:(Section *)section editing:(BOOL)editing;
36bb2ca2 5789
8da60fb7
JF
5790@end
5791
36bb2ca2 5792@implementation SectionCell
8da60fb7 5793
46aa9775
GP
5794- (id) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
5795 if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) {
7b33d201 5796 icon_ = [UIImage applicationImageNamed:@"folder.png"];
6415105e 5797 // XXX: this initial frame is wrong, but is fixed later
7b33d201 5798 switch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(218, 9, 60, 25)] autorelease];
46aa9775
GP
5799 [switch_ addTarget:self action:@selector(onSwitch:) forEvents:UIControlEventValueChanged];
5800
5801 UIView *content([self contentView]);
5802 CGRect bounds([content bounds]);
5803
b97fcfc6 5804 content_ = [[[CyteTableViewCellContentView alloc] initWithFrame:bounds] autorelease];
46aa9775
GP
5805 [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
5806 [content addSubview:content_];
5807 [content_ setBackgroundColor:[UIColor whiteColor]];
5808
5809 [content_ setDelegate:self];
b4d89997 5810 } return self;
b19871dd
JF
5811}
5812
6d9712c4 5813- (void) onSwitch:(id)sender {
a171abd4 5814 NSMutableDictionary *metadata([Sections_ objectForKey:basic_]);
6d9712c4
JF
5815 if (metadata == nil) {
5816 metadata = [NSMutableDictionary dictionaryWithCapacity:2];
0010fa77 5817 [Sections_ setObject:metadata forKey:basic_];
6d9712c4
JF
5818 }
5819
46aa9775 5820 [metadata setObject:[NSNumber numberWithBool:([switch_ isOn] == NO)] forKey:@"Hidden"];
a171abd4 5821 Changed_ = true;
6d9712c4
JF
5822}
5823
5824- (void) setSection:(Section *)section editing:(BOOL)editing {
5825 if (editing != editing_) {
5826 if (editing_)
5827 [switch_ removeFromSuperview];
5828 else
5829 [self addSubview:switch_];
5830 editing_ = editing;
5831 }
5832
7b33d201
JF
5833 basic_ = nil;
5834 section_ = nil;
5835 name_ = nil;
5836 count_ = nil;
6d9712c4 5837
36bb2ca2 5838 if (section == nil) {
7b33d201 5839 name_ = UCLocalize("ALL_PACKAGES");
f641a0e5 5840 count_ = nil;
36bb2ca2 5841 } else {
e59669fd 5842 basic_ = [section name];
677b8415 5843 section_ = [section localized];
0010fa77 5844
7b33d201 5845 name_ = section_ == nil || [section_ length] == 0 ? UCLocalize("NO_SECTION") : (NSString *) section_;
3e5a9f5d 5846 count_ = [NSString stringWithFormat:@"%zd", [section count]];
6d9712c4
JF
5847
5848 if (editing_)
46aa9775 5849 [switch_ setOn:(isSectionVisible(basic_) ? 1 : 0) animated:NO];
36bb2ca2 5850 }
46aa9775 5851
c21004b9 5852 [self setAccessoryType:editing ? UITableViewCellAccessoryNone : UITableViewCellAccessoryDisclosureIndicator];
0abb648c
JF
5853 [self setSelectionStyle:editing ? UITableViewCellSelectionStyleNone : UITableViewCellSelectionStyleBlue];
5854
46aa9775 5855 [content_ setNeedsDisplay];
f641a0e5
JF
5856}
5857
77801ff1
JF
5858- (void) setFrame:(CGRect)frame {
5859 [super setFrame:frame];
46aa9775 5860
77801ff1 5861 CGRect rect([switch_ frame]);
6415105e 5862 [switch_ setFrame:CGRectMake(frame.size.width - rect.size.width - 9, 9, rect.size.width, rect.size.height)];
77801ff1
JF
5863}
5864
003fc610
GP
5865- (NSString *) accessibilityLabel {
5866 return name_;
5867}
5868
46aa9775 5869- (void) drawContentRect:(CGRect)rect {
8cfba088 5870 bool highlighted(highlighted_ && !editing_);
bc11cf5b 5871
86a333c6 5872 [icon_ drawInRect:CGRectMake(7, 7, 32, 32)];
f641a0e5 5873
5d0438dc 5874 if (highlighted && kCFCoreFoundationVersionNumber < 800)
f641a0e5
JF
5875 UISetColor(White_);
5876
46aa9775 5877 float width(rect.size.width);
58241d4c 5878 if (editing_)
6c1ec3c7 5879 width -= 9 + [switch_ frame].size.width;
58241d4c 5880
555aaf71
JF
5881 if (!highlighted)
5882 UISetColor(Black_);
b129e6d9 5883 [name_ drawAtPoint:CGPointMake(48, 12) forWidth:(width - 58) withFont:Font18_ lineBreakMode:NSLineBreakByTruncatingTail];
6d9712c4 5884
f641a0e5
JF
5885 CGSize size = [count_ sizeWithFont:Font14_];
5886
86a333c6 5887 UISetColor(Folder_);
f641a0e5 5888 if (count_ != nil)
86a333c6 5889 [count_ drawAtPoint:CGPointMake(10 + (30 - size.width) / 2, 18) withFont:Font12Bold_];
b19871dd
JF
5890}
5891
36bb2ca2
JF
5892@end
5893/* }}} */
b19871dd 5894
ab398adf 5895/* File Table {{{ */
cd79e8cf 5896@interface FileTable : CyteViewController <
c21004b9
JF
5897 UITableViewDataSource,
5898 UITableViewDelegate
5899> {
36bb2ca2 5900 _transient Database *database_;
7b33d201
JF
5901 _H<Package> package_;
5902 _H<NSString> name_;
5903 _H<NSMutableArray> files_;
bf7c998c 5904 _H<UITableView, 2> list_;
ab398adf 5905}
b19871dd 5906
b5e7eebb 5907- (id) initWithDatabase:(Database *)database;
ab398adf
JF
5908- (void) setPackage:(Package *)package;
5909
5910@end
5911
5912@implementation FileTable
5913
eb30da80 5914- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
ab398adf
JF
5915 return files_ == nil ? 0 : [files_ count];
5916}
5917
21ea11a4
GP
5918/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
5919 return 24.0f;
5920}*/
ab398adf 5921
46aa9775 5922- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
b5e7eebb
GP
5923 static NSString *reuseIdentifier = @"Cell";
5924
46aa9775
GP
5925 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
5926 if (cell == nil) {
5927 cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
5928 [cell setFont:[UIFont systemFontOfSize:16]];
ab398adf 5929 }
46aa9775 5930 [cell setText:[files_ objectAtIndex:indexPath.row]];
c21004b9 5931 [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
b5e7eebb 5932
46aa9775 5933 return cell;
ab398adf 5934}
b19871dd 5935
fe8e721f
GP
5936- (NSURL *) navigationURL {
5937 return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/files", [package_ id]]];
5938}
5939
5940- (void) loadView {
e8cbebe4 5941 list_ = [[[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];
fe8e721f
GP
5942 [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
5943 [list_ setRowHeight:24.0f];
7b33d201 5944 [(UITableView *) list_ setDataSource:self];
fe8e721f 5945 [list_ setDelegate:self];
e8cbebe4 5946 [self setView:list_];
fe8e721f
GP
5947}
5948
5949- (void) viewDidLoad {
7d887d0b
JF
5950 [super viewDidLoad];
5951
fe8e721f
GP
5952 [[self navigationItem] setTitle:UCLocalize("INSTALLED_FILES")];
5953}
5954
5955- (void) releaseSubviews {
fe8e721f 5956 list_ = nil;
7be3eb32 5957
4f9acb7c
JF
5958 package_ = nil;
5959 files_ = nil;
5960
7be3eb32 5961 [super releaseSubviews];
fe8e721f
GP
5962}
5963
b5e7eebb
GP
5964- (id) initWithDatabase:(Database *)database {
5965 if ((self = [super init]) != nil) {
ab398adf 5966 database_ = database;
ab398adf
JF
5967 } return self;
5968}
5969
5970- (void) setPackage:(Package *)package {
7b33d201
JF
5971 package_ = nil;
5972 name_ = nil;
ab398adf 5973
4f9acb7c 5974 files_ = [NSMutableArray arrayWithCapacity:32];
ab398adf
JF
5975
5976 if (package != nil) {
7b33d201
JF
5977 package_ = package;
5978 name_ = [package id];
ab398adf 5979
affeffc7
JF
5980 if (NSArray *files = [package files])
5981 [files_ addObjectsFromArray:files];
ab398adf 5982
9ea8d159
JF
5983 if ([files_ count] != 0) {
5984 if ([[files_ objectAtIndex:0] isEqualToString:@"/."])
5985 [files_ removeObjectAtIndex:0];
a54b1c10 5986 [files_ sortUsingSelector:@selector(compareByPath:)];
2388b078
JF
5987
5988 NSMutableArray *stack = [NSMutableArray arrayWithCapacity:8];
5989 [stack addObject:@"/"];
5990
5991 for (int i(0), e([files_ count]); i != e; ++i) {
5992 NSString *file = [files_ objectAtIndex:i];
5993 while (![file hasPrefix:[stack lastObject]])
5994 [stack removeLastObject];
5995 NSString *directory = [stack lastObject];
5996 [stack addObject:[file stringByAppendingString:@"/"]];
5997 [files_ replaceObjectAtIndex:i withObject:[NSString stringWithFormat:@"%*s%@",
a54b1c10 5998 ([stack count] - 2) * 3, "",
2388b078
JF
5999 [file substringFromIndex:[directory length]]
6000 ]];
6001 }
ab398adf
JF
6002 }
6003 }
6004
6005 [list_ reloadData];
6006}
6007
ab398adf 6008- (void) reloadData {
fe8e721f
GP
6009 [super reloadData];
6010
ab398adf 6011 [self setPackage:[database_ packageWithName:name_]];
ab398adf 6012}
b4d89997 6013
b4d89997 6014@end
36bb2ca2 6015/* }}} */
adb61bda 6016/* Package Controller {{{ */
a576488f 6017@interface CYPackageController : CydiaWebViewController <
9daa7f25
DH
6018 UIActionSheetDelegate
6019> {
770f2a8e 6020 _transient Database *database_;
5d79f7bf
JF
6021 _H<Package> package_;
6022 _H<NSString> name_;
3bd1c2a2 6023 bool commercial_;
5d79f7bf
JF
6024 _H<NSMutableArray> buttons_;
6025 _H<UIBarButtonItem> button_;
b31b87cc
JF
6026}
6027
f050e4d9 6028- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name withReferrer:(NSString *)referrer;
b4d89997 6029
36bb2ca2 6030@end
b4d89997 6031
f6e13561 6032@implementation CYPackageController
b4d89997 6033
fe8e721f 6034- (NSURL *) navigationURL {
5d79f7bf 6035 return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@", (id) name_]];
fe8e721f
GP
6036}
6037
f79a4512 6038/* XXX: this is not safe at all... localization of /fail/ */
5a09ae08 6039- (void) _clickButtonWithName:(NSString *)name {
43f3d7f6 6040 if ([name isEqualToString:UCLocalize("CLEAR")])
dc63e78f 6041 [delegate_ clearPackage:package_];
43f3d7f6 6042 else if ([name isEqualToString:UCLocalize("INSTALL")])
5a09ae08 6043 [delegate_ installPackage:package_];
43f3d7f6 6044 else if ([name isEqualToString:UCLocalize("REINSTALL")])
5a09ae08 6045 [delegate_ installPackage:package_];
43f3d7f6 6046 else if ([name isEqualToString:UCLocalize("REMOVE")])
5a09ae08 6047 [delegate_ removePackage:package_];
43f3d7f6 6048 else if ([name isEqualToString:UCLocalize("UPGRADE")])
5a09ae08
JF
6049 [delegate_ installPackage:package_];
6050 else _assert(false);
6051}
6052
674dce72 6053- (void) actionSheet:(UIActionSheet *)sheet clickedButtonAtIndex:(NSInteger)button {
1cedb821 6054 NSString *context([sheet context]);
5a09ae08 6055
1cedb821 6056 if ([context isEqualToString:@"modify"]) {
674dce72
GP
6057 if (button != [sheet cancelButtonIndex]) {
6058 NSString *buttonName = [buttons_ objectAtIndex:button];
6059 [self _clickButtonWithName:buttonName];
6060 }
bc11cf5b 6061
674dce72 6062 [sheet dismissWithClickedButtonIndex:-1 animated:YES];
674dce72 6063 }
b4d89997 6064}
2367a917 6065
3e9c9e85 6066- (bool) _allowJavaScriptPanel {
3bd1c2a2 6067 return commercial_;
3e9c9e85
JF
6068}
6069
9487f027 6070#if !AlwaysReload
9daa7f25 6071- (void) _customButtonClicked {
670a0494
JF
6072 int count([buttons_ count]);
6073 if (count == 0)
6074 return;
2367a917 6075
5a09ae08
JF
6076 if (count == 1)
6077 [self _clickButtonWithName:[buttons_ objectAtIndex:0]];
6078 else {
674dce72 6079 NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:count];
5a09ae08 6080 [buttons addObjectsFromArray:buttons_];
36bb2ca2 6081
674dce72 6082 UIActionSheet *sheet = [[[UIActionSheet alloc]
9ea8d159 6083 initWithTitle:nil
36bb2ca2 6084 delegate:self
674dce72
GP
6085 cancelButtonTitle:nil
6086 destructiveButtonTitle:nil
6087 otherButtonTitles:nil
6088 ] autorelease];
6089
6090 for (NSString *button in buttons) [sheet addButtonWithTitle:button];
6091 if (!IsWildcat_) {
6092 [sheet addButtonWithTitle:UCLocalize("CANCEL")];
6093 [sheet setCancelButtonIndex:[sheet numberOfButtons] - 1];
6094 }
6095 [sheet setContext:@"modify"];
bc11cf5b 6096
b5e7eebb 6097 [delegate_ showActionSheet:sheet fromItem:[[self navigationItem] rightBarButtonItem]];
36bb2ca2 6098 }
b4d89997 6099}
12b59862 6100
2e6c1426 6101- (void) reloadButtonClicked {
07d0e88e
JF
6102 if (commercial_ && function_ == nil && [package_ uninstalled])
6103 return;
6104 [self customButtonClicked];
2fad210a
GP
6105}
6106
6107- (void) applyLoadingTitle {
6108 // Don't show "Loading" as the title. Ever.
2e6c1426 6109}
a5938ea5
DH
6110
6111- (UIBarButtonItem *) rightButton {
2634b249 6112 return button_;
a5938ea5 6113}
9487f027 6114#endif
2367a917 6115
f050e4d9 6116- (id) initWithDatabase:(Database *)database forPackage:(NSString *)name withReferrer:(NSString *)referrer {
b5e7eebb 6117 if ((self = [super init]) != nil) {
36bb2ca2 6118 database_ = database;
5d79f7bf 6119 buttons_ = [NSMutableArray arrayWithCapacity:4];
5612913e 6120 name_ = name == nil ? @"" : [NSString stringWithString:name];
f050e4d9 6121 [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/package/%@", UI_, (id) name_]] withReferrer:referrer];
36bb2ca2 6122 } return self;
dc5812ec
JF
6123}
6124
57e8b225 6125- (void) reloadData {
9dac415b
JF
6126 [super reloadData];
6127
57e8b225 6128 package_ = [database_ packageWithName:name_];
36bb2ca2 6129
5a09ae08
JF
6130 [buttons_ removeAllObjects];
6131
57e8b225 6132 if (package_ != nil) {
5d79f7bf 6133 [(Package *) package_ parse];
68d927e2 6134
57e8b225 6135 commercial_ = [package_ isCommercial];
36bb2ca2 6136
dc63e78f 6137 if ([package_ mode] != nil)
43f3d7f6 6138 [buttons_ addObject:UCLocalize("CLEAR")];
5a09ae08 6139 if ([package_ source] == nil);
31f3cfff 6140 else if ([package_ upgradableAndEssential:NO])
43f3d7f6 6141 [buttons_ addObject:UCLocalize("UPGRADE")];
6a155117 6142 else if ([package_ uninstalled])
43f3d7f6 6143 [buttons_ addObject:UCLocalize("INSTALL")];
5a09ae08 6144 else
43f3d7f6 6145 [buttons_ addObject:UCLocalize("REINSTALL")];
6a155117 6146 if (![package_ uninstalled])
43f3d7f6 6147 [buttons_ addObject:UCLocalize("REMOVE")];
2634b249 6148 }
f79a4512 6149
2634b249
JF
6150 NSString *title;
6151 switch ([buttons_ count]) {
6152 case 0: title = nil; break;
6153 case 1: title = [buttons_ objectAtIndex:0]; break;
6154 default: title = UCLocalize("MODIFY"); break;
36bb2ca2 6155 }
2634b249 6156
5d79f7bf 6157 button_ = [[[UIBarButtonItem alloc]
2634b249
JF
6158 initWithTitle:title
6159 style:UIBarButtonItemStylePlain
6160 target:self
6161 action:@selector(customButtonClicked)
5d79f7bf 6162 ] autorelease];
b5e7eebb 6163}
f79a4512 6164
3bd1c2a2
JF
6165- (bool) isLoading {
6166 return commercial_ ? [super isLoading] : false;
9fe5e5f8
JF
6167}
6168
b4d89997
JF
6169@end
6170/* }}} */
5829aea2 6171
f50860ee 6172/* Package List Controller {{{ */
cd79e8cf 6173@interface PackageListController : CyteViewController <
c21004b9
JF
6174 UITableViewDataSource,
6175 UITableViewDelegate
6176> {
36bb2ca2 6177 _transient Database *database_;
0175295c 6178 unsigned era_;
56bf1e78 6179 _H<NSArray> packages_;
e5491e28 6180 _H<NSArray> sections_;
bf7c998c 6181 _H<UITableView, 2> list_;
7b33d201 6182 _H<NSMutableArray> index_;
7b33d201 6183 _H<NSString> title_;
56bf1e78 6184 unsigned reloading_;
dc5812ec
JF
6185}
6186
f50860ee 6187- (id) initWithDatabase:(Database *)database title:(NSString *)title;
b4d89997 6188- (void) setDelegate:(id)delegate;
a0be02eb 6189- (void) resetCursor;
59f3d290 6190- (void) clearData;
b4d89997 6191
e5491e28
JF
6192- (NSArray *) sectionsForPackages:(NSMutableArray *)packages;
6193
b4d89997
JF
6194@end
6195
f50860ee 6196@implementation PackageListController
b4d89997 6197
f050e4d9
JF
6198- (NSURL *) referrerURL {
6199 return [self navigationURL];
6200}
6201
59f3d290
JF
6202- (bool) isSummarized {
6203 return false;
6204}
6205
9c5737d5
JF
6206- (bool) showsSections {
6207 return true;
6208}
6209
f50860ee
GP
6210- (void) deselectWithAnimation:(BOOL)animated {
6211 [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
6212}
6213
bfb45dcb
GP
6214- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve {
6215 CGRect base = [[self view] bounds];
6216 base.size.height -= bounds.size.height;
6217 base.origin = [list_ frame].origin;
6218
6219 [UIView beginAnimations:nil context:NULL];
6220 [UIView setAnimationBeginsFromCurrentState:YES];
6221 [UIView setAnimationCurve:curve];
6222 [UIView setAnimationDuration:duration];
6223 [list_ setFrame:base];
6224 [UIView commitAnimations];
6225}
6226
6227- (void) resizeForKeyboardBounds:(CGRect)bounds duration:(NSTimeInterval)duration {
6228 [self resizeForKeyboardBounds:bounds duration:duration curve:UIViewAnimationCurveLinear];
6229}
6230
6231- (void) resizeForKeyboardBounds:(CGRect)bounds {
6232 [self resizeForKeyboardBounds:bounds duration:0];
6233}
6234
655c7ded
JF
6235- (void) getKeyboardCurve:(UIViewAnimationCurve *)curve duration:(NSTimeInterval *)duration forNotification:(NSNotification *)notification {
6236 if (&UIKeyboardAnimationCurveUserInfoKey == NULL)
6237 *curve = UIViewAnimationCurveEaseInOut;
6238 else
6239 [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:curve];
6240
6241 if (&UIKeyboardAnimationDurationUserInfoKey == NULL)
6242 *duration = 0.3;
6243 else
6244 [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:duration];
6245}
6246
bfb45dcb
GP
6247- (void) keyboardWillShow:(NSNotification *)notification {
6248 CGRect bounds;
6249 CGPoint center;
bfb45dcb
GP
6250 [[[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey] getValue:&bounds];
6251 [[[notification userInfo] objectForKey:UIKeyboardCenterEndUserInfoKey] getValue:&center];
655c7ded
JF
6252
6253 NSTimeInterval duration;
6254 UIViewAnimationCurve curve;
6255 [self getKeyboardCurve:&curve duration:&duration forNotification:notification];
bfb45dcb
GP
6256
6257 CGRect kbframe = CGRectMake(round(center.x - bounds.size.width / 2.0), round(center.y - bounds.size.height / 2.0), bounds.size.width, bounds.size.height);
38991110 6258 UIViewController *base = self;
19f2d77f
JF
6259 while ([base parentOrPresentingViewController] != nil)
6260 base = [base parentOrPresentingViewController];
38991110 6261 CGRect viewframe = [[base view] convertRect:[list_ frame] fromView:[list_ superview]];
bfb45dcb
GP
6262 CGRect intersection = CGRectIntersection(viewframe, kbframe);
6263
2e35f65f 6264 if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) // XXX: _UIApplicationLinkedOnOrAfter(4)
0b7516cf 6265 intersection.size.height += CYStatusBarHeight();
2e35f65f 6266
bfb45dcb
GP
6267 [self resizeForKeyboardBounds:intersection duration:duration curve:curve];
6268}
6269
6270- (void) keyboardWillHide:(NSNotification *)notification {
6271 NSTimeInterval duration;
6272 UIViewAnimationCurve curve;
655c7ded 6273 [self getKeyboardCurve:&curve duration:&duration forNotification:notification];
bfb45dcb
GP
6274
6275 [self resizeForKeyboardBounds:CGRectZero duration:duration curve:curve];
6276}
6277
6278- (void) viewWillAppear:(BOOL)animated {
6279 [super viewWillAppear:animated];
6280
6281 [self resizeForKeyboardBounds:CGRectZero];
6282 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
6283 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
6284}
6285
6286- (void) viewWillDisappear:(BOOL)animated {
6287 [super viewWillDisappear:animated];
6288
6289 [self resizeForKeyboardBounds:CGRectZero];
6290 [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
6291 [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
6292}
6293
f50860ee
GP
6294- (void) viewDidAppear:(BOOL)animated {
6295 [super viewDidAppear:animated];
6296 [self deselectWithAnimation:animated];
6297}
6298
6299- (void) didSelectPackage:(Package *)package {
f050e4d9 6300 CYPackageController *view([[[CYPackageController alloc] initWithDatabase:database_ forPackage:[package id] withReferrer:[[self referrerURL] absoluteString]] autorelease]);
f50860ee
GP
6301 [view setDelegate:delegate_];
6302 [[self navigationController] pushViewController:view animated:YES];
6303}
6304
d03d7492 6305#if TryIndexedCollation
a891c345
GP
6306+ (BOOL) hasIndexedCollation {
6307 return NO; // XXX: objc_getClass("UILocalizedIndexedCollation") != nil;
6308}
d03d7492 6309#endif
a891c345 6310
327624b6
JF
6311- (NSInteger) numberOfSectionsInTableView:(UITableView *)list {
6312 NSInteger count([sections_ count]);
6313 return count == 0 ? 1 : count;
b4d89997 6314}
2367a917 6315
327624b6 6316- (NSString *) tableView:(UITableView *)list titleForHeaderInSection:(NSInteger)section {
a891c345 6317 if ([sections_ count] == 0 || [[sections_ objectAtIndex:section] count] == 0)
327624b6 6318 return nil;
b4d89997
JF
6319 return [[sections_ objectAtIndex:section] name];
6320}
dc5812ec 6321
327624b6
JF
6322- (NSInteger) tableView:(UITableView *)list numberOfRowsInSection:(NSInteger)section {
6323 if ([sections_ count] == 0)
6324 return 0;
6325 return [[sections_ objectAtIndex:section] count];
b4d89997 6326}
dc5812ec 6327
327624b6 6328- (Package *) packageAtIndexPath:(NSIndexPath *)path {
0175295c
JF
6329@synchronized (database_) {
6330 if ([database_ era] != era_)
6331 return nil;
6332
327624b6
JF
6333 Section *section([sections_ objectAtIndex:[path section]]);
6334 NSInteger row([path row]);
6335 Package *package([packages_ objectAtIndex:([section row] + row)]);
0175295c
JF
6336 return [[package retain] autorelease];
6337} }
dc5812ec 6338
327624b6 6339- (UITableViewCell *) tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)path {
c21004b9 6340 PackageCell *cell((PackageCell *) [table dequeueReusableCellWithIdentifier:@"Package"]);
327624b6
JF
6341 if (cell == nil)
6342 cell = [[[PackageCell alloc] init] autorelease];
60bef540
JF
6343
6344 Package *package([database_ packageWithName:[[self packageAtIndexPath:path] id]]);
6345 [cell setPackage:package asSummary:[self isSummarized]];
327624b6 6346 return cell;
b4d89997 6347}
dc5812ec 6348
f50860ee 6349- (void) tableView:(UITableView *)table didSelectRowAtIndexPath:(NSIndexPath *)path {
327624b6 6350 Package *package([self packageAtIndexPath:path]);
59c6ae22 6351 package = [database_ packageWithName:[package id]];
f50860ee 6352 [self didSelectPackage:package];
327624b6
JF
6353}
6354
6355- (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tableView {
64b12ab3 6356 if (![self showsSections])
59f3d290
JF
6357 return nil;
6358
1527b095 6359 return index_;
327624b6
JF
6360}
6361
a891c345 6362- (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
d03d7492 6363#if TryIndexedCollation
a891c345
GP
6364 if ([[self class] hasIndexedCollation]) {
6365 return [[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionForSectionIndexTitleAtIndex:index];
6366 }
d03d7492 6367#endif
a891c345 6368
327624b6 6369 return index;
dc5812ec
JF
6370}
6371
59f3d290
JF
6372- (void) updateHeight {
6373 [list_ setRowHeight:([self isSummarized] ? 38 : 73)];
6374}
6375
f50860ee
GP
6376- (id) initWithDatabase:(Database *)database title:(NSString *)title {
6377 if ((self = [super init]) != nil) {
36bb2ca2 6378 database_ = database;
f50860ee
GP
6379 title_ = [title copy];
6380 [[self navigationItem] setTitle:title_];
61cc4dbc
JF
6381 } return self;
6382}
dc5812ec 6383
61cc4dbc 6384- (void) loadView {
b62b3788
JF
6385 UIView *view([[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]);
6386 [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
6387 [self setView:view];
6388
6389 list_ = [[[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStylePlain] autorelease];
61cc4dbc 6390 [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
b62b3788 6391 [view addSubview:list_];
1527b095 6392
61cc4dbc
JF
6393 // XXX: is 20 the most optimal number here?
6394 [list_ setSectionIndexMinimumDisplayRowCount:20];
59f3d290 6395
61cc4dbc
JF
6396 [(UITableView *) list_ setDataSource:self];
6397 [list_ setDelegate:self];
6398
6399 [self updateHeight];
6400}
6401
6402- (void) releaseSubviews {
6403 list_ = nil;
7be3eb32 6404
4f9acb7c
JF
6405 packages_ = nil;
6406 sections_ = nil;
6407 index_ = nil;
4f9acb7c 6408
7be3eb32 6409 [super releaseSubviews];
dc5812ec
JF
6410}
6411
6412- (void) setDelegate:(id)delegate {
2367a917 6413 delegate_ = delegate;
b4d89997
JF
6414}
6415
3025d5b4
JF
6416- (bool) shouldYield {
6417 return false;
6418}
b4d89997 6419
56bf1e78
JF
6420- (bool) shouldBlock {
6421 return false;
6422}
6423
9c5737d5 6424- (NSMutableArray *) _reloadPackages {
695fdd5c 6425@synchronized (database_) {
c28bc6f1
JF
6426 era_ = [database_ era];
6427 NSArray *packages([database_ packages]);
6428
9c5737d5 6429 return [NSMutableArray arrayWithArray:packages];
695fdd5c 6430} }
3025d5b4
JF
6431
6432- (void) _reloadData {
56bf1e78
JF
6433 if (reloading_ != 0) {
6434 reloading_ = 2;
6435 return;
6436 }
6437
e5491e28 6438 NSMutableArray *packages;
ae60e2c1 6439
0c8f53c0 6440 reload:
3025d5b4 6441 if ([self shouldYield]) {
bec28dda
JF
6442 do {
6443 UIProgressHUD *hud;
56bf1e78 6444
bec28dda
JF
6445 if (![self shouldBlock])
6446 hud = nil;
6447 else {
6448 hud = [delegate_ addProgressHUD];
6449 [hud setText:UCLocalize("LOADING")];
6450 }
56bf1e78 6451
56bf1e78 6452 reloading_ = 1;
ae60e2c1 6453 packages = [self yieldToSelector:@selector(_reloadPackages)];
bec28dda
JF
6454
6455 if (hud != nil)
6456 [delegate_ removeProgressHUD:hud];
56bf1e78 6457 } while (reloading_ == 2);
3025d5b4 6458 } else {
ae60e2c1 6459 packages = [self _reloadPackages];
3025d5b4 6460 }
36bb2ca2 6461
0c8f53c0
JF
6462@synchronized (database_) {
6463 if (era_ != [database_ era])
6464 goto reload;
6465 reloading_ = 0;
6466
ae60e2c1 6467 packages_ = packages;
e5491e28 6468 sections_ = [self sectionsForPackages:packages];
ae60e2c1 6469
e5491e28
JF
6470 [self updateHeight];
6471
6472 _profile(PackageTable$reloadData$List)
6473 [(UITableView *) list_ setDataSource:self];
6474 [list_ reloadData];
6475 _end
6476} }
6477
6478- (NSArray *) sectionsForPackages:(NSMutableArray *)packages {
6479 NSMutableArray *sections([NSMutableArray arrayWithCapacity:16]);
327624b6 6480
b4d89997
JF
6481 Section *section = nil;
6482
d03d7492 6483#if TryIndexedCollation
a891c345 6484 if ([[self class] hasIndexedCollation]) {
4f9acb7c
JF
6485 index_ = [[objc_getClass("UILocalizedIndexedCollation") currentCollation] sectionIndexTitles];
6486
a891c345
GP
6487 id collation = [objc_getClass("UILocalizedIndexedCollation") currentCollation];
6488 NSArray *titles = [collation sectionIndexTitles];
6489 int secidx = -1;
c4dcf2c2 6490
a891c345 6491 _profile(PackageTable$reloadData$Section)
e5491e28 6492 for (size_t offset(0), end([packages count]); offset != end; ++offset) {
a891c345
GP
6493 Package *package;
6494 int index;
b4d89997 6495
a891c345 6496 _profile(PackageTable$reloadData$Section$Package)
e5491e28 6497 package = [packages objectAtIndex:offset];
a891c345 6498 index = [collation sectionForObject:package collationStringSelector:@selector(name)];
808c6eb6 6499 _end
b4d89997 6500
a891c345
GP
6501 while (secidx < index) {
6502 secidx += 1;
327624b6 6503
a891c345
GP
6504 _profile(PackageTable$reloadData$Section$Allocate)
6505 section = [[[Section alloc] initWithName:[titles objectAtIndex:secidx] row:offset localize:NO] autorelease];
6506 _end
6507
6508 _profile(PackageTable$reloadData$Section$Add)
e5491e28 6509 [sections addObject:section];
a891c345
GP
6510 _end
6511 }
6512
6513 [section addToCount];
808c6eb6 6514 }
a891c345 6515 _end
d03d7492
JF
6516 } else
6517#endif
6518 {
4f9acb7c 6519 index_ = [NSMutableArray arrayWithCapacity:32];
808c6eb6 6520
9c5737d5
JF
6521 bool sectioned([self showsSections]);
6522 if (!sectioned) {
59f3d290 6523 section = [[[Section alloc] initWithName:nil localize:false] autorelease];
e5491e28 6524 [sections addObject:section];
59f3d290
JF
6525 }
6526
a891c345 6527 _profile(PackageTable$reloadData$Section)
e5491e28 6528 for (size_t offset(0), end([packages count]); offset != end; ++offset) {
a891c345
GP
6529 Package *package;
6530 unichar index;
6531
6532 _profile(PackageTable$reloadData$Section$Package)
e5491e28 6533 package = [packages objectAtIndex:offset];
a891c345
GP
6534 index = [package index];
6535 _end
6536
9c5737d5 6537 if (sectioned && (section == nil || [section index] != index)) {
a891c345
GP
6538 _profile(PackageTable$reloadData$Section$Allocate)
6539 section = [[[Section alloc] initWithIndex:index row:offset] autorelease];
6540 _end
6541
6542 [index_ addObject:[section name]];
a891c345
GP
6543
6544 _profile(PackageTable$reloadData$Section$Add)
e5491e28 6545 [sections addObject:section];
a891c345
GP
6546 _end
6547 }
6548
6549 [section addToCount];
6550 }
6551 _end
6552 }
b4d89997 6553
e5491e28
JF
6554 return sections;
6555}
b4d89997 6556
3025d5b4
JF
6557- (void) reloadData {
6558 [super reloadData];
6d246265
JF
6559
6560 if ([self shouldYield])
6561 [self performSelector:@selector(_reloadData) withObject:nil afterDelay:0];
6562 else
6563 [self _reloadData];
3025d5b4
JF
6564}
6565
a0be02eb 6566- (void) resetCursor {
4c6a29cd 6567 [list_ scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
a0be02eb
JF
6568}
6569
59f3d290
JF
6570- (void) clearData {
6571 [self updateHeight];
6572
6573 [list_ setDataSource:nil];
6574 [list_ reloadData];
6575
6576 [self resetCursor];
6577}
6578
a3328c28
JF
6579@end
6580/* }}} */
f50860ee 6581/* Filtered Package List Controller {{{ */
89bdef78
JF
6582typedef Function<bool, Package *> PackageFilter;
6583typedef Function<void, NSMutableArray *> PackageSorter;
f50860ee 6584@interface FilteredPackageListController : PackageListController {
89bdef78
JF
6585 PackageFilter filter_;
6586 PackageSorter sorter_;
a3328c28
JF
6587}
6588
89bdef78 6589- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(PackageFilter)filter;
3025d5b4 6590
89bdef78
JF
6591- (void) setFilter:(PackageFilter)filter;
6592- (void) setSorter:(PackageSorter)sorter;
a3328c28
JF
6593
6594@end
6595
f50860ee 6596@implementation FilteredPackageListController
a3328c28 6597
89bdef78 6598- (void) setFilter:(PackageFilter)filter {
aa32d91b 6599@synchronized (self) {
01d93940 6600 filter_ = filter;
55066b9e
JF
6601} }
6602
89bdef78 6603- (void) setSorter:(PackageSorter)sorter {
55066b9e 6604@synchronized (self) {
89bdef78 6605 sorter_ = sorter;
d84597fe 6606} }
01d93940 6607
9c5737d5 6608- (NSMutableArray *) _reloadPackages {
aa32d91b 6609@synchronized (database_) {
c28bc6f1 6610 era_ = [database_ era];
c28bc6f1 6611
dd4e70dc
JF
6612 NSArray *packages([database_ packages]);
6613 NSMutableArray *filtered([NSMutableArray arrayWithCapacity:[packages count]]);
aa32d91b 6614
89bdef78 6615 PackageFilter filter;
dd4e70dc 6616 PackageSorter sorter;
aa32d91b
JF
6617
6618 @synchronized (self) {
aa32d91b 6619 filter = filter_;
89bdef78 6620 sorter = sorter_;
aa32d91b
JF
6621 }
6622
6623 _profile(PackageTable$reloadData$Filter)
6624 for (Package *package in packages)
89bdef78 6625 if ([package valid] && filter(package))
aa32d91b 6626 [filtered addObject:package];
76933519 6627 _end
aa32d91b 6628
89bdef78
JF
6629 if (sorter)
6630 sorter(filtered);
aa32d91b 6631 return filtered;
dd4e70dc 6632} }
55066b9e 6633
89bdef78 6634- (id) initWithDatabase:(Database *)database title:(NSString *)title filter:(PackageFilter)filter {
55066b9e
JF
6635 if ((self = [super initWithDatabase:database title:title]) != nil) {
6636 [self setFilter:filter];
a3328c28
JF
6637 } return self;
6638}
6639
dc5812ec 6640@end
b5e7eebb
GP
6641/* }}} */
6642
5829aea2 6643/* Home Controller {{{ */
a576488f 6644@interface HomeController : CydiaWebViewController {
02f21c73
JF
6645 CFRunLoopRef runloop_;
6646 SCNetworkReachabilityRef reachability_;
b4d89997 6647}
6840bff3 6648
7b0ce2da 6649@end
b4d89997 6650
5829aea2 6651@implementation HomeController
46aa9775 6652
02f21c73
JF
6653static void HomeControllerReachabilityCallback(SCNetworkReachabilityRef reachability, SCNetworkReachabilityFlags flags, void *info) {
6654 [(HomeController *) info dispatchEvent:@"CydiaReachabilityCallback"];
6655}
6656
3c62d654
JF
6657- (id) init {
6658 if ((self = [super init]) != nil) {
90351d93 6659 [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/home/", UI_]]];
ed7bfd8c 6660 [self reloadData];
02f21c73
JF
6661
6662 reachability_ = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "cydia.saurik.com");
6663 if (reachability_ != NULL) {
6664 SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
6665 SCNetworkReachabilitySetCallback(reachability_, HomeControllerReachabilityCallback, &context);
6666
6667 CFRunLoopRef runloop(CFRunLoopGetCurrent());
6668 if (SCNetworkReachabilityScheduleWithRunLoop(reachability_, runloop, kCFRunLoopDefaultMode))
6669 runloop_ = runloop;
6670 }
3c62d654
JF
6671 } return self;
6672}
6673
02f21c73
JF
6674- (void) dealloc {
6675 if (reachability_ != NULL && runloop_ != NULL)
6676 SCNetworkReachabilityUnscheduleFromRunLoop(reachability_, runloop_, kCFRunLoopDefaultMode);
6677 [super dealloc];
6678}
6679
fe8e721f
GP
6680- (NSURL *) navigationURL {
6681 return [NSURL URLWithString:@"cydia://home"];
6682}
6683
5829aea2
GP
6684- (void) aboutButtonClicked {
6685 UIAlertView *alert([[[UIAlertView alloc] init] autorelease]);
b4d89997 6686
5829aea2
GP
6687 [alert setTitle:UCLocalize("ABOUT_CYDIA")];
6688 [alert addButtonWithTitle:UCLocalize("CLOSE")];
6689 [alert setCancelButtonIndex:0];
46aa9775 6690
5829aea2 6691 [alert setMessage:
323746d3 6692 @"Copyright \u00a9 2008-2013\n"
7e865c1e
JF
6693 "SaurikIT, LLC\n"
6694 "\n"
5829aea2
GP
6695 "Jay Freeman (saurik)\n"
6696 "saurik@saurik.com\n"
6697 "http://www.saurik.com/"
6698 ];
46aa9775 6699
5829aea2 6700 [alert show];
a0376fc1
JF
6701}
6702
e6124cb6
JF
6703- (UIBarButtonItem *) leftButton {
6704 return [[[UIBarButtonItem alloc]
35f0a3b5
GP
6705 initWithTitle:UCLocalize("ABOUT")
6706 style:UIBarButtonItemStylePlain
6707 target:self
6708 action:@selector(aboutButtonClicked)
e6124cb6 6709 ] autorelease];
7b0ce2da
JF
6710}
6711
5829aea2
GP
6712@end
6713/* }}} */
7b0ce2da 6714
28e596e4 6715/* Cydia Navigation Controller Interface {{{ */
15f0d613 6716@interface UINavigationController (Cydia)
28e596e4
JF
6717
6718- (NSArray *) navigationURLCollection;
15f0d613 6719- (void) unloadData;
28e596e4
JF
6720
6721@end
6722/* }}} */
7b0ce2da 6723
5829aea2 6724/* Cydia Tab Bar Controller {{{ */
5fe2bcc6 6725@interface CydiaTabBarController : CyteTabBarController <
9f99f3da 6726 UITabBarControllerDelegate,
21ac0ce2 6727 FetchDelegate
5829aea2
GP
6728> {
6729 _transient Database *database_;
7b0ce2da 6730
e67ebdad
JF
6731 _H<UIActivityIndicatorView> indicator_;
6732
5829aea2
GP
6733 bool updating_;
6734 // XXX: ok, "updatedelegate_"?...
6735 _transient NSObject<CydiaDelegate> *updatedelegate_;
7b0ce2da
JF
6736}
6737
35f0a3b5 6738- (NSArray *) navigationURLCollection;
5829aea2 6739- (void) beginUpdate;
5829aea2 6740- (BOOL) updating;
1cedb821 6741
5829aea2 6742@end
2fc76a2d 6743
5fe2bcc6 6744@implementation CydiaTabBarController
9f99f3da 6745
35f0a3b5 6746- (NSArray *) navigationURLCollection {
fe8e721f
GP
6747 NSMutableArray *items([NSMutableArray array]);
6748
35f0a3b5 6749 // XXX: Should this deal with transient view controllers?
fe8e721f 6750 for (id navigation in [self viewControllers]) {
35f0a3b5 6751 NSArray *stack = [navigation performSelector:@selector(navigationURLCollection)];
fe8e721f
GP
6752 if (stack != nil)
6753 [items addObject:stack];
2fc76a2d 6754 }
c713af59 6755
fe8e721f
GP
6756 return items;
6757}
6758
b5e7eebb
GP
6759- (id) initWithDatabase:(Database *)database {
6760 if ((self = [super init]) != nil) {
7b0ce2da 6761 database_ = database;
9f99f3da 6762 [self setDelegate:self];
04fe1349 6763
e67ebdad
JF
6764 indicator_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteTiny] autorelease];
6765 [indicator_ setOrigin:CGPointMake(kCFCoreFoundationVersionNumber >= 800 ? 2 : 4, 2)];
7b0ce2da 6766
e67ebdad 6767 [[self view] setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
7b0ce2da
JF
6768 } return self;
6769}
6770
5829aea2
GP
6771- (void) setUpdate:(NSDate *)date {
6772 [self beginUpdate];
6773}
1cedb821 6774
5829aea2 6775- (void) beginUpdate {
20d59843
JF
6776 if (updating_)
6777 return;
6778
e67ebdad
JF
6779 UIViewController *controller([[self viewControllers] objectAtIndex:1]);
6780 UITabBarItem *item([controller tabBarItem]);
6781
6782 [item setBadgeValue:@""];
6783 UIView *badge(MSHookIvar<UIView *>([item view], "_badge"));
6784
6785 [indicator_ startAnimating];
6786 [badge addSubview:indicator_];
7b0ce2da 6787
5829aea2
GP
6788 [updatedelegate_ retainNetworkActivityIndicator];
6789 updating_ = true;
7b0ce2da 6790
5829aea2
GP
6791 [NSThread
6792 detachNewThreadSelector:@selector(performUpdate)
6793 toTarget:self
6794 withObject:nil
6795 ];
7b0ce2da
JF
6796}
6797
d13edf44
JF
6798- (void) performUpdate {
6799 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
6800
21ac0ce2 6801 SourceStatus status(self, database_);
5829aea2
GP
6802 [database_ updateWithStatus:status];
6803
6804 [self
6805 performSelectorOnMainThread:@selector(completeUpdate)
6806 withObject:nil
6807 waitUntilDone:NO
6808 ];
d13edf44
JF
6809
6810 [pool release];
f6e13561
GP
6811}
6812
5829aea2
GP
6813- (void) stopUpdateWithSelector:(SEL)selector {
6814 updating_ = false;
6815 [updatedelegate_ releaseNetworkActivityIndicator];
b5e7eebb 6816
e67ebdad
JF
6817 UIViewController *controller([[self viewControllers] objectAtIndex:1]);
6818 [[controller tabBarItem] setBadgeValue:nil];
6819
6820 [indicator_ removeFromSuperview];
6821 [indicator_ stopAnimating];
bc11cf5b 6822
5829aea2 6823 [updatedelegate_ performSelector:selector withObject:nil afterDelay:0];
2bdd73bd
GP
6824}
6825
5829aea2
GP
6826- (void) completeUpdate {
6827 if (!updating_)
6828 return;
6829 [self stopUpdateWithSelector:@selector(reloadData)];
7b0ce2da
JF
6830}
6831
5829aea2 6832- (void) cancelUpdate {
383a58ac 6833 [self stopUpdateWithSelector:@selector(updateDataAndLoad)];
7b0ce2da
JF
6834}
6835
5829aea2
GP
6836- (void) cancelPressed {
6837 [self cancelUpdate];
6838}
7b0ce2da 6839
5829aea2
GP
6840- (BOOL) updating {
6841 return updating_;
7b0ce2da
JF
6842}
6843
21ac0ce2 6844- (bool) isSourceCancelled {
5829aea2
GP
6845 return !updating_;
6846}
7b0ce2da 6847
21ac0ce2 6848- (void) startSourceFetch:(NSString *)uri {
7b0ce2da
JF
6849}
6850
21ac0ce2 6851- (void) stopSourceFetch:(NSString *)uri {
bcbac8f7
JF
6852}
6853
5829aea2
GP
6854- (void) setUpdateDelegate:(id)delegate {
6855 updatedelegate_ = delegate;
7b0ce2da
JF
6856}
6857
5829aea2 6858- (UIView *) transitionView {
87ecdabe
JF
6859 if (![self respondsToSelector:@selector(_transitionView)])
6860 return MSHookIvar<id>(self, "_viewControllerTransitionView");
6861 else if (kCFCoreFoundationVersionNumber < 800)
5829aea2
GP
6862 return [self _transitionView];
6863 else
7fd48a82 6864 return [[[self _transitionView] superview] superview];
f641a0e5
JF
6865}
6866
7b0ce2da 6867@end
98228790 6868/* }}} */
f6e13561 6869
28e596e4 6870/* Cydia Navigation Controller Implementation {{{ */
15f0d613 6871@implementation UINavigationController (Cydia)
bc11cf5b 6872
35f0a3b5 6873- (NSArray *) navigationURLCollection {
fe8e721f
GP
6874 NSMutableArray *stack([NSMutableArray array]);
6875
cd79e8cf 6876 for (CyteViewController *controller in [self viewControllers]) {
fe8e721f
GP
6877 NSString *url = [[controller navigationURL] absoluteString];
6878 if (url != nil)
6879 [stack addObject:url];
6880 }
6881
6882 return stack;
6883}
6884
15f0d613
JF
6885- (void) reloadData {
6886 [super reloadData];
6887
21263088
JF
6888 UIViewController *visible([self visibleViewController]);
6889 if (visible != nil)
15f0d613 6890 [visible reloadData];
21263088
JF
6891
6892 // on the iPad, this view controller is ALSO visible. :(
6893 if (IsWildcat_)
6894 if (UIViewController *top = [self topViewController])
6895 if (top != visible)
6896 [top reloadData];
15f0d613 6897}
6c0ba3d9 6898
15f0d613 6899- (void) unloadData {
cd79e8cf 6900 for (CyteViewController *page in [self viewControllers])
15f0d613 6901 [page unloadData];
6c0ba3d9 6902
15f0d613 6903 [super unloadData];
dc63e78f
JF
6904}
6905
5829aea2
GP
6906@end
6907/* }}} */
7b0ce2da 6908
5829aea2
GP
6909/* Cydia:// Protocol {{{ */
6910@interface CydiaURLProtocol : NSURLProtocol {
9fe5e5f8
JF
6911}
6912
7b0ce2da
JF
6913@end
6914
5829aea2
GP
6915@implementation CydiaURLProtocol
6916
6917+ (BOOL) canInitWithRequest:(NSURLRequest *)request {
6918 NSURL *url([request URL]);
6919 if (url == nil)
6920 return NO;
b0a2900d 6921
5829aea2 6922 NSString *scheme([[url scheme] lowercaseString]);
b0a2900d
JF
6923 if (scheme != nil && [scheme isEqualToString:@"cydia"])
6924 return YES;
6925 if ([[url absoluteString] hasPrefix:@"about:cydia-"])
6926 return YES;
6927
6928 return NO;
aa5e5990
JF
6929}
6930
5829aea2
GP
6931+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
6932 return request;
6933}
aa5e5990 6934
5829aea2
GP
6935- (void) _returnPNGWithImage:(UIImage *)icon forRequest:(NSURLRequest *)request {
6936 id<NSURLProtocolClient> client([self client]);
6937 if (icon == nil)
6938 [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
6939 else {
6940 NSData *data(UIImagePNGRepresentation(icon));
01d93940 6941
5829aea2
GP
6942 NSURLResponse *response([[[NSURLResponse alloc] initWithURL:[request URL] MIMEType:@"image/png" expectedContentLength:-1 textEncodingName:nil] autorelease]);
6943 [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
6944 [client URLProtocol:self didLoadData:data];
6945 [client URLProtocolDidFinishLoading:self];
6946 }
3931b718
JF
6947}
6948
5829aea2
GP
6949- (void) startLoading {
6950 id<NSURLProtocolClient> client([self client]);
6951 NSURLRequest *request([self request]);
bc11cf5b 6952
5829aea2
GP
6953 NSURL *url([request URL]);
6954 NSString *href([url absoluteString]);
b0a2900d
JF
6955 NSString *scheme([[url scheme] lowercaseString]);
6956
6957 NSString *path;
6958
6959 if ([scheme isEqualToString:@"cydia"])
6960 path = [href substringFromIndex:8];
6961 else if ([scheme isEqualToString:@"about"])
6962 path = [href substringFromIndex:12];
6963 else _assert(false);
bc11cf5b 6964
5829aea2 6965 NSRange slash([path rangeOfString:@"/"]);
bc11cf5b 6966
5829aea2
GP
6967 NSString *command;
6968 if (slash.location == NSNotFound) {
6969 command = path;
6970 path = nil;
6971 } else {
6972 command = [path substringToIndex:slash.location];
6973 path = [path substringFromIndex:(slash.location + 1)];
6974 }
39cda3a8 6975
5829aea2 6976 Database *database([Database sharedInstance]);
bc11cf5b 6977
5829aea2
GP
6978 if ([command isEqualToString:@"package-icon"]) {
6979 if (path == nil)
6980 goto fail;
6981 path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
6982 Package *package([database packageWithName:path]);
6983 if (package == nil)
6984 goto fail;
eff20a8d 6985 [package parse];
5829aea2
GP
6986 UIImage *icon([package icon]);
6987 [self _returnPNGWithImage:icon forRequest:request];
5829aea2
GP
6988 } else if ([command isEqualToString:@"uikit-image"]) {
6989 if (path == nil)
6990 goto fail;
6991 path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
6992 UIImage *icon(_UIImageWithName(path));
6993 [self _returnPNGWithImage:icon forRequest:request];
6994 } else if ([command isEqualToString:@"section-icon"]) {
6995 if (path == nil)
6996 goto fail;
6997 path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
611efc17 6998 UIImage *icon([UIImage imageAtPath:[NSString stringWithFormat:@"%@/Sections/%@.png", App_, [path stringByReplacingOccurrencesOfString:@" " withString:@"_"]]]);
5829aea2
GP
6999 if (icon == nil)
7000 icon = [UIImage applicationImageNamed:@"unknown.png"];
7001 [self _returnPNGWithImage:icon forRequest:request];
7002 } else fail: {
7003 [client URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]];
7004 }
39cda3a8
GP
7005}
7006
5829aea2
GP
7007- (void) stopLoading {
7008}
807ae6d7 7009
5829aea2
GP
7010@end
7011/* }}} */
807ae6d7 7012
5829aea2 7013/* Section Controller {{{ */
f50860ee 7014@interface SectionController : FilteredPackageListController {
55066b9e 7015 _H<NSString> key_;
123b380c 7016 _H<NSString> section_;
5829aea2 7017}
807ae6d7 7018
55066b9e 7019- (id) initWithDatabase:(Database *)database source:(Source *)source section:(NSString *)section;
807ae6d7 7020
5829aea2 7021@end
bc11cf5b 7022
5829aea2 7023@implementation SectionController
bc11cf5b 7024
f050e4d9 7025- (NSURL *) referrerURL {
55066b9e
JF
7026 NSString *name(section_);
7027 name = name ?: @"*";
7028 NSString *key(key_);
7029 key = key ?: @"*";
7030 return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/sections/%@/%@", UI_, [key stringByAddingPercentEscapesIncludingReserved], [name stringByAddingPercentEscapesIncludingReserved]]];
f050e4d9
JF
7031}
7032
fe8e721f 7033- (NSURL *) navigationURL {
55066b9e
JF
7034 NSString *name(section_);
7035 name = name ?: @"*";
7036 NSString *key(key_);
7037 key = key ?: @"*";
7038 return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sections/%@/%@", [key stringByAddingPercentEscapesIncludingReserved], [name stringByAddingPercentEscapesIncludingReserved]]];
fe8e721f
GP
7039}
7040
55066b9e 7041- (id) initWithDatabase:(Database *)database source:(Source *)source section:(NSString *)section {
5829aea2 7042 NSString *title;
55066b9e 7043 if (section == nil)
5829aea2 7044 title = UCLocalize("ALL_PACKAGES");
55066b9e
JF
7045 else if (![section isEqual:@""])
7046 title = [[NSBundle mainBundle] localizedStringForKey:Simplify(section) value:nil table:@"Sections"];
3707eb56 7047 else
5829aea2 7048 title = UCLocalize("NO_SECTION");
fe8e721f 7049
89bdef78 7050 if ((self = [super initWithDatabase:database title:title]) != nil) {
55066b9e
JF
7051 key_ = [source key];
7052 section_ = section;
5829aea2 7053 } return self;
b5e7eebb
GP
7054}
7055
55066b9e 7056- (void) reloadData {
89bdef78
JF
7057 Source *source([database_ sourceWithKey:key_]);
7058 _H<NSString> name(section_);
7059
7060 [self setFilter:[=](Package *package) {
7061 NSString *section([package section]);
7062
7063 return (
7064 name == nil ||
7065 section == nil && [name length] == 0 ||
7066 [name isEqualToString:section]
7067 ) && (
7068 source == nil ||
7069 [package source] == source
7070 ) && [package visible];
7071 }];
7072
55066b9e
JF
7073 [super reloadData];
7074}
7075
b5e7eebb
GP
7076@end
7077/* }}} */
5829aea2 7078/* Sections Controller {{{ */
cd79e8cf 7079@interface SectionsController : CyteViewController <
5829aea2
GP
7080 UITableViewDataSource,
7081 UITableViewDelegate
7585ce66 7082> {
3931b718 7083 _transient Database *database_;
55066b9e 7084 _H<NSString> key_;
7b33d201
JF
7085 _H<NSMutableArray> sections_;
7086 _H<NSMutableArray> filtered_;
bf7c998c 7087 _H<UITableView, 2> list_;
b5e7eebb
GP
7088}
7089
55066b9e 7090- (id) initWithDatabase:(Database *)database source:(Source *)source;
5829aea2 7091- (void) editButtonClicked;
7585ce66 7092
bc11cf5b 7093@end
b5e7eebb 7094
5829aea2 7095@implementation SectionsController
b5e7eebb 7096
fe8e721f 7097- (NSURL *) navigationURL {
55066b9e
JF
7098 return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://sources/%@", [key_ stringByAddingPercentEscapesIncludingReserved]]];
7099}
7100
7101- (Source *) source {
7102 if (key_ == nil)
7103 return nil;
7104 return [database_ sourceWithKey:key_];
fe8e721f
GP
7105}
7106
a784d0a4 7107- (void) updateNavigationItem {
8e5b801a 7108 [[self navigationItem] setTitle:[self isEditing] ? UCLocalize("SECTION_VISIBILITY") : UCLocalize("SECTIONS")];
a784d0a4
GP
7109 if ([sections_ count] == 0) {
7110 [[self navigationItem] setRightBarButtonItem:nil];
7111 } else {
7112 [[self navigationItem] setRightBarButtonItem:[[UIBarButtonItem alloc]
8e5b801a 7113 initWithBarButtonSystemItem:([self isEditing] ? UIBarButtonSystemItemDone : UIBarButtonSystemItemEdit)
a784d0a4
GP
7114 target:self
7115 action:@selector(editButtonClicked)
7116 ] animated:([[self navigationItem] rightBarButtonItem] != nil)];
7117 }
7118}
7119
8e5b801a
GP
7120- (void) setEditing:(BOOL)editing animated:(BOOL)animated {
7121 [super setEditing:editing animated:animated];
35f0a3b5 7122
8e5b801a 7123 if (editing)
5829aea2
GP
7124 [list_ reloadData];
7125 else
7126 [delegate_ updateData];
7585ce66 7127
a784d0a4 7128 [self updateNavigationItem];
b5e7eebb
GP
7129}
7130
5829aea2
GP
7131- (void) viewDidAppear:(BOOL)animated {
7132 [super viewDidAppear:animated];
7133 [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
7585ce66
JF
7134}
7135
5829aea2
GP
7136- (void) viewWillDisappear:(BOOL)animated {
7137 [super viewWillDisappear:animated];
31eedaae 7138 [self setEditing:NO];
7585ce66
JF
7139}
7140
5829aea2 7141- (Section *) sectionAtIndexPath:(NSIndexPath *)indexPath {
b3551da8 7142 Section *section = nil;
8e5b801a 7143 int index = [indexPath row];
b3551da8 7144 if (![self isEditing]) {
666b48ad 7145 index -= 1;
b3551da8
GP
7146 if (index >= 0)
7147 section = [filtered_ objectAtIndex:index];
7148 } else {
7149 section = [sections_ objectAtIndex:index];
7150 }
5829aea2
GP
7151 return section;
7152}
7585ce66 7153
5829aea2 7154- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
8e5b801a
GP
7155 if ([self isEditing])
7156 return [sections_ count];
7157 else
7158 return [filtered_ count] + 1;
7585ce66
JF
7159}
7160
5829aea2
GP
7161/*- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
7162 return 45.0f;
7163}*/
7585ce66 7164
5829aea2
GP
7165- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
7166 static NSString *reuseIdentifier = @"SectionCell";
7585ce66 7167
35f0a3b5 7168 SectionCell *cell = (SectionCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
5829aea2
GP
7169 if (cell == nil)
7170 cell = [[[SectionCell alloc] initWithFrame:CGRectZero reuseIdentifier:reuseIdentifier] autorelease];
7585ce66 7171
8e5b801a 7172 [cell setSection:[self sectionAtIndexPath:indexPath] editing:[self isEditing]];
54043703 7173
5829aea2 7174 return cell;
54043703
JF
7175}
7176
5829aea2 7177- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
8e5b801a 7178 if ([self isEditing])
54043703 7179 return;
7585ce66 7180
5829aea2 7181 Section *section = [self sectionAtIndexPath:indexPath];
7585ce66 7182
5829aea2
GP
7183 SectionController *controller = [[[SectionController alloc]
7184 initWithDatabase:database_
55066b9e 7185 source:[self source]
5829aea2
GP
7186 section:[section name]
7187 ] autorelease];
7188 [controller setDelegate:delegate_];
7585ce66 7189
5829aea2 7190 [[self navigationController] pushViewController:controller animated:YES];
7585ce66
JF
7191}
7192
fe8e721f 7193- (void) loadView {
e8cbebe4 7194 list_ = [[[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];
fe8e721f 7195 [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
86a333c6 7196 [list_ setRowHeight:46];
7b33d201 7197 [(UITableView *) list_ setDataSource:self];
fe8e721f 7198 [list_ setDelegate:self];
e8cbebe4 7199 [self setView:list_];
fe8e721f
GP
7200}
7201
7202- (void) viewDidLoad {
7d887d0b
JF
7203 [super viewDidLoad];
7204
fe8e721f
GP
7205 [[self navigationItem] setTitle:UCLocalize("SECTIONS")];
7206}
7207
7208- (void) releaseSubviews {
fe8e721f 7209 list_ = nil;
7be3eb32 7210
4f9acb7c
JF
7211 sections_ = nil;
7212 filtered_ = nil;
7213
7be3eb32 7214 [super releaseSubviews];
fe8e721f
GP
7215}
7216
55066b9e 7217- (id) initWithDatabase:(Database *)database source:(Source *)source {
5829aea2
GP
7218 if ((self = [super init]) != nil) {
7219 database_ = database;
55066b9e 7220 key_ = [source key];
5829aea2 7221 } return self;
7585ce66
JF
7222}
7223
5829aea2 7224- (void) reloadData {
fe8e721f
GP
7225 [super reloadData];
7226
5829aea2 7227 NSArray *packages = [database_ packages];
7585ce66 7228
4f9acb7c
JF
7229 sections_ = [NSMutableArray arrayWithCapacity:16];
7230 filtered_ = [NSMutableArray arrayWithCapacity:16];
7585ce66 7231
5829aea2 7232 NSMutableDictionary *sections([NSMutableDictionary dictionaryWithCapacity:32]);
7585ce66 7233
55066b9e
JF
7234 Source *source([self source]);
7235
5829aea2
GP
7236 _trace();
7237 for (Package *package in packages) {
55066b9e
JF
7238 if (source != nil && [package source] != source)
7239 continue;
7240
5829aea2
GP
7241 NSString *name([package section]);
7242 NSString *key(name == nil ? @"" : name);
7585ce66 7243
5829aea2 7244 Section *section;
7585ce66 7245
5829aea2
GP
7246 _profile(SectionsView$reloadData$Section)
7247 section = [sections objectForKey:key];
7248 if (section == nil) {
7249 _profile(SectionsView$reloadData$Section$Allocate)
729bc910 7250 section = [[[Section alloc] initWithName:key localize:YES] autorelease];
5829aea2
GP
7251 [sections setObject:section forKey:key];
7252 _end
7253 }
7254 _end
7585ce66 7255
5829aea2 7256 [section addToCount];
7585ce66 7257
5829aea2
GP
7258 _profile(SectionsView$reloadData$Filter)
7259 if (![package valid] || ![package visible])
7260 continue;
7261 _end
7585ce66 7262
5829aea2
GP
7263 [section addToRow];
7264 }
7265 _trace();
7585ce66 7266
5829aea2 7267 [sections_ addObjectsFromArray:[sections allValues]];
7585ce66 7268
5829aea2 7269 [sections_ sortUsingSelector:@selector(compareByLocalized:)];
7585ce66 7270
7b33d201 7271 for (Section *section in (id) sections_) {
5829aea2
GP
7272 size_t count([section row]);
7273 if (count == 0)
7274 continue;
7585ce66 7275
5829aea2
GP
7276 section = [[[Section alloc] initWithName:[section name] localized:[section localized]] autorelease];
7277 [section setCount:count];
7278 [filtered_ addObject:section];
7279 }
7585ce66 7280
a784d0a4 7281 [self updateNavigationItem];
5829aea2
GP
7282 [list_ reloadData];
7283 _trace();
7284}
7585ce66 7285
6840bff3 7286- (void) editButtonClicked {
8e5b801a 7287 [self setEditing:![self isEditing] animated:YES];
5829aea2 7288}
7585ce66 7289
5829aea2
GP
7290@end
7291/* }}} */
7585ce66 7292
5829aea2 7293/* Changes Controller {{{ */
e5491e28 7294@interface ChangesController : FilteredPackageListController {
5829aea2 7295 unsigned upgrades_;
5829aea2 7296}
7585ce66 7297
ea3bb538 7298- (id) initWithDatabase:(Database *)database;
7585ce66 7299
5829aea2 7300@end
7585ce66 7301
5829aea2 7302@implementation ChangesController
7585ce66 7303
e5491e28
JF
7304- (NSURL *) referrerURL {
7305 return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/changes/", UI_]];
b5e7eebb
GP
7306}
7307
e5491e28
JF
7308- (NSURL *) navigationURL {
7309 return [NSURL URLWithString:@"cydia://changes"];
5829aea2 7310}
b5e7eebb 7311
5829aea2
GP
7312- (Package *) packageAtIndexPath:(NSIndexPath *)path {
7313@synchronized (database_) {
7314 if ([database_ era] != era_)
7315 return nil;
b5e7eebb 7316
5829aea2
GP
7317 NSUInteger sectionIndex([path section]);
7318 if (sectionIndex >= [sections_ count])
7319 return nil;
7320 Section *section([sections_ objectAtIndex:sectionIndex]);
7321 NSInteger row([path row]);
56bf1e78 7322 return [[[packages_ objectAtIndex:([section row] + row)] retain] autorelease];
5829aea2 7323} }
b5e7eebb 7324
0e15b67c
JF
7325- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
7326 NSString *context([alert context]);
7327
7328 if ([context isEqualToString:@"norefresh"])
7329 [alert dismissWithClickedButtonIndex:-1 animated:YES];
7330}
7331
e67ebdad
JF
7332- (void) setLeftBarButtonItem {
7333 if ([delegate_ updating])
7334 [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
7335 initWithTitle:UCLocalize("CANCEL")
7336 style:UIBarButtonItemStyleDone
7337 target:self
7338 action:@selector(cancelButtonClicked)
7339 ] autorelease] animated:YES];
7340 else
7341 [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
7342 initWithTitle:UCLocalize("REFRESH")
7343 style:UIBarButtonItemStylePlain
7344 target:self
7345 action:@selector(refreshButtonClicked)
7346 ] autorelease] animated:YES];
7347}
7348
5829aea2 7349- (void) refreshButtonClicked {
e67ebdad
JF
7350 if ([delegate_ requestUpdate])
7351 [self setLeftBarButtonItem];
7352}
0e15b67c 7353
e67ebdad
JF
7354- (void) cancelButtonClicked {
7355 [delegate_ cancelUpdate];
b5e7eebb
GP
7356}
7357
5829aea2
GP
7358- (void) upgradeButtonClicked {
7359 [delegate_ distUpgrade];
cb6b3a7b 7360 [[self navigationItem] setRightBarButtonItem:nil animated:YES];
b5e7eebb
GP
7361}
7362
e5491e28
JF
7363- (bool) shouldYield {
7364 return true;
fe8e721f
GP
7365}
7366
e5491e28
JF
7367- (bool) shouldBlock {
7368 return true;
fe8e721f
GP
7369}
7370
e5491e28
JF
7371- (void) useFilter {
7372@synchronized (self) {
7373 [self setFilter:[](Package *package) {
7374 return [package upgradableAndEssential:YES] || [package visible];
7375 }];
4f9acb7c 7376
e5491e28
JF
7377 [self setSorter:[](NSMutableArray *packages) {
7378 [packages radixSortUsingFunction:reinterpret_cast<MenesRadixSortFunction>(&PackageChangesRadix) withContext:NULL];
7379 }];
7380} }
5829aea2 7381
ea3bb538 7382- (id) initWithDatabase:(Database *)database {
e5491e28
JF
7383 if ((self = [super initWithDatabase:database title:UCLocalize("CHANGES")]) != nil) {
7384 [self useFilter];
5829aea2 7385 } return self;
807ae6d7
JF
7386}
7387
e5491e28 7388- (void) reloadData {
e67ebdad 7389 [self setLeftBarButtonItem];
e5491e28
JF
7390 [super reloadData];
7391}
e67ebdad 7392
e5491e28
JF
7393- (NSArray *) sectionsForPackages:(NSMutableArray *)packages {
7394 NSMutableArray *sections([NSMutableArray arrayWithCapacity:16]);
bdca103d 7395
5829aea2
GP
7396 Section *upgradable = [[[Section alloc] initWithName:UCLocalize("AVAILABLE_UPGRADES") localize:NO] autorelease];
7397 Section *ignored = nil;
7398 Section *section = nil;
7399 time_t last = 0;
807ae6d7 7400
5829aea2
GP
7401 upgrades_ = 0;
7402 bool unseens = false;
807ae6d7 7403
5829aea2 7404 CFDateFormatterRef formatter(CFDateFormatterCreate(NULL, Locale_, kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle));
807ae6d7 7405
e5491e28
JF
7406 for (size_t offset = 0, count = [packages count]; offset != count; ++offset) {
7407 Package *package = [packages objectAtIndex:offset];
807ae6d7 7408
5829aea2 7409 BOOL uae = [package upgradableAndEssential:YES];
807ae6d7 7410
5829aea2
GP
7411 if (!uae) {
7412 unseens = true;
7413 time_t seen([package seen]);
d90a4cd6 7414
5829aea2
GP
7415 if (section == nil || last != seen) {
7416 last = seen;
7417
7418 NSString *name;
7419 name = (NSString *) CFDateFormatterCreateStringWithDate(NULL, formatter, (CFDateRef) [NSDate dateWithTimeIntervalSince1970:seen]);
7420 [name autorelease];
7421
7422 _profile(ChangesController$reloadData$Allocate)
7423 name = [NSString stringWithFormat:UCLocalize("NEW_AT"), name];
7424 section = [[[Section alloc] initWithName:name row:offset localize:NO] autorelease];
e5491e28 7425 [sections addObject:section];
5829aea2
GP
7426 _end
7427 }
7428
7429 [section addToCount];
7430 } else if ([package ignored]) {
7431 if (ignored == nil) {
7432 ignored = [[[Section alloc] initWithName:UCLocalize("IGNORED_UPGRADES") row:offset localize:NO] autorelease];
7433 }
7434 [ignored addToCount];
7435 } else {
7436 ++upgrades_;
7437 [upgradable addToCount];
7438 }
7439 }
7440 _trace();
7441
7442 CFRelease(formatter);
7443
7444 if (unseens) {
e5491e28 7445 Section *last = [sections lastObject];
5829aea2 7446 size_t count = [last count];
e5491e28
JF
7447 [packages removeObjectsInRange:NSMakeRange([packages count] - count, count)];
7448 [sections removeLastObject];
5829aea2
GP
7449 }
7450
7451 if ([ignored count] != 0)
e5491e28 7452 [sections insertObject:ignored atIndex:0];
5829aea2 7453 if (upgrades_ != 0)
e5491e28 7454 [sections insertObject:upgradable atIndex:0];
5829aea2
GP
7455
7456 [list_ reloadData];
7457
cb6b3a7b
JF
7458 [[self navigationItem] setRightBarButtonItem:(upgrades_ == 0 ? nil : [[[UIBarButtonItem alloc]
7459 initWithTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("UPGRADE"), [NSString stringWithFormat:@"%u", upgrades_]]
7460 style:UIBarButtonItemStylePlain
7461 target:self
7462 action:@selector(upgradeButtonClicked)
7463 ] autorelease]) animated:YES];
5829aea2 7464
5829aea2 7465 PrintTimes();
e5491e28 7466 return sections;
a70cd4ba
JF
7467}
7468
5829aea2
GP
7469@end
7470/* }}} */
7471/* Search Controller {{{ */
f50860ee 7472@interface SearchController : FilteredPackageListController <
5829aea2
GP
7473 UISearchBarDelegate
7474> {
bf7c998c 7475 _H<UISearchBar, 1> search_;
fe8e721f 7476 BOOL searchloaded_;
89bdef78 7477 bool summary_;
5829aea2
GP
7478}
7479
43625891 7480- (id) initWithDatabase:(Database *)database query:(NSString *)query;
5829aea2 7481- (void) reloadData;
d90a4cd6
GP
7482
7483@end
7484
5829aea2 7485@implementation SearchController
d90a4cd6 7486
f050e4d9
JF
7487- (NSURL *) referrerURL {
7488 return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/search?q=%@", UI_, [([search_ text] ?: @"") stringByAddingPercentEscapesIncludingReserved]]];
7489}
7490
fe8e721f 7491- (NSURL *) navigationURL {
35f0a3b5
GP
7492 if ([search_ text] == nil || [[search_ text] isEqualToString:@""])
7493 return [NSURL URLWithString:@"cydia://search"];
7494 else
b90c7892 7495 return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://search/%@", [[search_ text] stringByAddingPercentEscapesIncludingReserved]]];
fe8e721f
GP
7496}
7497
096e25d8 7498- (NSArray *) termsForQuery:(NSString *)query {
945812b9
JF
7499 NSMutableArray *terms([NSMutableArray arrayWithCapacity:2]);
7500 for (NSString *component in [query componentsSeparatedByString:@" "])
7501 if ([component length] != 0)
7502 [terms addObject:component];
7503
7504 return terms;
096e25d8
JF
7505}
7506
59f3d290 7507- (void) useSearch {
89bdef78
JF
7508 _H<NSArray> query([self termsForQuery:[search_ text]]);
7509 summary_ = false;
7510
7511@synchronized (self) {
7512 [self setFilter:[=](Package *package) {
7513 if (![package unfiltered])
7514 return false;
7515 if (![package matches:query])
7516 return false;
7517 return true;
7518 }];
7519
7520 [self setSorter:[](NSMutableArray *packages) {
7521 [packages radixSortUsingSelector:@selector(rank)];
7522 }];
7523}
7524
59f3d290
JF
7525 [self clearData];
7526 [self reloadData];
7527}
7528
89bdef78
JF
7529- (void) usePrefix:(NSString *)prefix {
7530 _H<NSString> query(prefix);
7531 summary_ = true;
7532
7533@synchronized (self) {
7534 [self setFilter:[=](Package *package) {
7535 if ([query length] == 0)
7536 return false;
7537 if (![package unfiltered])
7538 return false;
7539 if ([[package name] compare:query options:MatchCompareOptions_ range:NSMakeRange(0, [query length])] != NSOrderedSame)
7540 return false;
7541 return true;
7542 }];
7543
7544 [self setSorter:nullptr];
7545}
7546
7547 [self reloadData];
7548}
7549
3025d5b4 7550- (void) searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
59f3d290 7551 [self clearData];
89bdef78 7552 [self usePrefix:[search_ text]];
3025d5b4
JF
7553}
7554
7555- (void) searchBarButtonClicked:(UISearchBar *)searchBar {
5829aea2 7556 [search_ resignFirstResponder];
59f3d290 7557 [self useSearch];
5829aea2
GP
7558}
7559
3025d5b4
JF
7560- (void) searchBarCancelButtonClicked:(UISearchBar *)searchBar {
7561 [search_ setText:@""];
7562 [self searchBarButtonClicked:searchBar];
7563}
7564
7565- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
7566 [self searchBarButtonClicked:searchBar];
7567}
7568
5829aea2 7569- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)text {
89bdef78 7570 [self usePrefix:text];
5829aea2
GP
7571}
7572
3025d5b4 7573- (bool) shouldYield {
56bf1e78
JF
7574 return YES;
7575}
7576
7577- (bool) shouldBlock {
89bdef78 7578 return !summary_;
3025d5b4
JF
7579}
7580
59f3d290 7581- (bool) isSummarized {
89bdef78 7582 return summary_;
59f3d290
JF
7583}
7584
9c5737d5
JF
7585- (bool) showsSections {
7586 return false;
7587}
7588
43625891 7589- (id) initWithDatabase:(Database *)database query:(NSString *)query {
89bdef78 7590 if ((self = [super initWithDatabase:database title:UCLocalize("SEARCH")])) {
900095fd 7591 search_ = [[[UISearchBar alloc] init] autorelease];
830efb5d 7592 [search_ setPlaceholder:UCLocalize("SEARCH_EX")];
900095fd 7593 [search_ setDelegate:self];
43625891 7594
830efb5d
JF
7595 UITextField *textField;
7596 if ([search_ respondsToSelector:@selector(searchField)])
7597 textField = [search_ searchField];
7598 else
7599 textField = MSHookIvar<UITextField *>(search_, "_searchField");
7600
7601 [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
7602 [textField setEnablesReturnKeyAutomatically:NO];
7603 [[self navigationItem] setTitleView:textField];
7604
43625891
JF
7605 if (query != nil)
7606 [search_ setText:query];
89bdef78 7607 [self useSearch];
fe8e721f 7608 } return self;
5829aea2
GP
7609}
7610
6840bff3 7611- (void) viewDidAppear:(BOOL)animated {
5829aea2 7612 [super viewDidAppear:animated];
fe8e721f
GP
7613
7614 if (!searchloaded_) {
7615 searchloaded_ = YES;
7616 [search_ setFrame:CGRectMake(0, 0, [[self view] bounds].size.width, 44.0f)];
5829aea2 7617 [search_ layoutSubviews];
d90a4cd6 7618 }
058813ae
GP
7619
7620 if ([self isSummarized])
7621 [search_ becomeFirstResponder];
5829aea2 7622}
d90a4cd6 7623
d90a4cd6 7624- (void) reloadData {
f50860ee 7625 [self resetCursor];
9dac415b 7626 [super reloadData];
d90a4cd6
GP
7627}
7628
5829aea2
GP
7629- (void) didSelectPackage:(Package *)package {
7630 [search_ resignFirstResponder];
7631 [super didSelectPackage:package];
d90a4cd6
GP
7632}
7633
7634@end
7635/* }}} */
5829aea2 7636/* Package Settings Controller {{{ */
cd79e8cf 7637@interface PackageSettingsController : CyteViewController <
c21004b9
JF
7638 UITableViewDataSource,
7639 UITableViewDelegate
7640> {
807ae6d7 7641 _transient Database *database_;
7b33d201
JF
7642 _H<NSString> name_;
7643 _H<Package> package_;
bf7c998c 7644 _H<UITableView, 2> table_;
7b33d201
JF
7645 _H<UISwitch> subscribedSwitch_;
7646 _H<UISwitch> ignoredSwitch_;
7647 _H<UITableViewCell> subscribedCell_;
7648 _H<UITableViewCell> ignoredCell_;
807ae6d7
JF
7649}
7650
5829aea2 7651- (id) initWithDatabase:(Database *)database package:(NSString *)package;
c21004b9 7652
807ae6d7
JF
7653@end
7654
5829aea2 7655@implementation PackageSettingsController
807ae6d7 7656
fe8e721f 7657- (NSURL *) navigationURL {
8861e953 7658 return [NSURL URLWithString:[NSString stringWithFormat:@"cydia://package/%@/settings", (id) name_]];
fe8e721f
GP
7659}
7660
5829aea2
GP
7661- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
7662 if (package_ == nil)
7663 return 0;
e954c30a 7664
2136717a
DH
7665 if ([package_ installed] == nil)
7666 return 1;
7667 else
7668 return 2;
e954c30a
GP
7669}
7670
5829aea2
GP
7671- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
7672 if (package_ == nil)
7673 return 0;
7674
2136717a
DH
7675 // both sections contain just one item right now.
7676 return 1;
b5e7eebb
GP
7677}
7678
5829aea2 7679- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
2136717a 7680 return nil;
e954c30a
GP
7681}
7682
5829aea2 7683- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
2136717a
DH
7684 if (section == 0)
7685 return UCLocalize("SHOW_ALL_CHANGES_EX");
7686 else
7687 return UCLocalize("IGNORE_UPGRADES_EX");
807ae6d7
JF
7688}
7689
5829aea2
GP
7690- (void) onSubscribed:(id)control {
7691 bool value([control isOn]);
7692 if (package_ == nil)
7693 return;
7694 if ([package_ setSubscribed:value])
7695 [delegate_ updateData];
807ae6d7
JF
7696}
7697
e5e70358
JF
7698- (void) _updateIgnored {
7699 const char *package([name_ UTF8String]);
7700 bool on([ignoredSwitch_ isOn]);
7701
7702 pid_t pid(ExecFork());
7703 if (pid == 0) {
7704 FILE *dpkg(popen("dpkg --set-selections", "w"));
7705 fwrite(package, strlen(package), 1, dpkg);
7706
7707 if (on)
7708 fwrite(" hold\n", 6, 1, dpkg);
7709 else
7710 fwrite(" install\n", 9, 1, dpkg);
7711
7712 pclose(dpkg);
7713
7714 exit(0);
51c8106a 7715 } ReapZombie(pid);
e5e70358
JF
7716}
7717
5829aea2 7718- (void) onIgnored:(id)control {
e5e70358
JF
7719 NSInvocation *invocation([NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(_updateIgnored)]]);
7720 [invocation setTarget:self];
7721 [invocation setSelector:@selector(_updateIgnored)];
7722
7723 [delegate_ reloadDataWithInvocation:invocation];
5829aea2 7724}
807ae6d7 7725
46aa9775 7726- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
5829aea2
GP
7727 if (package_ == nil)
7728 return nil;
b5e7eebb 7729
2136717a 7730 switch ([indexPath section]) {
5829aea2
GP
7731 case 0: return subscribedCell_;
7732 case 1: return ignoredCell_;
36fbb2aa 7733
5829aea2
GP
7734 _nodefault
7735 }
807ae6d7 7736
5829aea2 7737 return nil;
807ae6d7
JF
7738}
7739
fe8e721f 7740- (void) loadView {
39470a3a
JF
7741 UIView *view([[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]);
7742 [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
7743 [self setView:view];
807ae6d7 7744
7b33d201 7745 table_ = [[[UITableView alloc] initWithFrame:[[self view] bounds] style:UITableViewStyleGrouped] autorelease];
fe8e721f 7746 [table_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
7b33d201 7747 [(UITableView *) table_ setDataSource:self];
fe8e721f 7748 [table_ setDelegate:self];
39470a3a 7749 [view addSubview:table_];
807ae6d7 7750
7b33d201 7751 subscribedSwitch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)] autorelease];
fe8e721f
GP
7752 [subscribedSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
7753 [subscribedSwitch_ addTarget:self action:@selector(onSubscribed:) forEvents:UIControlEventValueChanged];
807ae6d7 7754
7b33d201 7755 ignoredSwitch_ = [[[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 50, 20)] autorelease];
fe8e721f
GP
7756 [ignoredSwitch_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
7757 [ignoredSwitch_ addTarget:self action:@selector(onIgnored:) forEvents:UIControlEventValueChanged];
807ae6d7 7758
7b33d201 7759 subscribedCell_ = [[[UITableViewCell alloc] init] autorelease];
fe8e721f
GP
7760 [subscribedCell_ setText:UCLocalize("SHOW_ALL_CHANGES")];
7761 [subscribedCell_ setAccessoryView:subscribedSwitch_];
7762 [subscribedCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
5829aea2 7763
7b33d201 7764 ignoredCell_ = [[[UITableViewCell alloc] init] autorelease];
fe8e721f
GP
7765 [ignoredCell_ setText:UCLocalize("IGNORE_UPGRADES")];
7766 [ignoredCell_ setAccessoryView:ignoredSwitch_];
7767 [ignoredCell_ setSelectionStyle:UITableViewCellSelectionStyleNone];
fe8e721f 7768}
5829aea2 7769
fe8e721f 7770- (void) viewDidLoad {
7d887d0b
JF
7771 [super viewDidLoad];
7772
fe8e721f
GP
7773 [[self navigationItem] setTitle:UCLocalize("SETTINGS")];
7774}
5829aea2 7775
fe8e721f 7776- (void) releaseSubviews {
fe8e721f 7777 ignoredCell_ = nil;
fe8e721f 7778 subscribedCell_ = nil;
fe8e721f 7779 table_ = nil;
fe8e721f 7780 ignoredSwitch_ = nil;
fe8e721f 7781 subscribedSwitch_ = nil;
7be3eb32
JF
7782
7783 [super releaseSubviews];
fe8e721f
GP
7784}
7785
7786- (id) initWithDatabase:(Database *)database package:(NSString *)package {
6840bff3 7787 if ((self = [super init]) != nil) {
fe8e721f 7788 database_ = database;
7b33d201 7789 name_ = package;
807ae6d7
JF
7790 } return self;
7791}
7792
7793- (void) reloadData {
fe8e721f
GP
7794 [super reloadData];
7795
5829aea2 7796 package_ = [database_ packageWithName:name_];
f3e2c0ac 7797
5829aea2 7798 if (package_ != nil) {
5829aea2
GP
7799 [subscribedSwitch_ setOn:([package_ subscribed] ? 1 : 0) animated:NO];
7800 [ignoredSwitch_ setOn:([package_ ignored] ? 1 : 0) animated:NO];
f3e2c0ac 7801 } // XXX: what now, G?
dc5812ec 7802
5829aea2
GP
7803 [table_ reloadData];
7804}
6d9712c4 7805
dc5812ec 7806@end
2367a917 7807/* }}} */
d90a4cd6 7808
5829aea2 7809/* Installed Controller {{{ */
f50860ee 7810@interface InstalledController : FilteredPackageListController {
dc5812ec
JF
7811}
7812
5829aea2 7813- (id) initWithDatabase:(Database *)database;
5829aea2 7814- (void) queueStatusDidChange;
dc5812ec 7815
dc5812ec
JF
7816@end
7817
5829aea2 7818@implementation InstalledController
b4d89997 7819
f050e4d9
JF
7820- (NSURL *) referrerURL {
7821 return [NSURL URLWithString:[NSString stringWithFormat:@"%@/#!/installed/", UI_]];
7822}
7823
fe8e721f
GP
7824- (NSURL *) navigationURL {
7825 return [NSURL URLWithString:@"cydia://installed"];
7826}
b5e7eebb 7827
89bdef78
JF
7828- (void) useFilter:(UISegmentedControl *)segmented {
7829 bool simple([segmented selectedSegmentIndex] == 0);
7830
7831@synchronized (self) {
7832 [self setFilter:[=](Package *package) {
7833 return ![package uninstalled] && package->role_ <= (simple ? 1 : 3);
7834 }];
7835} }
7836
5829aea2 7837- (id) initWithDatabase:(Database *)database {
89bdef78 7838 if ((self = [super initWithDatabase:database title:UCLocalize("INSTALLED")]) != nil) {
3544397d
JF
7839 UISegmentedControl *segmented([[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:UCLocalize("SIMPLE"), UCLocalize("EXPERT"), nil]] autorelease]);
7840 [segmented setSelectedSegmentIndex:0];
7841 [segmented setSegmentedControlStyle:UISegmentedControlStyleBar];
7842 [[self navigationItem] setTitleView:segmented];
7843
7844 [segmented addTarget:self action:@selector(modeChanged:) forEvents:UIControlEventValueChanged];
89bdef78 7845 [self useFilter:segmented];
3544397d 7846
5829aea2
GP
7847 [self queueStatusDidChange];
7848 } return self;
dc5812ec
JF
7849}
7850
5829aea2
GP
7851#if !AlwaysReload
7852- (void) queueButtonClicked {
7853 [delegate_ queue];
dc5812ec 7854}
5829aea2 7855#endif
dc5812ec 7856
5829aea2
GP
7857- (void) queueStatusDidChange {
7858#if !AlwaysReload
55066b9e
JF
7859 if (Queuing_) {
7860 [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
7861 initWithTitle:UCLocalize("QUEUE")
7862 style:UIBarButtonItemStyleDone
7863 target:self
7864 action:@selector(queueButtonClicked)
7865 ] autorelease]];
7866 } else {
7867 [[self navigationItem] setLeftBarButtonItem:nil];
5829aea2
GP
7868 }
7869#endif
dc5812ec
JF
7870}
7871
3544397d 7872- (void) modeChanged:(UISegmentedControl *)segmented {
89bdef78 7873 [self useFilter:segmented];
f50860ee 7874 [self reloadData];
9a7b04c5
JF
7875}
7876
5829aea2
GP
7877@end
7878/* }}} */
b5e7eebb 7879
5829aea2 7880/* Source Cell {{{ */
a9311516 7881@interface SourceCell : CyteTableViewCell <
21ac0ce2
JF
7882 CyteTableViewCellDelegate,
7883 SourceDelegate
5829aea2 7884> {
21ac0ce2 7885 _H<Source, 1> source_;
02735663 7886 _H<NSURL> url_;
7b33d201
JF
7887 _H<UIImage> icon_;
7888 _H<NSString> origin_;
7889 _H<NSString> label_;
21ac0ce2 7890 _H<UIActivityIndicatorView> indicator_;
5829aea2 7891}
dc5812ec 7892
5829aea2 7893- (void) setSource:(Source *)source;
21ac0ce2 7894- (void) setFetch:(NSNumber *)fetch;
6da1297d 7895
5829aea2 7896@end
dc5812ec 7897
5829aea2 7898@implementation SourceCell
36bb2ca2 7899
02735663
JF
7900- (void) _setImage:(NSArray *)data {
7901 if ([url_ isEqual:[data objectAtIndex:0]]) {
7902 icon_ = [data objectAtIndex:1];
7903 [content_ setNeedsDisplay];
7904 }
8252b666
JF
7905}
7906
7bd76e97 7907- (void) _setSource:(NSURL *) url {
8252b666
JF
7908 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
7909
7bd76e97
JF
7910 if (NSData *data = [NSURLConnection
7911 sendSynchronousRequest:[NSURLRequest
7912 requestWithURL:url
de5f2673
JF
7913 cachePolicy:NSURLRequestUseProtocolCachePolicy
7914 timeoutInterval:10
7bd76e97
JF
7915 ]
7916
7917 returningResponse:NULL
7918 error:NULL
7919 ])
7920 if (UIImage *image = [UIImage imageWithData:data])
02735663 7921 [self performSelectorOnMainThread:@selector(_setImage:) withObject:[NSArray arrayWithObjects:url, image, nil] waitUntilDone:NO];
8252b666
JF
7922
7923 [pool release];
7924}
7925
5829aea2 7926- (void) setSource:(Source *)source {
21ac0ce2
JF
7927 source_ = source;
7928 [source_ setDelegate:self];
7929
7930 [self setFetch:[NSNumber numberWithBool:[source_ fetch]]];
7931
8252b666 7932 icon_ = [UIImage applicationImageNamed:@"unknown.png"];
5829aea2 7933
7b33d201 7934 origin_ = [source name];
7bd76e97 7935 label_ = [source rooturi];
5829aea2
GP
7936
7937 [content_ setNeedsDisplay];
8252b666 7938
02735663
JF
7939 url_ = [source iconURL];
7940 [NSThread detachNewThreadSelector:@selector(_setSource:) toTarget:self withObject:url_];
77801ff1
JF
7941}
7942
55066b9e 7943- (void) setAllSource {
21ac0ce2
JF
7944 source_ = nil;
7945 [indicator_ stopAnimating];
7946
55066b9e
JF
7947 icon_ = [UIImage applicationImageNamed:@"folder.png"];
7948 origin_ = UCLocalize("ALL_SOURCES");
7949 label_ = UCLocalize("ALL_SOURCES_EX");
7950 [content_ setNeedsDisplay];
7951}
7952
5829aea2
GP
7953- (SourceCell *) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
7954 if ((self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) != nil) {
7955 UIView *content([self contentView]);
7956 CGRect bounds([content bounds]);
77801ff1 7957
b97fcfc6 7958 content_ = [[[CyteTableViewCellContentView alloc] initWithFrame:bounds] autorelease];
5829aea2
GP
7959 [content_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
7960 [content_ setBackgroundColor:[UIColor whiteColor]];
7961 [content addSubview:content_];
dc5812ec 7962
5829aea2
GP
7963 [content_ setDelegate:self];
7964 [content_ setOpaque:YES];
53db9999 7965
21ac0ce2
JF
7966 indicator_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGraySmall] autorelease];
7967 [indicator_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin];// | UIViewAutoresizingFlexibleBottomMargin];
7968 [content addSubview:indicator_];
7969
53db9999 7970 [[content_ layer] setContentsGravity:kCAGravityTopLeft];
5829aea2
GP
7971 } return self;
7972}
b4d89997 7973
21ac0ce2
JF
7974- (void) layoutSubviews {
7975 [super layoutSubviews];
7976
7977 UIView *content([self contentView]);
7978 CGRect bounds([content bounds]);
7979
7980 CGRect frame([indicator_ frame]);
7981 frame.origin.x = bounds.size.width - frame.size.width;
7982 frame.origin.y = (bounds.size.height - frame.size.height) / 2;
7983
7984 if (kCFCoreFoundationVersionNumber < 800)
7985 frame.origin.x -= 8;
7986 [indicator_ setFrame:frame];
7987}
7988
003fc610 7989- (NSString *) accessibilityLabel {
66abff39 7990 return origin_;
003fc610
GP
7991}
7992
5829aea2
GP
7993- (void) drawContentRect:(CGRect)rect {
7994 bool highlighted(highlighted_);
7995 float width(rect.size.width);
36bb2ca2 7996
25c1dafb
JF
7997 if (icon_ != nil) {
7998 CGRect rect;
7999 rect.size = [(UIImage *) icon_ size];
8000
8001 while (rect.size.width > 32 || rect.size.height > 32) {
8002 rect.size.width /= 2;
8003 rect.size.height /= 2;
8004 }
8005
86a333c6
JF
8006 rect.origin.x = 26 - rect.size.width / 2;
8007 rect.origin.y = 26 - rect.size.height / 2;
25c1dafb
JF
8008
8009 [icon_ drawInRect:rect];
8010 }
36bb2ca2 8011
5d0438dc 8012 if (highlighted && kCFCoreFoundationVersionNumber < 800)
5829aea2 8013 UISetColor(White_);
dc5812ec 8014
5829aea2
GP
8015 if (!highlighted)
8016 UISetColor(Black_);
b129e6d9 8017 [origin_ drawAtPoint:CGPointMake(52, 8) forWidth:(width - 61) withFont:Font18Bold_ lineBreakMode:NSLineBreakByTruncatingTail];
f79a4512 8018
5829aea2 8019 if (!highlighted)
e93fd095 8020 UISetColor(Gray_);
b129e6d9 8021 [label_ drawAtPoint:CGPointMake(52, 29) forWidth:(width - 61) withFont:Font12_ lineBreakMode:NSLineBreakByTruncatingTail];
5829aea2 8022}
dc5812ec 8023
21ac0ce2
JF
8024- (void) setFetch:(NSNumber *)fetch {
8025 if ([fetch boolValue])
8026 [indicator_ startAnimating];
8027 else
8028 [indicator_ stopAnimating];
8029}
8030
3f8edf70
GP
8031@end
8032/* }}} */
8033/* Sources Controller {{{ */
cd79e8cf 8034@interface SourcesController : CyteViewController <
5829aea2
GP
8035 UITableViewDataSource,
8036 UITableViewDelegate
8037> {
8038 _transient Database *database_;
c33064f1
JF
8039 unsigned era_;
8040
bf7c998c 8041 _H<UITableView, 2> list_;
7b33d201 8042 _H<NSMutableArray> sources_;
5829aea2 8043 int offset_;
723a0072 8044
7b33d201
JF
8045 _H<NSString> href_;
8046 _H<UIProgressHUD> hud_;
8047 _H<NSError> error_;
dc63e78f 8048
5829aea2
GP
8049 NSURLConnection *trivial_bz2_;
8050 NSURLConnection *trivial_gz_;
b4d89997 8051
5829aea2
GP
8052 BOOL cydia_;
8053}
dc5812ec 8054
5829aea2 8055- (id) initWithDatabase:(Database *)database;
31eedaae 8056- (void) updateButtonsForEditingStatusAnimated:(BOOL)animated;
5829aea2
GP
8057
8058@end
8059
8060@implementation SourcesController
8061
8062- (void) _releaseConnection:(NSURLConnection *)connection {
8063 if (connection != nil) {
8064 [connection cancel];
8065 //[connection setDelegate:nil];
8066 [connection release];
36bb2ca2 8067 }
5829aea2 8068}
dc5812ec 8069
5829aea2 8070- (void) dealloc {
5829aea2
GP
8071 [self _releaseConnection:trivial_gz_];
8072 [self _releaseConnection:trivial_bz2_];
3178d79b 8073
5829aea2
GP
8074 [super dealloc];
8075}
b5e7eebb 8076
fe8e721f
GP
8077- (NSURL *) navigationURL {
8078 return [NSURL URLWithString:@"cydia://sources"];
8079}
8080
5829aea2
GP
8081- (void) viewDidAppear:(BOOL)animated {
8082 [super viewDidAppear:animated];
8083 [list_ deselectRowAtIndexPath:[list_ indexPathForSelectedRow] animated:animated];
8084}
9bb3b295 8085
5829aea2 8086- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
55066b9e 8087 return 2;
5829aea2 8088}
8fe19fc1 8089
5829aea2 8090- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
55066b9e
JF
8091 if (section == 1)
8092 return UCLocalize("INDIVIDUAL_SOURCES");
90ba4f86 8093 return nil;
36bb2ca2 8094}
b4d89997 8095
5829aea2 8096- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
55066b9e
JF
8097 switch (section) {
8098 case 0: return 1;
8099 case 1: return [sources_ count];
8100 default: return 0;
8101 }
5829aea2 8102}
3178d79b 8103
5829aea2 8104- (Source *) sourceAtIndexPath:(NSIndexPath *)indexPath {
c33064f1
JF
8105@synchronized (database_) {
8106 if ([database_ era] != era_)
8107 return nil;
55066b9e
JF
8108 if ([indexPath section] != 1)
8109 return nil;
8dc0d35d 8110 NSUInteger index([indexPath row]);
55066b9e
JF
8111 if (index >= [sources_ count])
8112 return nil;
8113 return [sources_ objectAtIndex:index];
c33064f1 8114} }
b4d89997 8115
5829aea2
GP
8116- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
8117 static NSString *cellIdentifier = @"SourceCell";
8118
8119 SourceCell *cell = (SourceCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
55066b9e 8120 if (cell == nil) cell = [[[SourceCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier] autorelease];
b89fa270 8121 [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
5829aea2 8122
55066b9e
JF
8123 Source *source([self sourceAtIndexPath:indexPath]);
8124 if (source == nil)
8125 [cell setAllSource];
8126 else
8127 [cell setSource:source];
8128
5829aea2 8129 return cell;
f6e13561
GP
8130}
8131
5829aea2 8132- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
55066b9e 8133 SectionsController *controller([[[SectionsController alloc]
5829aea2 8134 initWithDatabase:database_
55066b9e
JF
8135 source:[self sourceAtIndexPath:indexPath]
8136 ] autorelease]);
5829aea2 8137
3f8edf70 8138 [controller setDelegate:delegate_];
3f8edf70 8139 [[self navigationController] pushViewController:controller animated:YES];
36bb2ca2 8140}
b4d89997 8141
6840bff3 8142- (BOOL) tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
55066b9e
JF
8143 if ([indexPath section] != 1)
8144 return false;
5829aea2
GP
8145 Source *source = [self sourceAtIndexPath:indexPath];
8146 return [source record] != nil;
dcb47737
RP
8147}
8148
6840bff3 8149- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
55066b9e 8150 _assert([indexPath section] == 1);
0e1aa02c
JF
8151 if (editingStyle == UITableViewCellEditingStyleDelete) {
8152 Source *source = [self sourceAtIndexPath:indexPath];
8dc0d35d
JF
8153 if (source == nil) return;
8154
0e1aa02c 8155 [Sources_ removeObjectForKey:[source key]];
c38cbbec
JF
8156 Changed_ = true;
8157
392ff7e4 8158 [delegate_ _saveConfig];
cc3b7d31 8159 [delegate_ reloadDataWithInvocation:nil];
0e1aa02c 8160 }
5829aea2 8161}
bcccf498 8162
51807490
JF
8163- (void) tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
8164 [self updateButtonsForEditingStatusAnimated:YES];
8165}
8166
5829aea2 8167- (void) complete {
93460555 8168 [delegate_ addTrivialSource:href_];
da3ec19c
JF
8169 href_ = nil;
8170
5829aea2 8171 [delegate_ syncData];
3178d79b
JF
8172}
8173
5829aea2
GP
8174- (NSString *) getWarning {
8175 NSString *href(href_);
8176 NSRange colon([href rangeOfString:@"://"]);
8177 if (colon.location != NSNotFound)
8178 href = [href substringFromIndex:(colon.location + 3)];
8179 href = [href stringByAddingPercentEscapes];
8180 href = [CydiaURL(@"api/repotag/") stringByAppendingString:href];
5829aea2
GP
8181
8182 NSURL *url([NSURL URLWithString:href]);
8183
8184 NSStringEncoding encoding;
8185 NSError *error(nil);
8186
8187 if (NSString *warning = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error])
8188 return [warning length] == 0 ? nil : warning;
8189 return nil;
807ae6d7 8190}
b4d89997 8191
5829aea2
GP
8192- (void) _endConnection:(NSURLConnection *)connection {
8193 // XXX: the memory management in this method is horribly awkward
8194
8195 NSURLConnection **field = NULL;
44990507 8196 if (connection == trivial_bz2_)
5829aea2
GP
8197 field = &trivial_bz2_;
8198 else if (connection == trivial_gz_)
8199 field = &trivial_gz_;
8200 _assert(field != NULL);
8201 [connection release];
8202 *field = nil;
8203
8204 if (
5829aea2
GP
8205 trivial_bz2_ == nil &&
8206 trivial_gz_ == nil
8207 ) {
da3ec19c
JF
8208 NSString *warning(cydia_ ? [self yieldToSelector:@selector(getWarning)] : nil);
8209
9eae15b8
JF
8210 [delegate_ releaseNetworkActivityIndicator];
8211
8212 [delegate_ removeProgressHUD:hud_];
9eae15b8
JF
8213 hud_ = nil;
8214
5829aea2 8215 if (cydia_) {
da3ec19c 8216 if (warning != nil) {
5829aea2
GP
8217 UIAlertView *alert = [[[UIAlertView alloc]
8218 initWithTitle:UCLocalize("SOURCE_WARNING")
8219 message:warning
8220 delegate:self
8221 cancelButtonTitle:UCLocalize("CANCEL")
1aa29546
JF
8222 otherButtonTitles:
8223 UCLocalize("ADD_ANYWAY"),
8224 nil
5829aea2
GP
8225 ] autorelease];
8226
8227 [alert setContext:@"warning"];
8228 [alert setNumberOfRows:1];
8229 [alert show];
708cf61e
JF
8230
8231 // XXX: there used to be this great mechanism called yieldToPopup... who deleted it?
8232 error_ = nil;
8233 return;
8234 }
8235
8236 [self complete];
5829aea2
GP
8237 } else if (error_ != nil) {
8238 UIAlertView *alert = [[[UIAlertView alloc]
8239 initWithTitle:UCLocalize("VERIFICATION_ERROR")
8240 message:[error_ localizedDescription]
8241 delegate:self
8242 cancelButtonTitle:UCLocalize("OK")
8243 otherButtonTitles:nil
8244 ] autorelease];
8245
8246 [alert setContext:@"urlerror"];
8247 [alert show];
da3ec19c
JF
8248
8249 href_ = nil;
5829aea2
GP
8250 } else {
8251 UIAlertView *alert = [[[UIAlertView alloc]
8252 initWithTitle:UCLocalize("NOT_REPOSITORY")
8253 message:UCLocalize("NOT_REPOSITORY_EX")
8254 delegate:self
8255 cancelButtonTitle:UCLocalize("OK")
8256 otherButtonTitles:nil
8257 ] autorelease];
8258
8259 [alert setContext:@"trivial"];
8260 [alert show];
da3ec19c
JF
8261
8262 href_ = nil;
5829aea2
GP
8263 }
8264
7b33d201 8265 error_ = nil;
5829aea2 8266 }
807ae6d7 8267}
8fe19fc1 8268
5829aea2
GP
8269- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
8270 switch ([response statusCode]) {
8271 case 200:
8272 cydia_ = YES;
8273 }
8e05f686
RP
8274}
8275
5829aea2 8276- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
47ff9ab8 8277 lprintf("connection:\"%s\" didFailWithError:\"%s\"\n", [href_ UTF8String], [[error localizedDescription] UTF8String]);
7b33d201 8278 error_ = error;
5829aea2 8279 [self _endConnection:connection];
807ae6d7 8280}
b4d89997 8281
5829aea2
GP
8282- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
8283 [self _endConnection:connection];
8284}
b4d89997 8285
5829aea2 8286- (NSURLConnection *) _requestHRef:(NSString *)href method:(NSString *)method {
2e1652a9
JF
8287 NSURL *url([NSURL URLWithString:href]);
8288
5829aea2 8289 NSMutableURLRequest *request = [NSMutableURLRequest
2e1652a9 8290 requestWithURL:url
5829aea2 8291 cachePolicy:NSURLRequestUseProtocolCachePolicy
dd6f3b7b 8292 timeoutInterval:10
5829aea2 8293 ];
bc11cf5b 8294
5829aea2 8295 [request setHTTPMethod:method];
b4d89997 8296
5829aea2
GP
8297 if (Machine_ != NULL)
8298 [request setValue:[NSString stringWithUTF8String:Machine_] forHTTPHeaderField:@"X-Machine"];
2e1652a9 8299
1209b7de
JF
8300 if (UniqueID_ != nil)
8301 [request setValue:UniqueID_ forHTTPHeaderField:@"X-Unique-ID"];
8302
2e1652a9 8303 if ([url isCydiaSecure]) {
1209b7de 8304 if (UniqueID_ != nil)
c83678e8 8305 [request setValue:UniqueID_ forHTTPHeaderField:@"X-Cydia-Id"];
2e1652a9 8306 }
b4d89997 8307
5829aea2 8308 return [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
3178d79b
JF
8309}
8310
6840bff3 8311- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
5829aea2 8312 NSString *context([alert context]);
dc5812ec 8313
5829aea2
GP
8314 if ([context isEqualToString:@"source"]) {
8315 switch (button) {
8316 case 1: {
8317 NSString *href = [[alert textField] text];
baf80942 8318
2595e4c3
JF
8319 static Pcre href_r("^http(s?)://[^# ]*$");
8320 if (!href_r(href)) {
8321 UIAlertView *alert = [[[UIAlertView alloc]
8322 initWithTitle:Error_
8323 message:UCLocalize("INVALID_URL")
8324 delegate:self
8325 cancelButtonTitle:UCLocalize("OK")
8326 otherButtonTitles:nil
8327 ] autorelease];
8328
8329 [alert setContext:@"badurl"];
8330 [alert show];
8331
8332 break;
8333 }
8334
5829aea2
GP
8335 if (![href hasSuffix:@"/"])
8336 href_ = [href stringByAppendingString:@"/"];
8337 else
8338 href_ = href;
b4d89997 8339
5829aea2
GP
8340 trivial_bz2_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.bz2"] method:@"HEAD"] retain];
8341 trivial_gz_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.gz"] method:@"HEAD"] retain];
b4d89997 8342
5829aea2 8343 cydia_ = false;
8fe19fc1 8344
5829aea2 8345 // XXX: this is stupid
7b33d201 8346 hud_ = [delegate_ addProgressHUD];
5829aea2 8347 [hud_ setText:UCLocalize("VERIFYING_URL")];
dcaecde2 8348 [delegate_ retainNetworkActivityIndicator];
5829aea2 8349 } break;
770f2a8e 8350
5829aea2
GP
8351 case 0:
8352 break;
bc11cf5b 8353
5829aea2
GP
8354 _nodefault
8355 }
770f2a8e 8356
5829aea2
GP
8357 [alert dismissWithClickedButtonIndex:-1 animated:YES];
8358 } else if ([context isEqualToString:@"trivial"])
8359 [alert dismissWithClickedButtonIndex:-1 animated:YES];
8360 else if ([context isEqualToString:@"urlerror"])
8361 [alert dismissWithClickedButtonIndex:-1 animated:YES];
8362 else if ([context isEqualToString:@"warning"]) {
8363 switch (button) {
8364 case 1:
da3ec19c 8365 [self performSelector:@selector(complete) withObject:nil afterDelay:0];
5829aea2 8366 break;
770f2a8e 8367
5829aea2
GP
8368 case 0:
8369 break;
b5e7eebb 8370
5829aea2
GP
8371 _nodefault
8372 }
770f2a8e 8373
5829aea2
GP
8374 [alert dismissWithClickedButtonIndex:-1 animated:YES];
8375 }
8376}
770f2a8e 8377
e67ebdad
JF
8378- (void) updateButtonsForEditingStatusAnimated:(BOOL)animated {
8379 BOOL editing([list_ isEditing]);
8380
8381 if (editing)
8382 [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
8383 initWithTitle:UCLocalize("ADD")
8384 style:UIBarButtonItemStylePlain
8385 target:self
8386 action:@selector(addButtonClicked)
8387 ] autorelease] animated:animated];
8388 else if ([delegate_ updating])
8389 [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
8390 initWithTitle:UCLocalize("CANCEL")
8391 style:UIBarButtonItemStyleDone
8392 target:self
8393 action:@selector(cancelButtonClicked)
8394 ] autorelease] animated:animated];
8395 else
8396 [[self navigationItem] setLeftBarButtonItem:[[[UIBarButtonItem alloc]
8397 initWithTitle:UCLocalize("REFRESH")
8398 style:UIBarButtonItemStylePlain
8399 target:self
8400 action:@selector(refreshButtonClicked)
8401 ] autorelease] animated:animated];
8402
8403 [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc]
8404 initWithTitle:(editing ? UCLocalize("DONE") : UCLocalize("EDIT"))
8405 style:(editing ? UIBarButtonItemStyleDone : UIBarButtonItemStylePlain)
8406 target:self
8407 action:@selector(editButtonClicked)
8408 ] autorelease] animated:animated];
8409}
8410
fe8e721f 8411- (void) loadView {
e8cbebe4 8412 list_ = [[[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain] autorelease];
fe8e721f 8413 [list_ setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
6ac6e186 8414 [list_ setRowHeight:53];
7b33d201 8415 [(UITableView *) list_ setDataSource:self];
fe8e721f 8416 [list_ setDelegate:self];
e8cbebe4 8417 [self setView:list_];
fe8e721f 8418}
770f2a8e 8419
fe8e721f 8420- (void) viewDidLoad {
7d887d0b
JF
8421 [super viewDidLoad];
8422
fe8e721f 8423 [[self navigationItem] setTitle:UCLocalize("SOURCES")];
31eedaae
JF
8424 [self updateButtonsForEditingStatusAnimated:NO];
8425}
8426
393699d7 8427- (void) viewWillAppear:(BOOL)animated {
31eedaae
JF
8428 [super viewWillAppear:animated];
8429
8430 [list_ setEditing:NO];
8431 [self updateButtonsForEditingStatusAnimated:NO];
fe8e721f 8432}
770f2a8e 8433
fe8e721f 8434- (void) releaseSubviews {
fe8e721f 8435 list_ = nil;
7be3eb32 8436
4f9acb7c
JF
8437 sources_ = nil;
8438
7be3eb32 8439 [super releaseSubviews];
fe8e721f 8440}
770f2a8e 8441
fe8e721f
GP
8442- (id) initWithDatabase:(Database *)database {
8443 if ((self = [super init]) != nil) {
8444 database_ = database;
807ae6d7
JF
8445 } return self;
8446}
770f2a8e 8447
807ae6d7 8448- (void) reloadData {
fe8e721f 8449 [super reloadData];
e67ebdad 8450 [self updateButtonsForEditingStatusAnimated:YES];
fe8e721f 8451
c33064f1
JF
8452@synchronized (database_) {
8453 era_ = [database_ era];
8454
4f9acb7c 8455 sources_ = [NSMutableArray arrayWithCapacity:16];
5829aea2
GP
8456 [sources_ addObjectsFromArray:[database_ sources]];
8457 _trace();
90ba4f86 8458 [sources_ sortUsingSelector:@selector(compareByName:)];
5829aea2
GP
8459 _trace();
8460
8461 int count([sources_ count]);
8462 offset_ = 0;
8463 for (int i = 0; i != count; i++) {
8464 if ([[sources_ objectAtIndex:i] record] == nil)
8465 break;
8466 offset_++;
770f2a8e 8467 }
807ae6d7 8468
5829aea2 8469 [list_ reloadData];
c33064f1 8470} }
770f2a8e 8471
5829aea2
GP
8472- (void) showAddSourcePrompt {
8473 UIAlertView *alert = [[[UIAlertView alloc]
8474 initWithTitle:UCLocalize("ENTER_APT_URL")
8475 message:nil
8476 delegate:self
8477 cancelButtonTitle:UCLocalize("CANCEL")
1aa29546
JF
8478 otherButtonTitles:
8479 UCLocalize("ADD_SOURCE"),
8480 nil
5829aea2 8481 ] autorelease];
770f2a8e 8482
5829aea2 8483 [alert setContext:@"source"];
770f2a8e 8484
5829aea2
GP
8485 [alert setNumberOfRows:1];
8486 [alert addTextFieldWithValue:@"http://" label:@""];
770f2a8e 8487
5829aea2
GP
8488 UITextInputTraits *traits = [[alert textField] textInputTraits];
8489 [traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
8490 [traits setAutocorrectionType:UITextAutocorrectionTypeNo];
8491 [traits setKeyboardType:UIKeyboardTypeURL];
8492 // XXX: UIReturnKeyDone
8493 [traits setReturnKeyType:UIReturnKeyNext];
770f2a8e 8494
5829aea2 8495 [alert show];
770f2a8e
JF
8496}
8497
5829aea2
GP
8498- (void) addButtonClicked {
8499 [self showAddSourcePrompt];
770f2a8e
JF
8500}
8501
e67ebdad
JF
8502- (void) refreshButtonClicked {
8503 if ([delegate_ requestUpdate])
8504 [self updateButtonsForEditingStatusAnimated:YES];
8505}
5829aea2 8506
e67ebdad
JF
8507- (void) cancelButtonClicked {
8508 [delegate_ cancelUpdate];
5829aea2
GP
8509}
8510
8511- (void) editButtonClicked {
8512 [list_ setEditing:![list_ isEditing] animated:YES];
31eedaae 8513 [self updateButtonsForEditingStatusAnimated:YES];
770f2a8e
JF
8514}
8515
21c6da4b
GP
8516@end
8517/* }}} */
f3e11d24 8518
f3e11d24 8519/* Stash Controller {{{ */
cd79e8cf 8520@interface StashController : CyteViewController {
7b33d201
JF
8521 _H<UIActivityIndicatorView> spinner_;
8522 _H<UILabel> status_;
8523 _H<UILabel> caption_;
f3e11d24 8524}
6840bff3 8525
f3e11d24
GP
8526@end
8527
5829aea2 8528@implementation StashController
f3e11d24 8529
fe8e721f 8530- (void) loadView {
39470a3a
JF
8531 UIView *view([[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease]);
8532 [view setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
8533 [self setView:view];
8534
8535 [view setBackgroundColor:[UIColor viewFlipsideBackgroundColor]];
fe8e721f 8536
7b33d201 8537 spinner_ = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
fe8e721f
GP
8538 CGRect spinrect = [spinner_ frame];
8539 spinrect.origin.x = ([[self view] frame].size.width / 2) - (spinrect.size.width / 2);
8540 spinrect.origin.y = [[self view] frame].size.height - 80.0f;
8541 [spinner_ setFrame:spinrect];
8542 [spinner_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
39470a3a 8543 [view addSubview:spinner_];
fe8e721f
GP
8544 [spinner_ startAnimating];
8545
8546 CGRect captrect;
8547 captrect.size.width = [[self view] frame].size.width;
8548 captrect.size.height = 40.0f;
8549 captrect.origin.x = 0;
8550 captrect.origin.y = ([[self view] frame].size.height / 2) - (captrect.size.height * 2);
7b33d201 8551 caption_ = [[[UILabel alloc] initWithFrame:captrect] autorelease];
fe8e721f
GP
8552 [caption_ setText:UCLocalize("PREPARING_FILESYSTEM")];
8553 [caption_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
8554 [caption_ setFont:[UIFont boldSystemFontOfSize:28.0f]];
8555 [caption_ setTextColor:[UIColor whiteColor]];
8556 [caption_ setBackgroundColor:[UIColor clearColor]];
8557 [caption_ setShadowColor:[UIColor blackColor]];
b129e6d9 8558 [caption_ setTextAlignment:NSTextAlignmentCenter];
39470a3a 8559 [view addSubview:caption_];
fe8e721f
GP
8560
8561 CGRect statusrect;
8562 statusrect.size.width = [[self view] frame].size.width;
8563 statusrect.size.height = 30.0f;
8564 statusrect.origin.x = 0;
8565 statusrect.origin.y = ([[self view] frame].size.height / 2) - statusrect.size.height;
7b33d201 8566 status_ = [[[UILabel alloc] initWithFrame:statusrect] autorelease];
fe8e721f
GP
8567 [status_ setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
8568 [status_ setText:UCLocalize("EXIT_WHEN_COMPLETE")];
8569 [status_ setFont:[UIFont systemFontOfSize:16.0f]];
8570 [status_ setTextColor:[UIColor whiteColor]];
8571 [status_ setBackgroundColor:[UIColor clearColor]];
8572 [status_ setShadowColor:[UIColor blackColor]];
b129e6d9 8573 [status_ setTextAlignment:NSTextAlignmentCenter];
39470a3a 8574 [view addSubview:status_];
fe8e721f
GP
8575}
8576
67dd58c7
JF
8577- (void) releaseSubviews {
8578 spinner_ = nil;
8579 status_ = nil;
8580 caption_ = nil;
7be3eb32
JF
8581
8582 [super releaseSubviews];
67dd58c7
JF
8583}
8584
770f2a8e 8585@end
807ae6d7 8586/* }}} */
770f2a8e 8587
a06e9179
JF
8588@interface CYURLCache : SDURLCache {
8589}
8590
8591@end
8592
8593@implementation CYURLCache
8594
8595- (void) logEvent:(NSString *)event forRequest:(NSURLRequest *)request {
8596#if !ForRelease
8597 if (false);
8598 else if ([event isEqualToString:@"no-cache"])
8599 event = @"!!!";
8600 else if ([event isEqualToString:@"store"])
8601 event = @">>>";
8602 else if ([event isEqualToString:@"invalid"])
8603 event = @"???";
8604 else if ([event isEqualToString:@"memory"])
8605 event = @"mem";
8606 else if ([event isEqualToString:@"disk"])
8607 event = @"ssd";
8608 else if ([event isEqualToString:@"miss"])
8609 event = @"---";
8610
8611 NSLog(@"%@: %@", event, [[request URL] absoluteString]);
8612#endif
8613}
8614
5e845121
JF
8615- (void) storeCachedResponse:(NSCachedURLResponse *)cached forRequest:(NSURLRequest *)request {
8616 if (NSURLResponse *response = [cached response])
8617 if (NSString *mime = [response MIMEType])
8618 if ([mime isEqualToString:@"text/cache-manifest"]) {
8619 NSURL *url([response URL]);
8620
8621#if !ForRelease
8622 NSLog(@"###: %@", [url absoluteString]);
8623#endif
8624
8625 @synchronized (HostConfig_) {
8626 [CachedURLs_ addObject:url];
8627 }
8628 }
8629
8630 [super storeCachedResponse:cached forRequest:request];
8631}
8632
a06e9179
JF
8633@end
8634
2367a917 8635@interface Cydia : UIApplication <
adb61bda 8636 ConfirmationControllerDelegate,
6915b806 8637 DatabaseDelegate,
eb30da80 8638 CydiaDelegate,
68046ccc
JF
8639 UINavigationControllerDelegate,
8640 UITabBarControllerDelegate
2367a917 8641> {
7b33d201 8642 _H<UIWindow> window_;
5fe2bcc6 8643 _H<CydiaTabBarController> tabbar_;
70750ab3 8644 _H<CydiaLoadingViewController> emulated_;
3931b718 8645
7b33d201
JF
8646 _H<NSMutableArray> essential_;
8647 _H<NSMutableArray> broken_;
dc5812ec
JF
8648
8649 Database *database_;
dc5812ec 8650
7b33d201 8651 _H<NSURL> starturl_;
54043703 8652
235f5487 8653 unsigned locked_;
54043703 8654 unsigned activity_;
9b619239 8655
7b33d201 8656 _H<StashController> stash_;
f3e11d24 8657
8c02abc8 8658 bool loaded_;
dc5812ec
JF
8659}
8660
fed0d010 8661- (void) loadData;
670a0494 8662
dc5812ec
JF
8663@end
8664
8665@implementation Cydia
8666
2ef6faad 8667- (void) lockSuspend {
8a3b565c
JF
8668 if (locked_++ == 0) {
8669 if ($SBSSetInterceptsMenuButtonForever != NULL)
8670 (*$SBSSetInterceptsMenuButtonForever)(true);
26c8a4c8
JF
8671
8672 [self setIdleTimerDisabled:YES];
8a3b565c 8673 }
2ef6faad
JF
8674}
8675
8676- (void) unlockSuspend {
8a3b565c 8677 if (--locked_ == 0) {
26c8a4c8
JF
8678 [self setIdleTimerDisabled:NO];
8679
8a3b565c
JF
8680 if ($SBSSetInterceptsMenuButtonForever != NULL)
8681 (*$SBSSetInterceptsMenuButtonForever)(false);
8682 }
2ef6faad
JF
8683}
8684
b5e7eebb 8685- (void) beginUpdate {
7585ce66 8686 [tabbar_ beginUpdate];
b5e7eebb
GP
8687}
8688
e67ebdad
JF
8689- (void) cancelUpdate {
8690 [tabbar_ cancelUpdate];
8691}
8692
8693- (bool) requestUpdate {
8694 if (IsReachable("cydia.saurik.com")) {
8695 [self beginUpdate];
8696 return true;
8697 } else {
8698 UIAlertView *alert = [[[UIAlertView alloc]
8699 initWithTitle:[NSString stringWithFormat:Colon_, Error_, UCLocalize("REFRESH")]
8700 message:@"Host Unreachable" // XXX: Localize
8701 delegate:self
8702 cancelButtonTitle:UCLocalize("OK")
8703 otherButtonTitles:nil
8704 ] autorelease];
8705
8706 [alert setContext:@"norefresh"];
8707 [alert show];
8708
8709 return false;
8710 }
8711}
8712
b5e7eebb 8713- (BOOL) updating {
7585ce66 8714 return [tabbar_ updating];
b5e7eebb
GP
8715}
8716
9bedffaa
JF
8717- (void) _loaded {
8718 if ([broken_ count] != 0) {
8719 int count = [broken_ count];
8720
37d2b2a9 8721 UIAlertView *alert = [[[UIAlertView alloc]
43f3d7f6 8722 initWithTitle:(count == 1 ? UCLocalize("HALFINSTALLED_PACKAGE") : [NSString stringWithFormat:UCLocalize("HALFINSTALLED_PACKAGES"), count])
b5e7eebb
GP
8723 message:UCLocalize("HALFINSTALLED_PACKAGE_EX")
8724 delegate:self
1dc38e9c 8725 cancelButtonTitle:[NSString stringWithFormat:UCLocalize("PARENTHETICAL"), UCLocalize("FORCIBLY_CLEAR"), UCLocalize("UNSAFE")]
1aa29546
JF
8726 otherButtonTitles:
8727 UCLocalize("TEMPORARY_IGNORE"),
8728 nil
9bedffaa
JF
8729 ] autorelease];
8730
37d2b2a9 8731 [alert setContext:@"fixhalf"];
59befad5 8732 [alert setNumberOfRows:2];
37d2b2a9 8733 [alert show];
9bedffaa
JF
8734 } else if (!Ignored_ && [essential_ count] != 0) {
8735 int count = [essential_ count];
8736
37d2b2a9 8737 UIAlertView *alert = [[[UIAlertView alloc]
43f3d7f6 8738 initWithTitle:(count == 1 ? UCLocalize("ESSENTIAL_UPGRADE") : [NSString stringWithFormat:UCLocalize("ESSENTIAL_UPGRADES"), count])
b5e7eebb
GP
8739 message:UCLocalize("ESSENTIAL_UPGRADE_EX")
8740 delegate:self
8741 cancelButtonTitle:UCLocalize("TEMPORARY_IGNORE")
1aa29546
JF
8742 otherButtonTitles:
8743 UCLocalize("UPGRADE_ESSENTIAL"),
8744 UCLocalize("COMPLETE_UPGRADE"),
8745 nil
9bedffaa
JF
8746 ] autorelease];
8747
37d2b2a9
GP
8748 [alert setContext:@"upgrade"];
8749 [alert show];
9bedffaa
JF
8750 }
8751}
8752
2925cbba
JF
8753- (void) returnToCydia {
8754 [self _loaded];
8755}
8756
7623f855 8757- (void) _saveConfig {
9ac1ef9e
JF
8758 @synchronized (database_) {
8759 _trace();
8760 MetaFile_.Sync();
8761 _trace();
8762 }
ffbb3bd5 8763
7623f855 8764 if (Changed_) {
7623f855 8765 NSString *error(nil);
ffbb3bd5 8766
7623f855
JF
8767 if (NSData *data = [NSPropertyListSerialization dataFromPropertyList:Metadata_ format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error]) {
8768 _trace();
8769 NSError *error(nil);
8770 if (![data writeToFile:@"/var/lib/cydia/metadata.plist" options:NSAtomicWrite error:&error])
8771 NSLog(@"failure to save metadata data: %@", error);
8772 _trace();
ffbb3bd5
JF
8773
8774 Changed_ = false;
7623f855
JF
8775 } else {
8776 NSLog(@"failure to serialize metadata: %@", error);
7623f855 8777 }
7623f855 8778 }
33e30380 8779
25c1dafb 8780 CydiaWriteSources();
7623f855
JF
8781}
8782
89571a5b 8783// Navigation controller for the queuing badge.
15f0d613 8784- (UINavigationController *) queueNavigationController {
89571a5b
GP
8785 NSArray *controllers = [tabbar_ viewControllers];
8786 return [controllers objectAtIndex:3];
8787}
8788
302bf91c
JF
8789- (void) unloadData {
8790 [tabbar_ unloadData];
8791}
8792
7623f855
JF
8793- (void) _updateData {
8794 [self _saveConfig];
302bf91c 8795 [self unloadData];
f6371a33 8796
15f0d613 8797 UINavigationController *navigation = [self queueNavigationController];
b5e7eebb 8798
4305896c 8799 id queuedelegate = nil;
89571a5b
GP
8800 if ([[navigation viewControllers] count] > 0)
8801 queuedelegate = [[navigation viewControllers] objectAtIndex:0];
bc11cf5b 8802
89571a5b
GP
8803 [queuedelegate queueStatusDidChange];
8804 [[navigation tabBarItem] setBadgeValue:(Queuing_ ? UCLocalize("Q_D") : nil)];
7623f855
JF
8805}
8806
53fb38da 8807- (void) _refreshIfPossible:(NSDate *)update {
8c02abc8 8808 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
bc11cf5b 8809
45e21ffa 8810 bool recently = false;
45e21ffa
GP
8811 if (update != nil) {
8812 NSTimeInterval interval([update timeIntervalSinceNow]);
8813 if (interval <= 0 && interval > -(15*60))
8814 recently = true;
8815 }
8816
8817 // Don't automatic refresh if:
8818 // - We already refreshed recently.
8819 // - We already auto-refreshed this launch.
8820 // - Auto-refresh is disabled.
8d3505c5
JF
8821 // - Cydia's server is not reachable
8822 if (recently || loaded_ || ManualRefresh || !IsReachable("cydia.saurik.com")) {
35f0a3b5 8823 // If we are cancelling, we need to make sure it knows it's already loaded.
45e21ffa 8824 loaded_ = true;
d13edf44
JF
8825
8826 [self performSelectorOnMainThread:@selector(_loaded) withObject:nil waitUntilDone:NO];
45e21ffa
GP
8827 } else {
8828 // We are going to load, so remember that.
8829 loaded_ = true;
45e21ffa 8830
8d3505c5 8831 [tabbar_ performSelectorOnMainThread:@selector(setUpdate:) withObject:update waitUntilDone:NO];
d13edf44 8832 }
9aecdc9c 8833
8c02abc8 8834 [pool release];
9aecdc9c
GP
8835}
8836
8837- (void) refreshIfPossible {
53fb38da 8838 [NSThread detachNewThreadSelector:@selector(_refreshIfPossible:) toTarget:self withObject:[Metadata_ objectForKey:@"LastUpdate"]];
9aecdc9c
GP
8839}
8840
e09e1589
JF
8841- (void) reloadDataWithInvocation:(NSInvocation *)invocation {
8842@synchronized (self) {
851f4a99 8843 UIProgressHUD *hud(loaded_ ? [self addProgressHUD] : nil);
1450c2b3
JF
8844 if (hud != nil)
8845 [hud setText:UCLocalize("RELOADING_DATA")];
dc5812ec 8846
4ba8f30a 8847 [database_ yieldToSelector:@selector(reloadDataWithInvocation:) withObject:invocation];
d061f4ba 8848
36bb2ca2 8849 size_t changes(0);
9bedffaa 8850
a54b1c10 8851 [essential_ removeAllObjects];
9bedffaa 8852 [broken_ removeAllObjects];
b4d89997 8853
670a0494 8854 NSArray *packages([database_ packages]);
affeffc7 8855 for (Package *package in packages) {
9bedffaa
JF
8856 if ([package half])
8857 [broken_ addObject:package];
823e2d97
JF
8858 if ([package upgradableAndEssential:YES] && ![package ignored]) {
8859 if ([package essential] && [package installed] != nil)
a54b1c10 8860 [essential_ addObject:package];
36bb2ca2 8861 ++changes;
a54b1c10 8862 }
36bb2ca2 8863 }
b4d89997 8864
89571a5b 8865 UITabBarItem *changesItem = [[[tabbar_ viewControllers] objectAtIndex:2] tabBarItem];
36bb2ca2 8866 if (changes != 0) {
0e1784b4 8867 _trace();
36bb2ca2 8868 NSString *badge([[NSNumber numberWithInt:changes] stringValue]);
45e21ffa 8869 [changesItem setBadgeValue:badge];
65a03a7a 8870 [changesItem setAnimatedBadge:([essential_ count] > 0)];
0e1784b4 8871 [self setApplicationIconBadgeNumber:changes];
c25a610d 8872 } else {
0e1784b4 8873 _trace();
45e21ffa
GP
8874 [changesItem setBadgeValue:nil];
8875 [changesItem setAnimatedBadge:NO];
0e1784b4 8876 [self setApplicationIconBadgeNumber:0];
c25a610d 8877 }
b4d89997 8878
7623f855 8879 [self _updateData];
be64dfbf
JF
8880
8881 if (hud != nil)
8882 [self removeProgressHUD:hud];
e09e1589 8883} }
6d9712c4 8884
7b0ce2da 8885- (void) updateData {
7623f855 8886 [self _updateData];
b4d89997
JF
8887}
8888
383a58ac
JF
8889- (void) updateDataAndLoad {
8890 [self _updateData];
8891 if ([database_ progressDelegate] == nil)
8892 [self _loaded];
8893}
8894
7b0ce2da
JF
8895- (void) update_ {
8896 [database_ update];
fca2f596 8897 [self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
7b0ce2da
JF
8898}
8899
2e9123cb
JF
8900- (void) disemulate {
8901 if (emulated_ == nil)
8902 return;
8903
8904 [window_ addSubview:[tabbar_ view]];
81628115
JF
8905 if ([window_ respondsToSelector:@selector(setRootViewController:)])
8906 [window_ setRootViewController:tabbar_];
2e9123cb 8907 [[emulated_ view] removeFromSuperview];
2e9123cb
JF
8908 emulated_ = nil;
8909 [window_ setUserInteractionEnabled:YES];
8910}
8911
8912- (void) presentModalViewController:(UIViewController *)controller force:(BOOL)force {
15f0d613 8913 UINavigationController *navigation([[[UINavigationController alloc] initWithRootViewController:controller] autorelease]);
6915b806
JF
8914 if (IsWildcat_)
8915 [navigation setModalPresentationStyle:UIModalPresentationFormSheet];
2e9123cb
JF
8916
8917 UIViewController *parent;
8918 if (emulated_ == nil)
8919 parent = tabbar_;
8920 else if (!force)
8921 parent = emulated_;
8922 else {
8923 [self disemulate];
8924 parent = tabbar_;
8925 }
8926
8927 [parent presentModalViewController:navigation animated:YES];
6915b806
JF
8928}
8929
8930- (ProgressController *) invokeNewProgress:(NSInvocation *)invocation forController:(UINavigationController *)navigation withTitle:(NSString *)title {
8931 ProgressController *progress([[[ProgressController alloc] initWithDatabase:database_ delegate:self] autorelease]);
8932
8933 if (navigation != nil)
8934 [navigation pushViewController:progress animated:YES];
8935 else
2e9123cb 8936 [self presentModalViewController:progress force:YES];
6915b806
JF
8937
8938 [progress invoke:invocation withTitle:title];
8939 return progress;
8940}
8941
8942- (void) detachNewProgressSelector:(SEL)selector toTarget:(id)target forController:(UINavigationController *)navigation title:(NSString *)title {
8943 [self invokeNewProgress:[NSInvocation invocationWithSelector:selector forTarget:target] forController:navigation withTitle:title];
8944}
8945
8946- (void) repairWithInvocation:(NSInvocation *)invocation {
8947 _trace();
8d5bc2ad 8948 [self invokeNewProgress:invocation forController:nil withTitle:@"REPAIRING"];
6915b806
JF
8949 _trace();
8950}
8951
8952- (void) repairWithSelector:(SEL)selector {
8953 [self performSelectorOnMainThread:@selector(repairWithInvocation:) withObject:[NSInvocation invocationWithSelector:selector forTarget:database_] waitUntilDone:YES];
8954}
8955
e09e1589
JF
8956- (void) reloadData {
8957 [self reloadDataWithInvocation:nil];
2925cbba
JF
8958 if ([database_ progressDelegate] == nil)
8959 [self _loaded];
e09e1589
JF
8960}
8961
7b0ce2da 8962- (void) syncData {
1fe5dc43 8963 [self _saveConfig];
33e30380
JF
8964 [self detachNewProgressSelector:@selector(update_) toTarget:self forController:nil title:@"UPDATING_SOURCES"];
8965}
1fe5dc43 8966
33e30380 8967- (void) addSource:(NSDictionary *) source {
25c1dafb 8968 CydiaAddSource(source);
33e30380 8969}
7b0ce2da 8970
33e30380 8971- (void) addSource:(NSString *)href withDistribution:(NSString *)distribution andSections:(NSArray *)sections {
25c1dafb 8972 CydiaAddSource(href, distribution, sections);
7b0ce2da
JF
8973}
8974
93460555 8975- (void) addTrivialSource:(NSString *)href {
25c1dafb 8976 CydiaAddSource(href, @"./");
93460555
JF
8977}
8978
b3c8e69c
JF
8979- (void) updateValues {
8980 Changed_ = true;
8981}
8982
b4d89997
JF
8983- (void) resolve {
8984 pkgProblemResolver *resolver = [database_ resolver];
8985
8986 resolver->InstallProtect();
8987 if (!resolver->Resolve(true))
8988 _error->Discard();
2367a917
JF
8989}
8990
670a0494 8991- (bool) perform {
d6c371f5
JF
8992 // XXX: this is a really crappy way of doing this.
8993 // like, seriously: this state machine is still broken, and cancelling this here doesn't really /fix/ that.
8994 // for one, the user can still /start/ a reloading data event while they have a queue, which is stupid
8995 // for two, this just means there is a race condition between the refresh completing and the confirmation controller appearing.
8996 if ([tabbar_ updating])
8997 [tabbar_ cancelUpdate];
8998
670a0494
JF
8999 if (![database_ prepare])
9000 return false;
49048579 9001
adb61bda 9002 ConfirmationController *page([[[ConfirmationController alloc] initWithDatabase:database_] autorelease]);
affeffc7 9003 [page setDelegate:self];
15f0d613 9004 UINavigationController *confirm_([[[UINavigationController alloc] initWithRootViewController:page] autorelease]);
49048579 9005
36fbb2aa
JF
9006 if (IsWildcat_)
9007 [confirm_ setModalPresentationStyle:UIModalPresentationFormSheet];
7585ce66 9008 [tabbar_ presentModalViewController:confirm_ animated:YES];
670a0494
JF
9009
9010 return true;
3178d79b
JF
9011}
9012
dc63e78f
JF
9013- (void) queue {
9014 @synchronized (self) {
9015 [self perform];
9016 }
9017}
9018
9019- (void) clearPackage:(Package *)package {
9020 @synchronized (self) {
9021 [package clear];
9022 [self resolve];
9023 [self perform];
9024 }
9025}
9026
77801ff1
JF
9027- (void) installPackages:(NSArray *)packages {
9028 @synchronized (self) {
9029 for (Package *package in packages)
9030 [package install];
9031 [self resolve];
9032 [self perform];
9033 }
9034}
9035
36bb2ca2
JF
9036- (void) installPackage:(Package *)package {
9037 @synchronized (self) {
9038 [package install];
9039 [self resolve];
9040 [self perform];
9041 }
9042}
9043
9044- (void) removePackage:(Package *)package {
9045 @synchronized (self) {
9046 [package remove];
9047 [self resolve];
9048 [self perform];
9049 }
9050}
9051
9052- (void) distUpgrade {
9053 @synchronized (self) {
670a0494
JF
9054 if (![database_ upgrade])
9055 return;
36bb2ca2
JF
9056 [self perform];
9057 }
b4d89997
JF
9058}
9059
780cdb3b
JF
9060- (void) _uicache {
9061 _trace();
9062 system("su -c /usr/bin/uicache mobile");
9063 _trace();
9064}
9065
9066- (void) uicache {
9067 UIProgressHUD *hud([self addProgressHUD]);
9068 [hud setText:UCLocalize("LOADING")];
9069 [self yieldToSelector:@selector(_uicache)];
9070 [self removeProgressHUD:hud];
9071}
9072
fca2f596
JF
9073- (void) perform_ {
9074 [database_ perform];
9075 [self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
780cdb3b 9076 [self performSelectorOnMainThread:@selector(uicache) withObject:nil waitUntilDone:YES];
fca2f596
JF
9077}
9078
b5e7eebb 9079- (void) confirmWithNavigationController:(UINavigationController *)navigation {
f6371a33 9080 Queuing_ = false;
2ef6faad 9081 [self lockSuspend];
fca2f596 9082 [self detachNewProgressSelector:@selector(perform_) toTarget:self forController:navigation title:@"RUNNING"];
2ef6faad 9083 [self unlockSuspend];
dc5812ec
JF
9084}
9085
54043703
JF
9086- (void) retainNetworkActivityIndicator {
9087 if (activity_++ == 0)
9088 [self setNetworkActivityIndicatorVisible:YES];
848ed88b
JF
9089
9090#if TraceLogging
9091 NSLog(@"retainNetworkActivityIndicator->%d", activity_);
9092#endif
54043703
JF
9093}
9094
9095- (void) releaseNetworkActivityIndicator {
9096 if (--activity_ == 0)
9097 [self setNetworkActivityIndicatorVisible:NO];
848ed88b
JF
9098
9099#if TraceLogging
9100 NSLog(@"releaseNetworkActivityIndicator->%d", activity_);
9101#endif
9102
54043703
JF
9103}
9104
674dce72
GP
9105- (void) cancelAndClear:(bool)clear {
9106 @synchronized (self) {
9107 if (clear) {
c6ca67ba 9108 [database_ clear];
b5e7eebb 9109 Queuing_ = false;
674dce72 9110 } else {
b5e7eebb 9111 Queuing_ = true;
6067f1b8
JF
9112 }
9113
a4217bbb 9114 [self _updateData];
674dce72 9115 }
37d2b2a9 9116}
7b0ce2da 9117
b5e7eebb
GP
9118- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
9119 NSString *context([alert context]);
bc11cf5b 9120
6915b806
JF
9121 if ([context isEqualToString:@"conffile"]) {
9122 FILE *input = [database_ input];
9123 if (button == [alert cancelButtonIndex])
9124 fprintf(input, "N\n");
9125 else if (button == [alert firstOtherButtonIndex])
9126 fprintf(input, "Y\n");
9127 fflush(input);
9128
9129 [alert dismissWithClickedButtonIndex:-1 animated:YES];
9130 } else if ([context isEqualToString:@"fixhalf"]) {
efa53fa9 9131 if (button == [alert cancelButtonIndex]) {
37d2b2a9 9132 @synchronized (self) {
7b33d201 9133 for (Package *broken in (id) broken_) {
37d2b2a9
GP
9134 [broken remove];
9135
9136 NSString *id = [broken id];
9137 unlink([[NSString stringWithFormat:@"/var/lib/dpkg/info/%@.prerm", id] UTF8String]);
9138 unlink([[NSString stringWithFormat:@"/var/lib/dpkg/info/%@.postrm", id] UTF8String]);
9139 unlink([[NSString stringWithFormat:@"/var/lib/dpkg/info/%@.preinst", id] UTF8String]);
9140 unlink([[NSString stringWithFormat:@"/var/lib/dpkg/info/%@.postinst", id] UTF8String]);
9141 }
7b0ce2da 9142
37d2b2a9
GP
9143 [self resolve];
9144 [self perform];
9145 }
efa53fa9 9146 } else if (button == [alert firstOtherButtonIndex]) {
37d2b2a9
GP
9147 [broken_ removeAllObjects];
9148 [self _loaded];
7b0ce2da
JF
9149 }
9150
37d2b2a9 9151 [alert dismissWithClickedButtonIndex:-1 animated:YES];
1cedb821 9152 } else if ([context isEqualToString:@"upgrade"]) {
37d2b2a9
GP
9153 if (button == [alert firstOtherButtonIndex]) {
9154 @synchronized (self) {
7b33d201 9155 for (Package *essential in (id) essential_)
37d2b2a9 9156 [essential install];
7b0ce2da 9157
37d2b2a9
GP
9158 [self resolve];
9159 [self perform];
9160 }
9161 } else if (button == [alert firstOtherButtonIndex] + 1) {
9162 [self distUpgrade];
9163 } else if (button == [alert cancelButtonIndex]) {
9164 Ignored_ = YES;
7b0ce2da
JF
9165 }
9166
37d2b2a9 9167 [alert dismissWithClickedButtonIndex:-1 animated:YES];
1cedb821 9168 }
7b0ce2da
JF
9169}
9170
d13edf44
JF
9171- (void) system:(NSString *)command {
9172 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
9173
985d2dff 9174 _trace();
670a0494 9175 system([command UTF8String]);
985d2dff 9176 _trace();
d13edf44
JF
9177
9178 [pool release];
670a0494
JF
9179}
9180
9181- (void) applicationWillSuspend {
9182 [database_ clean];
9183 [super applicationWillSuspend];
bd150f54
JF
9184}
9185
235f5487 9186- (BOOL) isSafeToSuspend {
5927fe03
JF
9187 if (locked_ != 0) {
9188#if !ForRelease
9189 NSLog(@"isSafeToSuspend: locked_ != 0");
9190#endif
9191 return false;
9192 }
9193
262afe11
GP
9194 // Use external process status API internally.
9195 // This is probably a really bad idea.
235f5487 9196 // XXX: what is the point of this? does this solve anything at all?
262afe11
GP
9197 uint64_t status = 0;
9198 int notify_token;
9199 if (notify_register_check("com.saurik.Cydia.status", &notify_token) == NOTIFY_STATUS_OK) {
9200 notify_get_state(notify_token, &status);
9201 notify_cancel(notify_token);
9202 }
9203
5927fe03
JF
9204 if (status != 0) {
9205#if !ForRelease
9206 NSLog(@"isSafeToSuspend: status != 0");
9207#endif
9208 return false;
9209 }
9210
9211#if !ForRelease
9212 NSLog(@"isSafeToSuspend: -> true");
9213#endif
9214 return true;
235f5487
JF
9215}
9216
9217- (void) applicationSuspend:(__GSEvent *)event {
9218 if ([self isSafeToSuspend])
bd150f54 9219 [super applicationSuspend:event];
bd150f54
JF
9220}
9221
6d9712c4 9222- (void) _animateSuspension:(BOOL)arg0 duration:(double)arg1 startTime:(double)arg2 scale:(float)arg3 {
235f5487 9223 if ([self isSafeToSuspend])
6d9712c4
JF
9224 [super _animateSuspension:arg0 duration:arg1 startTime:arg2 scale:arg3];
9225}
9226
9227- (void) _setSuspended:(BOOL)value {
235f5487 9228 if ([self isSafeToSuspend])
6d9712c4
JF
9229 [super _setSuspended:value];
9230}
9231
7b0ce2da 9232- (UIProgressHUD *) addProgressHUD {
8c9453da 9233 UIProgressHUD *hud([[[UIProgressHUD alloc] init] autorelease]);
04fe1349
JF
9234 [hud setAutoresizingMask:UIViewAutoresizingFlexibleBoth];
9235
d061f4ba 9236 [window_ setUserInteractionEnabled:NO];
534e31fc 9237
f36e5eac
JF
9238 UIViewController *target(tabbar_);
9239 if (UIViewController *modal = [target modalViewController])
9240 target = modal;
9241
22b6c4b8 9242 [hud showInView:[target view]];
534e31fc 9243
2ef6faad 9244 [self lockSuspend];
7b0ce2da
JF
9245 return hud;
9246}
9247
d061f4ba 9248- (void) removeProgressHUD:(UIProgressHUD *)hud {
2ef6faad 9249 [self unlockSuspend];
8c9453da 9250 [hud hide];
d061f4ba
JF
9251 [hud removeFromSuperview];
9252 [window_ setUserInteractionEnabled:YES];
9253}
9254
f050e4d9
JF
9255- (CyteViewController *) pageForPackage:(NSString *)name withReferrer:(NSString *)referrer {
9256 return [[[CYPackageController alloc] initWithDatabase:database_ forPackage:name withReferrer:referrer] autorelease];
c390d3ab
JF
9257}
9258
f050e4d9 9259- (CyteViewController *) pageForURL:(NSURL *)url forExternal:(BOOL)external withReferrer:(NSString *)referrer {
e47c4742 9260 NSString *scheme([[url scheme] lowercaseString]);
f6e13561 9261 if ([[url absoluteString] length] <= [scheme length] + 3)
e47c4742 9262 return nil;
f6e13561 9263 NSString *path([[url absoluteString] substringFromIndex:[scheme length] + 3]);
3a159223 9264 NSArray *components([path componentsSeparatedByString:@"/"]);
f6e13561 9265
4fd0c466 9266 if ([scheme isEqualToString:@"apptapp"] && [components count] > 0 && [[components objectAtIndex:0] isEqualToString:@"package"]) {
f050e4d9 9267 CyteViewController *controller([self pageForPackage:[components objectAtIndex:1] withReferrer:referrer]);
4fd0c466
JF
9268 if (controller != nil)
9269 [controller setDelegate:self];
9270 return controller;
9271 }
f6e13561
GP
9272
9273 if ([components count] < 1 || ![scheme isEqualToString:@"cydia"])
e47c4742 9274 return nil;
f6e13561
GP
9275
9276 NSString *base([components objectAtIndex:0]);
9277
cd79e8cf 9278 CyteViewController *controller = nil;
f5a17517 9279
f70ea899 9280 if ([base isEqualToString:@"url"]) {
106d645f
GP
9281 // This kind of URL can contain slashes in the argument, so we can't parse them below.
9282 NSString *destination = [[url absoluteString] substringFromIndex:([scheme length] + [@"://" length] + [base length] + [@"/" length])];
a576488f 9283 controller = [[[CydiaWebViewController alloc] initWithURL:[NSURL URLWithString:destination]] autorelease];
028dbd1c 9284 } else if (!external && [components count] == 1) {
f6e13561 9285 if ([base isEqualToString:@"sources"]) {
f5a17517 9286 controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
f6e13561
GP
9287 }
9288
9289 if ([base isEqualToString:@"home"]) {
f5a17517 9290 controller = [[[HomeController alloc] init] autorelease];
f6e13561
GP
9291 }
9292
9293 if ([base isEqualToString:@"sections"]) {
55066b9e 9294 controller = [[[SectionsController alloc] initWithDatabase:database_ source:nil] autorelease];
f6e13561
GP
9295 }
9296
9297 if ([base isEqualToString:@"search"]) {
43625891 9298 controller = [[[SearchController alloc] initWithDatabase:database_ query:nil] autorelease];
f6e13561
GP
9299 }
9300
9301 if ([base isEqualToString:@"changes"]) {
ea3bb538 9302 controller = [[[ChangesController alloc] initWithDatabase:database_] autorelease];
f6e13561
GP
9303 }
9304
9305 if ([base isEqualToString:@"installed"]) {
f5a17517 9306 controller = [[[InstalledController alloc] initWithDatabase:database_] autorelease];
f6e13561
GP
9307 }
9308 } else if ([components count] == 2) {
8912cff7 9309 NSString *argument = [[components objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
f6e13561
GP
9310
9311 if ([base isEqualToString:@"package"]) {
f050e4d9 9312 controller = [self pageForPackage:argument withReferrer:referrer];
f6e13561
GP
9313 }
9314
028dbd1c 9315 if (!external && [base isEqualToString:@"search"]) {
8912cff7 9316 controller = [[[SearchController alloc] initWithDatabase:database_ query:argument] autorelease];
f6e13561
GP
9317 }
9318
028dbd1c 9319 if (!external && [base isEqualToString:@"sections"]) {
55066b9e 9320 if ([argument isEqualToString:@"all"] || [argument isEqualToString:@"*"])
f6e13561 9321 argument = nil;
55066b9e 9322 controller = [[[SectionController alloc] initWithDatabase:database_ source:nil section:argument] autorelease];
f6e13561
GP
9323 }
9324
028dbd1c 9325 if (!external && [base isEqualToString:@"sources"]) {
f6e13561 9326 if ([argument isEqualToString:@"add"]) {
f5a17517
GP
9327 controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
9328 [(SourcesController *)controller showAddSourcePrompt];
f6e13561 9329 } else {
8912cff7 9330 Source *source([database_ sourceWithKey:argument]);
55066b9e 9331 controller = [[[SectionsController alloc] initWithDatabase:database_ source:source] autorelease];
f6e13561
GP
9332 }
9333 }
9334
028dbd1c 9335 if (!external && [base isEqualToString:@"launch"]) {
f6e13561 9336 [self launchApplicationWithIdentifier:argument suspended:NO];
f5a17517 9337 return nil;
f6e13561 9338 }
028dbd1c 9339 } else if (!external && [components count] == 3) {
8912cff7
JF
9340 NSString *arg1 = [[components objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
9341 NSString *arg2 = [[components objectAtIndex:2] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
f6e13561
GP
9342
9343 if ([base isEqualToString:@"package"]) {
9344 if ([arg2 isEqualToString:@"settings"]) {
f5a17517 9345 controller = [[[PackageSettingsController alloc] initWithDatabase:database_ package:arg1] autorelease];
f6e13561
GP
9346 } else if ([arg2 isEqualToString:@"files"]) {
9347 if (Package *package = [database_ packageWithName:arg1]) {
f5a17517
GP
9348 controller = [[[FileTable alloc] initWithDatabase:database_] autorelease];
9349 [(FileTable *)controller setPackage:package];
f6e13561
GP
9350 }
9351 }
c390d3ab 9352 }
55066b9e
JF
9353
9354 if ([base isEqualToString:@"sections"]) {
9355 Source *source([arg1 isEqualToString:@"*"] ? nil : [database_ sourceWithKey:arg1]);
9356 NSString *section([arg2 isEqualToString:@"*"] ? nil : arg2);
9357 controller = [[[SectionController alloc] initWithDatabase:database_ source:source section:section] autorelease];
9358 }
c390d3ab
JF
9359 }
9360
f5a17517
GP
9361 [controller setDelegate:self];
9362 return controller;
c390d3ab
JF
9363}
9364
028dbd1c 9365- (BOOL) openCydiaURL:(NSURL *)url forExternal:(BOOL)external {
f050e4d9 9366 CyteViewController *page([self pageForURL:url forExternal:external withReferrer:nil]);
40364973 9367
e965092a
JF
9368 if (page != nil)
9369 [tabbar_ setUnselectedViewController:page];
40364973 9370
f6e13561 9371 return page != nil;
40364973
GP
9372}
9373
c390d3ab
JF
9374- (void) applicationOpenURL:(NSURL *)url {
9375 [super applicationOpenURL:url];
d817e4de 9376
7b33d201
JF
9377 if (!loaded_)
9378 starturl_ = url;
9379 else
9380 [self openCydiaURL:url forExternal:YES];
c390d3ab
JF
9381}
9382
bc11cf5b 9383- (void) applicationWillResignActive:(UIApplication *)application {
7eff7ea6 9384 // Stop refreshing if you get a phone call or lock the device.
7585ce66
JF
9385 if ([tabbar_ updating])
9386 [tabbar_ cancelUpdate];
bc11cf5b 9387
ca584c15
GP
9388 if ([[self superclass] instancesRespondToSelector:@selector(applicationWillResignActive:)])
9389 [super applicationWillResignActive:application];
7eff7ea6
GP
9390}
9391
9dd3045d 9392- (void) saveState {
35f0a3b5 9393 [Metadata_ setObject:[tabbar_ navigationURLCollection] forKey:@"InterfaceState"];
fe8e721f
GP
9394 [Metadata_ setObject:[NSDate date] forKey:@"LastClosed"];
9395 [Metadata_ setObject:[NSNumber numberWithInt:[tabbar_ selectedIndex]] forKey:@"InterfaceIndex"];
9dd3045d 9396 Changed_ = true;
fe8e721f
GP
9397
9398 [self _saveConfig];
9399}
9400
9dd3045d
JF
9401- (void) applicationWillTerminate:(UIApplication *)application {
9402 [self saveState];
9403}
9404
6915b806
JF
9405- (void) setConfigurationData:(NSString *)data {
9406 static Pcre conffile_r("^'(.*)' '(.*)' ([01]) ([01])$");
9407
9408 if (!conffile_r(data)) {
9409 lprintf("E:invalid conffile\n");
9410 return;
9411 }
9412
9413 NSString *ofile = conffile_r[1];
9414 //NSString *nfile = conffile_r[2];
9415
9416 UIAlertView *alert = [[[UIAlertView alloc]
9417 initWithTitle:UCLocalize("CONFIGURATION_UPGRADE")
9418 message:[NSString stringWithFormat:@"%@\n\n%@", UCLocalize("CONFIGURATION_UPGRADE_EX"), ofile]
9419 delegate:self
9420 cancelButtonTitle:UCLocalize("KEEP_OLD_COPY")
9421 otherButtonTitles:
9422 UCLocalize("ACCEPT_NEW_COPY"),
9423 // XXX: UCLocalize("SEE_WHAT_CHANGED"),
9424 nil
9425 ] autorelease];
9426
9427 [alert setContext:@"conffile"];
a08145a8 9428 [alert setNumberOfRows:2];
6915b806
JF
9429 [alert show];
9430}
9431
f3e11d24 9432- (void) addStashController {
2ef6faad 9433 [self lockSuspend];
7b33d201 9434 stash_ = [[[StashController alloc] init] autorelease];
f3e11d24
GP
9435 [window_ addSubview:[stash_ view]];
9436}
9437
9438- (void) removeStashController {
9439 [[stash_ view] removeFromSuperview];
7b33d201 9440 stash_ = nil;
2ef6faad 9441 [self unlockSuspend];
f3e11d24
GP
9442}
9443
9444- (void) stash {
9e1f1e91 9445 [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque];
f3e11d24 9446 UpdateExternalStatus(1);
f3e11d24 9447 [self yieldToSelector:@selector(system:) withObject:@"/usr/libexec/cydia/free.sh"];
f3e11d24 9448 UpdateExternalStatus(0);
f3e11d24
GP
9449
9450 [self removeStashController];
9451
d99e3659
JF
9452 pid_t pid(ExecFork());
9453 if (pid == 0) {
f3e11d24
GP
9454 execlp("launchctl", "launchctl", "stop", "com.apple.SpringBoard", NULL);
9455 perror("launchctl stop");
d99e3659 9456
51c8106a
JF
9457 exit(0);
9458 } ReapZombie(pid);
f3e11d24
GP
9459}
9460
f6e13561 9461- (void) setupViewControllers {
5fe2bcc6 9462 tabbar_ = [[[CydiaTabBarController alloc] initWithDatabase:database_] autorelease];
851f4a99 9463
6445279b
JF
9464 NSMutableArray *items;
9465 if (kCFCoreFoundationVersionNumber < 800) {
9466 items = [NSMutableArray arrayWithObjects:
9467 [[[UITabBarItem alloc] initWithTitle:@"Cydia" image:[UIImage applicationImageNamed:@"home.png"] tag:0] autorelease],
55066b9e 9468 [[[UITabBarItem alloc] initWithTitle:UCLocalize("SOURCES") image:[UIImage applicationImageNamed:@"install.png"] tag:0] autorelease],
3ccd7d11 9469 [[[UITabBarItem alloc] initWithTitle:UCLocalize("CHANGES") image:[UIImage applicationImageNamed:@"changes.png"] tag:0] autorelease],
55066b9e 9470 [[[UITabBarItem alloc] initWithTitle:UCLocalize("INSTALLED") image:[UIImage applicationImageNamed:@"manage.png"] tag:0] autorelease],
6445279b
JF
9471 [[[UITabBarItem alloc] initWithTitle:UCLocalize("SEARCH") image:[UIImage applicationImageNamed:@"search.png"] tag:0] autorelease],
9472 nil];
851f4a99 9473 } else {
6445279b
JF
9474 items = [NSMutableArray arrayWithObjects:
9475 [[[UITabBarItem alloc] initWithTitle:@"Cydia" image:[UIImage applicationImageNamed:@"home7.png"] selectedImage:[UIImage applicationImageNamed:@"home7s.png"]] autorelease],
55066b9e 9476 [[[UITabBarItem alloc] initWithTitle:UCLocalize("SOURCES") image:[UIImage applicationImageNamed:@"install7.png"] selectedImage:[UIImage applicationImageNamed:@"install7s.png"]] autorelease],
3ccd7d11 9477 [[[UITabBarItem alloc] initWithTitle:UCLocalize("CHANGES") image:[UIImage applicationImageNamed:@"changes7.png"] selectedImage:[UIImage applicationImageNamed:@"changes7s.png"]] autorelease],
55066b9e 9478 [[[UITabBarItem alloc] initWithTitle:UCLocalize("INSTALLED") image:[UIImage applicationImageNamed:@"manage7.png"] selectedImage:[UIImage applicationImageNamed:@"manage7s.png"]] autorelease],
6445279b
JF
9479 [[[UITabBarItem alloc] initWithTitle:UCLocalize("SEARCH") image:[UIImage applicationImageNamed:@"search7.png"] selectedImage:[UIImage applicationImageNamed:@"search7s.png"]] autorelease],
9480 nil];
851f4a99
GP
9481 }
9482
9483 NSMutableArray *controllers([NSMutableArray array]);
851f4a99 9484 for (UITabBarItem *item in items) {
15f0d613 9485 UINavigationController *controller([[[UINavigationController alloc] init] autorelease]);
851f4a99
GP
9486 [controller setTabBarItem:item];
9487 [controllers addObject:controller];
9488 }
851f4a99 9489 [tabbar_ setViewControllers:controllers];
4305896c 9490
f6e13561 9491 [tabbar_ setUpdateDelegate:self];
851f4a99
GP
9492}
9493
968afbcd 9494- (void) _sendMemoryWarningNotification {
85106ebe
JF
9495 if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_0) // XXX: maybe 4_0?
9496 [[NSNotificationCenter defaultCenter] postNotificationName:@"UIApplicationMemoryWarningNotification" object:[UIApplication sharedApplication]];
9497 else
9498 [[NSNotificationCenter defaultCenter] postNotificationName:@"UIApplicationDidReceiveMemoryWarningNotification" object:[UIApplication sharedApplication]];
968afbcd
JF
9499}
9500
9501- (void) _sendMemoryWarningNotifications {
9502 while (true) {
9503 [self performSelectorOnMainThread:@selector(_sendMemoryWarningNotification) withObject:nil waitUntilDone:NO];
740f9f75
JF
9504 sleep(2);
9505 //usleep(2000000);
968afbcd
JF
9506 }
9507}
9508
9256a1ce
JF
9509- (void) applicationDidReceiveMemoryWarning:(UIApplication *)application {
9510 NSLog(@"--");
9511 [[NSURLCache sharedURLCache] removeAllCachedResponses];
9512}
9513
bd150f54 9514- (void) applicationDidFinishLaunching:(id)unused {
968afbcd
JF
9515 //[NSThread detachNewThreadSelector:@selector(_sendMemoryWarningNotifications) toTarget:self withObject:nil];
9516
7e30ba6d 9517_trace();
1e94d48b
JF
9518 if ([self respondsToSelector:@selector(setApplicationSupportsShakeToEdit:)])
9519 [self setApplicationSupportsShakeToEdit:NO];
9520
48f1762f
JF
9521 @synchronized (HostConfig_) {
9522 [BridgedHosts_ addObject:[[NSURL URLWithString:CydiaURL(@"")] host]];
9523 }
3171f7fe 9524
4058b165
JF
9525 [NSURLCache setSharedURLCache:[[[CYURLCache alloc]
9526 initWithMemoryCapacity:524288
9527 diskCapacity:10485760
d1c7f1fd 9528 diskPath:[NSString stringWithFormat:@"%@/SDURLCache", Cache_]
4058b165 9529 ] autorelease]];
71cc7be1 9530
a576488f 9531 [CydiaWebViewController _initialize];
ea173384 9532
bfc87a4d
JF
9533 [NSURLProtocol registerClass:[CydiaURLProtocol class]];
9534
793aee35
JF
9535 // this would disallow http{,s} URLs from accessing this data
9536 //[WebView registerURLSchemeAsLocal:@"cydia"];
9537
7b33d201
JF
9538 Font12_ = [UIFont systemFontOfSize:12];
9539 Font12Bold_ = [UIFont boldSystemFontOfSize:12];
9540 Font14_ = [UIFont systemFontOfSize:14];
2cdc6e57 9541 Font18_ = [UIFont systemFontOfSize:18];
7b33d201
JF
9542 Font18Bold_ = [UIFont boldSystemFontOfSize:18];
9543 Font22Bold_ = [UIFont boldSystemFontOfSize:22];
f641a0e5 9544
7b33d201
JF
9545 essential_ = [NSMutableArray arrayWithCapacity:4];
9546 broken_ = [NSMutableArray arrayWithCapacity:4];
bd150f54 9547
4e89e880 9548 // XXX: I really need this thing... like, seriously... I'm sorry
29bb09d7 9549 [[[AppCacheController alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/appcache/", UI_]]] reloadData];
4e89e880 9550
7b33d201 9551 window_ = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
f641a0e5
JF
9552 [window_ orderFront:self];
9553 [window_ makeKey:self];
c390d3ab 9554 [window_ setHidden:NO];
04fe1349 9555
96ed699d 9556 if (false) stash: {
f3e11d24 9557 [self addStashController];
3931b718
JF
9558 // XXX: this would be much cleaner as a yieldToSelector:
9559 // that way the removeStashController could happen right here inline
9560 // we also could no longer require the useless stash_ field anymore
f3e11d24
GP
9561 [self performSelector:@selector(stash) withObject:nil afterDelay:0];
9562 return;
9563 }
9564
2656fd54
JF
9565 struct stat root;
9566 int error(stat("/", &root));
9567 _assert(error != -1);
9568
96ed699d 9569 #define Stash_(path) do { \
2656fd54
JF
9570 struct stat folder; \
9571 int error(lstat((path), &folder)); \
9572 if (error != -1 && ( \
9573 folder.st_dev == root.st_dev && \
9574 S_ISDIR(folder.st_mode) \
9575 ) || error == -1 && ( \
9576 errno == ENOENT || \
9577 errno == ENOTDIR \
9578 )) goto stash; \
96ed699d
JF
9579 } while (false)
9580
9581 Stash_("/Applications");
9582 Stash_("/Library/Ringtones");
9583 Stash_("/Library/Wallpaper");
9584 //Stash_("/usr/bin");
9585 Stash_("/usr/include");
9586 Stash_("/usr/lib/pam");
96ed699d
JF
9587 Stash_("/usr/share");
9588 //Stash_("/var/lib");
9589
770f2a8e 9590 database_ = [Database sharedInstance];
6915b806 9591 [database_ setDelegate:self];
bfc87a4d 9592
89571a5b 9593 [window_ setUserInteractionEnabled:NO];
0be165c8 9594 [self setupViewControllers];
6915b806 9595
70750ab3 9596 emulated_ = [[[CydiaLoadingViewController alloc] init] autorelease];
6915b806 9597 [window_ addSubview:[emulated_ view]];
81628115
JF
9598 if ([window_ respondsToSelector:@selector(setRootViewController:)])
9599 [window_ setRootViewController:emulated_];
5ccb47d8 9600
fed0d010 9601 [self performSelector:@selector(loadData) withObject:nil afterDelay:0];
c626a63f 9602_trace();
fed0d010
JF
9603}
9604
d3a28a81
GP
9605- (NSArray *) defaultStartPages {
9606 NSMutableArray *standard = [NSMutableArray array];
9607 [standard addObject:[NSArray arrayWithObject:@"cydia://home"]];
55066b9e 9608 [standard addObject:[NSArray arrayWithObject:@"cydia://sources"]];
d3a28a81 9609 [standard addObject:[NSArray arrayWithObject:@"cydia://changes"]];
55066b9e 9610 [standard addObject:[NSArray arrayWithObject:@"cydia://installed"]];
d3a28a81
GP
9611 [standard addObject:[NSArray arrayWithObject:@"cydia://search"]];
9612 return standard;
9613}
9614
fed0d010 9615- (void) loadData {
c626a63f 9616_trace();
4121c5e0
JF
9617 if ([emulated_ modalViewController] != nil)
9618 [emulated_ dismissModalViewControllerAnimated:YES];
9619 [window_ setUserInteractionEnabled:NO];
fed0d010 9620
2925cbba
JF
9621 [self reloadDataWithInvocation:nil];
9622 [self refreshIfPossible];
670a0494 9623 PrintTimes();
5ccb47d8 9624
2e9123cb 9625 [self disemulate];
5ccb47d8 9626
d3a28a81 9627 int savedIndex = [[Metadata_ objectForKey:@"InterfaceIndex"] intValue];
0c4fe0f4 9628 NSArray *saved = [[[Metadata_ objectForKey:@"InterfaceState"] mutableCopy] autorelease];
d3a28a81
GP
9629 int standardIndex = 0;
9630 NSArray *standard = [self defaultStartPages];
fe8e721f 9631
d3a28a81
GP
9632 BOOL valid = YES;
9633
9634 if (saved == nil)
9635 valid = NO;
9636
9637 NSDate *closed = [Metadata_ objectForKey:@"LastClosed"];
9638 if (valid && closed != nil) {
fe8e721f 9639 NSTimeInterval interval([closed timeIntervalSinceNow]);
ade2267f
JF
9640 // XXX: Is 30 minutes the optimal time here?
9641 if (interval <= -(30*60))
d3a28a81 9642 valid = NO;
fe8e721f
GP
9643 }
9644
d3a28a81
GP
9645 if (valid && [saved count] != [standard count])
9646 valid = NO;
efa53fa9 9647
d3a28a81
GP
9648 if (valid) {
9649 for (unsigned int i = 0; i < [standard count]; i++) {
9650 NSArray *std = [standard objectAtIndex:i], *sav = [saved objectAtIndex:i];
9651 // XXX: The "hasPrefix" sanity check here could be, in theory, fooled,
9652 // but it's good enough for now.
9653 if ([sav count] == 0 || ![[sav objectAtIndex:0] hasPrefix:[std objectAtIndex:0]]) {
9654 valid = NO;
9655 break;
9656 }
fe8e721f 9657 }
fe8e721f
GP
9658 }
9659
d3a28a81
GP
9660 NSArray *items = nil;
9661 if (valid) {
9662 [tabbar_ setSelectedIndex:savedIndex];
9663 items = saved;
9664 } else {
9665 [tabbar_ setSelectedIndex:standardIndex];
9666 items = standard;
9667 }
9668
fe8e721f
GP
9669 for (unsigned int tab = 0; tab < [[tabbar_ viewControllers] count]; tab++) {
9670 NSArray *stack = [items objectAtIndex:tab];
15f0d613 9671 UINavigationController *navigation = [[tabbar_ viewControllers] objectAtIndex:tab];
fe8e721f
GP
9672 NSMutableArray *current = [NSMutableArray array];
9673
9674 for (unsigned int nav = 0; nav < [stack count]; nav++) {
9675 NSString *addr = [stack objectAtIndex:nav];
9676 NSURL *url = [NSURL URLWithString:addr];
f050e4d9 9677 CyteViewController *page = [self pageForURL:url forExternal:NO withReferrer:nil];
fe8e721f
GP
9678 if (page != nil)
9679 [current addObject:page];
9680 }
9681
9682 [navigation setViewControllers:current];
9683 }
f6e13561 9684
89571a5b 9685 // (Try to) show the startup URL.
f6e13561 9686 if (starturl_ != nil) {
028dbd1c 9687 [self openCydiaURL:starturl_ forExternal:NO];
f6e13561
GP
9688 starturl_ = nil;
9689 }
bd150f54
JF
9690}
9691
b5e7eebb 9692- (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item {
674dce72 9693 if (item != nil && IsWildcat_) {
b5e7eebb 9694 [sheet showFromBarButtonItem:item animated:YES];
674dce72
GP
9695 } else {
9696 [sheet showInView:window_];
9697 }
36bb2ca2
JF
9698}
9699
6915b806
JF
9700- (void) addProgressEvent:(CydiaProgressEvent *)event forTask:(NSString *)task {
9701 id<ProgressDelegate> progress([database_ progressDelegate] ?: [self invokeNewProgress:nil forController:nil withTitle:task]);
9702 [progress setTitle:task];
9703 [progress addProgressEvent:event];
9704}
9705
9706- (void) addProgressEventForTask:(NSArray *)data {
9707 CydiaProgressEvent *event([data objectAtIndex:0]);
9708 NSString *task([data count] < 2 ? nil : [data objectAtIndex:1]);
9709 [self addProgressEvent:event forTask:task];
9710}
9711
9712- (void) addProgressEventOnMainThread:(CydiaProgressEvent *)event forTask:(NSString *)task {
9713 [self performSelectorOnMainThread:@selector(addProgressEventForTask:) withObject:[NSArray arrayWithObjects:event, task, nil] waitUntilDone:YES];
9714}
9715
dc5812ec
JF
9716@end
9717
b4d89997
JF
9718/*IMP alloc_;
9719id Alloc_(id self, SEL selector) {
9720 id object = alloc_(self, selector);
c390d3ab 9721 lprintf("[%s]A-%p\n", self->isa->name, object);
b4d89997
JF
9722 return object;
9723}*/
9724
36bb2ca2
JF
9725/*IMP dealloc_;
9726id Dealloc_(id self, SEL selector) {
9727 id object = dealloc_(self, selector);
c390d3ab 9728 lprintf("[%s]D-%p\n", self->isa->name, object);
36bb2ca2
JF
9729 return object;
9730}*/
b4d89997 9731
baf82ed4 9732static NSSet *MobilizedFiles_;
10d65752 9733
baf82ed4
JF
9734static NSURL *MobilizeURL(NSURL *url) {
9735 NSString *path([url path]);
9736 if ([path hasPrefix:@"/var/root/"]) {
9737 NSString *file([path substringFromIndex:10]);
9738 if ([MobilizedFiles_ containsObject:file])
9739 url = [NSURL fileURLWithPath:[@"/var/mobile/" stringByAppendingString:file] isDirectory:NO];
10d65752 9740 }
79b42fd1 9741
baf82ed4
JF
9742 return url;
9743}
79b42fd1 9744
baf82ed4
JF
9745Class $CFXPreferencesPropertyListSource;
9746@class CFXPreferencesPropertyListSource;
79b42fd1 9747
baf82ed4
JF
9748MSHook(BOOL, CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync, CFXPreferencesPropertyListSource *self, SEL _cmd) {
9749 NSURL *&url(MSHookIvar<NSURL *>(self, "_url")), *old(url);
9750 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
9751 url = MobilizeURL(url);
9752 BOOL value(_CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync(self, _cmd));
9753 //NSLog(@"%@ %s", [url absoluteString], value ? "YES" : "NO");
9754 url = old;
9755 [pool release];
9756 return value;
9757}
79b42fd1 9758
baf82ed4
JF
9759MSHook(void *, CFXPreferencesPropertyListSource$createPlistFromDisk, CFXPreferencesPropertyListSource *self, SEL _cmd) {
9760 NSURL *&url(MSHookIvar<NSURL *>(self, "_url")), *old(url);
9761 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
9762 url = MobilizeURL(url);
9763 void *value(_CFXPreferencesPropertyListSource$createPlistFromDisk(self, _cmd));
9764 //NSLog(@"%@ %@", [url absoluteString], value);
9765 url = old;
9766 [pool release];
9767 return value;
79b42fd1
GP
9768}
9769
30c5be06
JF
9770Class $NSURLConnection;
9771
9772MSHook(id, NSURLConnection$init$, NSURLConnection *self, SEL _cmd, NSURLRequest *request, id delegate, BOOL usesCache, int64_t maxContentLength, BOOL startImmediately, NSDictionary *connectionProperties) {
0c4fe0f4 9773 NSMutableURLRequest *copy([[request mutableCopy] autorelease]);
30c5be06
JF
9774
9775 NSURL *url([copy URL]);
5e845121 9776
30c5be06 9777 NSString *host([url host]);
e4b48f2f
JF
9778 NSString *scheme([[url scheme] lowercaseString]);
9779
9780 NSString *compound([NSString stringWithFormat:@"%@:%@", scheme, host]);
30c5be06 9781
48f1762f
JF
9782 @synchronized (HostConfig_) {
9783 if ([copy respondsToSelector:@selector(setHTTPShouldUsePipelining:)])
9784 if ([PipelinedHosts_ containsObject:host] || [PipelinedHosts_ containsObject:compound])
9785 [copy setHTTPShouldUsePipelining:YES];
5e845121
JF
9786
9787 if (NSString *control = [copy valueForHTTPHeaderField:@"Cache-Control"])
9788 if ([control isEqualToString:@"max-age=0"])
3a23d36e 9789 if ([CachedURLs_ containsObject:url]) {
5e845121 9790#if !ForRelease
3a23d36e 9791 NSLog(@"~~~: %@", url);
5e845121
JF
9792#endif
9793
9794 [copy setCachePolicy:NSURLRequestReturnCacheDataDontLoad];
9795
9796 [copy setValue:nil forHTTPHeaderField:@"Cache-Control"];
9797 [copy setValue:nil forHTTPHeaderField:@"If-Modified-Since"];
9798 [copy setValue:nil forHTTPHeaderField:@"If-None-Match"];
9799 }
48f1762f 9800 }
30c5be06
JF
9801
9802 if ((self = _NSURLConnection$init$(self, _cmd, copy, delegate, usesCache, maxContentLength, startImmediately, connectionProperties)) != nil) {
9803 } return self;
9804}
9805
b1497b56
JF
9806Class $WAKWindow;
9807
4cc9e99a 9808static CGSize $WAKWindow$screenSize(WAKWindow *self, SEL _cmd) {
b1497b56
JF
9809 CGSize size([[UIScreen mainScreen] bounds].size);
9810 /*if ([$WAKWindow respondsToSelector:@selector(hasLandscapeOrientation)])
9811 if ([$WAKWindow hasLandscapeOrientation])
9812 std::swap(size.width, size.height);*/
9813 return size;
438d6708
JF
9814}
9815
29cbf4e5
JF
9816Class $NSUserDefaults;
9817
9818MSHook(id, NSUserDefaults$objectForKey$, NSUserDefaults *self, SEL _cmd, NSString *key) {
9819 if ([key respondsToSelector:@selector(isEqualToString:)] && [key isEqualToString:@"WebKitLocalStorageDatabasePathPreferenceKey"])
9820 return [NSString stringWithFormat:@"%@/LocalStorage", Cache_];
9821 return _NSUserDefaults$objectForKey$(self, _cmd, key);
9822}
9823
d13edf44
JF
9824int main(int argc, char *argv[]) {
9825 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
9826
d6dad1b4 9827 _trace();
f79a4512 9828
9a60abe5
JF
9829 UpdateExternalStatus(0);
9830
57e8b225
JF
9831 UIScreen *screen([UIScreen mainScreen]);
9832 if ([screen respondsToSelector:@selector(scale)])
9833 ScreenScale_ = [screen scale];
9834 else
9835 ScreenScale_ = 1;
9836
c138614d 9837 UIDevice *device([UIDevice currentDevice]);
08157100 9838 if ([device respondsToSelector:@selector(userInterfaceIdiom)]) {
c138614d 9839 UIUserInterfaceIdiom idiom([device userInterfaceIdiom]);
08157100
JF
9840 if (idiom == UIUserInterfaceIdiomPad)
9841 IsWildcat_ = true;
c138614d
JF
9842 }
9843
08157100
JF
9844 Idiom_ = IsWildcat_ ? @"ipad" : @"iphone";
9845
9a4a4754 9846 Pcre pattern("^([0-9]+\\.[0-9]+)");
fd825a2d 9847
9a4a4754
JF
9848 if (pattern([device systemVersion]))
9849 Firmware_ = pattern[1];
fd825a2d
JF
9850 if (pattern(Cydia_))
9851 Major_ = pattern[1];
9a4a4754 9852
7b33d201 9853 SessionData_ = [NSMutableDictionary dictionaryWithCapacity:4];
ef974f52 9854
7b33d201 9855 HostConfig_ = [[[NSObject alloc] init] autorelease];
48f1762f
JF
9856 @synchronized (HostConfig_) {
9857 BridgedHosts_ = [NSMutableSet setWithCapacity:4];
247bedb6 9858 TokenHosts_ = [NSMutableSet setWithCapacity:4];
2e1652a9 9859 InsecureHosts_ = [NSMutableSet setWithCapacity:4];
48f1762f 9860 PipelinedHosts_ = [NSMutableSet setWithCapacity:4];
5e845121 9861 CachedURLs_ = [NSMutableSet setWithCapacity:32];
48f1762f 9862 }
5df7ecfb 9863
de595d91
JF
9864 NSString *ui(@"ui/ios");
9865 if (Idiom_ != nil)
9866 ui = [ui stringByAppendingString:[NSString stringWithFormat:@"~%@", Idiom_]];
fd825a2d 9867 ui = [ui stringByAppendingString:[NSString stringWithFormat:@"/%@", Major_]];
de595d91 9868 UI_ = CydiaURL(ui);
57e8b225 9869
df213583 9870 PackageName = reinterpret_cast<CYString &(*)(Package *, SEL)>(method_getImplementation(class_getInstanceMethod([Package class], @selector(cyname))));
677b8415 9871
baf82ed4
JF
9872 MobilizedFiles_ = [NSMutableSet setWithObjects:
9873 @"Library/Preferences/com.apple.Accessibility.plist",
9874 @"Library/Preferences/com.apple.preferences.sounds.plist",
9875 nil];
9876
7376b55c
JF
9877 /* Library Hacks {{{ */
9878 class_addMethod(objc_getClass("DOMNodeList"), @selector(countByEnumeratingWithState:objects:count:), (IMP) &DOMNodeList$countByEnumeratingWithState$objects$count$, "I20@0:4^{NSFastEnumerationState}8^@12I16");
9879
b1497b56
JF
9880 $WAKWindow = objc_getClass("WAKWindow");
9881 if ($WAKWindow != NULL)
438d6708
JF
9882 if (Method method = class_getInstanceMethod($WAKWindow, @selector(screenSize)))
9883 method_setImplementation(method, (IMP) &$WAKWindow$screenSize);
9884
baf82ed4
JF
9885 $CFXPreferencesPropertyListSource = objc_getClass("CFXPreferencesPropertyListSource");
9886
9887 Method CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync(class_getInstanceMethod($CFXPreferencesPropertyListSource, @selector(_backingPlistChangedSinceLastSync)));
9888 if (CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync != NULL) {
9889 _CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync = reinterpret_cast<BOOL (*)(CFXPreferencesPropertyListSource *, SEL)>(method_getImplementation(CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync));
9890 method_setImplementation(CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync, reinterpret_cast<IMP>(&$CFXPreferencesPropertyListSource$_backingPlistChangedSinceLastSync));
9891 }
9892
9893 Method CFXPreferencesPropertyListSource$createPlistFromDisk(class_getInstanceMethod($CFXPreferencesPropertyListSource, @selector(createPlistFromDisk)));
9894 if (CFXPreferencesPropertyListSource$createPlistFromDisk != NULL) {
9895 _CFXPreferencesPropertyListSource$createPlistFromDisk = reinterpret_cast<void *(*)(CFXPreferencesPropertyListSource *, SEL)>(method_getImplementation(CFXPreferencesPropertyListSource$createPlistFromDisk));
9896 method_setImplementation(CFXPreferencesPropertyListSource$createPlistFromDisk, reinterpret_cast<IMP>(&$CFXPreferencesPropertyListSource$createPlistFromDisk));
9897 }
9898
30c5be06
JF
9899 $NSURLConnection = objc_getClass("NSURLConnection");
9900 Method NSURLConnection$init$(class_getInstanceMethod($NSURLConnection, @selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)));
9901 if (NSURLConnection$init$ != NULL) {
9902 _NSURLConnection$init$ = reinterpret_cast<id (*)(NSURLConnection *, SEL, NSURLRequest *, id, BOOL, int64_t, BOOL, NSDictionary *)>(method_getImplementation(NSURLConnection$init$));
9903 method_setImplementation(NSURLConnection$init$, reinterpret_cast<IMP>(&$NSURLConnection$init$));
9904 }
29cbf4e5
JF
9905
9906 $NSUserDefaults = objc_getClass("NSUserDefaults");
9907 Method NSUserDefaults$objectForKey$(class_getInstanceMethod($NSUserDefaults, @selector(objectForKey:)));
9908 if (NSUserDefaults$objectForKey$ != NULL) {
9909 _NSUserDefaults$objectForKey$ = reinterpret_cast<id (*)(NSUserDefaults *, SEL, NSString *)>(method_getImplementation(NSUserDefaults$objectForKey$));
9910 method_setImplementation(NSUserDefaults$objectForKey$, reinterpret_cast<IMP>(&$NSUserDefaults$objectForKey$));
9911 }
7376b55c
JF
9912 /* }}} */
9913 /* Set Locale {{{ */
f79a4512 9914 Locale_ = CFLocaleCopyCurrent();
b1ce61ec 9915 Languages_ = [NSLocale preferredLanguages];
25fdc941 9916
b1ce61ec 9917 //CFStringRef locale(CFLocaleGetIdentifier(Locale_));
18876387 9918 //NSLog(@"%@", [Languages_ description]);
78430d06 9919
b1ce61ec 9920 const char *lang;
25fdc941
JF
9921 if (Locale_ != NULL)
9922 lang = [(NSString *) CFLocaleGetIdentifier(Locale_) UTF8String];
3caee0a4
JF
9923 else if (Languages_ != nil && [Languages_ count] != 0)
9924 lang = [[Languages_ objectAtIndex:0] UTF8String];
9925 else
78430d06 9926 // XXX: consider just setting to C and then falling through?
b1ce61ec 9927 lang = NULL;
3caee0a4
JF
9928
9929 if (lang != NULL) {
9930 Pcre pattern("^([a-z][a-z])(?:-[A-Za-z]*)?(_[A-Z][A-Z])?$");
9931 lang = !pattern(lang) ? NULL : [pattern->*@"%1$@%2$@" UTF8String];
78430d06
JF
9932 }
9933
b1ce61ec 9934 NSLog(@"Setting Language: %s", lang);
3caee0a4
JF
9935
9936 if (lang != NULL) {
9937 setenv("LANG", lang, true);
9938 std::setlocale(LC_ALL, lang);
9939 }
7376b55c 9940 /* }}} */
f79a4512 9941
d791dce4 9942 apr_app_initialize(&argc, const_cast<const char * const **>(&argv), NULL);
f79a4512 9943
7376b55c 9944 /* Parse Arguments {{{ */
de3b1ab4
JF
9945 bool substrate(false);
9946
9947 if (argc != 0) {
9948 char **args(argv);
9949 int arge(1);
9950
9951 for (int argi(1); argi != argc; ++argi)
9952 if (strcmp(argv[argi], "--") == 0) {
9953 arge = argi;
9954 argv[argi] = argv[0];
9955 argv += argi;
9956 argc -= argi;
9957 break;
9958 }
9959
9960 for (int argi(1); argi != arge; ++argi)
d791dce4 9961 if (strcmp(args[argi], "--substrate") == 0)
de3b1ab4
JF
9962 substrate = true;
9963 else
9964 fprintf(stderr, "unknown argument: %s\n", args[argi]);
9965 }
7376b55c 9966 /* }}} */
d73cede2 9967
7376b55c 9968 App_ = [[NSBundle mainBundle] bundlePath];
d791dce4 9969 Advanced_ = YES;
7376b55c 9970
b4d89997
JF
9971 setuid(0);
9972 setgid(0);
9973
57f0b05d
JF
9974 if (access("/var/mobile/Library/Keyboard/UserDictionary.sqlite", F_OK) == 0)
9975 system("mkdir -p /var/root/Library/Keyboard; cp -af /var/mobile/Library/Keyboard/UserDictionary.sqlite /var/root/Library/Keyboard/");
dc8b6681 9976
d1c7f1fd
JF
9977 Cache_ = [[NSString stringWithFormat:@"%@/Library/Caches/com.saurik.Cydia", @"/var/root"] retain];
9978
b4d89997
JF
9979 /*Method alloc = class_getClassMethod([NSObject class], @selector(alloc));
9980 alloc_ = alloc->method_imp;
9981 alloc->method_imp = (IMP) &Alloc_;*/
9982
36bb2ca2
JF
9983 /*Method dealloc = class_getClassMethod([NSObject class], @selector(dealloc));
9984 dealloc_ = dealloc->method_imp;
9985 dealloc->method_imp = (IMP) &Dealloc_;*/
9986
7c80833f
JF
9987 void *gestalt(dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL | RTLD_LAZY));
9988 $MGCopyAnswer = reinterpret_cast<CFStringRef (*)(CFStringRef)>(dlsym(gestalt, "MGCopyAnswer"));
9989
d791dce4 9990 /* System Information {{{ */
3178d79b 9991 size_t size;
c390d3ab
JF
9992
9993 int maxproc;
9994 size = sizeof(maxproc);
9995 if (sysctlbyname("kern.maxproc", &maxproc, &size, NULL, 0) == -1)
9996 perror("sysctlbyname(\"kern.maxproc\", ?)");
9997 else if (maxproc < 64) {
9998 maxproc = 64;
9999 if (sysctlbyname("kern.maxproc", NULL, NULL, &maxproc, sizeof(maxproc)) == -1)
10000 perror("sysctlbyname(\"kern.maxproc\", #)");
10001 }
10002
bfc87a4d
JF
10003 sysctlbyname("kern.osversion", NULL, &size, NULL, 0);
10004 char *osversion = new char[size];
10005 if (sysctlbyname("kern.osversion", osversion, &size, NULL, 0) == -1)
10006 perror("sysctlbyname(\"kern.osversion\", ?)");
10007 else
10008 System_ = [NSString stringWithUTF8String:osversion];
10009
3178d79b
JF
10010 sysctlbyname("hw.machine", NULL, &size, NULL, 0);
10011 char *machine = new char[size];
c390d3ab
JF
10012 if (sysctlbyname("hw.machine", machine, &size, NULL, 0) == -1)
10013 perror("sysctlbyname(\"hw.machine\", ?)");
10014 else
10015 Machine_ = machine;
3178d79b 10016
c31d7cdc
JF
10017 SerialNumber_ = (NSString *) CYIOGetValue("IOService:/", @"IOPlatformSerialNumber");
10018 ChipID_ = [CYHex((NSData *) CYIOGetValue("IODeviceTree:/chosen", @"unique-chip-id"), true) uppercaseString];
10019 BBSNum_ = CYHex((NSData *) CYIOGetValue("IOService:/AppleARMPE/baseband", @"snum"), false);
59dbe296 10020
7c80833f 10021 UniqueID_ = UniqueIdentifier(device);
3178d79b 10022
3e9c9e85
JF
10023 if (NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:@"/Applications/MobileSafari.app/Info.plist"]) {
10024 Product_ = [info objectForKey:@"SafariProductVersion"];
10025 Safari_ = [info objectForKey:@"CFBundleVersion"];
10026 }
e967efd5 10027
dd58e110 10028 NSString *agent([NSString stringWithFormat:@"Cydia/%@ CyF/%.2f", Cydia_, kCFCoreFoundationVersionNumber]);
e967efd5
JF
10029
10030 if (Pcre match = Pcre("^[0-9]+(\\.[0-9]+)+", Safari_))
10031 agent = [NSString stringWithFormat:@"Safari/%@ %@", match[0], agent];
10032 if (Pcre match = Pcre("^[0-9]+[A-Z][0-9]+[a-z]?", System_))
10033 agent = [NSString stringWithFormat:@"Mobile/%@ %@", match[0], agent];
10034 if (Pcre match = Pcre("^[0-9]+(\\.[0-9]+)+", Product_))
10035 agent = [NSString stringWithFormat:@"Version/%@ %@", match[0], agent];
10036
10037 UserAgent_ = agent;
d791dce4 10038 /* }}} */
7376b55c 10039 /* Load Database {{{ */
d6dad1b4 10040 _trace();
f79a4512
JF
10041 Metadata_ = [[[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/lib/cydia/metadata.plist"] autorelease];
10042 _trace();
10043 SectionMap_ = [[[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Sections" ofType:@"plist"]] autorelease];
d6dad1b4
JF
10044
10045 if (Metadata_ == NULL)
f79a4512 10046 Metadata_ = [NSMutableDictionary dictionaryWithCapacity:2];
6d9712c4 10047 else {
2bdd73bd 10048 Settings_ = [Metadata_ objectForKey:@"Settings"];
7b0ce2da 10049
b4d89997 10050 Packages_ = [Metadata_ objectForKey:@"Packages"];
b3c8e69c
JF
10051
10052 Values_ = [Metadata_ objectForKey:@"Values"];
6d9712c4 10053 Sections_ = [Metadata_ objectForKey:@"Sections"];
7b0ce2da 10054 Sources_ = [Metadata_ objectForKey:@"Sources"];
ef055c6c
JF
10055
10056 Token_ = [Metadata_ objectForKey:@"Token"];
33e30380
JF
10057
10058 Version_ = [Metadata_ objectForKey:@"Version"];
7b0ce2da
JF
10059 }
10060
b3c8e69c
JF
10061 if (Values_ == nil) {
10062 Values_ = [[[NSMutableDictionary alloc] initWithCapacity:4] autorelease];
10063 [Metadata_ setObject:Values_ forKey:@"Values"];
10064 }
10065
7b0ce2da
JF
10066 if (Sections_ == nil) {
10067 Sections_ = [[[NSMutableDictionary alloc] initWithCapacity:32] autorelease];
10068 [Metadata_ setObject:Sections_ forKey:@"Sections"];
10069 }
10070
10071 if (Sources_ == nil) {
10072 Sources_ = [[[NSMutableDictionary alloc] initWithCapacity:0] autorelease];
10073 [Metadata_ setObject:Sources_ forKey:@"Sources"];
6d9712c4 10074 }
33e30380
JF
10075
10076 if (Version_ == nil) {
10077 Version_ = [NSNumber numberWithUnsignedInt:0];
10078 [Metadata_ setObject:Version_ forKey:@"Version"];
10079 }
10080
33e30380 10081 if ([Version_ unsignedIntValue] == 0) {
25c1dafb
JF
10082 CydiaAddSource(@"http://apt.thebigboss.org/repofiles/cydia/", @"stable", [NSMutableArray arrayWithObject:@"main"]);
10083 CydiaAddSource(@"http://apt.modmyi.com/", @"stable", [NSMutableArray arrayWithObject:@"main"]);
10084 CydiaAddSource(@"http://cydia.zodttd.com/repo/cydia/", @"stable", [NSMutableArray arrayWithObject:@"main"]);
10085 CydiaAddSource(@"http://repo666.ultrasn0w.com/", @"./");
33e30380
JF
10086
10087 Version_ = [NSNumber numberWithUnsignedInt:1];
10088 [Metadata_ setObject:Version_ forKey:@"Version"];
10089
a26ad329
JF
10090 [Metadata_ removeObjectForKey:@"LastUpdate"];
10091
33e30380
JF
10092 Changed_ = true;
10093 }
2595e4c3
JF
10094
10095 _H<NSMutableArray> broken([NSMutableArray array]);
10096 for (NSString *key in (id) Sources_)
10097 if ([key rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"# "]].location != NSNotFound)
10098 [broken addObject:key];
10099 if ([broken count] != 0) {
10100 for (NSString *key in (id) broken)
10101 [Sources_ removeObjectForKey:key];
10102 Changed_ = true;
10103 } broken = nil;
7376b55c 10104 /* }}} */
b4d89997 10105
25c1dafb 10106 CydiaWriteSources();
33e30380 10107
94b0b3e5
JF
10108 _trace();
10109 MetaFile_.Open("/var/lib/cydia/metadata.cb0");
10110 _trace();
10111
10112 if (Packages_ != nil) {
c65611b9
JF
10113 bool fail(false);
10114 CFDictionaryApplyFunction((CFDictionaryRef) Packages_, &PackageImport, &fail);
94b0b3e5 10115 _trace();
c65611b9
JF
10116
10117 if (!fail) {
10118 [Metadata_ removeObjectForKey:@"Packages"];
10119 Packages_ = nil;
10120 Changed_ = true;
10121 }
94b0b3e5
JF
10122 }
10123
d791dce4
JF
10124 Finishes_ = [NSArray arrayWithObjects:@"return", @"reopen", @"restart", @"reload", @"reboot", nil];
10125
d71f3a07 10126#define MobileSubstrate_(name) \
d13577cf
JF
10127 if (substrate && access("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", F_OK) == 0) { \
10128 void *handle(dlopen("/Library/MobileSubstrate/DynamicLibraries/" #name ".dylib", RTLD_LAZY | RTLD_GLOBAL)); \
10129 if (handle == NULL) \
10130 NSLog(@"%s", dlerror()); \
10131 }
d71f3a07
JF
10132
10133 MobileSubstrate_(Activator)
10134 MobileSubstrate_(libstatusbar)
10135 MobileSubstrate_(SimulatedKeyEvents)
10136 MobileSubstrate_(WinterBoard)
10137
9dd60d81
JF
10138 /*if (substrate && access("/Library/MobileSubstrate/MobileSubstrate.dylib", F_OK) == 0)
10139 dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib", RTLD_LAZY | RTLD_GLOBAL);*/
dddbc481 10140
01d93940
JF
10141 int version([[NSString stringWithContentsOfFile:@"/var/lib/cydia/firmware.ver"] intValue]);
10142
5dcff401 10143 if (access("/User", F_OK) != 0 || version != 6) {
d6dad1b4 10144 _trace();
26c2dd8c 10145 system("/usr/libexec/cydia/firmware.sh");
d6dad1b4
JF
10146 _trace();
10147 }
9e98e020 10148
87f46a96
JF
10149 _assert([[NSFileManager defaultManager]
10150 createDirectoryAtPath:@"/var/cache/apt/archives/partial"
10151 withIntermediateDirectories:YES
10152 attributes:nil
10153 error:NULL
10154 ]);
10155
7376b55c
JF
10156 if (access("/tmp/cydia.chk", F_OK) == 0) {
10157 if (unlink("/var/cache/apt/pkgcache.bin") == -1)
10158 _assert(errno == ENOENT);
10159 if (unlink("/var/cache/apt/srcpkgcache.bin") == -1)
10160 _assert(errno == ENOENT);
10161 }
10162
59dbe296 10163 /* APT Initialization {{{ */
b1ce61ec
JF
10164 _assert(pkgInitConfig(*_config));
10165 _assert(pkgInitSystem(*_config, _system));
10166
10167 if (lang != NULL)
10168 _config->Set("APT::Acquire::Translation", lang);
cb94ff21
JF
10169
10170 // XXX: this timeout might be important :(
10171 //_config->Set("Acquire::http::Timeout", 15);
10172
7623f855 10173 _config->Set("Acquire::http::MaxParallel", 3);
59dbe296 10174 /* }}} */
7376b55c 10175 /* Color Choices {{{ */
36bb2ca2
JF
10176 space_ = CGColorSpaceCreateDeviceRGB();
10177
f641a0e5 10178 Blue_.Set(space_, 0.2, 0.2, 1.0, 1.0);
77fcccaf 10179 Blueish_.Set(space_, 0x19/255.f, 0x32/255.f, 0x50/255.f, 1.0);
36bb2ca2 10180 Black_.Set(space_, 0.0, 0.0, 0.0, 1.0);
86a333c6 10181 Folder_.Set(space_, 0x8e/255.f, 0x8e/255.f, 0x93/255.f, 1.0);
baf80942 10182 Off_.Set(space_, 0.9, 0.9, 0.9, 1.0);
36bb2ca2 10183 White_.Set(space_, 1.0, 1.0, 1.0, 1.0);
7b0ce2da 10184 Gray_.Set(space_, 0.4, 0.4, 0.4, 1.0);
3bd1c2a2
JF
10185 Green_.Set(space_, 0.0, 0.5, 0.0, 1.0);
10186 Purple_.Set(space_, 0.0, 0.0, 0.7, 1.0);
10187 Purplish_.Set(space_, 0.4, 0.4, 0.8, 1.0);
59dbe296 10188
dc63e78f
JF
10189 InstallingColor_ = [UIColor colorWithRed:0.88f green:1.00f blue:0.88f alpha:1.00f];
10190 RemovingColor_ = [UIColor colorWithRed:1.00f green:0.88f blue:0.88f alpha:1.00f];
7376b55c 10191 /* }}}*/
7376b55c 10192 /* UIKit Configuration {{{ */
600d005d
JF
10193 // XXX: I have a feeling this was important
10194 //UIKeyboardDisableAutomaticAppearance();
7376b55c 10195 /* }}} */
87f46a96 10196
8a3b565c
JF
10197 $SBSSetInterceptsMenuButtonForever = reinterpret_cast<void (*)(bool)>(dlsym(RTLD_DEFAULT, "SBSSetInterceptsMenuButtonForever"));
10198
b37b0a4a
JF
10199 const char *symbol(kCFCoreFoundationVersionNumber >= 800 ? "MGGetBoolAnswer" : "GSSystemHasCapability");
10200 BOOL (*GSSystemHasCapability)(CFStringRef) = reinterpret_cast<BOOL (*)(CFStringRef)>(dlsym(RTLD_DEFAULT, symbol));
c73d524b
JF
10201 bool fast = GSSystemHasCapability != NULL && GSSystemHasCapability(CFSTR("armv7"));
10202
c73d524b 10203 PulseInterval_ = fast ? 50000 : 500000;
8731fdb0 10204
670a0494 10205 Colon_ = UCLocalize("COLON_DELIMITED");
72fb3616 10206 Elision_ = UCLocalize("ELISION");
670a0494
JF
10207 Error_ = UCLocalize("ERROR");
10208 Warning_ = UCLocalize("WARNING");
10209
d6dad1b4 10210 _trace();
77df4f82 10211 int value(UIApplicationMain(argc, argv, @"Cydia", @"Cydia"));
36bb2ca2
JF
10212
10213 CGColorSpaceRelease(space_);
199d0ba5 10214 CFRelease(Locale_);
36bb2ca2 10215
d13edf44 10216 [pool release];
36bb2ca2 10217 return value;
6d166849 10218}