source: trunk/macosx/Controller.m @ 247

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

Adds Action button that allows quick setting of upload and download limits,
Makes the bottom bar look like Mail's,
Moves stats to a top, hideable bar.

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