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

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

Added [NSString stringWithInt:] for convenience.
UI cosmetic: bigger margin left from the pause/resume button

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