source: trunk/macosx/Controller.m @ 107

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

Update the torrent history as the app runs, so it won't be lost if it
doesn't exit properly

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