source: trunk/macosx/Controller.m @ 261

Last change on this file since 261 was 261, checked in by titer, 16 years ago

Updated svn:keywords

  • Property svn:keywords set to Date Rev Author Id
File size: 40.2 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 261 2006-05-29 21:27:31Z titer $
3 *
4 * Copyright (c) 2005-2006 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 "StringAdditions.h"
31#import "Utils.h"
32#import "TorrentTableView.h"
33
34#import "PrefsController.h"
35
36#define TOOLBAR_OPEN        @"Toolbar Open"
37#define TOOLBAR_REMOVE      @"Toolbar Remove"
38#define TOOLBAR_INFO        @"Toolbar Info"
39#define TOOLBAR_PAUSE_ALL   @"Toolbar Pause All"
40#define TOOLBAR_RESUME_ALL  @"Toolbar Resume All"
41
42#define WEBSITE_URL         @"http://transmission.m0k.org/"
43#define FORUM_URL           @"http://transmission.m0k.org/forum/"
44#define VERSION_PLIST_URL   @"http://transmission.m0k.org/version.plist"
45
46#define GROWL_PATH  @"/Library/PreferencePanes/Growl.prefPane/Contents/Resources/GrowlHelperApp.app"
47
48static void sleepCallBack( void * controller, io_service_t y,
49        natural_t messageType, void * messageArgument )
50{
51    Controller * c = controller;
52    [c sleepCallBack: messageType argument: messageArgument];
53}
54
55
56@implementation Controller
57
58- (id) init
59{
60    if ((self = [super init]))
61    {
62        fLib = tr_init();
63        fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
64    }
65    return self;
66}
67
68- (void) dealloc
69{
70    [fTorrents release];
71   
72    [fInfoController release];
73   
74    tr_close( fLib );
75    [super dealloc];
76}
77
78- (void) awakeFromNib
79{
80    [fPrefsController setPrefsWindow: fLib];
81    fDefaults = [NSUserDefaults standardUserDefaults];
82
83    fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
84   
85    [fAdvancedBarItem setState: [fDefaults
86        boolForKey: @"UseAdvancedBar"] ? NSOnState : NSOffState];
87
88    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
89    [fToolbar setDelegate: self];
90    [fToolbar setAllowsUserCustomization: YES];
91    [fToolbar setAutosavesConfiguration: YES];
92    [fWindow setToolbar: fToolbar];
93    [fWindow setDelegate: self];
94   
95    fStatusBar = YES;
96    if (![fDefaults boolForKey: @"StatusBar"])
97        [self toggleStatusBar: nil];
98   
99    [fActionButton setToolTip: @"Shortcuts for performing special actions."];
100
101    [fTableView setTorrents: fTorrents];
102    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell:
103        [[TorrentCell alloc] init]];
104
105    [fTableView registerForDraggedTypes:
106        [NSArray arrayWithObject: NSFilenamesPboardType]];
107
108    //Register for sleep notifications
109    IONotificationPortRef notify;
110    io_object_t anIterator;
111    if (fRootPort= IORegisterForSystemPower(self, & notify,
112                                sleepCallBack, & anIterator))
113    {
114        CFRunLoopAddSource( CFRunLoopGetCurrent(),
115                            IONotificationPortGetRunLoopSource( notify ),
116                            kCFRunLoopCommonModes );
117    }
118    else
119        NSLog( @"Could not IORegisterForSystemPower" );
120
121    //load torrents from history
122    Torrent * torrent;
123    NSDictionary * historyItem;
124    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
125    while ((historyItem = [enumerator nextObject]))
126        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
127        {
128            [fTorrents addObject: torrent];
129            [torrent release];
130        }
131   
132    [self torrentNumberChanged];
133   
134    //set sort
135    fSortType = [fDefaults stringForKey: @"Sort"];
136    if ([fSortType isEqualToString: @"Name"])
137        fCurrentSortItem = fNameSortItem;
138    else if ([fSortType isEqualToString: @"State"])
139        fCurrentSortItem = fStateSortItem;
140    else
141        fCurrentSortItem = fDateSortItem;
142    [fCurrentSortItem setState: NSOnState];
143
144    //check and register Growl if it is installed for this user or all users
145    NSFileManager * manager = [NSFileManager defaultManager];
146    fHasGrowl = [manager fileExistsAtPath: GROWL_PATH]
147                || [manager fileExistsAtPath: [[NSString stringWithFormat: @"~%@",
148                GROWL_PATH] stringByExpandingTildeInPath]];
149    [self growlRegister: self];
150
151    //initialize badging
152    fBadger = [[Badger alloc] init];
153
154    //timer to update the interface
155    fCompleted = 0;
156    [self updateUI: nil];
157    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
158        selector: @selector( updateUI: ) userInfo: nil repeats: YES];
159    [[NSRunLoop currentRunLoop] addTimer: fTimer
160        forMode: NSModalPanelRunLoopMode];
161    [[NSRunLoop currentRunLoop] addTimer: fTimer
162        forMode: NSEventTrackingRunLoopMode];
163
164    //timer to check for updates
165    [self checkForUpdateTimer: nil];
166    fUpdateTimer = [NSTimer scheduledTimerWithTimeInterval: 60.0
167        target: self selector: @selector( checkForUpdateTimer: )
168        userInfo: nil repeats: YES];
169   
170    [self sortTorrents];
171   
172    //show windows
173    [fWindow makeKeyAndOrderFront: nil];
174    if ([fDefaults boolForKey: @"InfoVisible"])
175        [self showInfo: nil];
176}
177
178- (void) windowDidBecomeKey: (NSNotification *) n
179{
180    /* Reset the number of recently completed downloads */
181    fCompleted = 0;
182}
183
184- (BOOL) applicationShouldHandleReopen: (NSApplication *) app
185    hasVisibleWindows: (BOOL) flag
186{
187    [self showMainWindow: nil];
188    return NO;
189}
190
191- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
192{
193    int active = 0;
194    Torrent * torrent;
195    NSEnumerator * enumerator = [fTorrents objectEnumerator];
196    while( ( torrent = [enumerator nextObject] ) )
197        if( [torrent isActive] )
198            active++;
199
200    if (active > 0 && [fDefaults boolForKey: @"CheckQuit"])
201    {
202        NSString * message = active == 1
203            ? @"There is an active torrent. Do you really want to quit?"
204            : [NSString stringWithFormat:
205                @"There are %d active torrents. Do you really want to quit?",
206                active];
207
208        NSBeginAlertSheet(@"Confirm Quit",
209                            @"Quit", @"Cancel", nil,
210                            fWindow, self,
211                            @selector(quitSheetDidEnd:returnCode:contextInfo:),
212                            nil, nil, message);
213        return NSTerminateLater;
214    }
215
216    return NSTerminateNow;
217}
218
219- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
220                        contextInfo: (void *) contextInfo
221{
222    [NSApp stopModal];
223    [NSApp replyToApplicationShouldTerminate:
224        (returnCode == NSAlertDefaultReturn)];
225}
226
227- (void) applicationWillTerminate: (NSNotification *) notification
228{
229    // Stop updating the interface
230    [fTimer invalidate];
231    [fUpdateTimer invalidate];
232
233    //clear badge
234    [fBadger clearBadge];
235    [fBadger release];
236   
237    //remember info window state
238    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
239
240    //save history
241    [self updateTorrentHistory];
242
243    // Stop running torrents
244    [fTorrents makeObjectsPerformSelector: @selector(stop)];
245
246    // Wait for torrents to stop (5 seconds timeout)
247    NSDate * start = [NSDate date];
248    Torrent * torrent;
249    while ([fTorrents count] > 0)
250    {
251        torrent = [fTorrents objectAtIndex: 0];
252        while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
253                ![torrent isPaused] )
254        {
255            usleep( 100000 );
256            [torrent update];
257        }
258        [fTorrents removeObject: torrent];
259    }
260}
261
262- (void) showPreferenceWindow: (id) sender
263{
264    if (![fPrefsWindow isVisible])
265        [fPrefsWindow center];
266
267    [fPrefsWindow makeKeyAndOrderFront: nil];
268}
269
270- (void) folderChoiceClosed: (NSOpenPanel *) s returnCode: (int) code
271    contextInfo: (Torrent *) torrent
272{
273    if (code == NSOKButton)
274    {
275        [torrent setFolder: [[s filenames] objectAtIndex: 0]];
276        if ([fDefaults boolForKey: @"AutoStartDownload"])
277            [torrent start];
278        [fTorrents addObject: torrent];
279       
280        [self torrentNumberChanged];
281    }
282   
283    [NSApp stopModal];
284}
285
286- (void) application: (NSApplication *) sender
287         openFiles: (NSArray *) filenames
288{
289    BOOL autoStart = [fDefaults boolForKey: @"AutoStartDownload"];
290   
291    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
292    NSString * torrentPath;
293    Torrent * torrent;
294    NSEnumerator * enumerator = [filenames objectEnumerator];
295    while ((torrentPath = [enumerator nextObject]))
296    {
297        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib]))
298            continue;
299
300        /* Add it to the "File > Open Recent" menu */
301        [[NSDocumentController sharedDocumentController]
302            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
303
304        if ([downloadChoice isEqualToString: @"Ask"])
305        {
306            NSOpenPanel * panel = [NSOpenPanel openPanel];
307
308            [panel setPrompt: @"Select Download Folder"];
309            [panel setAllowsMultipleSelection: NO];
310            [panel setCanChooseFiles: NO];
311            [panel setCanChooseDirectories: YES];
312
313            [panel setMessage: [NSString stringWithFormat:
314                                @"Select the download folder for %@",
315                                [torrentPath lastPathComponent]]];
316
317            [panel beginSheetForDirectory: nil file: nil types: nil
318                modalForWindow: fWindow modalDelegate: self didEndSelector:
319                @selector( folderChoiceClosed:returnCode:contextInfo: )
320                contextInfo: torrent];
321            [NSApp runModalForWindow: panel];
322        }
323        else
324        {
325            NSString * folder = [downloadChoice isEqualToString: @"Constant"]
326                                ? [[fDefaults stringForKey: @"DownloadFolder"]
327                                        stringByExpandingTildeInPath]
328                                : [torrentPath stringByDeletingLastPathComponent];
329
330            [torrent setFolder: folder];
331            if (autoStart)
332                [torrent start];
333            [fTorrents addObject: torrent];
334        }
335       
336        [torrent release];
337    }
338
339    [self torrentNumberChanged];
340
341    [self updateUI: nil];
342    [self sortTorrents];
343    [self updateTorrentHistory];
344}
345
346- (NSArray *) torrentsAtIndexes: (NSIndexSet *) indexSet
347{
348    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [indexSet count]];
349    unsigned int i;
350    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
351        [torrents addObject: [fTorrents objectAtIndex: i]];
352
353    return torrents;
354}
355
356- (void) torrentNumberChanged
357{
358    int count = [fTorrents count];
359    [fTotalTorrentsField setStringValue: [NSString stringWithFormat:
360                @"%d Torrent%s", count, count == 1 ? "" : "s"]];
361}
362
363- (void) advancedChanged: (id) sender
364{
365    [fAdvancedBarItem setState: ![fAdvancedBarItem state]];
366    [fDefaults setBool: [fAdvancedBarItem state] forKey: @"UseAdvancedBar"];
367
368    [fTableView display];
369}
370
371//called on by applescript
372- (void) open: (NSArray *) files
373{
374    [self performSelectorOnMainThread: @selector(cantFindAName:)
375                withObject: files waitUntilDone: NO];
376}
377
378- (void) openShowSheet: (id) sender
379{
380    NSOpenPanel * panel = [NSOpenPanel openPanel];
381    NSArray * fileTypes = [NSArray arrayWithObject: @"torrent"];
382
383    [panel setAllowsMultipleSelection: YES];
384    [panel setCanChooseFiles: YES];
385    [panel setCanChooseDirectories: NO];
386
387    [panel beginSheetForDirectory: nil file: nil types: fileTypes
388        modalForWindow: fWindow modalDelegate: self didEndSelector:
389        @selector( openSheetClosed:returnCode:contextInfo: )
390        contextInfo: nil];
391}
392
393- (void) cantFindAName: (NSArray *) filenames
394{
395    [self application: NSApp openFiles: filenames];
396}
397
398- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code
399    contextInfo: (void *) info
400{
401    if( code == NSOKButton )
402        [self performSelectorOnMainThread: @selector(cantFindAName:)
403                    withObject: [panel filenames] waitUntilDone: NO];
404}
405
406- (void) resumeTorrent: (id) sender
407{
408    [self resumeTorrentWithIndex: [fTableView selectedRowIndexes]];
409}
410
411- (void) resumeAllTorrents: (id) sender
412{
413    [self resumeTorrentWithIndex: [NSIndexSet indexSetWithIndexesInRange:
414                                    NSMakeRange(0, [fTorrents count])]];
415}
416
417- (void) resumeTorrentWithIndex: (NSIndexSet *) indexSet
418{
419    Torrent * torrent;
420    unsigned int i;
421    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
422    {
423        torrent = [fTorrents objectAtIndex: i];
424        [torrent start];
425    }
426   
427    [self updateUI: nil];
428    [self sortTorrents];
429    [self updateTorrentHistory];
430}
431
432- (void) stopTorrent: (id) sender
433{
434    [self stopTorrentWithIndex: [fTableView selectedRowIndexes]];
435}
436
437- (void) stopAllTorrents: (id) sender
438{
439    [self stopTorrentWithIndex: [NSIndexSet indexSetWithIndexesInRange:
440                                    NSMakeRange(0, [fTorrents count])]];
441}
442
443- (void) stopTorrentWithIndex: (NSIndexSet *) indexSet
444{
445    Torrent * torrent;
446    unsigned int i;
447    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
448    {
449        torrent = [fTorrents objectAtIndex: i];
450        [torrent stop];
451    }
452   
453    [self updateUI: nil];
454    [self sortTorrents];
455    [self updateTorrentHistory];
456}
457
458- (void) removeTorrentWithIndex: (NSIndexSet *) indexSet
459                  deleteTorrent: (BOOL) deleteTorrent
460                     deleteData: (BOOL) deleteData
461{
462    NSArray * torrents = [[self torrentsAtIndexes: indexSet] retain];
463    int active = 0;
464
465    Torrent * torrent;
466    NSEnumerator * enumerator = [torrents objectEnumerator];
467    while ((torrent = [enumerator nextObject]))
468        if ([torrent isActive])
469            active++;
470
471    if( active > 0 && [fDefaults boolForKey: @"CheckRemove"] )
472    {
473        NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
474            torrents, @"Torrents",
475            [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent",
476            [NSNumber numberWithBool: deleteData], @"DeleteData",
477            nil];
478        [dict retain];
479
480        NSString * title, * message;
481       
482        int selected = [fTableView numberOfSelectedRows];
483        if (selected == 1)
484        {
485            title = [NSString stringWithFormat: @"Comfirm Removal of %@",
486                        [[fTorrents objectAtIndex: [fTableView selectedRow]] name]];
487            message = @"This torrent is active. Do you really want to remove it?";
488        }
489        else
490        {
491            title = [NSString stringWithFormat: @"Comfirm Removal of %d Torrents", active];
492            if (selected == active)
493                message = [NSString stringWithFormat:
494                    @"There are %d active torrents. Do you really want to remove them?", active];
495            else
496                message = [NSString stringWithFormat:
497                    @"There are %d torrents (%d active). Do you really want to remove them?", selected, active];
498        }
499
500        NSBeginAlertSheet(title,
501            @"Remove", @"Cancel", nil, fWindow, self,
502            @selector(removeSheetDidEnd:returnCode:contextInfo:),
503            nil, dict, message);
504    }
505    else
506    {
507        [self confirmRemoveTorrents: torrents
508                deleteTorrent: deleteTorrent
509                deleteData: deleteData];
510    }
511}
512
513- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
514                        contextInfo: (NSDictionary *) dict
515{
516    [NSApp stopModal];
517
518    NSArray * torrents = [dict objectForKey: @"Torrents"];
519    BOOL deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
520    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue];
521    [dict release];
522   
523    if (returnCode == NSAlertDefaultReturn)
524    {
525        [self confirmRemoveTorrents: torrents
526            deleteTorrent: deleteTorrent
527            deleteData: deleteData];
528    }
529    else
530        [torrents release];
531}
532
533- (void) confirmRemoveTorrents: (NSArray *) torrents
534            deleteTorrent: (BOOL) deleteTorrent
535            deleteData: (BOOL) deleteData
536{
537    Torrent * torrent;
538    NSEnumerator * enumerator = [torrents objectEnumerator];
539    while ((torrent = [enumerator nextObject]))
540    {
541        [torrent stop];
542
543        if( deleteData )
544            [torrent trashData];
545           
546        if( deleteTorrent )
547            [torrent trashTorrent];
548
549        [fTorrents removeObject: torrent];
550    }
551    [torrents release];
552   
553    [self torrentNumberChanged];
554
555    [fTableView deselectAll: nil];
556    [self updateUI: nil];
557    [self updateTorrentHistory];
558}
559
560- (void) removeTorrent: (id) sender
561{
562    [self removeTorrentWithIndex: [fTableView selectedRowIndexes] deleteTorrent: NO deleteData: NO];
563}
564
565- (void) removeTorrentDeleteFile: (id) sender
566{
567    [self removeTorrentWithIndex: [fTableView selectedRowIndexes] deleteTorrent: YES deleteData: NO];
568}
569
570- (void) removeTorrentDeleteData: (id) sender
571{
572    [self removeTorrentWithIndex: [fTableView selectedRowIndexes] deleteTorrent: NO deleteData: YES];
573}
574
575- (void) removeTorrentDeleteBoth: (id) sender
576{
577    [self removeTorrentWithIndex: [fTableView selectedRowIndexes] deleteTorrent: YES deleteData: YES];
578}
579
580- (void) revealTorrent: (id) sender
581{
582    Torrent * torrent;
583    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
584    unsigned int i;
585   
586    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
587    {
588        torrent = [fTorrents objectAtIndex: i];
589        [torrent reveal];
590    }
591}
592
593- (void) updateUI: (NSTimer *) t
594{
595    NSEnumerator * enumerator = [fTorrents objectEnumerator];
596    Torrent * torrent;
597    while( ( torrent = [enumerator nextObject] ) )
598    {
599        [torrent update];
600
601        if( [torrent justFinished] )
602        {
603            /* Notifications */
604            [self notifyGrowl: [torrent name]];
605            if( ![fWindow isKeyWindow] )
606                fCompleted++;
607               
608            [self sortTorrents];
609        }
610    }
611   
612    [fTableView reloadData];
613   
614    //Update the global DL/UL rates
615    float dl, ul;
616    tr_torrentRates( fLib, &dl, &ul );
617    NSString * downloadRate = [NSString stringForSpeed: dl];
618    NSString * uploadRate = [NSString stringForSpeed: ul];
619    [fTotalDLField setStringValue: downloadRate];
620    [fTotalULField setStringValue: uploadRate];
621
622    [self updateInfoStats];
623
624    //badge dock
625    [fBadger updateBadgeWithCompleted: fCompleted
626        uploadRate: ul >= 0.1 && [fDefaults boolForKey: @"BadgeUploadRate"]
627          ? [NSString stringForSpeedAbbrev: ul] : nil
628        downloadRate: dl >= 0.1 && [fDefaults boolForKey: @"BadgeDownloadRate"]
629          ? [NSString stringForSpeedAbbrev: dl] : nil];
630}
631
632- (void) updateTorrentHistory
633{
634    NSMutableArray * history = [NSMutableArray
635        arrayWithCapacity: [fTorrents count]];
636
637    NSEnumerator * enumerator = [fTorrents objectEnumerator];
638    Torrent * torrent;
639    while( ( torrent = [enumerator nextObject] ) )
640        [history addObject: [torrent history]];
641
642    [fDefaults setObject: history forKey: @"History"];
643}
644
645- (void) showInfo: (id) sender
646{
647    if ([[fInfoController window] isVisible])
648        [[fInfoController window] performClose: nil];
649    else
650    {
651        [fInfoController updateInfoForTorrents: [self torrentsAtIndexes:
652                                            [fTableView selectedRowIndexes]]];
653        [[fInfoController window] orderFront: nil];
654    }
655}
656
657- (void) updateInfo
658{
659    if ([[fInfoController window] isVisible])
660        [fInfoController updateInfoForTorrents: [self torrentsAtIndexes:
661                                            [fTableView selectedRowIndexes]]];
662}
663
664- (void) updateInfoStats
665{
666    if ([[fInfoController window] isVisible])
667        [fInfoController updateInfoStatsForTorrents: [self torrentsAtIndexes:
668                                            [fTableView selectedRowIndexes]]];
669}
670
671- (void) sortTorrents
672{
673    //remember selected rows if needed
674    NSArray * selectedTorrents = nil;
675    int numSelected = [fTableView numberOfSelectedRows];
676    if (numSelected > 0 && numSelected < [fTorrents count])
677        selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
678
679    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:
680                                            @"name" ascending: YES] autorelease],
681                    * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
682                                            @"date" ascending: YES] autorelease];
683
684    NSArray * descriptors;
685    if ([fSortType isEqualToString: @"Name"])
686        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, dateDescriptor, nil];
687    else if ([fSortType isEqualToString: @"State"])
688    {
689        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
690                                                @"stateSortKey" ascending: NO] autorelease];
691        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, nameDescriptor, dateDescriptor, nil];
692    }
693    else
694        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, nameDescriptor, nil];
695
696    [fTorrents sortUsingDescriptors: descriptors];
697    [descriptors release];
698   
699    [fTableView reloadData];
700   
701    //set selected rows if needed
702    if (selectedTorrents)
703    {
704        [fTableView deselectAll: nil];
705       
706        Torrent * torrent;
707        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
708        while ((torrent = [enumerator nextObject]))
709            [fTableView selectRow: [fTorrents indexOfObject: torrent] byExtendingSelection: YES];
710    }
711}
712
713- (void) setSort: (id) sender
714{
715    [fCurrentSortItem setState: NSOffState];
716    fCurrentSortItem = sender;
717    [sender setState: NSOnState];
718
719    if (sender == fNameSortItem)
720        fSortType = @"Name";
721    else if (sender == fStateSortItem)
722        fSortType = @"State";
723    else
724        fSortType = @"Date";
725       
726    [fDefaults setObject: fSortType forKey: @"Sort"];
727
728    [self sortTorrents];
729}
730
731- (int) numberOfRowsInTableView: (NSTableView *) t
732{
733    return [fTorrents count];
734}
735
736- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
737    forTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex
738{
739    [cell setTorrent: [fTorrents objectAtIndex: rowIndex]];
740
741    if( [fWindow isKeyWindow] && [fTableView isRowSelected: rowIndex] )
742        [cell setTextColor: [NSColor whiteColor]];
743    else
744        [cell setTextColor: [NSColor blackColor]];
745}
746
747- (BOOL) tableView: (NSTableView *) t acceptDrop:
748    (id <NSDraggingInfo>) info row: (int) row dropOperation:
749    (NSTableViewDropOperation) operation
750{
751    [self application: NSApp openFiles: [[[info draggingPasteboard]
752        propertyListForType: NSFilenamesPboardType]
753        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
754    return YES;
755}
756
757- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
758    (id <NSDraggingInfo>) info proposedRow: (int) row
759    proposedDropOperation: (NSTableViewDropOperation) operation
760{
761    NSPasteboard * pasteboard = [info draggingPasteboard];
762
763    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
764            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
765                pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]
766                count] == 0)
767        return NSDragOperationNone;
768
769    [fTableView setDropRow: [fTableView numberOfRows]
770        dropOperation: NSTableViewDropAbove];
771    return NSDragOperationGeneric;
772}
773
774- (void) tableViewSelectionDidChange: (NSNotification *) n
775{
776    [self updateInfo];
777}
778
779- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
780    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
781{
782    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
783
784    if( [ident isEqualToString: TOOLBAR_OPEN] )
785    {
786        [item setLabel: @"Open"];
787        [item setPaletteLabel: [item label]];
788        [item setToolTip: @"Open torrent files"];
789        [item setImage: [NSImage imageNamed: @"Open.png"]];
790        [item setTarget: self];
791        [item setAction: @selector( openShowSheet: )];
792    }
793    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
794    {
795        [item setLabel: @"Remove"];
796        [item setPaletteLabel: [item label]];
797        [item setToolTip: @"Remove selected torrents"];
798        [item setImage: [NSImage imageNamed: @"Remove.png"]];
799        [item setTarget: self];
800        [item setAction: @selector( removeTorrent: )];
801    }
802    else if( [ident isEqualToString: TOOLBAR_INFO] )
803    {
804        [item setLabel: @"Info"];
805        [item setPaletteLabel: [item label]];
806        [item setToolTip: @"Display torrent info"];
807        [item setImage: [NSImage imageNamed: @"Info.png"]];
808        [item setTarget: self];
809        [item setAction: @selector( showInfo: )];
810    }
811    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
812    {
813        [item setLabel: @"Resume All"];
814        [item setPaletteLabel: [item label]];
815        [item setToolTip: @"Resume all torrents"];
816        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
817        [item setTarget: self];
818        [item setAction: @selector( resumeAllTorrents: )];
819    }
820    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
821    {
822        [item setLabel: @"Pause All"];
823        [item setPaletteLabel: [item label]];
824        [item setToolTip: @"Pause all torrents"];
825        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
826        [item setTarget: self];
827        [item setAction: @selector( stopAllTorrents: )];
828    }
829    else
830    {
831        [item release];
832        return nil;
833    }
834
835    return item;
836}
837
838- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
839{
840    return [NSArray arrayWithObjects:
841            TOOLBAR_OPEN, TOOLBAR_REMOVE,
842            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
843            TOOLBAR_INFO,
844            NSToolbarSeparatorItemIdentifier,
845            NSToolbarSpaceItemIdentifier,
846            NSToolbarFlexibleSpaceItemIdentifier,
847            NSToolbarCustomizeToolbarItemIdentifier, nil];
848}
849
850- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
851{
852    return [NSArray arrayWithObjects:
853            TOOLBAR_OPEN, TOOLBAR_REMOVE,
854            NSToolbarSeparatorItemIdentifier,
855            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
856            NSToolbarFlexibleSpaceItemIdentifier,
857            TOOLBAR_INFO, nil];
858}
859
860- (void) toggleStatusBar: (id) sender
861{
862    fStatusBar = !fStatusBar;
863
864    NSSize frameSize = [fScrollView frame].size;
865    [fStats setHidden: !fStatusBar];
866   
867    if (fStatusBar)
868        frameSize.height -= 18;
869    else
870        frameSize.height += 18;
871
872    [fScrollView setFrameSize: frameSize];
873    [fWindow display];
874   
875    [fDefaults setBool: fStatusBar forKey: @"StatusBar"];
876}
877
878- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
879{
880    SEL action = [toolbarItem action];
881
882    //enable remove item
883    if (action == @selector(removeTorrent:))
884        return [fTableView numberOfSelectedRows] > 0;
885
886    Torrent * torrent;
887    NSEnumerator * enumerator;
888
889    //enable resume all item
890    if (action == @selector(resumeAllTorrents:))
891    {
892        enumerator = [fTorrents objectEnumerator];
893        while( ( torrent = [enumerator nextObject] ) )
894            if( [torrent isPaused] )
895                return YES;
896        return NO;
897    }
898
899    //enable pause all item
900    if (action == @selector(stopAllTorrents:))
901    {
902        enumerator = [fTorrents objectEnumerator];
903        while( ( torrent = [enumerator nextObject] ) )
904            if( [torrent isActive] )
905                return YES;
906        return NO;
907    }
908
909    return YES;
910}
911
912- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
913{
914    SEL action = [menuItem action];
915
916    //only enable some menus if window is useable
917    BOOL canUseWindow = [fWindow isKeyWindow] && ![fToolbar customizationPaletteIsRunning];
918
919    //enable show info
920    if (action == @selector(showInfo:))
921    {
922        [menuItem setTitle: [[fInfoController window] isVisible] ? @"Hide Info" : @"Show Info"];
923        return YES;
924    }
925   
926    //enable toggle toolbar
927    if (action == @selector(toggleStatusBar:))
928    {
929        [menuItem setTitle: fStatusBar ? @"Hide Status Bar" : @"Show Status Bar"];
930        return canUseWindow;
931    }
932
933    //enable resume all item
934    if (action == @selector(resumeAllTorrents:))
935    {
936        Torrent * torrent;
937        NSEnumerator * enumerator = [fTorrents objectEnumerator];
938        while( ( torrent = [enumerator nextObject] ) )
939            if( [torrent isPaused] )
940                return YES;
941        return NO;
942    }
943
944    //enable pause all item
945    if (action == @selector(stopAllTorrents:))
946    {
947        Torrent * torrent;
948        NSEnumerator * enumerator = [fTorrents objectEnumerator];
949        while( ( torrent = [enumerator nextObject] ) )
950            if( [torrent isActive] )
951                return YES;
952        return NO;
953    }
954
955    if (action == @selector(revealTorrent:))
956    {
957        return canUseWindow && [fTableView numberOfSelectedRows] > 0;
958    }
959
960    //enable remove items
961    if (action == @selector(removeTorrent:)
962        || action == @selector(removeTorrentDeleteFile:)
963        || action == @selector(removeTorrentDeleteData:)
964        || action == @selector(removeTorrentDeleteBoth:))
965    {
966        BOOL active = NO;
967        Torrent * torrent;
968        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
969        unsigned int i;
970       
971        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
972        {
973            torrent = [fTorrents objectAtIndex: i];
974            if ([torrent isActive])
975            {
976                active = YES;
977                break;
978            }
979        }
980   
981        //append or remove ellipsis when needed
982        NSString * title = [menuItem title];
983        if (active && [fDefaults boolForKey: @"CheckRemove"])
984        {
985            if (![title hasSuffix: NS_ELLIPSIS])
986                [menuItem setTitle: [title stringByAppendingString: NS_ELLIPSIS]];
987        }
988        else
989        {
990            if ([title hasSuffix: NS_ELLIPSIS])
991                [menuItem setTitle:[title substringToIndex:
992                            [title rangeOfString: NS_ELLIPSIS].location]];
993        }
994        return canUseWindow && [fTableView numberOfSelectedRows] > 0;
995    }
996
997    //enable pause item
998    if( action == @selector(stopTorrent:) )
999    {
1000        if (!canUseWindow)
1001            return NO;
1002   
1003        Torrent * torrent;
1004        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1005        unsigned int i;
1006       
1007        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1008        {
1009            torrent = [fTorrents objectAtIndex: i];
1010            if ([torrent isActive])
1011                return YES;
1012        }
1013        return NO;
1014    }
1015   
1016    //enable resume item
1017    if( action == @selector(resumeTorrent:) )
1018    {
1019        if (!canUseWindow)
1020            return NO;
1021   
1022        Torrent * torrent;
1023        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1024        unsigned int i;
1025       
1026        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1027        {
1028            torrent = [fTorrents objectAtIndex: i];
1029            if ([torrent isPaused])
1030                return YES;
1031        }
1032        return NO;
1033    }
1034   
1035    //enable resume item
1036    if (action == @selector(setSort:))
1037        return canUseWindow;
1038
1039    return YES;
1040}
1041
1042- (void) sleepCallBack: (natural_t) messageType argument:
1043                          (void *) messageArgument
1044{
1045    NSEnumerator * enumerator;
1046    Torrent * torrent;
1047    BOOL active;
1048
1049    switch( messageType )
1050    {
1051        case kIOMessageSystemWillSleep:
1052            /* Close all connections before going to sleep and remember
1053               we should resume when we wake up */
1054            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1055
1056            /* Wait for torrents to stop (5 seconds timeout) */
1057            NSDate * start = [NSDate date];
1058            enumerator = [fTorrents objectEnumerator];
1059            while( ( torrent = [enumerator nextObject] ) )
1060            {
1061                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1062                        ![torrent isPaused] )
1063                {
1064                    usleep( 100000 );
1065                    [torrent update];
1066                }
1067            }
1068
1069            IOAllowPowerChange( fRootPort, (long) messageArgument );
1070            break;
1071
1072        case kIOMessageCanSystemSleep:
1073            /* Prevent idle sleep unless all paused */
1074            active = NO;
1075            enumerator = [fTorrents objectEnumerator];
1076            while( !active && ( torrent = [enumerator nextObject] ) )
1077                if( [torrent isActive] )
1078                    active = YES;
1079
1080            if (active)
1081                IOCancelPowerChange( fRootPort, (long) messageArgument );
1082            else
1083                IOAllowPowerChange( fRootPort, (long) messageArgument );
1084            break;
1085
1086        case kIOMessageSystemHasPoweredOn:
1087            /* Resume download after we wake up */
1088            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1089            break;
1090    }
1091}
1092
1093- (NSRect) windowWillUseStandardFrame: (NSWindow *) w
1094    defaultFrame: (NSRect) defaultFrame
1095{
1096    NSRect rectWin = [fWindow frame];
1097    float newHeight = rectWin.size.height - [fScrollView frame].size.height
1098            + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height);
1099
1100    float minHeight = [fWindow minSize].height;
1101    if (newHeight < minHeight)
1102        newHeight = minHeight;
1103
1104    rectWin.origin.y -= (newHeight - rectWin.size.height);
1105    rectWin.size.height = newHeight;
1106
1107    return rectWin;
1108}
1109
1110- (void) showMainWindow: (id) sender
1111{
1112    [fWindow makeKeyAndOrderFront: nil];
1113}
1114
1115- (void) linkHomepage: (id) sender
1116{
1117    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1118        URLWithString: WEBSITE_URL]];
1119}
1120
1121- (void) linkForums: (id) sender
1122{
1123    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1124        URLWithString: FORUM_URL]];
1125}
1126
1127- (void) notifyGrowl: (NSString * ) file
1128{
1129    NSString * growlScript;
1130    NSAppleScript * appleScript;
1131    NSDictionary * error;
1132
1133    if( !fHasGrowl )
1134        return;
1135
1136    growlScript = [NSString stringWithFormat:
1137        @"tell application \"System Events\"\n"
1138         "  if exists application process \"GrowlHelperApp\" then\n"
1139         "    tell application \"GrowlHelperApp\"\n "
1140         "      notify with name \"Download Complete\""
1141         "        title \"Download Complete\""
1142         "        description \"%@\""
1143         "        application name \"Transmission\"\n"
1144         "    end tell\n"
1145         "  end if\n"
1146         "end tell", file];
1147    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1148    if( ![appleScript executeAndReturnError: &error] )
1149    {
1150        NSLog( @"Growl notify failed" );
1151    }
1152    [appleScript release];
1153}
1154
1155- (void) growlRegister: (id) sender
1156{
1157    NSString * growlScript;
1158    NSAppleScript * appleScript;
1159    NSDictionary * error;
1160
1161    if( !fHasGrowl )
1162        return;
1163
1164    growlScript = [NSString stringWithFormat:
1165        @"tell application \"System Events\"\n"
1166         "  if exists application process \"GrowlHelperApp\" then\n"
1167         "    tell application \"GrowlHelperApp\"\n"
1168         "      register as application \"Transmission\" "
1169         "        all notifications {\"Download Complete\"}"
1170         "        default notifications {\"Download Complete\"}"
1171         "        icon of application \"Transmission\"\n"
1172         "    end tell\n"
1173         "  end if\n"
1174         "end tell"];
1175
1176    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1177    if( ![appleScript executeAndReturnError: &error] )
1178    {
1179        NSLog( @"Growl registration failed" );
1180    }
1181    [appleScript release];
1182}
1183
1184- (void) checkForUpdate: (id) sender
1185{
1186    [self checkForUpdateAuto: NO];
1187}
1188
1189- (void) checkForUpdateTimer: (NSTimer *) timer
1190{
1191    NSString * check = [fDefaults stringForKey: @"VersionCheck"];
1192
1193    NSTimeInterval interval;
1194    if( [check isEqualToString: @"Daily"] )
1195        interval = 24 * 3600;
1196    else if( [check isEqualToString: @"Weekly"] )
1197        interval = 7 * 24 * 3600;
1198    else
1199        return;
1200
1201    NSDate * lastDate = [fDefaults objectForKey: @"VersionCheckLast"];
1202    if( lastDate )
1203    {
1204        NSTimeInterval actualInterval = [[NSDate date]
1205                            timeIntervalSinceDate: lastDate];
1206        if( actualInterval > 0 && actualInterval < interval )
1207            return;
1208    }
1209
1210    [self checkForUpdateAuto: YES];
1211    [fDefaults setObject: [NSDate date] forKey: @"VersionCheckLast"];
1212}
1213
1214- (void) checkForUpdateAuto: (BOOL) automatic
1215{
1216    fCheckIsAutomatic = automatic;
1217    [[NSURL URLWithString: VERSION_PLIST_URL]
1218            loadResourceDataNotifyingClient: self usingCache: NO];
1219}
1220
1221- (void) URLResourceDidFinishLoading: (NSURL *) sender
1222{
1223    //check if plist was actually found and contains a version
1224    NSDictionary * dict = [NSPropertyListSerialization
1225                            propertyListFromData: [sender resourceDataUsingCache: NO]
1226                            mutabilityOption: NSPropertyListImmutable
1227                            format: nil errorDescription: nil];
1228    NSString * webVersion;
1229    if (!dict || !(webVersion = [dict objectForKey: @"Version"]))
1230    {
1231        if (!fCheckIsAutomatic)
1232        {
1233            NSAlert * dialog = [[NSAlert alloc] init];
1234            [dialog addButtonWithTitle: @"OK"];
1235            [dialog setMessageText: @"Error checking for updates."];
1236            [dialog setInformativeText:
1237                    @"Transmission was not able to check the latest version available."];
1238            [dialog setAlertStyle: NSInformationalAlertStyle];
1239
1240            [dialog runModal];
1241            [dialog release];
1242        }
1243        return;
1244    }
1245
1246    NSString * currentVersion = [[[NSBundle mainBundle] infoDictionary]
1247                                objectForKey: (NSString *)kCFBundleVersionKey];
1248
1249    NSEnumerator * webEnum = [[webVersion componentsSeparatedByString: @"."] objectEnumerator],
1250            * currentEnum = [[currentVersion componentsSeparatedByString: @"."] objectEnumerator];
1251    NSString * webSub, * currentSub;
1252
1253    BOOL webGreater = NO;
1254    NSComparisonResult result;
1255    while ((webSub = [webEnum nextObject]))
1256    {
1257        if (!(currentSub = [currentEnum nextObject]))
1258        {
1259            webGreater = YES;
1260            break;
1261        }
1262
1263        result = [currentSub compare: webSub options: NSNumericSearch];
1264        if (result != NSOrderedSame)
1265        {
1266            if (result == NSOrderedAscending)
1267                webGreater = YES;
1268            break;
1269        }
1270    }
1271
1272    if (webGreater)
1273    {
1274        NSAlert * dialog = [[NSAlert alloc] init];
1275        [dialog addButtonWithTitle: @"Go to Website"];
1276        [dialog addButtonWithTitle:@"Cancel"];
1277        [dialog setMessageText: @"New version is available!"];
1278        [dialog setInformativeText: [NSString stringWithFormat:
1279            @"A newer version (%@) is available for download from the Transmission website.", webVersion]];
1280        [dialog setAlertStyle: NSInformationalAlertStyle];
1281
1282        if ([dialog runModal] == NSAlertFirstButtonReturn)
1283            [self linkHomepage: nil];
1284
1285        [dialog release];
1286    }
1287    else if (!fCheckIsAutomatic)
1288    {
1289        NSAlert * dialog = [[NSAlert alloc] init];
1290        [dialog addButtonWithTitle: @"OK"];
1291        [dialog setMessageText: @"No new versions are available."];
1292        [dialog setInformativeText: [NSString stringWithFormat:
1293            @"You are running the most current version of Transmission (%@).", currentVersion]];
1294        [dialog setAlertStyle: NSInformationalAlertStyle];
1295
1296        [dialog runModal];
1297        [dialog release];
1298    }
1299    else;
1300}
1301
1302@end
Note: See TracBrowser for help on using the repository browser.