source: branches/new_api/macosx/Controller.m @ 151

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

Moved 'reveal in finder' to the Torrent class, and do it in a PathFinder?-
compatible way (fixes #6)

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