source: trunk/macosx/Controller.m @ 244

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

Saves in history the date at which the torrent was loaded

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