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

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

Simplifies removing torrents code

File size: 37.2 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] && [fDefaults boolForKey: @"CheckRemove"] )
463    {
464        NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
465            [NSString stringWithFormat: @"%d", idx], @"Index",
466            [NSString stringWithFormat: @"%d", deleteTorrent], @"DeleteTorrent",
467            [NSString stringWithFormat: @"%d", deleteData], @"DeleteData",
468            nil];
469        [dict retain];
470
471        NSBeginAlertSheet(@"Confirm Remove",
472            @"Remove", @"Cancel", nil, fWindow, self,
473            @selector(removeSheetDidEnd:returnCode:contextInfo:),  NULL, dict,
474            @"This torrent is active. Do you really want to remove it?");
475    }
476    else
477    {
478        [self confirmRemoveTorrentWithIndex: idx
479                deleteTorrent: deleteTorrent
480                deleteData: deleteData];
481    }
482}
483
484- (void) removeSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode
485                        contextInfo:(NSDictionary  *)dict
486{
487    [NSApp stopModal];
488
489    if( returnCode == NSAlertDefaultReturn )
490    {
491        int idx = [[dict objectForKey:@"Index"] intValue];
492
493        [self confirmRemoveTorrentWithIndex: idx
494            deleteTorrent: [[dict objectForKey:@"DeleteTorrent"] intValue]
495            deleteData: [[dict objectForKey:@"DeleteData"] intValue]];
496    }
497
498    [dict release];
499}
500
501- (void) confirmRemoveTorrentWithIndex: (int) idx
502            deleteTorrent: (BOOL) deleteTorrent
503            deleteData: (BOOL) deleteData
504{
505    /* Pause if not paused already */
506    [self stopTorrentWithIndex:idx];
507
508#if 0
509    if( deleteData )
510    {
511        [self finderTrash: [NSString stringWithFormat: @"%@/%@",
512            [NSString stringWithUTF8String: fStat[idx].folder],
513            [NSString stringWithUTF8String: fStat[idx].info.name]]];
514    }
515    if( deleteTorrent )
516    {
517        [self finderTrash: [NSString stringWithUTF8String:
518            fStat[idx].info.torrent]];
519    }
520
521    tr_torrentClose( fHandle, idx );
522    [self updateUI: NULL];
523    [self updateTorrentHistory];
524#endif
525}
526
527- (void) removeTorrent: (id) sender
528{
529    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: NO ];
530}
531
532- (void) removeTorrentDeleteFile: (id) sender
533{
534    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: NO];
535}
536
537- (void) removeTorrentDeleteData: (id) sender
538{
539    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: YES];
540}
541
542- (void) removeTorrentDeleteBoth: (id) sender
543{
544    [self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: YES];
545}
546
547- (void) showInfo: (id) sender
548{
549    if( [fInfoPanel isVisible] )
550    {
551        [fInfoPanel close];
552    }
553    else
554    {
555        [fInfoPanel orderFront: sender];
556    }
557}
558
559- (void) updateUI: (NSTimer *) t
560{
561    float dl, ul;
562    NSEnumerator * enumerator;
563    Torrent * torrent;
564
565    /* Update the TableView */
566    enumerator = [fTorrents objectEnumerator];
567    while( ( torrent = [enumerator nextObject] ) )
568    {
569        [torrent update];
570
571        if( [torrent justFinished] )
572        {
573            /* Notifications */
574            [self notifyGrowl: [torrent name]];
575            if( ![fWindow isKeyWindow] )
576            {
577                fCompleted++;
578            }
579        }
580    }
581    [fTableView reloadData];
582
583    //Update the global DL/UL rates
584    tr_torrentRates( fLib, &dl, &ul );
585    NSString * downloadRate = [NSString stringForSpeed: dl];
586    NSString * uploadRate = [NSString stringForSpeed: ul];
587    [fTotalDLField setStringValue: downloadRate];
588    [fTotalULField setStringValue: uploadRate];
589
590#if 0
591    //Update DL/UL totals in the Info panel
592    row = [fTableView selectedRow];
593    if( row >= 0 )
594    {
595        [fInfoDownloaded setStringValue:
596            [NSString stringForFileSize: fStat[row].downloaded]];
597        [fInfoUploaded setStringValue:
598            [NSString stringForFileSize: fStat[row].uploaded]];
599    }
600#endif
601
602    //badge dock
603    [fBadger updateBadgeWithCompleted: fCompleted
604        uploadRate: ul >= 0.1 && [fDefaults boolForKey: @"BadgeUploadRate"]
605          ? [NSString stringForSpeedAbbrev: ul] : nil
606        downloadRate: dl >= 0.1 && [fDefaults boolForKey: @"BadgeDownloadRate"]
607          ? [NSString stringForSpeedAbbrev: dl] : nil];
608}
609
610- (void) updateTorrentHistory
611{
612    if( [fTorrents count] < 1 )
613        return;
614
615    NSMutableArray * history = [NSMutableArray
616        arrayWithCapacity: [fTorrents count]];
617
618    NSEnumerator * enumerator = [fTorrents objectEnumerator];
619    Torrent * torrent;
620    while( ( torrent = [enumerator nextObject] ) )
621    {
622        [history addObject: [NSDictionary dictionaryWithObjectsAndKeys:
623            [torrent path],                      @"TorrentPath",
624            [torrent getFolder],                 @"DownloadFolder",
625            [torrent isActive] ? @"NO" : @"YES", @"Paused",
626            nil]];
627    }
628
629    [fDefaults setObject: history forKey: @"History"];
630}
631
632- (int) numberOfRowsInTableView: (NSTableView *) t
633{
634    return [fTorrents count];
635}
636
637- (id) tableView: (NSTableView *) t objectValueForTableColumn:
638    (NSTableColumn *) tableColumn row: (int) rowIndex
639{
640    return nil;
641}
642
643
644- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
645    forTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex
646{
647    [cell setTorrent: [fTorrents objectAtIndex: rowIndex]];
648    [cell setTextColor: ( [fWindow isKeyWindow] &&
649        rowIndex == [fTableView selectedRow] ) ?
650        [NSColor whiteColor] : [NSColor blackColor]];
651}
652
653- (BOOL) tableView: (NSTableView *) t acceptDrop:
654    (id <NSDraggingInfo>) info row: (int) row dropOperation:
655    (NSTableViewDropOperation) operation
656{
657    [self application: NSApp openFiles: [[[info draggingPasteboard]
658        propertyListForType: NSFilenamesPboardType]
659        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
660    return YES;
661}
662
663- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
664    (id <NSDraggingInfo>) info proposedRow: (int) row
665    proposedDropOperation: (NSTableViewDropOperation) operation
666{
667    NSPasteboard * pasteboard = [info draggingPasteboard];
668
669    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
670            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
671                pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]
672                count] == 0)
673        return NSDragOperationNone;
674
675    [fTableView setDropRow: [fTorrents count]
676        dropOperation: NSTableViewDropAbove];
677    return NSDragOperationGeneric;
678}
679
680- (void) tableViewSelectionDidChange: (NSNotification *) n
681{
682    int row = [fTableView selectedRow];
683
684    if( row < 0 )
685    {
686        [fInfoTitle      setStringValue: @"No torrent selected"];
687        [fInfoTracker    setStringValue: @""];
688        [fInfoAnnounce   setStringValue: @""];
689        [fInfoSize       setStringValue: @""];
690        [fInfoPieces     setStringValue: @""];
691        [fInfoPieceSize  setStringValue: @""];
692        [fInfoFolder     setStringValue: @""];
693        [fInfoDownloaded setStringValue: @""];
694        [fInfoUploaded   setStringValue: @""];
695        [fInfoSeeders    setStringValue: @""];
696        [fInfoLeechers   setStringValue: @""];
697        return;
698    }
699
700#if 0
701    /* Update info window */
702    [fInfoTitle setStringValue: [NSString stringWithUTF8String:
703        fStat[row].info.name]];
704    [fInfoTracker setStringValue: [NSString stringWithFormat:
705        @"%s:%d", fStat[row].info.trackerAddress, fStat[row].info.trackerPort]];
706    [fInfoAnnounce setStringValue: [NSString stringWithCString:
707        fStat[row].info.trackerAnnounce]];
708    [fInfoSize setStringValue:
709        [NSString stringForFileSize: fStat[row].info.totalSize]];
710    [fInfoPieces setStringValue: [NSString stringWithFormat: @"%d",
711        fStat[row].info.pieceCount]];
712    [fInfoPieceSize setStringValue:
713        [NSString stringForFileSize: fStat[row].info.pieceSize]];
714    [fInfoFolder setStringValue: [[NSString stringWithUTF8String:
715        tr_torrentGetFolder( fHandle, row )] lastPathComponent]];
716
717    if ( fStat[row].seeders == -1 ) {
718        [fInfoSeeders setStringValue: [NSString stringWithUTF8String: "?"]];
719    } else {
720        [fInfoSeeders setStringValue: [NSString stringWithFormat: @"%d",
721            fStat[row].seeders]];
722    }
723    if ( fStat[row].leechers == -1 ) {
724        [fInfoLeechers setStringValue: [NSString stringWithUTF8String: "?"]];
725    } else {
726        [fInfoLeechers setStringValue: [NSString stringWithFormat: @"%d",
727            fStat[row].leechers]];
728    }
729#endif
730}
731
732- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
733    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
734{
735    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
736
737    if( [ident isEqualToString: TOOLBAR_OPEN] )
738    {
739        [item setLabel: @"Open"];
740        [item setPaletteLabel: [item label]];
741        [item setToolTip: @"Open a torrent"];
742        [item setImage: [NSImage imageNamed: @"Open.png"]];
743        [item setTarget: self];
744        [item setAction: @selector( openShowSheet: )];
745    }
746    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
747    {
748        [item setLabel: @"Remove"];
749        [item setPaletteLabel: [item label]];
750        [item setToolTip: @"Remove torrent from list"];
751        [item setImage: [NSImage imageNamed: @"Remove.png"]];
752        [item setTarget: self];
753        [item setAction: @selector( removeTorrent: )];
754    }
755    else if( [ident isEqualToString: TOOLBAR_INFO] )
756    {
757        [item setLabel: @"Info"];
758        [item setPaletteLabel: [item label]];
759        [item setToolTip: @"Information"];
760        [item setImage: [NSImage imageNamed: @"Info.png"]];
761        [item setTarget: self];
762        [item setAction: @selector( showInfo: )];
763    }
764    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
765    {
766        [item setLabel: @"Resume All"];
767        [item setPaletteLabel: [item label]];
768        [item setToolTip: @"Resume all torrents"];
769        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
770        [item setTarget: self];
771        [item setAction: @selector( resumeAllTorrents: )];
772    }
773    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
774    {
775        [item setLabel: @"Pause All"];
776        [item setPaletteLabel: [item label]];
777        [item setToolTip: @"Pause all torrents"];
778        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
779        [item setTarget: self];
780        [item setAction: @selector( stopAllTorrents: )];
781    }
782    else
783    {
784        [item release];
785        return NULL;
786    }
787
788    return item;
789}
790
791- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
792{
793    return [NSArray arrayWithObjects:
794            TOOLBAR_OPEN, TOOLBAR_REMOVE,
795            TOOLBAR_RESUME_ALL, TOOLBAR_PAUSE_ALL,
796            TOOLBAR_INFO,
797            NSToolbarSeparatorItemIdentifier,
798            NSToolbarSpaceItemIdentifier,
799            NSToolbarFlexibleSpaceItemIdentifier,
800            NSToolbarCustomizeToolbarItemIdentifier,
801            NULL];
802}
803
804- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
805{
806    return [NSArray arrayWithObjects:
807            TOOLBAR_OPEN, TOOLBAR_REMOVE,
808            NSToolbarSeparatorItemIdentifier,
809            TOOLBAR_RESUME_ALL, TOOLBAR_PAUSE_ALL,
810            NSToolbarFlexibleSpaceItemIdentifier,
811            TOOLBAR_INFO,
812            NULL];
813}
814
815- (void) runCustomizationPalette: (id) sender
816{
817    [fToolbar runCustomizationPalette:sender];
818}
819
820- (void) showHideToolbar: (id) sender
821{
822    [fWindow toggleToolbarShown:sender];
823}
824
825- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem
826{
827    SEL action = [toolbarItem action];
828
829    //enable remove item
830    if (action == @selector(removeTorrent:))
831        return [fTableView selectedRow] >= 0;
832
833    Torrent * torrent;
834    NSEnumerator * enumerator;
835
836    //enable resume all item
837    if (action == @selector(resumeAllTorrents:))
838    {
839        enumerator = [fTorrents objectEnumerator];
840        while( ( torrent = [enumerator nextObject] ) )
841            if( [torrent isPaused] )
842                return YES;
843        return NO;
844    }
845
846    //enable pause all item
847    if (action == @selector(stopAllTorrents:))
848    {
849        enumerator = [fTorrents objectEnumerator];
850        while( ( torrent = [enumerator nextObject] ) )
851            if( [torrent isActive] )
852                return YES;
853        return NO;
854    }
855
856    return YES;
857}
858
859- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
860{
861    SEL action = [menuItem action];
862
863    //disable menus if customize sheet is active
864    if ([fToolbar customizationPaletteIsRunning])
865        return NO;
866
867    //enable customize toolbar item
868    if (action == @selector(showHideToolbar:))
869    {
870        [menuItem setTitle: [fToolbar isVisible] ? @"Hide Toolbar" : @"Show Toolbar"];
871        return YES;
872    }
873
874    //enable show info
875    if (action == @selector(showInfo:))
876    {
877        [menuItem setTitle: [fInfoPanel isVisible] ? @"Hide Info" : @"Show Info"];
878        return YES;
879    }
880
881    Torrent * torrent;
882    NSEnumerator * enumerator;
883
884    //enable resume all item
885    if (action == @selector(resumeAllTorrents:))
886    {
887        enumerator = [fTorrents objectEnumerator];
888        while( ( torrent = [enumerator nextObject] ) )
889            if( [torrent isPaused] )
890                return YES;
891        return NO;
892    }
893
894    //enable pause all item
895    if (action == @selector(stopAllTorrents:))
896    {
897        enumerator = [fTorrents objectEnumerator];
898        while( ( torrent = [enumerator nextObject] ) )
899            if( [torrent isActive] )
900                return YES;
901        return NO;
902    }
903
904    int row = [fTableView selectedRow];
905    torrent = ( row < 0 ) ? nil : [fTorrents objectAtIndex: row];
906
907    if (action == @selector(revealFromMenu:))
908    {
909        return ( torrent != nil );
910    }
911
912    //enable remove items
913    if (action == @selector(removeTorrent:)
914        || action == @selector(removeTorrentDeleteFile:)
915        || action == @selector(removeTorrentDeleteData:)
916        || action == @selector(removeTorrentDeleteBoth:))
917    {
918        if( !torrent )
919            return NO;
920
921        //append or remove ellipsis when needed
922        if( [torrent isActive] && [fDefaults boolForKey: @"CheckRemove"] )
923        {
924            if (![[menuItem title] hasSuffix:NS_ELLIPSIS])
925                [menuItem setTitle:[[menuItem title] stringByAppendingString:NS_ELLIPSIS]];
926        }
927        else
928        {
929            if ([[menuItem title] hasSuffix:NS_ELLIPSIS])
930                [menuItem setTitle:[[menuItem title] substringToIndex:[[menuItem title] length]-[NS_ELLIPSIS length]]];
931        }
932        return YES;
933    }
934
935    //enable and change pause / remove item
936    if( action == @selector(resumeTorrent:) ||
937        action == @selector(stopTorrent:) )
938    {
939        if( !torrent )
940            return NO;
941
942        if( [torrent isActive] )
943        {
944            [menuItem setTitle: @"Pause"];
945            [menuItem setAction: @selector( stopTorrent: )];
946        }
947        else
948        {
949            [menuItem setTitle: @"Resume"];
950            [menuItem setAction: @selector( resumeTorrent: )];
951        }
952    }
953
954    return YES;
955}
956
957- (void) sleepCallBack: (natural_t) messageType argument:
958                          (void *) messageArgument
959{
960    NSEnumerator * enumerator;;
961    Torrent * torrent;
962
963    switch( messageType )
964    {
965        case kIOMessageSystemWillSleep:
966            /* Close all connections before going to sleep and remember
967               we should resume when we wake up */
968            enumerator = [fTorrents objectEnumerator];
969            while( ( torrent = [enumerator nextObject] ) )
970            {
971                [torrent sleep];
972            }
973
974            /* Wait for torrents to stop (5 seconds timeout) */
975            NSDate * start = [NSDate date];
976            enumerator = [fTorrents objectEnumerator];
977            while( ( torrent = [enumerator nextObject] ) )
978            {
979                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
980                        ![torrent isPaused] )
981                {
982                    usleep( 100000 );
983                    [torrent update];
984                }
985            }
986
987            IOAllowPowerChange( fRootPort, (long) messageArgument );
988            break;
989
990        case kIOMessageCanSystemSleep:
991            /* Prevent idle sleep unless all paused */
992            if (fDownloading > 0 || fSeeding > 0)
993                IOCancelPowerChange( fRootPort, (long) messageArgument );
994            else
995                IOAllowPowerChange( fRootPort, (long) messageArgument );
996            break;
997
998        case kIOMessageSystemHasPoweredOn:
999            /* Resume download after we wake up */
1000            enumerator = [fTorrents objectEnumerator];
1001            while( ( torrent = [enumerator nextObject] ) )
1002            {
1003                [torrent wakeUp];
1004            }
1005            break;
1006    }
1007}
1008
1009- (NSRect) windowWillUseStandardFrame: (NSWindow *) w
1010    defaultFrame: (NSRect) defaultFrame
1011{
1012    NSRect rectWin, rectView;
1013    float  foo;
1014
1015    rectWin  = [fWindow frame];
1016    rectView = [fScrollView frame];
1017    foo      = 25.0 - rectView.size.height + MAX( 1U, [fTorrents count] ) *
1018        ( [fTableView rowHeight] + [fTableView intercellSpacing].height );
1019
1020    rectWin.size.height += foo;
1021    rectWin.origin.y    -= foo;
1022
1023    return rectWin;
1024}
1025
1026- (void) showMainWindow: (id) sender
1027{
1028    [fWindow makeKeyAndOrderFront: nil];
1029}
1030
1031- (void) linkHomepage: (id) sender
1032{
1033    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1034        URLWithString: WEBSITE_URL]];
1035}
1036
1037- (void) linkForums: (id) sender
1038{
1039    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1040        URLWithString: FORUM_URL]];
1041}
1042
1043- (void) notifyGrowl: (NSString * ) file
1044{
1045    NSString * growlScript;
1046    NSAppleScript * appleScript;
1047    NSDictionary * error;
1048
1049    if( !fHasGrowl )
1050        return;
1051
1052    growlScript = [NSString stringWithFormat:
1053        @"tell application \"System Events\"\n"
1054         "  if exists application process \"GrowlHelperApp\" then\n"
1055         "    tell application \"GrowlHelperApp\"\n "
1056         "      notify with name \"Download Complete\""
1057         "        title \"Download Complete\""
1058         "        description \"%@\""
1059         "        application name \"Transmission\"\n"
1060         "    end tell\n"
1061         "  end if\n"
1062         "end tell", file];
1063    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1064    if( ![appleScript executeAndReturnError: &error] )
1065    {
1066        printf( "Growl notify failed\n" );
1067    }
1068    [appleScript release];
1069}
1070
1071- (void) growlRegister: (id) sender
1072{
1073    NSString * growlScript;
1074    NSAppleScript * appleScript;
1075    NSDictionary * error;
1076
1077    if( !fHasGrowl )
1078        return;
1079
1080    growlScript = [NSString stringWithFormat:
1081        @"tell application \"System Events\"\n"
1082         "  if exists application process \"GrowlHelperApp\" then\n"
1083         "    tell application \"GrowlHelperApp\"\n"
1084         "      register as application \"Transmission\" "
1085         "        all notifications {\"Download Complete\"}"
1086         "        default notifications {\"Download Complete\"}"
1087         "        icon of application \"Transmission\"\n"
1088         "    end tell\n"
1089         "  end if\n"
1090         "end tell"];
1091
1092    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1093    if( ![appleScript executeAndReturnError: &error] )
1094    {
1095        printf( "Growl registration failed\n" );
1096    }
1097    [appleScript release];
1098}
1099
1100- (void) revealFromMenu: (id) sender
1101{
1102    Torrent * torrent;
1103    torrent = [fTorrents objectAtIndex: [fTableView selectedRow]];
1104    [torrent reveal];
1105}
1106
1107- (void) finderTrash: (NSString *) path
1108{
1109    NSString * string;
1110    NSAppleScript * appleScript;
1111    NSDictionary * error;
1112
1113    string = [NSString stringWithFormat:
1114        @"tell application \"Finder\"\n"
1115         "  move (POSIX file \"%@\") to trash\n"
1116         "end tell", path];
1117
1118    appleScript = [[NSAppleScript alloc] initWithSource: string];
1119    if( ![appleScript executeAndReturnError: &error] )
1120    {
1121        printf( "finderTrash failed\n" );
1122    }
1123    [appleScript release];
1124}
1125
1126- (void) checkForUpdate: (id) sender
1127{
1128    [self checkForUpdateAuto: NO];
1129}
1130
1131- (void) checkForUpdateTimer: (NSTimer *) timer
1132{
1133    NSString * check = [fDefaults stringForKey: @"VersionCheck"];
1134    if( [check isEqualToString: @"Never"] )
1135        return;
1136
1137    NSTimeInterval interval;
1138    if( [check isEqualToString: @"Daily"] )
1139        interval = 24 * 3600;
1140    else if( [check isEqualToString: @"Weekly"] )
1141        interval = 7 * 24 * 3600;
1142    else
1143        return;
1144
1145    id lastObject = [fDefaults objectForKey: @"VersionCheckLast"];
1146    NSDate * lastDate = [lastObject isKindOfClass: [NSDate class]] ?
1147        lastObject : nil;
1148    if( lastDate )
1149    {
1150        NSTimeInterval actualInterval =
1151            [[NSDate date] timeIntervalSinceDate: lastDate];
1152        if( actualInterval > 0 && actualInterval < interval )
1153        {
1154            return;
1155        }
1156    }
1157
1158    [self checkForUpdateAuto: YES];
1159    [fDefaults setObject: [NSDate date] forKey: @"VersionCheckLast"];
1160}
1161
1162- (void) checkForUpdateAuto: (BOOL) automatic
1163{
1164    fCheckIsAutomatic = automatic;
1165    [[NSURL URLWithString: VERSION_PLIST_URL]
1166            loadResourceDataNotifyingClient: self usingCache: NO];
1167}
1168
1169- (void) URLResourceDidFinishLoading: (NSURL *) sender
1170{
1171    NSDictionary * dict = [NSPropertyListSerialization
1172                            propertyListFromData: [sender resourceDataUsingCache: NO]
1173                            mutabilityOption: NSPropertyListImmutable
1174                            format: nil errorDescription: nil];
1175
1176    //check if plist was actually found and contains a version
1177    NSString * webVersion = nil;
1178    if (dict)
1179        webVersion = [dict objectForKey: @"Version"];
1180    if (!webVersion)
1181    {
1182        if (!fCheckIsAutomatic)
1183        {
1184            NSAlert * dialog = [[NSAlert alloc] init];
1185            [dialog addButtonWithTitle: @"OK"];
1186            [dialog setMessageText: @"Error checking for updates."];
1187            [dialog setInformativeText:
1188                    @"Transmission was not able to check the latest version available."];
1189            [dialog setAlertStyle: NSInformationalAlertStyle];
1190
1191            [dialog runModal];
1192            [dialog release];
1193        }
1194        return;
1195    }
1196
1197    NSString * currentVersion = [[[NSBundle mainBundle] infoDictionary]
1198                                objectForKey: (NSString *)kCFBundleVersionKey];
1199
1200    NSEnumerator * webEnum = [[webVersion componentsSeparatedByString: @"."] objectEnumerator],
1201            * currentEnum = [[currentVersion componentsSeparatedByString: @"."] objectEnumerator];
1202    NSString * webSub, * currentSub;
1203
1204    BOOL webGreater = NO;
1205    NSComparisonResult result;
1206    while ((webSub = [webEnum nextObject]))
1207    {
1208        if (!(currentSub = [currentEnum nextObject]))
1209        {
1210            webGreater = YES;
1211            break;
1212        }
1213
1214        result = [currentSub compare: webSub options: NSNumericSearch];
1215        if (result != NSOrderedSame)
1216        {
1217            if (result == NSOrderedAscending)
1218                webGreater = YES;
1219            break;
1220        }
1221    }
1222
1223    if (webGreater)
1224    {
1225        NSAlert * dialog = [[NSAlert alloc] init];
1226        [dialog addButtonWithTitle: @"Go to Website"];
1227        [dialog addButtonWithTitle:@"Cancel"];
1228        [dialog setMessageText: @"New version is available!"];
1229        [dialog setInformativeText: [NSString stringWithFormat:
1230            @"A newer version (%@) is available for download from the Transmission website.", webVersion]];
1231        [dialog setAlertStyle: NSInformationalAlertStyle];
1232
1233        if ([dialog runModal] == NSAlertFirstButtonReturn)
1234            [self linkHomepage: nil];
1235
1236        [dialog release];
1237    }
1238    else if (!fCheckIsAutomatic)
1239    {
1240        NSAlert * dialog = [[NSAlert alloc] init];
1241        [dialog addButtonWithTitle: @"OK"];
1242        [dialog setMessageText: @"No new versions are available."];
1243        [dialog setInformativeText: [NSString stringWithFormat:
1244            @"You are running the most current version of Transmission (%@).", currentVersion]];
1245        [dialog setAlertStyle: NSInformationalAlertStyle];
1246
1247        [dialog runModal];
1248        [dialog release];
1249    }
1250    else;
1251}
1252
1253@end
Note: See TracBrowser for help on using the repository browser.