source: trunk/macosx/Controller.m @ 3690

Last change on this file since 3690 was 3690, checked in by livings124, 15 years ago

stop timers and notification checking on quit right away (real this time)

  • Property svn:keywords set to Date Rev Author Id
File size: 127.6 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 3690 2007-11-02 03:15:12Z livings124 $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import <IOKit/IOMessage.h>
26
27#import "Controller.h"
28#import "Torrent.h"
29#import "TorrentCell.h"
30#import "TorrentTableView.h"
31#import "CreatorWindowController.h"
32#import "AboutWindowController.h"
33#import "NSApplicationAdditions.h"
34#import "NSStringAdditions.h"
35#import "UKKQueue.h"
36#import "ActionMenuSpeedToDisplayLimitTransformer.h"
37#import "ActionMenuRatioToDisplayRatioTransformer.h"
38#import "ExpandedPathToPathTransformer.h"
39#import "ExpandedPathToIconTransformer.h"
40#import "SpeedLimitToTurtleIconTransformer.h"
41
42#import <Sparkle/Sparkle.h>
43
44#define TOOLBAR_CREATE          @"Toolbar Create"
45#define TOOLBAR_OPEN            @"Toolbar Open"
46#define TOOLBAR_REMOVE          @"Toolbar Remove"
47#define TOOLBAR_INFO            @"Toolbar Info"
48#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
49#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
50#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
51#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
52#define TOOLBAR_FILTER          @"Toolbar Toggle Filter"
53
54#define SORT_DATE       @"Date"
55#define SORT_NAME       @"Name"
56#define SORT_STATE      @"State"
57#define SORT_PROGRESS   @"Progress"
58#define SORT_TRACKER    @"Tracker"
59#define SORT_ORDER      @"Order"
60
61#define FILTER_NONE     @"None"
62#define FILTER_DOWNLOAD @"Download"
63#define FILTER_SEED     @"Seed"
64#define FILTER_PAUSE    @"Pause"
65
66#define FILTER_TYPE_NAME    @"Name"
67#define FILTER_TYPE_TRACKER @"Tracker"
68
69#define FILTER_TYPE_TAG_NAME    401
70#define FILTER_TYPE_TAG_TRACKER 402
71
72#define GROWL_DOWNLOAD_COMPLETE @"Download Complete"
73#define GROWL_SEEDING_COMPLETE  @"Seeding Complete"
74#define GROWL_AUTO_ADD          @"Torrent Auto Added"
75#define GROWL_AUTO_SPEED_LIMIT  @"Speed Limit Auto Changed"
76
77#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
78
79#define ROW_HEIGHT_REGULAR      65.0
80#define ROW_HEIGHT_SMALL        40.0
81#define WINDOW_REGULAR_WIDTH    468.0
82
83#define SEARCH_FILTER_MIN_WIDTH 55.0
84#define SEARCH_FILTER_MAX_WIDTH 115.0
85
86#define UPDATE_UI_SECONDS           1.0
87#define AUTO_SPEED_LIMIT_SECONDS    5.0
88
89#define DOCK_SEEDING_TAG        101
90#define DOCK_DOWNLOADING_TAG    102
91
92#define SUPPORT_FOLDER  @"/Library/Application Support/Transmission/Transfers.plist"
93
94#define WEBSITE_URL @"http://transmission.m0k.org/"
95#define FORUM_URL   @"http://transmission.m0k.org/forum/"
96#define DONATE_URL  @"http://transmission.m0k.org/donate.php"
97
98void sleepCallBack(void * controller, io_service_t y, natural_t messageType, void * messageArgument)
99{
100    [(Controller *)controller sleepCallBack: messageType argument: messageArgument];
101}
102
103@implementation Controller
104
105+ (void) initialize
106{
107    //make sure another Transmission.app isn't running already
108    NSString * bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
109    int processIdentifier = [[NSProcessInfo processInfo] processIdentifier];
110
111    NSDictionary * dic;
112    NSEnumerator * enumerator = [[[NSWorkspace sharedWorkspace] launchedApplications] objectEnumerator];
113    while ((dic = [enumerator nextObject]))
114    {
115        if ([[dic objectForKey: @"NSApplicationBundleIdentifier"] isEqualToString: bundleIdentifier]
116            && [[dic objectForKey: @"NSApplicationProcessIdentifier"] intValue] != processIdentifier)
117        {
118            NSAlert * alert = [[NSAlert alloc] init];
119            [alert addButtonWithTitle: NSLocalizedString(@"Quit", "Transmission already running alert -> button")];
120            [alert setMessageText: NSLocalizedString(@"Transmission is already running.",
121                                                    "Transmission already running alert -> title")];
122            [alert setInformativeText: NSLocalizedString(@"There is already a copy of Transmission running. "
123                "This copy cannot be opened until that instance is quit.", "Transmission already running alert -> message")];
124            [alert setAlertStyle: NSWarningAlertStyle];
125           
126            [alert runModal];
127            [alert release];
128           
129            //kill ourselves right away
130            exit(0);
131        }
132    }
133   
134    [[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile:
135        [[NSBundle mainBundle] pathForResource: @"Defaults" ofType: @"plist"]]];
136   
137    //set custom value transformers
138    ActionMenuSpeedToDisplayLimitTransformer * limitTransformer =
139                        [[[ActionMenuSpeedToDisplayLimitTransformer alloc] init] autorelease];
140    [NSValueTransformer setValueTransformer: limitTransformer forName: @"ActionMenuSpeedToDisplayLimitTransformer"];
141   
142    ActionMenuRatioToDisplayRatioTransformer * ratioTransformer =
143                        [[[ActionMenuRatioToDisplayRatioTransformer alloc] init] autorelease];
144    [NSValueTransformer setValueTransformer: ratioTransformer forName: @"ActionMenuRatioToDisplayRatioTransformer"];
145   
146    ExpandedPathToPathTransformer * pathTransformer =
147                        [[[ExpandedPathToPathTransformer alloc] init] autorelease];
148    [NSValueTransformer setValueTransformer: pathTransformer forName: @"ExpandedPathToPathTransformer"];
149   
150    ExpandedPathToIconTransformer * iconTransformer =
151                        [[[ExpandedPathToIconTransformer alloc] init] autorelease];
152    [NSValueTransformer setValueTransformer: iconTransformer forName: @"ExpandedPathToIconTransformer"];
153   
154    SpeedLimitToTurtleIconTransformer * speedLimitIconTransformer =
155                        [[[SpeedLimitToTurtleIconTransformer alloc] init] autorelease];
156    [NSValueTransformer setValueTransformer: speedLimitIconTransformer forName: @"SpeedLimitToTurtleIconTransformer"];
157}
158
159- (id) init
160{
161    if ((self = [super init]))
162    {
163        fLib = tr_init("macosx");
164       
165        [NSApp setDelegate: self];
166       
167        fTorrents = [[NSMutableArray alloc] init];
168        fDisplayedTorrents = [[NSMutableArray alloc] init];
169       
170        fDefaults = [NSUserDefaults standardUserDefaults];
171       
172        fMessageController = [[MessageWindowController alloc] init];
173        fInfoController = [[InfoWindowController alloc] init];
174        fPrefsController = [[PrefsController alloc] initWithHandle: fLib];
175       
176        fBadger = [[Badger alloc] initWithLib: fLib];
177       
178        fIPCController = [[IPCController alloc] init];
179        [fIPCController setDelegate: self];
180        fRemoteQuit = NO;
181       
182        [GrowlApplicationBridge setGrowlDelegate: self];
183        [[UKKQueue sharedFileWatcher] setDelegate: self];
184    }
185    return self;
186}
187
188- (void) awakeFromNib
189{
190    [fBottomBar setBackgroundImage: [NSImage imageNamed: @"BottomBorder.png"]];
191   
192    NSToolbar * toolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
193    [toolbar setDelegate: self];
194    [toolbar setAllowsUserCustomization: YES];
195    [toolbar setAutosavesConfiguration: YES];
196    [fWindow setToolbar: toolbar];
197    [toolbar release];
198   
199    [fWindow setDelegate: self]; //do manually to avoid placement issue
200   
201    [fWindow makeFirstResponder: fTableView];
202    [fWindow setExcludedFromWindowsMenu: YES];
203    [fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
204   
205    //set table size
206    if ([fDefaults boolForKey: @"SmallView"])
207        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
208   
209    //window min height
210    NSSize contentMinSize = [fWindow contentMinSize];
211    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
212                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
213    [fWindow setContentMinSize: contentMinSize];
214   
215    //set info and filter keyboard shortcuts
216    unichar rightChar = NSRightArrowFunctionKey, leftChar = NSLeftArrowFunctionKey;
217    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
218    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
219   
220    [fNextFilterItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
221    [fNextFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
222    [fPrevFilterItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
223    [fPrevFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
224   
225    //set up filter bar
226    NSView * contentView = [fWindow contentView];
227    NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
228    [fFilterBar setHidden: YES];
229   
230    NSRect filterBarFrame = [fFilterBar frame];
231    filterBarFrame.size.width = windowSize.width;
232    [fFilterBar setFrame: filterBarFrame];
233   
234    [contentView addSubview: fFilterBar];
235    [fFilterBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
236
237    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
238   
239    //set up status bar
240    [fStatusBar setHidden: YES];
241   
242    NSRect statusBarFrame = [fStatusBar frame];
243    statusBarFrame.size.width = windowSize.width;
244    [fStatusBar setFrame: statusBarFrame];
245
246    [contentView addSubview: fStatusBar];
247    [fStatusBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
248    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
249
250    [fActionButton setToolTip: NSLocalizedString(@"Shortcuts for changing global settings.",
251                                "Main window -> 1st bottom left button (action) tooltip")];
252    [fSpeedLimitButton setToolTip: NSLocalizedString(@"Speed Limit overrides the total bandwidth limits with its own limits.",
253                                "Main window -> 2nd bottom left button (turtle) tooltip")];
254   
255    [fPrefsController setUpdater: fUpdater];
256   
257    [fTableView setTorrents: fDisplayedTorrents];
258    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
259   
260    [fTableView registerForDraggedTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE]];
261    [fWindow registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
262
263    //register for sleep notifications
264    IONotificationPortRef notify;
265    io_object_t iterator;
266    if ((fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator)) != 0)
267        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopCommonModes);
268    else
269        NSLog(@"Could not IORegisterForSystemPower");
270   
271    //load previous transfers
272    NSArray * history = [[NSArray alloc] initWithContentsOfFile: [NSHomeDirectory() stringByAppendingPathComponent: SUPPORT_FOLDER]];
273   
274    //old version saved transfer info in prefs file
275    if (!history)
276    {
277        if ((history = [fDefaults arrayForKey: @"History"]))
278            [history retain];
279        [fDefaults removeObjectForKey: @"History"];
280    }
281   
282    if (history)
283    {
284        Torrent * torrent;
285        NSDictionary * historyItem;
286        NSEnumerator * enumerator = [history objectEnumerator];
287        while ((historyItem = [enumerator nextObject]))
288            if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
289            {
290                [fTorrents addObject: torrent];
291                [torrent release];
292            }
293       
294        [history release];
295    }
296   
297    //set sort
298    NSString * sortType = [fDefaults stringForKey: @"Sort"];
299   
300    NSMenuItem * currentSortItem, * currentSortActionItem;
301    if ([sortType isEqualToString: SORT_NAME])
302    {
303        currentSortItem = fNameSortItem;
304        currentSortActionItem = fNameSortActionItem;
305    }
306    else if ([sortType isEqualToString: SORT_STATE])
307    {
308        currentSortItem = fStateSortItem;
309        currentSortActionItem = fStateSortActionItem;
310    }
311    else if ([sortType isEqualToString: SORT_PROGRESS])
312    {
313        currentSortItem = fProgressSortItem;
314        currentSortActionItem = fProgressSortActionItem;
315    }
316    else if ([sortType isEqualToString: SORT_TRACKER])
317    {
318        currentSortItem = fTrackerSortItem;
319        currentSortActionItem = fTrackerSortActionItem;
320    }
321    else if ([sortType isEqualToString: SORT_ORDER])
322    {
323        currentSortItem = fOrderSortItem;
324        currentSortActionItem = fOrderSortActionItem;
325    }
326    else
327    {
328        //safety
329        if (![sortType isEqualToString: SORT_DATE])
330            [fDefaults setObject: SORT_DATE forKey: @"Sort"];
331        currentSortItem = fDateSortItem;
332        currentSortActionItem = fDateSortActionItem;
333    }
334    [currentSortItem setState: NSOnState];
335    [currentSortActionItem setState: NSOnState];
336   
337    //set filter
338    NSString * filterType = [fDefaults stringForKey: @"Filter"];
339   
340    FilterBarButton * currentFilterButton;
341    if ([filterType isEqualToString: FILTER_PAUSE])
342        currentFilterButton = fPauseFilterButton;
343    else if ([filterType isEqualToString: FILTER_SEED])
344        currentFilterButton = fSeedFilterButton;
345    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
346        currentFilterButton = fDownloadFilterButton;
347    else
348    {
349        //safety
350        if (![filterType isEqualToString: FILTER_NONE])
351            [fDefaults setObject: FILTER_NONE forKey: @"Filter"];
352        currentFilterButton = fNoFilterButton;
353    }
354    [currentFilterButton setState: NSOnState];
355   
356    //set filter search type
357    NSString * filterSearchType = [fDefaults stringForKey: @"FilterSearchType"];
358   
359    NSMenu * filterSearchMenu = [[fSearchFilterField cell] searchMenuTemplate];
360    NSString * filterSearchTypeTitle;
361    if ([filterSearchType isEqualToString: FILTER_TYPE_TRACKER])
362        filterSearchTypeTitle = [[filterSearchMenu itemWithTag: FILTER_TYPE_TAG_TRACKER] title];
363    else
364    {
365        //safety
366        if (![filterType isEqualToString: FILTER_TYPE_NAME])
367            [fDefaults setObject: FILTER_TYPE_NAME forKey: @"FilterSearchType"];
368        filterSearchTypeTitle = [[filterSearchMenu itemWithTag: FILTER_TYPE_TAG_NAME] title];
369    }
370    [[fSearchFilterField cell] setPlaceholderString: filterSearchTypeTitle];
371   
372    //observe notifications
373    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
374   
375    [nc addObserver: self selector: @selector(updateUI)
376                    name: @"UpdateUI" object: nil];
377   
378    [nc addObserver: self selector: @selector(torrentFinishedDownloading:)
379                    name: @"TorrentFinishedDownloading" object: nil];
380   
381    [nc addObserver: self selector: @selector(torrentRestartedDownloading:)
382                    name: @"TorrentRestartedDownloading" object: nil];
383   
384    //avoids need of setting delegate
385    [nc addObserver: self selector: @selector(torrentTableViewSelectionDidChange:)
386                    name: NSTableViewSelectionDidChangeNotification object: fTableView];
387   
388    [nc addObserver: self selector: @selector(updateControlTint:)
389                    name: NSControlTintDidChangeNotification object: nil];
390   
391    [nc addObserver: self selector: @selector(prepareForUpdate:)
392                    name: SUUpdaterWillRestartNotification object: nil];
393    fUpdateInProgress = NO;
394   
395    [nc addObserver: self selector: @selector(autoSpeedLimitChange:)
396                    name: @"AutoSpeedLimitChange" object: nil];
397   
398    [nc addObserver: self selector: @selector(changeAutoImport)
399                    name: @"AutoImportSettingChange" object: nil];
400   
401    [nc addObserver: self selector: @selector(setWindowSizeToFit)
402                    name: @"AutoSizeSettingChange" object: nil];
403   
404    [nc addObserver: fWindow selector: @selector(makeKeyWindow)
405                    name: @"MakeWindowKey" object: nil];
406   
407    //check if torrent should now start
408    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
409                    name: @"TorrentStoppedForRatio" object: nil];
410   
411    [nc addObserver: self selector: @selector(updateTorrentsInQueue)
412                    name: @"UpdateQueue" object: nil];
413   
414    //change that just impacts the dock badge
415    [nc addObserver: self selector: @selector(updateDockBadge:)
416                    name: @"DockBadgeChange" object: nil];
417   
418    //open newly created torrent file
419    [nc addObserver: self selector: @selector(beginCreateFile:)
420                    name: @"BeginCreateTorrentFile" object: nil];
421   
422    //open newly created torrent file
423    [nc addObserver: self selector: @selector(openCreatedFile:)
424                    name: @"OpenCreatedTorrentFile" object: nil];
425
426    //timer to update the interface every second
427    [self updateUI];
428    fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_UI_SECONDS target: self
429        selector: @selector(updateUI) userInfo: nil repeats: YES];
430    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
431    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
432   
433    [self applyFilter: nil];
434   
435    [fWindow makeKeyAndOrderFront: nil];
436   
437    if ([fDefaults boolForKey: @"InfoVisible"])
438        [self showInfo: nil];
439   
440    //timer to auto toggle speed limit
441    [self autoSpeedLimitChange: nil];
442    fSpeedLimitTimer = [NSTimer scheduledTimerWithTimeInterval: AUTO_SPEED_LIMIT_SECONDS target: self
443        selector: @selector(autoSpeedLimit) userInfo: nil repeats: YES];
444    [[NSRunLoop currentRunLoop] addTimer: fSpeedLimitTimer forMode: NSModalPanelRunLoopMode];
445    [[NSRunLoop currentRunLoop] addTimer: fSpeedLimitTimer forMode: NSEventTrackingRunLoopMode];
446}
447
448- (void) applicationDidFinishLaunching: (NSNotification *) notification
449{
450    [NSApp setServicesProvider: self];
451   
452    //register for dock icon drags
453    [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self
454        andSelector: @selector(handleOpenContentsEvent:replyEvent:)
455        forEventClass: kCoreEventClass andEventID: kAEOpenContents];
456   
457    //auto importing
458    [self checkAutoImportDirectory];
459   
460    //debug warning
461    if ([fDefaults boolForKey: @"WarningDebug"] && [fDefaults integerForKey: @"MessageLevel"] == TR_MSG_DBG)
462    {
463        NSAlert * alert = [[NSAlert alloc] init];
464        [alert setMessageText: NSLocalizedString(@"The Message Log is set to \"Debug\"", "Debug log alert -> message")];
465        [alert setInformativeText: NSLocalizedString(@"Continuous use at this level might increase memory usage."
466                                    " This setting can be changed in the Message Log window (accessible from the Window menu).",
467                                    "Debug log alert -> informative message")];
468        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Debug log alert -> button")];
469       
470        BOOL onLeopard = [NSApp isOnLeopardOrBetter];
471        if (onLeopard)
472            [alert setShowsSuppressionButton: YES];
473        else
474            [alert addButtonWithTitle: NSLocalizedString(@"Don't Alert Again", "Debug log alert -> button")];
475       
476        NSInteger result = [alert runModal];
477        if ((onLeopard ? [[alert suppressionButton] state] == NSOnState : result == NSAlertSecondButtonReturn))
478            [fDefaults setBool: NO forKey: @"WarningDebug"];
479        [alert release];
480    }
481}
482
483- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
484{
485    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
486        [fWindow makeKeyAndOrderFront: nil];
487    return NO;
488}
489
490- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
491{
492    if (!fUpdateInProgress && !fRemoteQuit && [fDefaults boolForKey: @"CheckQuit"])
493    {
494        int active = 0, downloading = 0;
495        Torrent * torrent;
496        NSEnumerator * enumerator = [fTorrents objectEnumerator];
497        while ((torrent = [enumerator nextObject]))
498            if ([torrent isActive])
499            {
500                active++;
501                if (![torrent allDownloaded])
502                    downloading++;
503            }
504       
505        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
506        {
507            NSString * message = active == 1
508                ? NSLocalizedString(@"There is an active transfer. Do you really want to quit?",
509                    "Confirm Quit panel -> message")
510                : [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers. Do you really want to quit?",
511                    "Confirm Quit panel -> message"), active];
512
513            NSBeginAlertSheet(NSLocalizedString(@"Confirm Quit", "Confirm Quit panel -> title"),
514                                NSLocalizedString(@"Quit", "Confirm Quit panel -> button"),
515                                NSLocalizedString(@"Cancel", "Confirm Quit panel -> button"), nil, fWindow, self,
516                                @selector(quitSheetDidEnd:returnCode:contextInfo:), nil, nil, message);
517            return NSTerminateLater;
518        }
519    }
520
521    return NSTerminateNow;
522}
523
524- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
525{
526    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
527}
528
529- (void) applicationWillTerminate: (NSNotification *) notification
530{
531    //stop timers and notification checking
532    [[NSNotificationCenter defaultCenter] removeObserver: self];
533   
534    [fTimer invalidate];
535    [fSpeedLimitTimer invalidate];
536    if (fAutoImportTimer)
537    {   
538        if ([fAutoImportTimer isValid])
539            [fAutoImportTimer invalidate];
540        [fAutoImportTimer release];
541    }
542   
543    //remove all torrent downloads
544    if (fPendingTorrentDownloads)
545    {
546        NSEnumerator * downloadEnumerator = [[fPendingTorrentDownloads allValues] objectEnumerator];
547        NSDictionary * downloadDict;
548        NSURLDownload * download;
549        while ((downloadDict = [downloadEnumerator nextObject]))
550        {
551            download = [downloadDict objectForKey: @"Download"];
552            [download cancel];
553            [download release];
554        }
555        [fPendingTorrentDownloads removeAllObjects];
556    }
557   
558    //remove all remaining torrent files in the temporary directory
559    if (fTempTorrentFiles)
560    {
561        NSEnumerator * torrentEnumerator = [fTempTorrentFiles objectEnumerator];
562        NSString * path;
563        while ((path = [torrentEnumerator nextObject]))
564            [[NSFileManager defaultManager] removeFileAtPath: path handler: nil];
565        [fTempTorrentFiles removeAllObjects];
566    }
567   
568    //remember window states and close all windows
569    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
570    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
571    [self showStatusBar: NO animate: NO];
572    [self showFilterBar: NO animate: NO];
573   
574    //save history
575    [self updateTorrentHistory];
576   
577    //make sure torrents are closed
578    NSEnumerator * enumerator = [fTorrents objectEnumerator];
579    Torrent * torrent;
580    while ((torrent = [enumerator nextObject]))
581        [torrent closeTorrent];
582   
583    [fDisplayedTorrents removeAllObjects];
584    [fTorrents removeAllObjects];
585   
586    //disable NAT traversal
587    tr_natTraversalEnable(fLib, 0);
588   
589    //clear badge
590    [fBadger clearBadge];
591   
592    //wait for running transfers to stop and for NAT to be disabled (5 second timeout)
593    NSDate * startDate = [NSDate date];
594   
595    tr_close(fLib);
596   
597    while ([startDate timeIntervalSinceNow] >= -5.0 && tr_handleStatus(fLib)->natTraversalStatus != TR_NAT_TRAVERSAL_DISABLED)
598        usleep(100000);
599   
600    [fInfoController release];
601    [fMessageController release];
602    [fPrefsController release];
603   
604    [fTorrents release];
605    [fDisplayedTorrents release];
606   
607    [fBadger release];
608    [fOverlayWindow release];
609    [fIPCController release];
610   
611    [fAutoImportedNames release];
612    [fPendingTorrentDownloads release];
613    [fTempTorrentFiles release];
614}
615
616- (void) handleOpenContentsEvent: (NSAppleEventDescriptor *) event replyEvent: (NSAppleEventDescriptor *) replyEvent
617{
618    NSString * urlString = nil;
619
620    NSAppleEventDescriptor * directObject = [event paramDescriptorForKeyword: keyDirectObject];
621    if ([directObject descriptorType] == typeAEList)
622    {
623        unsigned i;
624        for (i = 1; i <= [directObject numberOfItems]; i++)
625            if ((urlString = [[directObject descriptorAtIndex: i] stringValue]))
626                break;
627    }
628    else
629        urlString = [directObject stringValue];
630   
631    if (urlString)
632        [self openURL: [[[NSURL alloc] initWithString: urlString] autorelease]];
633}
634
635- (void) download: (NSURLDownload *) download decideDestinationWithSuggestedFilename: (NSString *) suggestedName
636{
637    if ([[suggestedName pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
638    {
639        [download cancel];
640       
641        NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
642            @"Download not a torrent -> title"), [NSString stringWithFormat:
643            NSLocalizedString(@"It appears that the file \"%@\" from %@ is not a torrent file.",
644            @"Download not a torrent -> message"), suggestedName,
645            [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]],
646            NSLocalizedString(@"OK", @"Download not a torrent -> button"), nil, nil);
647       
648        [download release];
649    }
650    else
651        [download setDestination: [NSTemporaryDirectory() stringByAppendingPathComponent: [suggestedName lastPathComponent]]
652                    allowOverwrite: NO];
653}
654
655-(void) download: (NSURLDownload *) download didCreateDestination: (NSString *) path
656{
657    if (!fPendingTorrentDownloads)
658        fPendingTorrentDownloads = [[NSMutableDictionary alloc] init];
659   
660    [fPendingTorrentDownloads setObject: [NSDictionary dictionaryWithObjectsAndKeys:
661                    path, @"Path", download, @"Download", nil] forKey: [[download request] URL]];
662}
663
664- (void) download: (NSURLDownload *) download didFailWithError: (NSError *) error
665{
666    NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
667        @"Torrent download error -> title"), [NSString stringWithFormat:
668        NSLocalizedString(@"The torrent could not be downloaded from %@ because an error occurred (%@).",
669        @"Torrent download failed -> message"),
670        [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding],
671        [error localizedDescription]], NSLocalizedString(@"OK", @"Torrent download failed -> button"), nil, nil);
672   
673    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
674    [download release];
675}
676
677- (void) downloadDidFinish: (NSURLDownload *) download
678{
679    NSString * path = [[fPendingTorrentDownloads objectForKey: [[download request] URL]] objectForKey: @"Path"];
680   
681    [self openFiles: [NSArray arrayWithObject: path] forcePath: nil ignoreDownloadFolder:
682        ![[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Constant"] deleteTorrentFile: TORRENT_FILE_DELETE];
683   
684    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
685    [download release];
686   
687    //delete temp torrent file on quit
688    if (!fTempTorrentFiles)
689        fTempTorrentFiles = [[NSMutableArray alloc] init];
690    [fTempTorrentFiles addObject: path];
691}
692
693- (void) application: (NSApplication *) app openFiles: (NSArray *) filenames
694{
695    [self openFiles: filenames forcePath: nil ignoreDownloadFolder: NO deleteTorrentFile: TORRENT_FILE_DEFAULT];
696}
697
698- (void) openFiles: (NSArray *) filenames forcePath: (NSString *) path ignoreDownloadFolder: (BOOL) ignore
699        deleteTorrentFile: (torrentFileState) deleteTorrent
700{
701    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
702    if (ignore || (!path && [downloadChoice isEqualToString: @"Ask"]))
703    {
704        [self openFilesAsk: [filenames mutableCopy] deleteTorrentFile: deleteTorrent];
705        return;
706    }
707   
708    #warning make submethod
709    if (!path && [fDefaults boolForKey: @"UseIncompleteDownloadFolder"]
710        && access([[[fDefaults stringForKey: @"IncompleteDownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
711    {
712        NSOpenPanel * panel = [NSOpenPanel openPanel];
713       
714        [panel setPrompt: NSLocalizedString(@"Select", "Default incomplete folder cannot be used alert -> prompt")];
715        [panel setAllowsMultipleSelection: NO];
716        [panel setCanChooseFiles: NO];
717        [panel setCanChooseDirectories: YES];
718        [panel setCanCreateDirectories: YES];
719
720        [panel setMessage: NSLocalizedString(@"The incomplete folder cannot be used. Choose a new location or cancel for none.",
721                                        "Default incomplete folder cannot be used alert -> message")];
722       
723        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
724                                        filenames, @"Filenames",
725                                        [NSNumber numberWithBool: ignore], @"Ignore",
726                                        [NSNumber numberWithInt: deleteTorrent], @"DeleteTorrent", nil];
727       
728        [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
729                didEndSelector: @selector(incompleteChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
730        return;
731    }
732    if (!path && [downloadChoice isEqualToString: @"Constant"]
733        && access([[[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
734    {
735        NSOpenPanel * panel = [NSOpenPanel openPanel];
736       
737        [panel setPrompt: NSLocalizedString(@"Select", "Default folder cannot be used alert -> prompt")];
738        [panel setAllowsMultipleSelection: NO];
739        [panel setCanChooseFiles: NO];
740        [panel setCanChooseDirectories: YES];
741        [panel setCanCreateDirectories: YES];
742
743        [panel setMessage: NSLocalizedString(@"The download folder cannot be used. Choose a new location.",
744                                        "Default folder cannot be used alert -> message")];
745       
746        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
747                                        filenames, @"Filenames",
748                                        [NSNumber numberWithBool: ignore], @"Ignore",
749                                        [NSNumber numberWithInt: deleteTorrent], @"DeleteTorrent", nil];
750       
751        [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
752                didEndSelector: @selector(downloadChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
753        return;
754    }
755   
756    Torrent * torrent;
757    NSString * torrentPath;
758    tr_info info;
759    NSEnumerator * enumerator = [filenames objectEnumerator];
760    while ((torrentPath = [enumerator nextObject]))
761    {
762        NSString * location;
763        if (path)
764            location = path;
765        else if ([downloadChoice isEqualToString: @"Constant"])
766            location = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
767        else
768            location = [torrentPath stringByDeletingLastPathComponent];
769       
770        if (tr_torrentParse(fLib, [torrentPath UTF8String], NULL, &info) == TR_EDUPLICATE)
771        {
772            [self duplicateOpenAlert: [NSString stringWithUTF8String: info.name]];
773            tr_metainfoFree(&info);
774            continue;
775        }
776        tr_metainfoFree(&info);
777       
778        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath location: location deleteTorrentFile: deleteTorrent lib: fLib]))
779            continue;
780       
781        //add it to the "File > Open Recent" menu
782        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
783       
784        [torrent update];
785        [fTorrents addObject: torrent];
786        [torrent release];
787    }
788
789    [self updateTorrentsInQueue];
790}
791
792- (void) openCreatedFile: (NSNotification *) notification
793{
794    NSDictionary * dict = [notification userInfo];
795    [self openFiles: [NSArray arrayWithObject: [dict objectForKey: @"File"]] forcePath: [dict objectForKey: @"Path"]
796            ignoreDownloadFolder: NO deleteTorrentFile: TORRENT_FILE_SAVE];
797    [dict release];
798}
799
800- (void) incompleteChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
801{
802    if (code == NSOKButton)
803        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"IncompleteDownloadFolder"];
804    else
805        [fDefaults setBool: NO forKey: @"UseIncompleteDownloadFolder"];
806   
807    [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
808}
809
810- (void) downloadChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
811{
812    if (code == NSOKButton)
813    {
814        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"DownloadFolder"];
815        [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
816    }
817    else
818        [dictionary release];
819}
820
821- (void) openFilesWithDict: (NSDictionary *) dictionary
822{
823    [self openFiles: [dictionary objectForKey: @"Filenames"] forcePath: nil
824        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue]
825        deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue]];
826   
827    [dictionary release];
828}
829
830//called by the main open method to show sheet for choosing download location
831- (void) openFilesAsk: (NSMutableArray *) files deleteTorrentFile: (torrentFileState) deleteTorrent
832{
833    //determine the next file that can be opened
834    NSString * torrentPath;
835    int canAdd;
836    tr_info info;
837    while ([files count] > 0)
838    {
839        torrentPath = [[files objectAtIndex: 0] retain];
840        canAdd = tr_torrentParse(fLib, [torrentPath UTF8String], NULL, &info);
841        if (canAdd == TR_OK)
842            break;
843        else if (canAdd == TR_EDUPLICATE)
844            [self duplicateOpenAlert: [NSString stringWithUTF8String: info.name]];
845        else;
846       
847        tr_metainfoFree(&info);
848        [files removeObjectAtIndex: 0];
849    }
850   
851    //no files left to open
852    if ([files count] <= 0)
853    {
854        [files release];
855        [self updateTorrentHistory];
856        return;
857    }
858   
859    [files removeObjectAtIndex: 0];
860   
861    NSOpenPanel * panel = [NSOpenPanel openPanel];
862
863    [panel setPrompt: NSLocalizedString(@"Select", "Open torrent -> prompt")];
864    [panel setAllowsMultipleSelection: NO];
865    [panel setCanChooseFiles: NO];
866    [panel setCanChooseDirectories: YES];
867    [panel setCanCreateDirectories: YES];
868   
869    [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
870                        "Open torrent -> select destination folder"), [NSString stringWithUTF8String: info.name]]];
871    tr_metainfoFree(&info);
872   
873    NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: torrentPath, @"Path",
874                                    files, @"Files", [NSNumber numberWithInt: deleteTorrent], @"DeleteTorrent", nil];
875    [torrentPath release];
876   
877    [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
878            didEndSelector: @selector(folderChoiceClosed:returnCode:contextInfo:) contextInfo: dictionary];
879}
880
881- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
882{
883    if (code == NSOKButton)
884    {
885        NSString * torrentPath = [dictionary objectForKey: @"Path"];
886        Torrent * torrent = [[Torrent alloc] initWithPath: torrentPath location: [[openPanel filenames] objectAtIndex: 0]
887                            deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue] lib: fLib];
888       
889        //add it to the "File > Open Recent" menu
890        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
891       
892        [torrent update];
893        [fTorrents addObject: torrent];
894        [torrent release];
895       
896        [self updateTorrentsInQueue];
897    }
898   
899    [self performSelectorOnMainThread: @selector(openFilesAskWithDict:) withObject: dictionary waitUntilDone: NO];
900}
901
902- (void) openFilesAskWithDict: (NSDictionary *) dictionary
903{
904    [self openFilesAsk: [dictionary objectForKey: @"Files"]
905            deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue]];
906    [dictionary release];
907}
908
909//called on by applescript
910- (void) open: (NSArray *) files
911{
912    [self performSelectorOnMainThread: @selector(openFiles:) withObject: files waitUntilDone: NO];
913}
914
915- (void) openFiles: (NSArray *) filenames
916{
917    [self openFiles: filenames forcePath: nil ignoreDownloadFolder: NO deleteTorrentFile: TORRENT_FILE_DEFAULT];
918}
919
920- (void) openShowSheet: (id) sender
921{
922    NSOpenPanel * panel = [NSOpenPanel openPanel];
923
924    [panel setAllowsMultipleSelection: YES];
925    [panel setCanChooseFiles: YES];
926    [panel setCanChooseDirectories: NO];
927
928    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
929        modalForWindow: fWindow modalDelegate: self didEndSelector: @selector(openSheetClosed:returnCode:contextInfo:)
930        contextInfo: [NSNumber numberWithBool: sender == fOpenIgnoreDownloadFolder]];
931}
932
933- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSNumber *) ignore
934{
935    if (code == NSOKButton)
936    {
937        NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
938                                        [panel filenames], @"Files", ignore, @"Ignore", nil];
939        [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: dictionary waitUntilDone: NO];
940    }
941}
942
943- (void) openFromSheet: (NSDictionary *) dictionary
944{
945    [self openFiles: [dictionary objectForKey: @"Files"] forcePath: nil
946        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue] deleteTorrentFile: TORRENT_FILE_DEFAULT];
947   
948    [dictionary release];
949}
950
951- (void) duplicateOpenAlert: (NSString *) name
952{
953    if (![fDefaults boolForKey: @"WarningDuplicate"])
954        return;
955   
956    NSAlert * alert = [[NSAlert alloc] init];
957    [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" is already running.",
958                            "Open duplicate alert -> title"), name]];
959    [alert setInformativeText:
960            NSLocalizedString(@"The torrent file cannot be opened because it is a duplicate of an already running transfer.",
961                            "Open duplicate alert -> message")];
962    [alert setAlertStyle: NSWarningAlertStyle];
963    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate alert -> button")];
964   
965    BOOL onLeopard = [NSApp isOnLeopardOrBetter];
966    if (onLeopard)
967        [alert setShowsSuppressionButton: YES];
968    else
969        [alert addButtonWithTitle: NSLocalizedString(@"Don't Alert Again", "Open duplicate alert -> button")];
970   
971    NSInteger result = [alert runModal];
972    if ((onLeopard ? [[alert suppressionButton] state] == NSOnState : result == NSAlertSecondButtonReturn))
973        [fDefaults setBool: NO forKey: @"WarningDuplicate"];
974    [alert release];
975}
976
977- (void) openURL: (NSURL *) url
978{
979    [[NSURLDownload alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: self];
980}
981
982- (void) openURLShowSheet: (id) sender
983{
984    [NSApp beginSheet: fURLSheetWindow modalForWindow: fWindow modalDelegate: self
985            didEndSelector: @selector(urlSheetDidEnd:returnCode:contextInfo:) contextInfo: nil];
986}
987
988- (void) openURLEndSheet: (id) sender
989{
990    [fURLSheetWindow orderOut: sender];
991    [NSApp endSheet: fURLSheetWindow returnCode: 1];
992}
993
994- (void) openURLCancelEndSheet: (id) sender
995{
996    [fURLSheetWindow orderOut: sender];
997    [NSApp endSheet: fURLSheetWindow returnCode: 0];
998}
999
1000- (void) urlSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
1001{
1002    [fURLSheetTextField selectText: self];
1003    if (returnCode != 1)
1004        return;
1005   
1006    NSString * urlString = [fURLSheetTextField stringValue];
1007    if (![urlString isEqualToString: @""])
1008    {
1009        if ([urlString rangeOfString: @"://"].location == NSNotFound)
1010        {
1011            if ([urlString rangeOfString: @"."].location == NSNotFound)
1012            {
1013                int beforeCom;
1014                if ((beforeCom = [urlString rangeOfString: @"/"].location) != NSNotFound)
1015                    urlString = [NSString stringWithFormat: @"http://www.%@.com/%@",
1016                                    [urlString substringToIndex: beforeCom],
1017                                    [urlString substringFromIndex: beforeCom + 1]];
1018                else
1019                    urlString = [NSString stringWithFormat: @"http://www.%@.com", urlString];
1020            }
1021            else
1022                urlString = [@"http://" stringByAppendingString: urlString];
1023        }
1024       
1025        NSURL * url = [NSURL URLWithString: urlString];
1026        [self performSelectorOnMainThread: @selector(openURL:) withObject: url waitUntilDone: NO];
1027    }
1028}
1029
1030- (void) createFile: (id) sender
1031{
1032    [CreatorWindowController createTorrentFile: fLib];
1033}
1034
1035- (void) resumeSelectedTorrents: (id) sender
1036{
1037    [self resumeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
1038}
1039
1040- (void) resumeAllTorrents: (id) sender
1041{
1042    [self resumeTorrents: fTorrents];
1043}
1044
1045- (void) resumeTorrents: (NSArray *) torrents
1046{
1047    NSEnumerator * enumerator = [torrents objectEnumerator];
1048    Torrent * torrent;
1049    while ((torrent = [enumerator nextObject]))
1050        [torrent setWaitToStart: YES];
1051   
1052    [self updateTorrentsInQueue];
1053}
1054
1055- (void) resumeSelectedTorrentsNoWait:  (id) sender
1056{
1057    [self resumeTorrentsNoWait: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
1058}
1059
1060- (void) resumeWaitingTorrents: (id) sender
1061{
1062    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1063   
1064    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1065    Torrent * torrent;
1066    while ((torrent = [enumerator nextObject]))
1067        if (![torrent isActive] && [torrent waitingToStart])
1068            [torrents addObject: torrent];
1069   
1070    [self resumeTorrentsNoWait: torrents];
1071}
1072
1073- (void) resumeTorrentsNoWait: (NSArray *) torrents
1074{
1075    //iterate through instead of all at once to ensure no conflicts
1076    NSEnumerator * enumerator = [torrents objectEnumerator];
1077    Torrent * torrent;
1078    while ((torrent = [enumerator nextObject]))
1079        [torrent startTransfer];
1080   
1081    [self updateUI];
1082    [self applyFilter: nil];
1083    [self updateTorrentHistory];
1084}
1085
1086- (void) stopSelectedTorrents: (id) sender
1087{
1088    [self stopTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
1089}
1090
1091- (void) stopAllTorrents: (id) sender
1092{
1093    [self stopTorrents: fTorrents];
1094}
1095
1096- (void) stopTorrents: (NSArray *) torrents
1097{
1098    //don't want any of these starting then stopping
1099    NSEnumerator * enumerator = [torrents objectEnumerator];
1100    Torrent * torrent;
1101    while ((torrent = [enumerator nextObject]))
1102        [torrent setWaitToStart: NO];
1103
1104    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
1105   
1106    [self updateUI];
1107    [self applyFilter: nil];
1108    [self updateTorrentHistory];
1109}
1110
1111- (void) removeTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
1112{
1113    [torrents retain];
1114    int active = 0, downloading = 0;
1115
1116    if ([fDefaults boolForKey: @"CheckRemove"])
1117    {
1118        Torrent * torrent;
1119        NSEnumerator * enumerator = [torrents objectEnumerator];
1120        while ((torrent = [enumerator nextObject]))
1121            if ([torrent isActive])
1122            {
1123                active++;
1124                if (![torrent isSeeding])
1125                    downloading++;
1126            }
1127
1128        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
1129        {
1130            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
1131                torrents, @"Torrents",
1132                [NSNumber numberWithBool: deleteData], @"DeleteData",
1133                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
1134
1135            NSString * title, * message;
1136           
1137            int selected = [fTableView numberOfSelectedRows];
1138            if (selected == 1)
1139            {
1140                NSString * torrentName = [[fDisplayedTorrents objectAtIndex: [fTableView selectedRow]] name];
1141               
1142                if (!deleteData && !deleteTorrent)
1143                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of \"%@\" from the transfer list.",
1144                                "Removal confirm panel -> title"), torrentName];
1145                else if (deleteData && !deleteTorrent)
1146                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of \"%@\" from the transfer list"
1147                                " and trash data file.", "Removal confirm panel -> title"), torrentName];
1148                else if (!deleteData && deleteTorrent)
1149                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of \"%@\" from the transfer list"
1150                                " and trash torrent file.", "Removal confirm panel -> title"), torrentName];
1151                else
1152                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of \"%@\" from the transfer list"
1153                            " and trash both data and torrent files.", "Removal confirm panel -> title"), torrentName];
1154               
1155                message = NSLocalizedString(@"This transfer is active."
1156                            " Once removed, continuing the transfer will require the torrent file."
1157                            " Do you really want to remove it?", "Removal confirm panel -> message");
1158            }
1159            else
1160            {
1161                if (!deleteData && !deleteTorrent)
1162                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of %d transfers"
1163                                " from the transfer list.", "Removal confirm panel -> title"), selected];
1164                else if (deleteData && !deleteTorrent)
1165                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of %d transfers"
1166                                " from the transfer list and trash data file.", "Removal confirm panel -> title"), selected];
1167                else if (!deleteData && deleteTorrent)
1168                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of %d transfers"
1169                                " from the transfer list and trash torrent file.", "Removal confirm panel -> title"), selected];
1170                else
1171                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of %d transfers"
1172                                " from the transfer list and trash both data and torrent files.",
1173                                "Removal confirm panel -> title"), selected];
1174               
1175                if (selected == active)
1176                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers.",
1177                                "Removal confirm panel -> message part 1"), active];
1178                else
1179                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d transfers (%d active).",
1180                                "Removal confirm panel -> message part 1"), selected, active];
1181                message = [message stringByAppendingString:
1182                    NSLocalizedString(@" Once removed, continuing the transfers will require the torrent files."
1183                    " Do you really want to remove them?", "Removal confirm panel -> message part 2")];
1184            }
1185
1186            NSBeginAlertSheet(title, NSLocalizedString(@"Remove", "Removal confirm panel -> button"),
1187                NSLocalizedString(@"Cancel", "Removal confirm panel -> button"), nil, fWindow, self,
1188                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
1189            return;
1190        }
1191    }
1192   
1193    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
1194}
1195
1196- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
1197{
1198    NSArray * torrents = [dict objectForKey: @"Torrents"];
1199    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
1200        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
1201    [dict release];
1202   
1203    if (returnCode == NSAlertDefaultReturn)
1204        [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
1205    else
1206        [torrents release];
1207}
1208
1209- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
1210{
1211    //don't want any of these starting then stopping
1212    NSEnumerator * enumerator = [torrents objectEnumerator];
1213    Torrent * torrent;
1214    while ((torrent = [enumerator nextObject]))
1215        [torrent setWaitToStart: NO];
1216
1217    NSNumber * lowestOrderValue = nil, * currentOrderValue;
1218
1219    enumerator = [torrents objectEnumerator];
1220    while ((torrent = [enumerator nextObject]))
1221    {
1222        [torrent stopTransfer];
1223
1224        if (deleteData)
1225            [torrent trashData];
1226        if (deleteTorrent)
1227            [torrent trashTorrent];
1228       
1229        //determine lowest order value
1230        currentOrderValue = [torrent orderValue];
1231        if (!lowestOrderValue || [lowestOrderValue compare: currentOrderValue] == NSOrderedDescending)
1232            lowestOrderValue = currentOrderValue;
1233       
1234        [torrent closeRemoveTorrent];
1235        [fTorrents removeObject: torrent];
1236        [fDisplayedTorrents removeObject: torrent];
1237    }
1238    [torrents release];
1239
1240    //reset the order values if necessary
1241    if ([lowestOrderValue intValue] < [fTorrents count])
1242    {
1243        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1244                                                @"orderValue" ascending: YES] autorelease];
1245        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1246
1247        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1248        [descriptors release];
1249
1250        int i;
1251        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
1252            [[tempTorrents objectAtIndex: i] setOrderValue: i];
1253    }
1254   
1255    [fTableView deselectAll: nil];
1256   
1257    [self updateUI];
1258    [self applyFilter: nil];
1259    [self updateTorrentHistory];
1260}
1261
1262- (void) removeNoDelete: (id) sender
1263{
1264    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1265                deleteData: NO deleteTorrent: NO];
1266}
1267
1268- (void) removeDeleteData: (id) sender
1269{
1270    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1271                deleteData: YES deleteTorrent: NO];
1272}
1273
1274- (void) removeDeleteTorrent: (id) sender
1275{
1276    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1277                deleteData: NO deleteTorrent: YES];
1278}
1279
1280- (void) removeDeleteDataAndTorrent: (id) sender
1281{
1282    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1283                deleteData: YES deleteTorrent: YES];
1284}
1285
1286- (void) moveDataFiles: (id) sender
1287{
1288    NSOpenPanel * panel = [NSOpenPanel openPanel];
1289    [panel setPrompt: NSLocalizedString(@"Select", "Move torrent -> prompt")];
1290    [panel setAllowsMultipleSelection: NO];
1291    [panel setCanChooseFiles: NO];
1292    [panel setCanChooseDirectories: YES];
1293    [panel setCanCreateDirectories: YES];
1294   
1295    NSArray * torrents = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] retain];
1296    int count = [torrents count];
1297    if (count == 1)
1298        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for \"%@\".",
1299                            "Move torrent -> select destination folder"), [[torrents objectAtIndex: 0] name]]];
1300    else
1301        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for %d data files.",
1302                            "Move torrent -> select destination folder"), count]];
1303       
1304    [panel beginSheetForDirectory: nil file: nil modalForWindow: fWindow modalDelegate: self
1305        didEndSelector: @selector(moveDataFileChoiceClosed:returnCode:contextInfo:) contextInfo: torrents];
1306}
1307
1308- (void) moveDataFileChoiceClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSArray *) torrents
1309{
1310    if (code == NSOKButton)
1311    {
1312        NSEnumerator * enumerator = [torrents objectEnumerator];
1313        Torrent * torrent;
1314        while ((torrent = [enumerator nextObject]))
1315            [torrent moveTorrentDataFileTo: [[panel filenames] objectAtIndex: 0]];
1316    }
1317   
1318    [torrents release];
1319}
1320
1321- (void) copyTorrentFiles: (id) sender
1322{
1323    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
1324            [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]]];
1325}
1326
1327- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
1328{
1329    if ([torrents count] <= 0)
1330    {
1331        [torrents release];
1332        return;
1333    }
1334
1335    Torrent * torrent = [torrents objectAtIndex: 0];
1336
1337    //warn user if torrent file can't be found
1338    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
1339    {
1340        NSAlert * alert = [[NSAlert alloc] init];
1341        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file copy alert -> button")];
1342        [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Copy of \"%@\" Cannot Be Created",
1343                                "Torrent file copy alert -> title"), [torrent name]]];
1344        [alert setInformativeText: [NSString stringWithFormat:
1345                NSLocalizedString(@"The torrent file (%@) cannot be found.", "Torrent file copy alert -> message"),
1346                                    [torrent torrentLocation]]];
1347        [alert setAlertStyle: NSWarningAlertStyle];
1348       
1349        [alert runModal];
1350        [alert release];
1351       
1352        [torrents removeObjectAtIndex: 0];
1353        [self copyTorrentFileForTorrents: torrents];
1354    }
1355    else
1356    {
1357        NSSavePanel * panel = [NSSavePanel savePanel];
1358        [panel setRequiredFileType: @"torrent"];
1359        [panel setCanSelectHiddenExtension: YES];
1360       
1361        [panel beginSheetForDirectory: nil file: [torrent name] modalForWindow: fWindow modalDelegate: self
1362            didEndSelector: @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
1363    }
1364}
1365
1366- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code contextInfo: (NSMutableArray *) torrents
1367{
1368    //copy torrent to new location with name of data file
1369    if (code == NSOKButton)
1370        [[torrents objectAtIndex: 0] copyTorrentFileTo: [panel filename]];
1371   
1372    [torrents removeObjectAtIndex: 0];
1373    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:) withObject: torrents waitUntilDone: NO];
1374}
1375
1376- (void) revealFile: (id) sender
1377{
1378    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1379    Torrent * torrent;
1380    while ((torrent = [enumerator nextObject]))
1381        [torrent revealData];
1382}
1383
1384- (void) announceSelectedTorrents: (id) sender
1385{
1386    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1387    Torrent * torrent;
1388    while ((torrent = [enumerator nextObject]))
1389    {
1390        if ([torrent canManualAnnounce])
1391            [torrent manualAnnounce];
1392    }
1393}
1394
1395- (void) resetCacheForSelectedTorrents: (id) sender
1396{
1397    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1398    Torrent * torrent;
1399    while ((torrent = [enumerator nextObject]))
1400        [torrent resetCache];
1401   
1402    [self applyFilter: nil];
1403}
1404
1405- (void) showPreferenceWindow: (id) sender
1406{
1407    NSWindow * window = [fPrefsController window];
1408    if (![window isVisible])
1409        [window center];
1410
1411    [window makeKeyAndOrderFront: nil];
1412}
1413
1414- (void) showAboutWindow: (id) sender
1415{
1416    [[AboutWindowController aboutController] showWindow: nil];
1417}
1418
1419- (void) showInfo: (id) sender
1420{
1421    if ([[fInfoController window] isVisible])
1422        [fInfoController close];
1423    else
1424    {
1425        [fInfoController updateInfoStats];
1426        [[fInfoController window] orderFront: nil];
1427    }
1428}
1429
1430- (void) setInfoTab: (id) sender
1431{
1432    if (sender == fNextInfoTabItem)
1433        [fInfoController setNextTab];
1434    else
1435        [fInfoController setPreviousTab];
1436}
1437
1438- (void) showMessageWindow: (id) sender
1439{
1440    [fMessageController showWindow: nil];
1441}
1442
1443- (void) updateUI
1444{
1445    #warning make main thread?
1446    [fTorrents makeObjectsPerformSelector: @selector(update)];
1447   
1448    if (![NSApp isHidden])
1449    {
1450        if ([fWindow isVisible])
1451        {
1452            //resort if necessary or just update the table
1453            NSString * sortType = [fDefaults stringForKey: @"Sort"];
1454            if ([sortType isEqualToString: SORT_PROGRESS] || [sortType isEqualToString: SORT_STATE]
1455                    || [sortType isEqualToString: SORT_TRACKER])
1456                [self sortTorrents];
1457            else
1458                [fTableView reloadData];
1459           
1460            //update the global DL/UL rates
1461            if (![fStatusBar isHidden])
1462            {
1463                float downloadRate, uploadRate;
1464                tr_torrentRates(fLib, & downloadRate, & uploadRate);
1465               
1466                [fTotalDLField setStringValue: [NSLocalizedString(@"Total DL: ", "Status bar -> total download")
1467                                                stringByAppendingString: [NSString stringForSpeed: downloadRate]]];
1468                [fTotalULField setStringValue: [NSLocalizedString(@"Total UL: ", "Status bar -> total upload")
1469                                                stringByAppendingString: [NSString stringForSpeed: uploadRate]]];
1470            }
1471        }
1472
1473        //update non-constant parts of info window
1474        if ([[fInfoController window] isVisible])
1475            [fInfoController updateInfoStats];
1476    }
1477   
1478    //badge dock
1479    [fBadger updateBadge];
1480}
1481
1482- (void) updateTorrentsInQueue
1483{
1484    BOOL download = [fDefaults boolForKey: @"Queue"],
1485        seed = [fDefaults boolForKey: @"QueueSeed"];
1486   
1487    int desiredDownloadActive = [self numToStartFromQueue: YES],
1488        desiredSeedActive = [self numToStartFromQueue: NO];
1489   
1490    //sort torrents by order value
1491    NSArray * sortedTorrents;
1492    if ([fTorrents count] > 1 && (desiredDownloadActive > 0 || desiredSeedActive > 0))
1493    {
1494        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1495                                                @"orderValue" ascending: YES] autorelease];
1496        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1497       
1498        sortedTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1499        [descriptors release];
1500    }
1501    else
1502        sortedTorrents = fTorrents;
1503
1504    Torrent * torrent;
1505    NSEnumerator * enumerator = [sortedTorrents objectEnumerator];
1506    while ((torrent = [enumerator nextObject]))
1507    {
1508        if (![torrent isActive] && ![torrent isChecking] && [torrent waitingToStart])
1509        {
1510            if (![torrent allDownloaded])
1511            {
1512                if (!download || desiredDownloadActive > 0)
1513                {
1514                    [torrent startTransfer];
1515                    if ([torrent isActive])
1516                        desiredDownloadActive--;
1517                    [torrent update];
1518                }
1519            }
1520            else
1521            {
1522                if (!seed || desiredSeedActive > 0)
1523                {
1524                    [torrent startTransfer];
1525                    if ([torrent isActive])
1526                        desiredSeedActive--;
1527                    [torrent update];
1528                }
1529            }
1530        }
1531    }
1532   
1533    [self updateUI];
1534    [self applyFilter: nil];
1535    [self updateTorrentHistory];
1536}
1537
1538- (int) numToStartFromQueue: (BOOL) downloadQueue
1539{
1540    if (![fDefaults boolForKey: downloadQueue ? @"Queue" : @"QueueSeed"])
1541        return 0;
1542   
1543    int desired = [fDefaults integerForKey: downloadQueue ? @"QueueDownloadNumber" : @"QueueSeedNumber"];
1544       
1545    Torrent * torrent;
1546    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1547    while ((torrent = [enumerator nextObject]))
1548    {
1549        if ([torrent isChecking])
1550        {
1551            desired--;
1552            if (desired <= 0)
1553                return 0;
1554        }
1555        else if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
1556        {
1557            if ([torrent allDownloaded] != downloadQueue)
1558            {
1559                desired--;
1560                if (desired <= 0)
1561                    return 0;
1562            }
1563        }
1564        else;
1565    }
1566   
1567    return desired;
1568}
1569
1570- (void) torrentFinishedDownloading: (NSNotification *) notification
1571{
1572    Torrent * torrent = [notification object];
1573    if ([torrent isActive])
1574    {
1575        if ([fDefaults boolForKey: @"PlayDownloadSound"])
1576        {
1577            NSSound * sound;
1578            if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"DownloadSound"]]))
1579                [sound play];
1580        }
1581       
1582        NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_DOWNLOAD_COMPLETE, @"Type",
1583                                        [torrent dataLocation] , @"Location", nil];
1584        [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Download Complete", "Growl notification title")
1585                                    description: [torrent name] notificationName: GROWL_DOWNLOAD_COMPLETE
1586                                    iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1587       
1588        if (![fWindow isKeyWindow])
1589            [fBadger incrementCompleted];
1590       
1591        if ([fDefaults boolForKey: @"QueueSeed"] && [self numToStartFromQueue: NO] <= 0)
1592        {
1593            [torrent stopTransfer];
1594            [torrent setWaitToStart: YES];
1595        }
1596    }
1597   
1598    [self updateTorrentsInQueue];
1599}
1600
1601- (void) torrentRestartedDownloading: (NSNotification *) notification
1602{
1603    Torrent * torrent = [notification object];
1604    if ([torrent isActive])
1605    {
1606        if ([fDefaults boolForKey: @"Queue"] && [self numToStartFromQueue: YES] <= 0)
1607        {
1608            [torrent stopTransfer];
1609            [torrent setWaitToStart: YES];
1610        }
1611    }
1612   
1613    [self updateTorrentsInQueue];
1614}
1615
1616- (void) updateTorrentHistory
1617{
1618    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1619
1620    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1621    Torrent * torrent;
1622    while ((torrent = [enumerator nextObject]))
1623        [history addObject: [torrent history]];
1624
1625    [history writeToFile: [NSHomeDirectory() stringByAppendingPathComponent: SUPPORT_FOLDER] atomically: YES];
1626}
1627
1628- (void) sortTorrents
1629{
1630    //remember selected rows if needed
1631    NSArray * selectedTorrents = nil;
1632    int numSelected = [fTableView numberOfSelectedRows];
1633    if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
1634        selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
1635
1636    [self sortTorrentsIgnoreSelected]; //actually sort
1637   
1638    //set selected rows if needed
1639    if (selectedTorrents)
1640    {
1641        Torrent * torrent;
1642        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1643        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1644        while ((torrent = [enumerator nextObject]))
1645            [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
1646       
1647        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1648        [indexSet release];
1649    }
1650}
1651
1652//doesn't remember selected rows
1653- (void) sortTorrentsIgnoreSelected
1654{
1655    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1656    BOOL asc = ![fDefaults boolForKey: @"SortReverse"];
1657   
1658    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"name"
1659                                            ascending: asc selector: @selector(caseInsensitiveCompare:)] autorelease],
1660                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"orderValue"
1661                                            ascending: asc] autorelease];
1662   
1663    NSArray * descriptors;
1664    if ([sortType isEqualToString: SORT_NAME])
1665        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
1666    else if ([sortType isEqualToString: SORT_STATE])
1667    {
1668        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1669                                                @"stateSortKey" ascending: !asc] autorelease],
1670                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1671                                            @"progressSortKey" ascending: !asc] autorelease],
1672                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1673                                            @"ratioSortKey" ascending: !asc] autorelease];
1674       
1675        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor, ratioDescriptor,
1676                                                            nameDescriptor, orderDescriptor, nil];
1677    }
1678    else if ([sortType isEqualToString: SORT_PROGRESS])
1679    {
1680        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1681                                            @"progressSortKey" ascending: asc] autorelease],
1682                        * ratioProgressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1683                                            @"ratioProgressSortKey" ascending: asc] autorelease],
1684                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1685                                            @"ratioSortKey" ascending: asc] autorelease];
1686       
1687        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, ratioProgressDescriptor, ratioDescriptor,
1688                                                            nameDescriptor, orderDescriptor, nil];
1689    }
1690    else if ([sortType isEqualToString: SORT_TRACKER])
1691    {
1692        NSSortDescriptor * trackerDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"trackerAddress"
1693                                                ascending: asc selector: @selector(caseInsensitiveCompare:)] autorelease];
1694       
1695        descriptors = [[NSArray alloc] initWithObjects: trackerDescriptor, nameDescriptor, orderDescriptor, nil];
1696    }
1697    else if ([sortType isEqualToString: SORT_ORDER])
1698        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1699    else
1700    {
1701        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateAdded" ascending: asc] autorelease];
1702   
1703        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
1704    }
1705
1706    [fDisplayedTorrents sortUsingDescriptors: descriptors];
1707    [descriptors release];
1708   
1709    [fTableView reloadData];
1710}
1711
1712- (void) setSort: (id) sender
1713{
1714    NSString * oldSortType = [fDefaults stringForKey: @"Sort"];
1715   
1716    //get checked items
1717    NSMenuItem * prevSortItem, * prevSortActionItem;
1718    if ([oldSortType isEqualToString: SORT_NAME])
1719    {
1720        prevSortItem = fNameSortItem;
1721        prevSortActionItem = fNameSortActionItem;
1722    }
1723    else if ([oldSortType isEqualToString: SORT_STATE])
1724    {
1725        prevSortItem = fStateSortItem;
1726        prevSortActionItem = fStateSortActionItem;
1727    }
1728    else if ([oldSortType isEqualToString: SORT_PROGRESS])
1729    {
1730        prevSortItem = fProgressSortItem;
1731        prevSortActionItem = fProgressSortActionItem;
1732    }
1733    else if ([oldSortType isEqualToString: SORT_TRACKER])
1734    {
1735        prevSortItem = fTrackerSortItem;
1736        prevSortActionItem = fTrackerSortActionItem;
1737    }
1738    else if ([oldSortType isEqualToString: SORT_ORDER])
1739    {
1740        prevSortItem = fOrderSortItem;
1741        prevSortActionItem = fOrderSortActionItem;
1742    }
1743    else
1744    {
1745        prevSortItem = fDateSortItem;
1746        prevSortActionItem = fDateSortActionItem;
1747    }
1748   
1749    if (sender != prevSortItem && sender != prevSortActionItem)
1750    {
1751        //get new items to check
1752        NSMenuItem * currentSortItem, * currentSortActionItem;
1753        NSString * sortType;
1754        if (sender == fNameSortItem || sender == fNameSortActionItem)
1755        {
1756            currentSortItem = fNameSortItem;
1757            currentSortActionItem = fNameSortActionItem;
1758            sortType = SORT_NAME;
1759        }
1760        else if (sender == fStateSortItem || sender == fStateSortActionItem)
1761        {
1762            currentSortItem = fStateSortItem;
1763            currentSortActionItem = fStateSortActionItem;
1764            sortType = SORT_STATE;
1765        }
1766        else if (sender == fProgressSortItem || sender == fProgressSortActionItem)
1767        {
1768            currentSortItem = fProgressSortItem;
1769            currentSortActionItem = fProgressSortActionItem;
1770            sortType = SORT_PROGRESS;
1771        }
1772        else if (sender == fTrackerSortItem || sender == fTrackerSortActionItem)
1773        {
1774            currentSortItem = fTrackerSortItem;
1775            currentSortActionItem = fTrackerSortActionItem;
1776            sortType = SORT_TRACKER;
1777        }
1778        else if (sender == fOrderSortItem || sender == fOrderSortActionItem)
1779        {
1780            currentSortItem = fOrderSortItem;
1781            currentSortActionItem = fOrderSortActionItem;
1782            sortType = SORT_ORDER;
1783           
1784            [fDefaults setBool: NO forKey: @"SortReverse"];
1785        }
1786        else
1787        {
1788            currentSortItem = fDateSortItem;
1789            currentSortActionItem = fDateSortActionItem;
1790            sortType = SORT_DATE;
1791        }
1792       
1793        [fDefaults setObject: sortType forKey: @"Sort"];
1794   
1795        [prevSortItem setState: NSOffState];
1796        [prevSortActionItem setState: NSOffState];
1797        [currentSortItem setState: NSOnState];
1798        [currentSortActionItem setState: NSOnState];
1799    }
1800
1801    [self sortTorrents];
1802}
1803
1804- (void) setSortReverse: (id) sender
1805{
1806    [fDefaults setBool: ![fDefaults boolForKey: @"SortReverse"] forKey: @"SortReverse"];
1807    [self sortTorrents];
1808}
1809
1810- (void) applyFilter: (id) sender
1811{
1812    //remember selected rows if needed
1813    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1814                ? [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1815
1816    NSMutableArray * tempTorrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1817   
1818    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1819   
1820    int downloading = 0, seeding = 0, paused = 0;
1821    BOOL isDownload = [filterType isEqualToString: FILTER_DOWNLOAD],
1822            isSeed = [filterType isEqualToString: FILTER_SEED],
1823            isPause = [filterType isEqualToString: FILTER_PAUSE];
1824    BOOL filtering = isDownload || isSeed || isPause;
1825   
1826    //get count of each type
1827    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1828    Torrent * torrent;
1829    while ((torrent = [enumerator nextObject]))
1830    {
1831        if ([torrent isActive])
1832        {
1833            if ([torrent isSeeding])
1834            {
1835                seeding++;
1836                if (isSeed)
1837                    [tempTorrents addObject: torrent];
1838            }
1839            else
1840            {
1841                downloading++;
1842                if (isDownload)
1843                    [tempTorrents addObject: torrent];
1844            }
1845        }
1846        else
1847        {
1848            paused++;
1849            if (isPause)
1850                [tempTorrents addObject: torrent];
1851        }
1852    }
1853   
1854    if (!filtering)
1855        [tempTorrents setArray: fTorrents];
1856   
1857    //set buttons with counts
1858    [fNoFilterButton setCount: [fTorrents count]];
1859    [fDownloadFilterButton setCount: downloading];
1860    [fSeedFilterButton setCount: seeding];
1861    [fPauseFilterButton setCount: paused];
1862   
1863    NSString * searchString = [fSearchFilterField stringValue];
1864    if ([searchString length] > 0)
1865    {
1866        filtering = YES;
1867       
1868        Torrent * torrent;
1869        BOOL filterTracker = [[fDefaults stringForKey: @"FilterSearchType"] isEqualToString: FILTER_TYPE_TRACKER], remove;
1870       
1871        int i;
1872        for (i = [tempTorrents count]-1; i >= 0; i--)
1873        {
1874            torrent = [tempTorrents objectAtIndex: i];
1875            remove = YES;
1876           
1877            if (filterTracker)
1878            {
1879                NSEnumerator * trackerEnumerator = [[torrent allTrackers] objectEnumerator], * subTrackerEnumerator;
1880                NSArray * subTrackers;
1881                NSString * tracker;
1882                while (remove && (subTrackers = [trackerEnumerator nextObject]))
1883                {
1884                    subTrackerEnumerator = [subTrackers objectEnumerator];
1885                    while ((tracker = [subTrackerEnumerator nextObject]))
1886                    {
1887                        if ([tracker rangeOfString: searchString options: NSCaseInsensitiveSearch].location != NSNotFound)
1888                        {
1889                            remove = NO;
1890                            break;
1891                        }
1892                    }
1893                }
1894            }
1895            else
1896                remove = [[torrent name] rangeOfString: searchString options: NSCaseInsensitiveSearch].location == NSNotFound;
1897           
1898            if (remove)
1899                [tempTorrents removeObjectAtIndex: i];
1900        }
1901    }
1902   
1903    [fDisplayedTorrents setArray: tempTorrents];
1904   
1905    [self sortTorrentsIgnoreSelected];
1906   
1907    //set selected rows if needed
1908    if (selectedTorrents)
1909    {
1910        Torrent * torrent;
1911        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1912        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1913        unsigned index;
1914        while ((torrent = [enumerator nextObject]))
1915            if ((index = [fDisplayedTorrents indexOfObject: torrent]) != NSNotFound)
1916                [indexSet addIndex: index];
1917       
1918        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1919        [indexSet release];
1920    }
1921   
1922    //set status bar torrent count text
1923    NSMutableString * totalTorrentsString = [[NSMutableString alloc] initWithString: @""];
1924    if (filtering)
1925        [totalTorrentsString appendFormat: @"%d/", [fDisplayedTorrents count]];
1926   
1927    int totalCount = [fTorrents count];
1928    if (totalCount != 1)
1929        [totalTorrentsString appendFormat: NSLocalizedString(@"%d Transfers", "Status bar transfer count"), totalCount];
1930    else
1931        [totalTorrentsString appendString: NSLocalizedString(@"1 Transfer", "Status bar transfer count")];
1932   
1933    [fTotalTorrentsField setStringValue: totalTorrentsString];
1934    [totalTorrentsString release];
1935
1936    [self setWindowSizeToFit];
1937}
1938
1939//resets filter and sorts torrents
1940- (void) setFilter: (id) sender
1941{
1942    NSString * oldFilterType = [fDefaults stringForKey: @"Filter"];
1943   
1944    FilterBarButton * prevFilterButton;
1945    if ([oldFilterType isEqualToString: FILTER_PAUSE])
1946        prevFilterButton = fPauseFilterButton;
1947    else if ([oldFilterType isEqualToString: FILTER_SEED])
1948        prevFilterButton = fSeedFilterButton;
1949    else if ([oldFilterType isEqualToString: FILTER_DOWNLOAD])
1950        prevFilterButton = fDownloadFilterButton;
1951    else
1952        prevFilterButton = fNoFilterButton;
1953   
1954    if (sender != prevFilterButton)
1955    {
1956        [prevFilterButton setState: NSOffState];
1957        [sender setState: NSOnState];
1958
1959        NSString * filterType;
1960        if (sender == fDownloadFilterButton)
1961            filterType = FILTER_DOWNLOAD;
1962        else if (sender == fPauseFilterButton)
1963            filterType = FILTER_PAUSE;
1964        else if (sender == fSeedFilterButton)
1965            filterType = FILTER_SEED;
1966        else
1967            filterType = FILTER_NONE;
1968
1969        [fDefaults setObject: filterType forKey: @"Filter"];
1970    }
1971
1972    [self applyFilter: nil];
1973}
1974
1975- (void) setFilterSearchType: (id) sender
1976{
1977    NSString * oldFilterType = [fDefaults stringForKey: @"FilterSearchType"];
1978   
1979    int prevTag, currentTag = [sender tag];
1980    if ([oldFilterType isEqualToString: FILTER_TYPE_TRACKER])
1981        prevTag = FILTER_TYPE_TAG_TRACKER;
1982    else
1983        prevTag = FILTER_TYPE_TAG_NAME;
1984   
1985    if (currentTag != prevTag)
1986    {
1987        NSString * filterType;
1988        if (currentTag == FILTER_TYPE_TAG_TRACKER)
1989            filterType = FILTER_TYPE_TRACKER;
1990        else
1991            filterType = FILTER_TYPE_NAME;
1992       
1993        [fDefaults setObject: filterType forKey: @"FilterSearchType"];
1994       
1995        [[fSearchFilterField cell] setPlaceholderString: [sender title]];
1996    }
1997   
1998    [self applyFilter: nil];
1999}
2000
2001- (void) switchFilter: (id) sender
2002{
2003    NSString * filterType = [fDefaults stringForKey: @"Filter"];
2004   
2005    FilterBarButton * button;
2006    if ([filterType isEqualToString: FILTER_NONE])
2007        button = sender == fNextFilterItem ? fDownloadFilterButton : fPauseFilterButton;
2008    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
2009        button = sender == fNextFilterItem ? fSeedFilterButton : fNoFilterButton;
2010    else if ([filterType isEqualToString: FILTER_SEED])
2011        button = sender == fNextFilterItem ? fPauseFilterButton : fDownloadFilterButton;
2012    else if ([filterType isEqualToString: FILTER_PAUSE])
2013        button = sender == fNextFilterItem ? fNoFilterButton : fSeedFilterButton;
2014    else
2015        button = fNoFilterButton;
2016   
2017    [self setFilter: button];
2018}
2019
2020- (void) updateControlTint: (NSNotification *) notification
2021{
2022    if ([fDefaults boolForKey: @"SpeedLimit"])
2023        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSGraphiteControlTint
2024            ? [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"] : [NSImage imageNamed: @"SpeedLimitButtonBlue.png"]];
2025}
2026
2027- (void) toggleSpeedLimit: (id) sender
2028{
2029    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
2030    [fPrefsController applySpeedSettings: nil];
2031}
2032
2033- (void) autoSpeedLimitChange: (NSNotification *) notification
2034{
2035    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
2036        return;
2037 
2038    NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2039                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]],
2040        * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2041                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]],
2042        * nowDate = [NSCalendarDate calendarDate];
2043   
2044    //check if should be on if within range
2045    int onTime = [onDate hourOfDay] * 60 + [onDate minuteOfHour],
2046        offTime = [offDate hourOfDay] * 60 + [offDate minuteOfHour],
2047        nowTime = [nowDate hourOfDay] * 60 + [nowDate minuteOfHour];
2048   
2049    BOOL shouldBeOn = NO;
2050    if (onTime < offTime)
2051        shouldBeOn = onTime <= nowTime && nowTime < offTime;
2052    else if (onTime > offTime)
2053        shouldBeOn = onTime <= nowTime || nowTime < offTime;
2054    else;
2055   
2056    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldBeOn)
2057        [self toggleSpeedLimit: nil];
2058}
2059
2060- (void) autoSpeedLimit
2061{
2062    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
2063        return;
2064   
2065    //only toggle if within first few seconds of minutes
2066    NSCalendarDate * nowDate = [NSCalendarDate calendarDate];
2067    if ([nowDate secondOfMinute] > AUTO_SPEED_LIMIT_SECONDS)
2068        return;
2069   
2070    NSCalendarDate * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2071                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]];
2072   
2073    BOOL toggle;
2074    if ([fDefaults boolForKey: @"SpeedLimit"])
2075        toggle = [nowDate hourOfDay] == [offDate hourOfDay] && [nowDate minuteOfHour] == [offDate minuteOfHour];
2076    else
2077    {
2078        NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2079                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]];
2080        toggle = ([nowDate hourOfDay] == [onDate hourOfDay] && [nowDate minuteOfHour] == [onDate minuteOfHour])
2081                    && !([onDate hourOfDay] == [offDate hourOfDay] && [onDate minuteOfHour] == [offDate minuteOfHour]);
2082    }
2083   
2084    if (toggle)
2085    {
2086        [self toggleSpeedLimit: nil];
2087       
2088        [GrowlApplicationBridge notifyWithTitle: [fDefaults boolForKey: @"SpeedLimit"]
2089                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
2090                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
2091            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
2092            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
2093    }
2094}
2095
2096- (void) setLimitGlobalEnabled: (id) sender
2097{
2098    [fPrefsController applySpeedSettings: nil];
2099}
2100
2101- (void) setQuickLimitGlobal: (id) sender
2102{
2103    [fDefaults setInteger: [[sender title] intValue] forKey: [sender menu] == fUploadMenu ? @"UploadLimit" : @"DownloadLimit"];
2104    [fDefaults setBool: YES forKey: [sender menu] == fUploadMenu ? @"CheckUpload" : @"CheckDownload"];
2105   
2106    [fPrefsController updateLimitFields];
2107    [fPrefsController applySpeedSettings: nil];
2108}
2109
2110- (void) setQuickRatioGlobal: (id) sender
2111{
2112    [fDefaults setBool: YES forKey: @"RatioCheck"];
2113    [fDefaults setFloat: [[sender title] floatValue] forKey: @"RatioLimit"];
2114   
2115    [fPrefsController updateRatioStopField];
2116}
2117
2118- (void) torrentStoppedForRatio: (NSNotification *) notification
2119{
2120    Torrent * torrent = [notification object];
2121   
2122    [self updateTorrentsInQueue];
2123    [fInfoController updateInfoStats];
2124    [fInfoController updateOptions];
2125   
2126    if ([fDefaults boolForKey: @"PlaySeedingSound"])
2127    {
2128        NSSound * sound;
2129        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
2130            [sound play];
2131    }
2132   
2133    NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_SEEDING_COMPLETE, @"Type",
2134                                    [torrent dataLocation], @"Location", nil];
2135    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
2136                        description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
2137                        iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
2138}
2139
2140-(void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
2141{
2142    if ([notification isEqualToString: UKFileWatcherWriteNotification])
2143    {
2144        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
2145            return;
2146       
2147        if (fAutoImportTimer)
2148        {
2149            if ([fAutoImportTimer isValid])
2150                [fAutoImportTimer invalidate];
2151            [fAutoImportTimer release];
2152            fAutoImportTimer = nil;
2153        }
2154       
2155        //check again in 10 seconds in case torrent file wasn't complete
2156        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
2157            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
2158       
2159        [self checkAutoImportDirectory];
2160    }
2161}
2162
2163- (void) changeAutoImport
2164{
2165    if (fAutoImportTimer)
2166    {
2167        if ([fAutoImportTimer isValid])
2168            [fAutoImportTimer invalidate];
2169        [fAutoImportTimer release];
2170        fAutoImportTimer = nil;
2171    }
2172   
2173    if (fAutoImportedNames)
2174        [fAutoImportedNames removeAllObjects];
2175    [self checkAutoImportDirectory];
2176}
2177
2178- (void) checkAutoImportDirectory
2179{
2180    NSString * path;
2181    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
2182        return;
2183   
2184    path = [path stringByExpandingTildeInPath];
2185   
2186    NSArray * importedNames;
2187    if (!(importedNames = [[NSFileManager defaultManager] directoryContentsAtPath: path]))
2188        return;
2189   
2190    //only check files that have not been checked yet
2191    NSMutableArray * newNames = [importedNames mutableCopy];
2192   
2193    if (fAutoImportedNames)
2194        [newNames removeObjectsInArray: fAutoImportedNames];
2195    else
2196        fAutoImportedNames = [[NSMutableArray alloc] init];
2197    [fAutoImportedNames setArray: importedNames];
2198   
2199    NSString * file;
2200    int i;
2201    for (i = [newNames count] - 1; i >= 0; i--)
2202    {
2203        file = [newNames objectAtIndex: i];
2204        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
2205            [newNames removeObjectAtIndex: i];
2206        else
2207            [newNames replaceObjectAtIndex: i withObject: [path stringByAppendingPathComponent: file]];
2208    }
2209   
2210    BOOL ask = [[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Ask"];
2211   
2212    NSEnumerator * enumerator = [newNames objectEnumerator];
2213    int canAdd, count;
2214    while ((file = [enumerator nextObject]))
2215    {
2216        canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2217        if (canAdd == TR_OK)
2218        {
2219            if (!ask)
2220                count = [fTorrents count];
2221            [self openFiles: [NSArray arrayWithObject: file]];
2222           
2223            //check if torrent was opened
2224            if (!ask && [fTorrents count] > count)
2225                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added",
2226                    "Growl notification title") description: [file lastPathComponent]
2227                    notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO clickContext: nil];
2228        }
2229        else if (canAdd == TR_EINVALID)
2230            [fAutoImportedNames removeObject: [file lastPathComponent]];
2231        else;
2232    }
2233   
2234    [newNames release];
2235}
2236
2237- (void) beginCreateFile: (NSNotification *) notification
2238{
2239    if (![fDefaults boolForKey: @"AutoImport"])
2240        return;
2241   
2242    NSString * location = [notification object],
2243            * path = [fDefaults stringForKey: @"AutoImportDirectory"];
2244   
2245    if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
2246                                    isEqualToString: [path stringByExpandingTildeInPath]])
2247        [fAutoImportedNames addObject: [location lastPathComponent]];
2248}
2249
2250- (int) numberOfRowsInTableView: (NSTableView *) tableview
2251{
2252    return [fDisplayedTorrents count];
2253}
2254
2255- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (int) row
2256{
2257    return nil;
2258}
2259
2260- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes toPasteboard: (NSPasteboard *) pasteboard
2261{
2262    //only allow reordering of rows if sorting by order with no filter
2263    if ([[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER]
2264        && [[fDefaults stringForKey: @"Filter"] isEqualToString: FILTER_NONE]
2265        && [[fSearchFilterField stringValue] length] == 0)
2266    {
2267        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2268        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes] forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2269        return YES;
2270    }
2271    return NO;
2272}
2273
2274- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
2275    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
2276{
2277    NSPasteboard * pasteboard = [info draggingPasteboard];
2278    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2279    {
2280        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
2281        return NSDragOperationGeneric;
2282    }
2283   
2284    return NSDragOperationNone;
2285}
2286
2287- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
2288    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
2289{
2290    NSPasteboard * pasteboard = [info draggingPasteboard];
2291    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2292    {
2293        //remember selected rows if needed
2294        NSArray * selectedTorrents = nil;
2295        int numSelected = [fTableView numberOfSelectedRows];
2296        if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
2297            selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
2298   
2299        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
2300                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2301       
2302        //move torrent in array
2303        NSArray * movingTorrents = [[fDisplayedTorrents objectsAtIndexes: indexes] retain];
2304        [fDisplayedTorrents removeObjectsInArray: movingTorrents];
2305       
2306        //determine the insertion index now that transfers to move have been removed
2307        int i, decrease = 0;
2308        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2309            decrease++;
2310       
2311        //insert objects at new location
2312        for (i = 0; i < [movingTorrents count]; i++)
2313            [fDisplayedTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
2314       
2315        [movingTorrents release];
2316       
2317        //redo order values
2318        int low = [indexes firstIndex], high = [indexes lastIndex];
2319        if (newRow < low)
2320            low = newRow;
2321        else if (newRow > high + 1)
2322            high = newRow - 1;
2323        else;
2324       
2325        for (i = low; i <= high; i++)
2326            [[fDisplayedTorrents objectAtIndex: i] setOrderValue: i];
2327       
2328        [fTableView reloadData];
2329       
2330        //set selected rows if needed
2331        if (selectedTorrents)
2332        {
2333            Torrent * torrent;
2334            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
2335            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
2336            while ((torrent = [enumerator nextObject]))
2337                [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
2338           
2339            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
2340            [indexSet release];
2341        }
2342    }
2343   
2344    return YES;
2345}
2346
2347- (void) torrentTableViewSelectionDidChange: (NSNotification *) notification
2348{
2349    [fInfoController setInfoForTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
2350}
2351
2352- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2353{
2354    NSPasteboard * pasteboard = [info draggingPasteboard];
2355    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2356    {
2357        //check if any torrent files can be added
2358        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2359        NSEnumerator * enumerator = [files objectEnumerator];
2360        NSString * file;
2361        BOOL torrent = NO;
2362        int canAdd;
2363        while ((file = [enumerator nextObject]))
2364        {
2365            canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2366            if (canAdd == TR_OK)
2367            {
2368                if (!fOverlayWindow)
2369                    fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2370                [fOverlayWindow setTorrents: files];
2371               
2372                return NSDragOperationCopy;
2373            }
2374            else if (canAdd == TR_EDUPLICATE)
2375                torrent = YES;
2376            else;
2377        }
2378       
2379        //create a torrent file if a single file
2380        if (!torrent && [files count] == 1)
2381        {
2382            if (!fOverlayWindow)
2383                fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2384            [fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
2385           
2386            return NSDragOperationCopy;
2387        }
2388    }
2389    else if ([[pasteboard types] containsObject: NSURLPboardType])
2390    {
2391        if (!fOverlayWindow)
2392            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2393        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2394       
2395        return NSDragOperationCopy;
2396    }
2397    else;
2398   
2399    return NSDragOperationNone;
2400}
2401
2402- (void) draggingExited: (id <NSDraggingInfo>) info
2403{
2404    if (fOverlayWindow)
2405        [fOverlayWindow fadeOut];
2406}
2407
2408- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2409{
2410    if (fOverlayWindow)
2411        [fOverlayWindow fadeOut];
2412   
2413    NSPasteboard * pasteboard = [info draggingPasteboard];
2414    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2415    {
2416        BOOL torrent = NO, accept = YES;
2417       
2418        //create an array of files that can be opened
2419        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
2420        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2421        NSEnumerator * enumerator = [files objectEnumerator];
2422        NSString * file;
2423        int canAdd;
2424        while ((file = [enumerator nextObject]))
2425        {
2426            canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2427            if (canAdd == TR_OK)
2428            {
2429                [filesToOpen addObject: file];
2430                torrent = YES;
2431            }
2432            else if (canAdd == TR_EDUPLICATE)
2433                torrent = YES;
2434            else;
2435        }
2436       
2437        if ([filesToOpen count] > 0)
2438            [self application: NSApp openFiles: filesToOpen];
2439        else
2440        {
2441            if (!torrent && [files count] == 1)
2442                [CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
2443            else
2444                accept = NO;
2445        }
2446        [filesToOpen release];
2447       
2448        return accept;
2449    }
2450    else if ([[pasteboard types] containsObject: NSURLPboardType])
2451    {
2452        NSURL * url;
2453        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2454        {
2455            [self openURL: url];
2456            return YES;
2457        }
2458    }
2459    else;
2460   
2461    return NO;
2462}
2463
2464- (void) toggleSmallView: (id) sender
2465{
2466    BOOL makeSmall = ![fDefaults boolForKey: @"SmallView"];
2467    [fDefaults setBool: makeSmall forKey: @"SmallView"];
2468   
2469    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2470   
2471    //window min height
2472    NSSize contentMinSize = [fWindow contentMinSize],
2473            contentSize = [[fWindow contentView] frame].size;
2474    contentMinSize.height = contentSize.height - [fScrollView frame].size.height
2475                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
2476    [fWindow setContentMinSize: contentMinSize];
2477   
2478    //resize for larger min height if not set to auto size
2479    if (![fDefaults boolForKey: @"AutoSize"])
2480    {
2481        if (!makeSmall && contentSize.height < contentMinSize.height)
2482        {
2483            NSRect frame = [fWindow frame];
2484            float heightChange = contentMinSize.height - contentSize.height;
2485            frame.size.height += heightChange;
2486            frame.origin.y -= heightChange;
2487           
2488            [fWindow setFrame: frame display: YES];
2489            [fTableView reloadData];
2490        }
2491    }
2492    else
2493        [self setWindowSizeToFit];
2494}
2495
2496- (void) toggleStatusBar: (id) sender
2497{
2498    [self showStatusBar: [fStatusBar isHidden] animate: YES];
2499    [fDefaults setBool: ![fStatusBar isHidden] forKey: @"StatusBar"];
2500}
2501
2502- (NSRect) windowFrameByAddingHeight: (float) height checkLimits: (BOOL) check
2503{
2504    //convert pixels to points
2505    NSRect windowFrame = [fWindow frame];
2506    NSSize windowSize = [fScrollView convertSize: windowFrame.size fromView: nil];
2507    windowSize.height += height;
2508   
2509    if (check)
2510    {
2511        NSSize minSize = [fScrollView convertSize: [fWindow minSize] fromView: nil];
2512       
2513        if (windowSize.height < minSize.height)
2514            windowSize.height = minSize.height;
2515        else
2516        {
2517            NSSize maxSize = [fScrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
2518            if ([fStatusBar isHidden])
2519                maxSize.height -= [fStatusBar frame].size.height;
2520            if ([fFilterBar isHidden])
2521                maxSize.height -= [fFilterBar frame].size.height;
2522            if (windowSize.height > maxSize.height)
2523                windowSize.height = maxSize.height;
2524        }
2525    }
2526
2527    //convert points to pixels
2528    windowSize = [fScrollView convertSize: windowSize toView: nil];
2529
2530    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
2531    windowFrame.size.height = windowSize.height;
2532    return windowFrame;
2533}
2534
2535- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
2536{
2537    if (show != [fStatusBar isHidden])
2538        return;
2539
2540    if (show)
2541        [fStatusBar setHidden: NO];
2542
2543    NSRect frame;
2544    float heightChange = [fStatusBar frame].size.height;
2545    if (!show)
2546        heightChange *= -1;
2547   
2548    //allow bar to show even if not enough room
2549    if (show && ![fDefaults boolForKey: @"AutoSize"])
2550    {
2551        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2552        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2553        if (change < 0.0)
2554        {
2555            frame = [fWindow frame];
2556            frame.size.height += change;
2557            frame.origin.y -= change;
2558            [fWindow setFrame: frame display: NO animate: NO];
2559        }
2560    }
2561
2562    [self updateUI];
2563   
2564    //set views to not autoresize
2565    unsigned int statsMask = [fStatusBar autoresizingMask];
2566    unsigned int filterMask = [fFilterBar autoresizingMask];
2567    unsigned int scrollMask = [fScrollView autoresizingMask];
2568    [fStatusBar setAutoresizingMask: NSViewNotSizable];
2569    [fFilterBar setAutoresizingMask: NSViewNotSizable];
2570    [fScrollView setAutoresizingMask: NSViewNotSizable];
2571   
2572    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2573    [fWindow setFrame: frame display: YES animate: animate];
2574   
2575    //re-enable autoresize
2576    [fStatusBar setAutoresizingMask: statsMask];
2577    [fFilterBar setAutoresizingMask: filterMask];
2578    [fScrollView setAutoresizingMask: scrollMask];
2579   
2580    //change min size
2581    NSSize minSize = [fWindow contentMinSize];
2582    minSize.height += heightChange;
2583    [fWindow setContentMinSize: minSize];
2584   
2585    if (!show)
2586        [fStatusBar setHidden: YES];
2587}
2588
2589- (void) toggleFilterBar: (id) sender
2590{
2591    //disable filtering when hiding
2592    if (![fFilterBar isHidden])
2593    {
2594        [fSearchFilterField setStringValue: @""];
2595        [self setFilter: fNoFilterButton];
2596    }
2597
2598    [self showFilterBar: [fFilterBar isHidden] animate: YES];
2599    [fDefaults setBool: ![fFilterBar isHidden] forKey: @"FilterBar"];
2600}
2601
2602- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2603{
2604    if (show != [fFilterBar isHidden])
2605        return;
2606
2607    if (show)
2608        [fFilterBar setHidden: NO];
2609
2610    NSRect frame;
2611    float heightChange = [fFilterBar frame].size.height;
2612    if (!show)
2613        heightChange *= -1;
2614   
2615    //allow bar to show even if not enough room
2616    if (show && ![fDefaults boolForKey: @"AutoSize"])
2617    {
2618        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2619        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2620        if (change < 0.0)
2621        {
2622            frame = [fWindow frame];
2623            frame.size.height += change;
2624            frame.origin.y -= change;
2625            [fWindow setFrame: frame display: NO animate: NO];
2626        }
2627    }
2628
2629    //set views to not autoresize
2630    unsigned int filterMask = [fFilterBar autoresizingMask];
2631    unsigned int scrollMask = [fScrollView autoresizingMask];
2632    [fFilterBar setAutoresizingMask: NSViewNotSizable];
2633    [fScrollView setAutoresizingMask: NSViewNotSizable];
2634   
2635    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2636    [fWindow setFrame: frame display: YES animate: animate];
2637   
2638    //re-enable autoresize
2639    [fFilterBar setAutoresizingMask: filterMask];
2640    [fScrollView setAutoresizingMask: scrollMask];
2641   
2642    //change min size
2643    NSSize minSize = [fWindow contentMinSize];
2644    minSize.height += heightChange;
2645    [fWindow setContentMinSize: minSize];
2646   
2647    if (!show)
2648    {
2649        [fFilterBar setHidden: YES];
2650        [fWindow makeFirstResponder: fTableView];
2651    }
2652}
2653
2654/*- (void) toggleAdvancedBar: (id) sender
2655{
2656    [fTableView display];
2657}*/
2658
2659- (void) doNothing: (id) sender {}
2660
2661- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
2662{
2663    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
2664
2665    if ([ident isEqualToString: TOOLBAR_CREATE])
2666    {
2667        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
2668        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
2669        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
2670        [item setImage: [NSImage imageNamed: @"Create.png"]];
2671        [item setTarget: self];
2672        [item setAction: @selector(createFile:)];
2673        [item setAutovalidates: NO];
2674    }
2675    else if ([ident isEqualToString: TOOLBAR_OPEN])
2676    {
2677        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
2678        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
2679        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
2680        [item setImage: [NSImage imageNamed: @"Open.png"]];
2681        [item setTarget: self];
2682        [item setAction: @selector(openShowSheet:)];
2683        [item setAutovalidates: NO];
2684    }
2685    else if ([ident isEqualToString: TOOLBAR_REMOVE])
2686    {
2687        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
2688        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
2689        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
2690        [item setImage: [NSImage imageNamed: @"Remove.png"]];
2691        [item setTarget: self];
2692        [item setAction: @selector(removeNoDelete:)];
2693    }
2694    else if ([ident isEqualToString: TOOLBAR_INFO])
2695    {
2696        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
2697        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
2698        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
2699        [item setImage: [NSImage imageNamed: @"Info.png"]];
2700        [item setTarget: self];
2701        [item setAction: @selector(showInfo:)];
2702        [item setAutovalidates: NO];
2703    }
2704    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2705    {
2706        [item setLabel: NSLocalizedString(@"Pause All", "Pause All toolbar item -> label")];
2707        [item setPaletteLabel: [item label]];
2708        [item setToolTip: NSLocalizedString(@"Pause all transfers", "Pause All toolbar item -> tooltip")];
2709        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
2710        [item setTarget: self];
2711        [item setAction: @selector(stopAllTorrents:)];
2712    }
2713    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2714    {
2715        [item setLabel: NSLocalizedString(@"Resume All", "Resume All toolbar item -> label")];
2716        [item setPaletteLabel: [item label]];
2717        [item setToolTip: NSLocalizedString(@"Resume all transfers", "Resume All toolbar item -> tooltip")];
2718        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
2719        [item setTarget: self];
2720        [item setAction: @selector(resumeAllTorrents:)];
2721    }
2722    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2723    {
2724        [item setLabel: NSLocalizedString(@"Pause", "Pause toolbar item -> label")];
2725        [item setPaletteLabel: NSLocalizedString(@"Pause Selected", "Pause toolbar item -> palette label")];
2726        [item setToolTip: NSLocalizedString(@"Pause selected transfers", "Pause toolbar item -> tooltip")];
2727        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
2728        [item setTarget: self];
2729        [item setAction: @selector(stopSelectedTorrents:)];
2730    }
2731    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2732    {
2733        [item setLabel: NSLocalizedString(@"Resume", "Resume toolbar item -> label")];
2734        [item setPaletteLabel: NSLocalizedString(@"Resume Selected", "Resume toolbar item -> palette label")];
2735        [item setToolTip: NSLocalizedString(@"Resume selected transfers", "Resume toolbar item -> tooltip")];
2736        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
2737        [item setTarget: self];
2738        [item setAction: @selector(resumeSelectedTorrents:)];
2739    }
2740    else if ([ident isEqualToString: TOOLBAR_FILTER])
2741    {
2742        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
2743        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
2744        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
2745        [item setImage: [NSImage imageNamed: @"Filter.png"]];
2746        [item setTarget: self];
2747        [item setAction: @selector(toggleFilterBar:)];
2748        [item setAutovalidates: NO];
2749    }
2750    else
2751    {
2752        [item release];
2753        return nil;
2754    }
2755
2756    return [item autorelease];
2757}
2758
2759- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
2760{
2761    return [NSArray arrayWithObjects:
2762            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2763            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
2764            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
2765            NSToolbarSeparatorItemIdentifier,
2766            NSToolbarSpaceItemIdentifier,
2767            NSToolbarFlexibleSpaceItemIdentifier,
2768            NSToolbarCustomizeToolbarItemIdentifier, nil];
2769}
2770
2771- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
2772{
2773    return [NSArray arrayWithObjects:
2774            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2775            NSToolbarSeparatorItemIdentifier,
2776            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
2777            NSToolbarFlexibleSpaceItemIdentifier,
2778            TOOLBAR_FILTER, TOOLBAR_INFO, nil];
2779}
2780
2781#warning validateUserInterfaceItem: ???
2782- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2783{
2784    NSString * ident = [toolbarItem itemIdentifier];
2785
2786    //enable remove item
2787    if ([ident isEqualToString: TOOLBAR_REMOVE])
2788        return [fTableView numberOfSelectedRows] > 0;
2789
2790    //enable pause all item
2791    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2792    {
2793        Torrent * torrent;
2794        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2795        while ((torrent = [enumerator nextObject]))
2796            if ([torrent isActive] || [torrent waitingToStart])
2797                return YES;
2798        return NO;
2799    }
2800
2801    //enable resume all item
2802    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2803    {
2804        Torrent * torrent;
2805        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2806        while ((torrent = [enumerator nextObject]))
2807            if (![torrent isActive] && ![torrent waitingToStart])
2808                return YES;
2809        return NO;
2810    }
2811
2812    //enable pause item
2813    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2814    {
2815        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2816        Torrent * torrent;
2817        while ((torrent = [enumerator nextObject]))
2818            if ([torrent isActive] || [torrent waitingToStart])
2819                return YES;
2820        return NO;
2821    }
2822   
2823    //enable resume item
2824    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2825    {
2826        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2827        Torrent * torrent;
2828        while ((torrent = [enumerator nextObject]))
2829            if (![torrent isActive] && ![torrent waitingToStart])
2830                return YES;
2831        return NO;
2832    }
2833
2834    return YES;
2835}
2836
2837- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
2838{
2839    SEL action = [menuItem action];
2840   
2841    if (action == @selector(toggleSpeedLimit:))
2842    {
2843        [menuItem setState: [fDefaults boolForKey: @"SpeedLimit"] ? NSOnState : NSOffState];
2844        return YES;
2845    }
2846   
2847    //only enable some items if it is in a context menu or the window is useable
2848    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
2849
2850    //enable open items
2851    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
2852        return [fWindow attachedSheet] == nil;
2853   
2854    //enable sort and advanced bar items
2855    if (action == @selector(setSort:) /*|| action == @selector(toggleAdvancedBar:) ||*/)
2856        return [fWindow isVisible];
2857   
2858    if (action == @selector(toggleSmallView:))
2859    {
2860        [menuItem setState: [fDefaults boolForKey: @"SmallView"] ? NSOnState : NSOffState];
2861        return [fWindow isVisible];
2862    }
2863
2864    //enable show info
2865    if (action == @selector(showInfo:))
2866    {
2867        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector", "View menu -> Inspector")
2868                            : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
2869        [menuItem setTitle: title];
2870
2871        return YES;
2872    }
2873   
2874    //enable prev/next inspector tab
2875    if (action == @selector(setInfoTab:))
2876        return [[fInfoController window] isVisible];
2877   
2878    //enable toggle status bar
2879    if (action == @selector(toggleStatusBar:))
2880    {
2881        NSString * title = [fStatusBar isHidden] ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
2882                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
2883        [menuItem setTitle: title];
2884
2885        return [fWindow isVisible];
2886    }
2887   
2888    //enable toggle filter bar
2889    if (action == @selector(toggleFilterBar:))
2890    {
2891        NSString * title = [fFilterBar isHidden] ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
2892                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
2893        [menuItem setTitle: title];
2894
2895        return [fWindow isVisible];
2896    }
2897   
2898    //enable prev/next filter button
2899    if (action == @selector(switchFilter:))
2900        return [fWindow isVisible] && ![fFilterBar isHidden];
2901
2902    //enable reveal in finder
2903    if (action == @selector(revealFile:))
2904        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2905
2906    //enable remove items
2907    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
2908        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
2909    {
2910        BOOL warning = NO,
2911            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
2912            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
2913       
2914        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2915        Torrent * torrent;
2916        while ((torrent = [enumerator nextObject]))
2917        {
2918            if (!warning && [torrent isActive])
2919            {
2920                warning = onlyDownloading ? ![torrent isSeeding] : YES;
2921                if (warning && canDelete)
2922                    break;
2923            }
2924            if (!canDelete && [torrent publicTorrent])
2925            {
2926                canDelete = YES;
2927                if (warning)
2928                    break;
2929            }
2930        }
2931   
2932        //append or remove ellipsis when needed
2933        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
2934        if (warning && [fDefaults boolForKey: @"CheckRemove"])
2935        {
2936            if (![title hasSuffix: ellipsis])
2937                [menuItem setTitle: [title stringByAppendingEllipsis]];
2938        }
2939        else
2940        {
2941            if ([title hasSuffix: ellipsis])
2942                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
2943        }
2944       
2945        return canUseTable && canDelete && [fTableView numberOfSelectedRows] > 0;
2946    }
2947
2948    //enable pause all item
2949    if (action == @selector(stopAllTorrents:))
2950    {
2951        Torrent * torrent;
2952        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2953        while ((torrent = [enumerator nextObject]))
2954            if ([torrent isActive] || [torrent waitingToStart])
2955                return YES;
2956        return NO;
2957    }
2958   
2959    //enable resume all item
2960    if (action == @selector(resumeAllTorrents:))
2961    {
2962        Torrent * torrent;
2963        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2964        while ((torrent = [enumerator nextObject]))
2965            if (![torrent isActive] && ![torrent waitingToStart])
2966                return YES;
2967        return NO;
2968    }
2969   
2970    //enable resume all waiting item
2971    if (action == @selector(resumeWaitingTorrents:))
2972    {
2973        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2974            return NO;
2975   
2976        Torrent * torrent;
2977        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2978        while ((torrent = [enumerator nextObject]))
2979            if (![torrent isActive] && [torrent waitingToStart])
2980                return YES;
2981        return NO;
2982    }
2983   
2984    //enable resume selected waiting item
2985    if (action == @selector(resumeSelectedTorrentsNoWait:))
2986    {
2987        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2988            return NO;
2989   
2990        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2991        Torrent * torrent;
2992        while ((torrent = [enumerator nextObject]))
2993            if (![torrent isActive])
2994                return YES;
2995        return NO;
2996    }
2997
2998    //enable pause item
2999    if (action == @selector(stopSelectedTorrents:))
3000    {
3001        if (!canUseTable)
3002            return NO;
3003   
3004        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
3005        Torrent * torrent;
3006        while ((torrent = [enumerator nextObject]))
3007            if ([torrent isActive] || [torrent waitingToStart])
3008                return YES;
3009        return NO;
3010    }
3011   
3012    //enable resume item
3013    if (action == @selector(resumeSelectedTorrents:))
3014    {
3015        if (!canUseTable)
3016            return NO;
3017   
3018        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
3019        Torrent * torrent;
3020        while ((torrent = [enumerator nextObject]))
3021            if (![torrent isActive] && ![torrent waitingToStart])
3022                return YES;
3023        return NO;
3024    }
3025   
3026    //enable manual announce item
3027    if (action == @selector(announceSelectedTorrents:))
3028    {
3029        if (!canUseTable)
3030            return NO;
3031       
3032        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
3033        Torrent * torrent;
3034        while ((torrent = [enumerator nextObject]))
3035            if ([torrent canManualAnnounce])
3036                return YES;
3037        return NO;
3038    }
3039   
3040    //enable reset cache item
3041    if (action == @selector(resetCacheForSelectedTorrents:))
3042        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3043   
3044    //enable move torrent file item
3045    if (action == @selector(moveDataFiles:))
3046        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3047   
3048    //enable copy torrent file item
3049    if (action == @selector(copyTorrentFiles:))
3050        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3051   
3052    //enable reverse sort item
3053    if (action == @selector(setSortReverse:))
3054    {
3055        [menuItem setState: [fDefaults boolForKey: @"SortReverse"] ? NSOnState : NSOffState];
3056        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER];
3057    }
3058   
3059    //check proper filter search item
3060    if (action == @selector(setFilterSearchType:))
3061    {
3062        NSString * filterType = [fDefaults stringForKey: @"FilterSearchType"];
3063       
3064        BOOL state;
3065        if ([menuItem tag] == FILTER_TYPE_TAG_TRACKER)
3066            state = [filterType isEqualToString: FILTER_TYPE_TRACKER];
3067        else
3068            state = [filterType isEqualToString: FILTER_TYPE_NAME];
3069       
3070        [menuItem setState: state ? NSOnState : NSOffState];
3071        return YES;
3072    }
3073   
3074    return YES;
3075}
3076
3077- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
3078{
3079    NSEnumerator * enumerator;
3080    Torrent * torrent;
3081    BOOL allowSleep;
3082
3083    switch (messageType)
3084    {
3085        case kIOMessageSystemWillSleep:
3086            //close all connections before going to sleep and remember we should resume when we wake up
3087            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
3088
3089            //wait for running transfers to stop (5 second timeout)
3090            NSDate * start = [NSDate date];
3091            BOOL timeUp = NO;
3092           
3093            enumerator = [fTorrents objectEnumerator];
3094            while (!timeUp && (torrent = [enumerator nextObject]))
3095                while ([torrent isActive] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
3096                {
3097                    usleep(100000);
3098                    [torrent update];
3099                }
3100
3101            IOAllowPowerChange(fRootPort, (long) messageArgument);
3102            break;
3103
3104        case kIOMessageCanSystemSleep:
3105            allowSleep = YES;
3106            if ([fDefaults boolForKey: @"SleepPrevent"])
3107            {
3108                //prevent idle sleep unless no torrents are active
3109                enumerator = [fTorrents objectEnumerator];
3110                while ((torrent = [enumerator nextObject]))
3111                    if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
3112                    {
3113                        allowSleep = NO;
3114                        break;
3115                    }
3116            }
3117
3118            if (allowSleep)
3119                IOAllowPowerChange(fRootPort, (long) messageArgument);
3120            else
3121                IOCancelPowerChange(fRootPort, (long) messageArgument);
3122            break;
3123
3124        case kIOMessageSystemHasPoweredOn:
3125            //resume sleeping transfers after we wake up
3126            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
3127            [self autoSpeedLimitChange: nil];
3128            break;
3129    }
3130}
3131
3132- (NSMenu *) applicationDockMenu: (NSApplication *) sender
3133{
3134    int seeding = 0, downloading = 0;
3135    NSEnumerator * enumerator = [fTorrents objectEnumerator];
3136    Torrent * torrent;
3137    while ((torrent = [enumerator nextObject]))
3138    {
3139        if ([torrent isSeeding])
3140            seeding++;
3141        else if ([torrent isActive])
3142            downloading++;
3143        else;
3144    }
3145   
3146    NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
3147            * downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];
3148   
3149    BOOL hasSeparator = seedingItem || downloadingItem;
3150   
3151    if (seeding > 0)
3152    {
3153        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding",
3154                                                        "Dock item - Seeding"), seeding];
3155        if (!seedingItem)
3156        {
3157            seedingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3158            [seedingItem setTag: DOCK_SEEDING_TAG];
3159            [fDockMenu insertItem: seedingItem atIndex: 0];
3160        }
3161        else
3162            [seedingItem setTitle: title];
3163    }
3164    else
3165    {
3166        if (seedingItem)
3167            [fDockMenu removeItem: seedingItem];
3168    }
3169   
3170    if (downloading > 0)
3171    {
3172        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading",
3173                                                        "Dock item - Downloading"), downloading];
3174        if (!downloadingItem)
3175        {
3176            downloadingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3177            [downloadingItem setTag: DOCK_DOWNLOADING_TAG];
3178            [fDockMenu insertItem: downloadingItem atIndex: seeding > 0 ? 1 : 0];
3179        }
3180        else
3181            [downloadingItem setTitle: title];
3182    }
3183    else
3184    {
3185        if (downloadingItem)
3186            [fDockMenu removeItem: downloadingItem];
3187    }
3188   
3189    if (seeding > 0 || downloading > 0)
3190    {
3191        if (!hasSeparator)
3192            [fDockMenu insertItem: [NSMenuItem separatorItem] atIndex: seeding > 0 && downloading > 0 ? 2 : 1];
3193    }
3194    else
3195    {
3196        if (hasSeparator)
3197            [fDockMenu removeItemAtIndex: 0];
3198    }
3199   
3200    return fDockMenu;
3201}
3202
3203- (void) updateDockBadge: (NSNotification *) notification
3204{
3205    [fBadger updateBadge];
3206}
3207
3208- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
3209{
3210    //if auto size is enabled, the current frame shouldn't need to change
3211    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
3212   
3213    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
3214    return frame;
3215}
3216
3217- (void) setWindowSizeToFit
3218{
3219    if ([fDefaults boolForKey: @"AutoSize"])
3220    {
3221        [fScrollView setHasVerticalScroller: NO];
3222        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
3223        [fScrollView setHasVerticalScroller: YES];
3224    }
3225}
3226
3227- (NSRect) sizedWindowFrame
3228{
3229    float heightChange = [fDisplayedTorrents count] * ([fTableView rowHeight] +
3230            [fTableView intercellSpacing].height) - [fScrollView frame].size.height;
3231    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
3232}
3233
3234- (void) showMainWindow: (id) sender
3235{
3236    [fWindow makeKeyAndOrderFront: nil];
3237}
3238
3239- (void) windowDidBecomeMain: (NSNotification *) notification
3240{
3241    [fStatusBar setNeedsDisplay: YES];
3242   
3243    [fBadger clearCompleted];
3244    [self updateUI];
3245}
3246
3247- (void) windowDidResignMain: (NSNotification *) notification
3248{
3249    [fStatusBar setNeedsDisplay: YES];
3250}
3251
3252- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
3253{
3254    //only resize horizontally if autosize is enabled
3255    if ([fDefaults boolForKey: @"AutoSize"])
3256        proposedFrameSize.height = [fWindow frame].size.height;
3257    return proposedFrameSize;
3258}
3259
3260- (void) windowDidResize: (NSNotification *) notification
3261{
3262    //size search filter to not overlap buttons
3263    float pointX = NSMaxX([fPauseFilterButton frame]) + 5.0;
3264    NSRect oldFrame = [fSearchFilterField frame],
3265            frame = NSMakeRect(pointX, oldFrame.origin.y, NSMaxX(oldFrame) - pointX, oldFrame.size.height);
3266   
3267    BOOL show;
3268    if (show = (frame.size.width >= SEARCH_FILTER_MIN_WIDTH))
3269    {
3270        //make sure it is not too long
3271        if (frame.size.width > SEARCH_FILTER_MAX_WIDTH)
3272        {
3273            float different = frame.size.width - SEARCH_FILTER_MAX_WIDTH;
3274            frame.origin.x += different;
3275            frame.size.width -= different;
3276        }
3277        [fSearchFilterField setFrame: frame];
3278    }
3279   
3280    //hide search filter if it overlaps filter buttons
3281    [fSearchFilterField setHidden: !show];
3282}
3283
3284- (void) applicationWillUnhide: (NSNotification *) notification
3285{
3286    [self updateUI];
3287}
3288
3289- (void) linkHomepage: (id) sender
3290{
3291    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
3292}
3293
3294- (void) linkForums: (id) sender
3295{
3296    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
3297}
3298
3299- (void) linkDonate: (id) sender
3300{
3301    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]];
3302}
3303
3304- (void) prepareForUpdate: (NSNotification *) notification
3305{
3306    fUpdateInProgress = YES;
3307}
3308
3309- (NSDictionary *) registrationDictionaryForGrowl
3310{
3311    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
3312                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
3313    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
3314                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
3315}
3316
3317- (void) growlNotificationWasClicked: (id) clickContext
3318{
3319    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
3320        return;
3321   
3322    NSString * type = [clickContext objectForKey: @"Type"], * location;
3323    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
3324            && (location = [clickContext objectForKey: @"Location"]))
3325        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
3326}
3327
3328- (void) ipcQuit
3329{
3330    fRemoteQuit = YES;
3331    [NSApp terminate: self];
3332}
3333
3334- (NSArray *) ipcGetTorrentsByID: (NSArray *) idlist
3335{
3336    if (!idlist)
3337        return fTorrents;
3338   
3339    NSMutableArray * torrents = [NSMutableArray array];
3340   
3341    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * idEnum;
3342    int torId;
3343    Torrent * torrent;
3344    NSNumber * tempId;
3345    while ((torrent = [torrentEnum nextObject]))
3346    {
3347        torId = [torrent torrentID];
3348       
3349        idEnum = [idlist objectEnumerator];
3350        while ((tempId = [idEnum nextObject]))
3351        {
3352            if ([tempId intValue] == torId)
3353            {
3354                [torrents addObject: torrent];
3355                break;
3356            }
3357        }
3358    }
3359
3360    return torrents;
3361}
3362
3363- (NSArray *) ipcGetTorrentsByHash: (NSArray *) hashlist
3364{
3365    if (!hashlist)
3366        return fTorrents;
3367   
3368    NSMutableArray * torrents = [NSMutableArray array];
3369   
3370    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * hashEnum;
3371    NSString * torHash, * tempHash;
3372    Torrent * torrent;
3373    while ((torrent = [torrentEnum nextObject]))
3374    {
3375        torHash = [torrent hashString];
3376       
3377        hashEnum = [hashlist objectEnumerator];
3378        while ((tempHash = [hashEnum nextObject]))
3379        {
3380            if ([torHash caseInsensitiveCompare: tempHash] == NSOrderedSame)
3381            {
3382                [torrents addObject: torrent];
3383                break;
3384            }
3385        }
3386    }
3387   
3388    return torrents;
3389}
3390
3391- (BOOL) ipcAddTorrents: (NSArray *) torrents
3392{
3393    int oldCount = [fTorrents count];
3394   
3395    [self openFiles: torrents];
3396   
3397    return [fTorrents count] > oldCount;
3398}
3399
3400- (BOOL) ipcAddTorrentFile: (NSString *) path directory: (NSString *) directory
3401{
3402    int oldCount = [fTorrents count];
3403   
3404    [self openFiles: [NSArray arrayWithObject: path] forcePath: directory ignoreDownloadFolder: NO
3405            deleteTorrentFile: TORRENT_FILE_DEFAULT];
3406   
3407    return [fTorrents count] > oldCount;
3408}
3409
3410- (BOOL) ipcAddTorrentFileAutostart: (NSString *) path directory: (NSString *) directory autostart: (BOOL) autostart
3411{
3412    /* 'path' is path to torrent file, 'dir' is the directory it
3413       should download it's files to and may be nil, 'autostart' is a
3414       boolean indicating if the torrent should be automatically
3415       started (or queued to start, I guess), should return NO if
3416       torrent fails to load */
3417    return NO;
3418}
3419
3420- (BOOL) ipcAddTorrentData: (NSData *) data directory: (NSString *) directory
3421{
3422    /* 'data' is the contents of a torrent file, 'directory' is the
3423       directory it should download it's files to and may be nil,
3424       should return NO if torrent fails to load */
3425    return NO;
3426}
3427
3428- (BOOL) ipcAddTorrentDataAutostart: (NSData *) path directory: (NSString *) directory autostart: (BOOL) autostart
3429{
3430    /* 'data' is the contents of a torrent file, 'directory' is the
3431       directory it should download it's files to and may be nil,
3432       'autostart' is a boolean indicating if the torrent should be
3433       automatically started (or queued to start, I guess), should
3434       return NO if torrent fails to load */
3435    return NO;
3436}
3437
3438- (BOOL) ipcStartTorrents: (NSArray *) torrents
3439{
3440    if (!torrents)
3441        [self resumeAllTorrents: self];
3442    else
3443        [self resumeTorrents: torrents];
3444
3445    return YES;
3446}
3447
3448- (BOOL) ipcStopTorrents: (NSArray *) torrents
3449{
3450    if (!torrents)
3451        [self stopAllTorrents: self];
3452    else
3453        [self stopTorrents: torrents];
3454
3455    return YES;
3456}
3457
3458- (BOOL) ipcRemoveTorrents: (NSArray *) torrents
3459{
3460    if (!torrents)
3461        torrents = [NSArray arrayWithArray: fTorrents];
3462
3463    [self confirmRemoveTorrents: torrents deleteData: NO deleteTorrent: NO];
3464
3465    return YES;
3466}
3467
3468@end
Note: See TracBrowser for help on using the repository browser.