source: trunk/macosx/Controller.m @ 234

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

Use NSLog on Mac OS X for errors, cosmetic changes

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