1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-25 09:13:06 +00:00
transmission/macosx/Controller.m
Eric Petit 878aa6828b When both torrent and non-torrent files are dropped, only open the torrents.
Don't badge with completed downloads when the window is key.
 Update the table right after start so you don't stay with an empty list
 for one second.
2006-02-11 06:46:40 +00:00

1236 lines
38 KiB
Objective-C

/******************************************************************************
* Copyright (c) 2005 Eric Petit
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import <IOKit/IOMessage.h>
#import "NameCell.h"
#import "ProgressCell.h"
#import "StringAdditions.h"
#import "Utils.h"
#import "TorrentTableView.h"
#import "PrefsController.h"
#define TOOLBAR_OPEN @"Toolbar Open"
#define TOOLBAR_REMOVE @"Toolbar Remove"
#define TOOLBAR_INFO @"Toolbar Info"
#define TOOLBAR_PAUSE_ALL @"Toolbar Pause All"
#define TOOLBAR_RESUME_ALL @"Toolbar Resume All"
#define WEBSITE_URL @"http://transmission.m0k.org/"
#define FORUM_URL @"http://transmission.m0k.org/forum/"
#define VERSION_PLIST_URL @"http://transmission.m0k.org/version.plist"
#define GROWL_PATH @"/Library/PreferencePanes/Growl.prefPane/Contents/Resources/GrowlHelperApp.app"
static void sleepCallBack( void * controller, io_service_t y,
natural_t messageType, void * messageArgument )
{
Controller * c = controller;
[c sleepCallBack: messageType argument: messageArgument];
}
@implementation Controller
- (void) awakeFromNib
{
[fWindow setContentMinSize: NSMakeSize( 400, 120 )];
fHandle = tr_init();
[fPrefsController setPrefsWindow: fHandle];
fDefaults = [NSUserDefaults standardUserDefaults];
[fInfoPanel setFrameAutosaveName:@"InfoPanel"];
//check advanced bar menu item
[fAdvancedBarItem setState: [fDefaults
boolForKey:@"UseAdvancedBar"] ? NSOnState : NSOffState];
fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
[fToolbar setDelegate: self];
[fToolbar setAllowsUserCustomization: YES];
[fToolbar setAutosavesConfiguration: YES];
[fWindow setToolbar: fToolbar];
[fWindow setDelegate: self];
NSTableColumn * tableColumn;
NameCell * nameCell = [[NameCell alloc] init];
ProgressCell * progressCell = [[ProgressCell alloc] init];
tableColumn = [fTableView tableColumnWithIdentifier: @"Name"];
[tableColumn setDataCell: nameCell];
tableColumn = [fTableView tableColumnWithIdentifier: @"Progress"];
[tableColumn setDataCell: progressCell];
[fTableView setAutosaveTableColumns: YES];
//[fTableView sizeToFit];
[fTableView registerForDraggedTypes: [NSArray arrayWithObjects:
NSFilenamesPboardType, NULL]];
//Register for sleep notifications
IONotificationPortRef notify;
io_object_t anIterator;
fRootPort = IORegisterForSystemPower( self, & notify, sleepCallBack,
& anIterator);
if (fRootPort)
{
CFRunLoopAddSource( CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource( notify ),
kCFRunLoopCommonModes );
}
else
printf( "Could not IORegisterForSystemPower\n" );
NSString * torrentPath, * downloadFolder, * paused;
NSDictionary * dic;
NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
while ((dic = [enumerator nextObject]))
{
torrentPath = [dic objectForKey: @"TorrentPath"];
downloadFolder = [dic objectForKey: @"DownloadFolder"];
paused = [dic objectForKey: @"Paused"];
if (!torrentPath || !downloadFolder || !paused)
continue;
if (tr_torrentInit(fHandle, [torrentPath UTF8String]))
continue;
tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1,
[downloadFolder UTF8String] );
if ([paused isEqualToString: @"NO"])
tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 );
}
//check and register Growl if it is installed for this user or all users
NSFileManager * manager = [NSFileManager defaultManager];
fHasGrowl = [manager fileExistsAtPath: GROWL_PATH]
|| [manager fileExistsAtPath: [[NSString stringWithFormat: @"~%@",
GROWL_PATH] stringByExpandingTildeInPath]];
[self growlRegister: self];
//initialize badging
fBadger = [[Badger alloc] init];
//update the interface every 500 ms
fCount = 0;
fDownloading = 0;
fSeeding = 0;
fCompleted = 0;
fStat = nil;
[self updateUI: nil];
fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
selector: @selector( updateUI: ) userInfo: nil repeats: YES];
[[NSRunLoop currentRunLoop] addTimer: fTimer
forMode: NSModalPanelRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer: fTimer
forMode: NSEventTrackingRunLoopMode];
[self checkForUpdateTimer: nil];
fUpdateTimer = [NSTimer scheduledTimerWithTimeInterval: 60.0
target: self selector: @selector( checkForUpdateTimer: )
userInfo: nil repeats: YES];
}
- (void) windowDidBecomeKey: (NSNotification *) n
{
/* Reset the number of recently completed downloads */
fCompleted = 0;
}
- (void) windowDidResize: (NSNotification *) n
{
[fTableView sizeToFit];
}
- (BOOL) windowShouldClose: (id) sender
{
[fWindow orderOut: NULL];
return NO;
}
- (BOOL) applicationShouldHandleReopen: (NSApplication *) app
hasVisibleWindows: (BOOL) flag
{
[self showMainWindow: NULL];
return NO;
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
int active = fDownloading + fSeeding;
if (active > 0 && [fDefaults boolForKey: @"CheckQuit"])
{
NSString * message = active == 1
? @"There is an active torrent. Do you really want to quit?"
: [NSString stringWithFormat:
@"There are %d active torrents. Do you really want to quit?",
active];
NSBeginAlertSheet(@"Confirm Quit",
@"Quit", @"Cancel", nil,
fWindow, self,
@selector(quitSheetDidEnd:returnCode:contextInfo:),
nil, nil, message);
return NSTerminateLater;
}
return NSTerminateNow;
}
- (void) quitSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode
contextInfo:(void *)contextInfo
{
[NSApp stopModal];
[NSApp replyToApplicationShouldTerminate:
(returnCode == NSAlertDefaultReturn)];
}
- (void) applicationWillTerminate: (NSNotification *) notification
{
int i;
// Stop updating the interface
[fTimer invalidate];
[fUpdateTimer invalidate];
//clear badge
[fBadger clearBadge];
[fBadger release];
// Save history
[self updateTorrentHistory];
// Stop running torrents
for( i = 0; i < fCount; i++ )
if( fStat[i].status & ( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD |
TR_STATUS_SEED ) )
tr_torrentStop( fHandle, i );
// Wait for torrents to stop (5 seconds timeout)
NSDate * start = [NSDate date];
while( fCount > 0 )
{
while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
!( fStat[0].status & TR_STATUS_PAUSE ) )
{
usleep( 100000 );
tr_torrentStat( fHandle, &fStat );
}
tr_torrentClose( fHandle, 0 );
fCount = tr_torrentStat( fHandle, &fStat );
}
tr_close( fHandle );
}
- (void) showPreferenceWindow: (id) sender
{
//place the window if not open
if (![fPrefsWindow isVisible])
{
[fPrefsWindow center];
}
[fPrefsWindow makeKeyAndOrderFront:NULL];
}
- (void) folderChoiceClosed: (NSOpenPanel *) s returnCode: (int) code
contextInfo: (void *) info
{
if (code == NSOKButton)
{
tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1,
[[[s filenames] objectAtIndex: 0] UTF8String] );
tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 );
}
else
{
tr_torrentClose( fHandle, tr_torrentCount( fHandle ) - 1 );
}
[NSApp stopModal];
}
- (void) application: (NSApplication *) sender
openFiles: (NSArray *) filenames
{
NSString * downloadChoice, * downloadFolder, * torrentPath;
downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
downloadFolder = [fDefaults stringForKey: @"DownloadFolder"];
NSEnumerator * enumerator = [filenames objectEnumerator];
while ((torrentPath = [enumerator nextObject]))
{
if( tr_torrentInit( fHandle, [torrentPath UTF8String] ) )
continue;
/* Add it to the "File > Open Recent" menu */
[[NSDocumentController sharedDocumentController]
noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
if( [downloadChoice isEqualToString: @"Constant"] )
{
tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1,
[[downloadFolder stringByExpandingTildeInPath] UTF8String] );
tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 );
}
else if( [downloadChoice isEqualToString: @"Torrent"] )
{
tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1,
[[torrentPath stringByDeletingLastPathComponent] UTF8String] );
tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 );
}
else
{
NSOpenPanel * panel = [NSOpenPanel openPanel];
[panel setPrompt: @"Select Download Folder"];
[panel setMessage: [NSString stringWithFormat:
@"Select the download folder for %@",
[torrentPath lastPathComponent]]];
[panel setAllowsMultipleSelection: NO];
[panel setCanChooseFiles: NO];
[panel setCanChooseDirectories: YES];
[panel beginSheetForDirectory: NULL file: NULL types: NULL
modalForWindow: fWindow modalDelegate: self didEndSelector:
@selector( folderChoiceClosed:returnCode:contextInfo: )
contextInfo: NULL];
[NSApp runModalForWindow: panel];
}
}
[self updateUI: NULL];
[self updateTorrentHistory];
}
- (void) advancedChanged: (id) sender
{
[fAdvancedBarItem setState: ![fAdvancedBarItem state]];
[fDefaults setObject: [fAdvancedBarItem state] == NSOffState ? @"NO" : @"YES"
forKey:@"UseAdvancedBar"];
[fTableView display];
}
//called on by applescript
- (void) open: (NSArray *) files
{
fFilenames = [files retain];
[self performSelectorOnMainThread: @selector(cantFindAName:)
withObject: NULL waitUntilDone: NO];
}
- (void) openShowSheet: (id) sender
{
NSOpenPanel * panel;
NSArray * fileTypes;
panel = [NSOpenPanel openPanel];
fileTypes = [NSArray arrayWithObject: @"torrent"];
[panel setAllowsMultipleSelection: YES];
[panel setCanChooseFiles: YES];
[panel setCanChooseDirectories: NO];
[panel beginSheetForDirectory: NULL file: NULL types: fileTypes
modalForWindow: fWindow modalDelegate: self didEndSelector:
@selector( openSheetClosed:returnCode:contextInfo: )
contextInfo: NULL];
}
- (void) cantFindAName: (id) sender
{
[self application: NSApp openFiles: fFilenames];
[fFilenames release];
}
- (void) openSheetClosed: (NSOpenPanel *) s returnCode: (int) code
contextInfo: (void *) info
{
if( code != NSOKButton )
{
return;
}
fFilenames = [[s filenames] retain];
[self performSelectorOnMainThread: @selector(cantFindAName:)
withObject: NULL waitUntilDone: NO];
}
- (void) resumeTorrent: (id) sender
{
[self resumeTorrentWithIndex: [fTableView selectedRow]];
}
- (void) resumeAllTorrents: (id) sender
{
int i;
for ( i = 0; i < fCount; i++)
{
if ( fStat[i].status & ( TR_STATUS_STOPPING
| TR_STATUS_PAUSE | TR_STATUS_STOPPED ) )
{
[self resumeTorrentWithIndex: i];
}
}
}
- (void) resumeTorrentWithIndex: (int) idx
{
tr_torrentStart( fHandle, idx );
[self updateUI: NULL];
[self updateTorrentHistory];
}
- (void) stopTorrent: (id) sender
{
[self stopTorrentWithIndex: [fTableView selectedRow]];
}
- (void) stopAllTorrents: (id) sender
{
int i;
for ( i = 0; i < fCount; i++)
{
if ( fStat[i].status & ( TR_STATUS_CHECK
| TR_STATUS_DOWNLOAD | TR_STATUS_SEED) )
{
[self stopTorrentWithIndex: i];
}
}
}
- (void) stopTorrentWithIndex: (int) idx
{
tr_torrentStop( fHandle, idx );
[self updateUI: NULL];
[self updateTorrentHistory];
}
- (void) removeTorrentWithIndex: (int) idx
deleteTorrent: (BOOL) deleteTorrent
deleteData: (BOOL) deleteData
{
if ( fStat[idx].status & ( TR_STATUS_CHECK
| TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
{
if ([fDefaults boolForKey: @"CheckRemove"])
{
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat: @"%d", idx], @"Index",
[NSString stringWithFormat: @"%d", deleteTorrent], @"DeleteTorrent",
[NSString stringWithFormat: @"%d", deleteData], @"DeleteData",
nil];
[dict retain];
NSBeginAlertSheet(@"Confirm Remove",
@"Remove", @"Cancel", nil,
fWindow, self,
@selector(removeSheetDidEnd:returnCode:contextInfo:),
NULL, dict, @"This torrent is active. Do you really want to remove it?");
return;
}
//stop if not stopped
else
[self stopTorrentWithIndex:idx];
}
[self confirmRemoveTorrentWithIndex: idx
deleteTorrent: deleteTorrent
deleteData: deleteData];
}
- (void) removeSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode
contextInfo:(NSDictionary *)dict
{
[NSApp stopModal];
if (returnCode != NSAlertDefaultReturn)
{
[dict release];
return;
}
int idx = [[dict objectForKey:@"Index"] intValue];
[self stopTorrentWithIndex:idx];
[self confirmRemoveTorrentWithIndex: idx
deleteTorrent: [[dict objectForKey:@"DeleteTorrent"] intValue]
deleteData: [[dict objectForKey:@"DeleteData"] intValue]];
[dict release];
}
- (void) confirmRemoveTorrentWithIndex: (int) idx
deleteTorrent: (BOOL) deleteTorrent
deleteData: (BOOL) deleteData
{
if( deleteData )
{
[self finderTrash: [NSString stringWithFormat: @"%@/%@",
[NSString stringWithUTF8String: fStat[idx].folder],
[NSString stringWithUTF8String: fStat[idx].info.name]]];
}
if( deleteTorrent )
{
[self finderTrash: [NSString stringWithUTF8String:
fStat[idx].info.torrent]];
}
tr_torrentClose( fHandle, idx );
[self updateUI: NULL];
[self updateTorrentHistory];
}
- (void) removeTorrent: (id) sender
{
[self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: NO ];
}
- (void) removeTorrentDeleteFile: (id) sender
{
[self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: NO];
}
- (void) removeTorrentDeleteData: (id) sender
{
[self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: YES];
}
- (void) removeTorrentDeleteBoth: (id) sender
{
[self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: YES];
}
- (void) showInfo: (id) sender
{
if( [fInfoPanel isVisible] )
{
[fInfoPanel close];
}
else
{
[fInfoPanel orderFront: sender];
}
}
- (void) updateUI: (NSTimer *) t
{
float dl, ul;
int row, i;
//Update the NSTableView
if (fStat)
free(fStat);
fCount = tr_torrentStat( fHandle, &fStat );
fDownloading = 0;
fSeeding = 0;
[fTableView updateUI: fStat];
//Update the global DL/UL rates
tr_torrentRates( fHandle, &dl, &ul );
NSString * downloadRate = [NSString stringForSpeed: dl];
NSString * uploadRate = [NSString stringForSpeed: ul];
[fTotalDLField setStringValue: downloadRate];
[fTotalULField setStringValue: uploadRate];
//Update DL/UL totals in the Info panel
row = [fTableView selectedRow];
if( row >= 0 )
{
[fInfoDownloaded setStringValue:
[NSString stringForFileSize: fStat[row].downloaded]];
[fInfoUploaded setStringValue:
[NSString stringForFileSize: fStat[row].uploaded]];
}
//check if torrents have recently ended.
for (i = 0; i < fCount; i++)
{
if (fStat[i].status & (TR_STATUS_CHECK | TR_STATUS_DOWNLOAD))
fDownloading++;
else if (fStat[i].status & TR_STATUS_SEED)
fSeeding++;
if( !tr_getFinished( fHandle, i ) )
continue;
if( ![fWindow isKeyWindow] )
fCompleted++;
[self notifyGrowl: [NSString stringWithUTF8String:
fStat[i].info.name]];
tr_setFinished( fHandle, i, 0 );
}
//badge dock
[fBadger updateBadgeWithCompleted: fCompleted
uploadRate: ul >= 0.1 && [fDefaults boolForKey: @"BadgeUploadRate"]
? [NSString stringForSpeedAbbrev: ul] : nil
downloadRate: dl >= 0.1 && [fDefaults boolForKey: @"BadgeDownloadRate"]
? [NSString stringForSpeedAbbrev: dl] : nil];
}
- (void) updateTorrentHistory
{
if( !fStat )
return;
NSMutableArray * history = [NSMutableArray arrayWithCapacity: fCount];
int i;
for (i = 0; i < fCount; i++)
[history addObject: [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String: fStat[i].info.torrent],
@"TorrentPath",
[NSString stringWithUTF8String: tr_torrentGetFolder( fHandle, i )],
@"DownloadFolder",
fStat[i].status & (TR_STATUS_CHECK | TR_STATUS_DOWNLOAD |
TR_STATUS_SEED) ? @"NO" : @"YES",
@"Paused",
nil]];
[fDefaults setObject: history forKey: @"History"];
}
- (int) numberOfRowsInTableView: (NSTableView *) t
{
return fCount;
}
- (id) tableView: (NSTableView *) t objectValueForTableColumn:
(NSTableColumn *) tableColumn row: (int) rowIndex
{
return NULL;
}
- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
forTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex
{
BOOL w;
w = [fWindow isKeyWindow] && rowIndex == [fTableView selectedRow];
if( [[tableColumn identifier] isEqualToString: @"Name"] )
{
[(NameCell *) cell setStat: &fStat[rowIndex] whiteText: w];
}
else if( [[tableColumn identifier] isEqualToString: @"Progress"] )
{
[(ProgressCell *) cell setStat: &fStat[rowIndex] whiteText: w];
}
}
- (BOOL) tableView: (NSTableView *) t acceptDrop:
(id <NSDraggingInfo>) info row: (int) row dropOperation:
(NSTableViewDropOperation) operation
{
[self application: NSApp openFiles: [[[info draggingPasteboard]
propertyListForType: NSFilenamesPboardType]
pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
return YES;
}
- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
(id <NSDraggingInfo>) info proposedRow: (int) row
proposedDropOperation: (NSTableViewDropOperation) operation
{
NSPasteboard * pasteboard = [info draggingPasteboard];
if (![[pasteboard types] containsObject: NSFilenamesPboardType]
|| [[[pasteboard propertyListForType: NSFilenamesPboardType]
pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]
count] == 0)
return NSDragOperationNone;
[fTableView setDropRow: fCount dropOperation: NSTableViewDropAbove];
return NSDragOperationGeneric;
}
- (void) tableViewSelectionDidChange: (NSNotification *) n
{
int row = [fTableView selectedRow];
if( row < 0 )
{
[fInfoTitle setStringValue: @"No torrent selected"];
[fInfoTracker setStringValue: @""];
[fInfoAnnounce setStringValue: @""];
[fInfoSize setStringValue: @""];
[fInfoPieces setStringValue: @""];
[fInfoPieceSize setStringValue: @""];
[fInfoFolder setStringValue: @""];
[fInfoDownloaded setStringValue: @""];
[fInfoUploaded setStringValue: @""];
[fInfoSeeders setStringValue: @""];
[fInfoLeechers setStringValue: @""];
return;
}
/* Update info window */
[fInfoTitle setStringValue: [NSString stringWithUTF8String:
fStat[row].info.name]];
[fInfoTracker setStringValue: [NSString stringWithFormat:
@"%s:%d", fStat[row].info.trackerAddress, fStat[row].info.trackerPort]];
[fInfoAnnounce setStringValue: [NSString stringWithCString:
fStat[row].info.trackerAnnounce]];
[fInfoSize setStringValue:
[NSString stringForFileSize: fStat[row].info.totalSize]];
[fInfoPieces setStringValue: [NSString stringWithFormat: @"%d",
fStat[row].info.pieceCount]];
[fInfoPieceSize setStringValue:
[NSString stringForFileSize: fStat[row].info.pieceSize]];
[fInfoFolder setStringValue: [[NSString stringWithUTF8String:
tr_torrentGetFolder( fHandle, row )] lastPathComponent]];
if ( fStat[row].seeders == -1 ) {
[fInfoSeeders setStringValue: [NSString stringWithUTF8String: "?"]];
} else {
[fInfoSeeders setStringValue: [NSString stringWithFormat: @"%d",
fStat[row].seeders]];
}
if ( fStat[row].leechers == -1 ) {
[fInfoLeechers setStringValue: [NSString stringWithUTF8String: "?"]];
} else {
[fInfoLeechers setStringValue: [NSString stringWithFormat: @"%d",
fStat[row].leechers]];
}
}
- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
(NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
{
NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
if( [ident isEqualToString: TOOLBAR_OPEN] )
{
[item setLabel: @"Open"];
[item setPaletteLabel: [item label]];
[item setToolTip: @"Open a torrent"];
[item setImage: [NSImage imageNamed: @"Open.png"]];
[item setTarget: self];
[item setAction: @selector( openShowSheet: )];
}
else if( [ident isEqualToString: TOOLBAR_REMOVE] )
{
[item setLabel: @"Remove"];
[item setPaletteLabel: [item label]];
[item setToolTip: @"Remove torrent from list"];
[item setImage: [NSImage imageNamed: @"Remove.png"]];
[item setTarget: self];
[item setAction: @selector( removeTorrent: )];
}
else if( [ident isEqualToString: TOOLBAR_INFO] )
{
[item setLabel: @"Info"];
[item setPaletteLabel: [item label]];
[item setToolTip: @"Information"];
[item setImage: [NSImage imageNamed: @"Info.png"]];
[item setTarget: self];
[item setAction: @selector( showInfo: )];
}
else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
{
[item setLabel: @"Resume All"];
[item setPaletteLabel: [item label]];
[item setToolTip: @"Resume all torrents"];
[item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
[item setTarget: self];
[item setAction: @selector( resumeAllTorrents: )];
}
else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
{
[item setLabel: @"Pause All"];
[item setPaletteLabel: [item label]];
[item setToolTip: @"Pause all torrents"];
[item setImage: [NSImage imageNamed: @"PauseAll.png"]];
[item setTarget: self];
[item setAction: @selector( stopAllTorrents: )];
}
else
{
[item release];
return NULL;
}
return item;
}
- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
{
return [NSArray arrayWithObjects:
TOOLBAR_OPEN, TOOLBAR_REMOVE,
TOOLBAR_RESUME_ALL, TOOLBAR_PAUSE_ALL,
TOOLBAR_INFO,
NSToolbarSeparatorItemIdentifier,
NSToolbarSpaceItemIdentifier,
NSToolbarFlexibleSpaceItemIdentifier,
NSToolbarCustomizeToolbarItemIdentifier,
NULL];
}
- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
{
return [NSArray arrayWithObjects:
TOOLBAR_OPEN, TOOLBAR_REMOVE,
NSToolbarSeparatorItemIdentifier,
TOOLBAR_RESUME_ALL, TOOLBAR_PAUSE_ALL,
NSToolbarFlexibleSpaceItemIdentifier,
TOOLBAR_INFO,
NULL];
}
- (void) runCustomizationPalette: (id) sender
{
[fToolbar runCustomizationPalette:sender];
}
- (void) showHideToolbar: (id) sender
{
[fWindow toggleToolbarShown:sender];
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem
{
SEL action = [toolbarItem action];
//enable remove item
if (action == @selector(removeTorrent:))
return [fTableView selectedRow] >= 0;
//enable resume all item
if (action == @selector(resumeAllTorrents:))
return fCount > fDownloading + fSeeding;
//enable pause all item
if (action == @selector(stopAllTorrents:))
return fDownloading > 0 || fSeeding > 0;
return YES;
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
SEL action = [menuItem action];
//disable menus if customize sheet is active
if ([fToolbar customizationPaletteIsRunning])
return NO;
//enable customize toolbar item
if (action == @selector(showHideToolbar:))
{
[menuItem setTitle: [fToolbar isVisible] ? @"Hide Toolbar" : @"Show Toolbar"];
return YES;
}
//enable show info
if (action == @selector(showInfo:))
{
[menuItem setTitle: [fInfoPanel isVisible] ? @"Hide Info" : @"Show Info"];
return YES;
}
//enable resume all item
if (action == @selector(resumeAllTorrents:))
return fCount > fDownloading + fSeeding;
//enable pause all item
if (action == @selector(stopAllTorrents:))
return fDownloading > 0 || fSeeding > 0;
int row = [fTableView selectedRow];
//enable remove items
if (action == @selector(removeTorrent:)
|| action == @selector(removeTorrentDeleteFile:)
|| action == @selector(removeTorrentDeleteData:)
|| action == @selector(removeTorrentDeleteBoth:))
{
//append or remove ellipsis when needed
if (row >= 0 && fStat[row].status & ( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD)
&& [[fDefaults stringForKey: @"CheckRemove"] isEqualToString:@"YES"])
{
if (![[menuItem title] hasSuffix:NS_ELLIPSIS])
[menuItem setTitle:[[menuItem title] stringByAppendingString:NS_ELLIPSIS]];
}
else
{
if ([[menuItem title] hasSuffix:NS_ELLIPSIS])
[menuItem setTitle:[[menuItem title] substringToIndex:[[menuItem title] length]-[NS_ELLIPSIS length]]];
}
return row >= 0;
}
//enable reveal in finder item
if (action == @selector(revealFromMenu:))
return row >= 0;
//enable and change pause / remove item
if (action == @selector(resumeTorrent:) || action == @selector(stopTorrent:))
{
if (row >= 0 && fStat[row].status & TR_STATUS_PAUSE)
{
[menuItem setTitle: @"Resume"];
[menuItem setAction: @selector( resumeTorrent: )];
}
else
{
[menuItem setTitle: @"Pause"];
[menuItem setAction: @selector( stopTorrent: )];
}
return row >= 0;
}
return YES;
}
- (void) sleepCallBack: (natural_t) messageType argument:
(void *) messageArgument
{
int i;
switch( messageType )
{
case kIOMessageSystemWillSleep:
/* Close all connections before going to sleep and remember
we should resume when we wake up */
for( i = 0; i < fCount; i++ )
{
if( fStat[i].status & ( TR_STATUS_CHECK |
TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
{
tr_torrentStop( fHandle, i );
fResumeOnWake[i] = 1;
}
else
{
fResumeOnWake[i] = 0;
}
}
/* TODO: wait a few seconds to let the torrents
stop properly */
IOAllowPowerChange( fRootPort, (long) messageArgument );
break;
case kIOMessageCanSystemSleep:
/* Prevent idle sleep unless all paused */
if (fDownloading > 0 || fSeeding > 0)
IOCancelPowerChange( fRootPort, (long) messageArgument );
else
IOAllowPowerChange( fRootPort, (long) messageArgument );
break;
case kIOMessageSystemHasPoweredOn:
/* Resume download after we wake up */
for( i = 0; i < fCount; i++ )
{
if( fResumeOnWake[i] )
{
tr_torrentStart( fHandle, i );
}
}
break;
}
}
- (NSRect) windowWillUseStandardFrame: (NSWindow *) w
defaultFrame: (NSRect) defaultFrame
{
NSRect rectWin, rectView;
float foo;
rectWin = [fWindow frame];
rectView = [fScrollView frame];
foo = 25.0 + MAX( 1, fCount ) * ( [fTableView rowHeight] +
[fTableView intercellSpacing].height ) -
rectView.size.height;
rectWin.size.height += foo;
rectWin.origin.y -= foo;
return rectWin;
}
- (void) showMainWindow: (id) sender
{
[fWindow makeKeyAndOrderFront: nil];
}
- (void) linkHomepage: (id) sender
{
[[NSWorkspace sharedWorkspace] openURL: [NSURL
URLWithString: WEBSITE_URL]];
}
- (void) linkForums: (id) sender
{
[[NSWorkspace sharedWorkspace] openURL: [NSURL
URLWithString: FORUM_URL]];
}
- (void) notifyGrowl: (NSString * ) file
{
NSString * growlScript;
NSAppleScript * appleScript;
NSDictionary * error;
if( !fHasGrowl )
return;
growlScript = [NSString stringWithFormat:
@"tell application \"System Events\"\n"
" if exists application process \"GrowlHelperApp\" then\n"
" tell application \"GrowlHelperApp\"\n "
" notify with name \"Download Complete\""
" title \"Download Complete\""
" description \"%@\""
" application name \"Transmission\"\n"
" end tell\n"
" end if\n"
"end tell", file];
appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
if( ![appleScript executeAndReturnError: &error] )
{
printf( "Growl notify failed\n" );
}
[appleScript release];
}
- (void) growlRegister: (id) sender
{
NSString * growlScript;
NSAppleScript * appleScript;
NSDictionary * error;
if( !fHasGrowl )
return;
growlScript = [NSString stringWithFormat:
@"tell application \"System Events\"\n"
" if exists application process \"GrowlHelperApp\" then\n"
" tell application \"GrowlHelperApp\"\n"
" register as application \"Transmission\" "
" all notifications {\"Download Complete\"}"
" default notifications {\"Download Complete\"}"
" icon of application \"Transmission\"\n"
" end tell\n"
" end if\n"
"end tell"];
appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
if( ![appleScript executeAndReturnError: &error] )
{
printf( "Growl registration failed\n" );
}
[appleScript release];
}
- (void) revealFromMenu: (id) sender
{
int row = [fTableView selectedRow];
if (row >= 0)
{
[self finderReveal: [NSString stringWithFormat: @"%@/%@",
[NSString stringWithUTF8String: fStat[row].folder],
[NSString stringWithUTF8String: fStat[row].info.name]]];
}
}
- (void) finderReveal: (NSString *) path
{
NSString * string;
NSAppleScript * appleScript;
NSDictionary * error;
string = [NSString stringWithFormat:
@"tell application \"Finder\"\n"
" activate\n"
" reveal (POSIX file \"%@\")\n"
"end tell", path];
appleScript = [[NSAppleScript alloc] initWithSource: string];
if( ![appleScript executeAndReturnError: &error] )
{
printf( "finderReveal failed\n" );
}
[appleScript release];
}
- (void) finderTrash: (NSString *) path
{
NSString * string;
NSAppleScript * appleScript;
NSDictionary * error;
string = [NSString stringWithFormat:
@"tell application \"Finder\"\n"
" move (POSIX file \"%@\") to trash\n"
"end tell", path];
appleScript = [[NSAppleScript alloc] initWithSource: string];
if( ![appleScript executeAndReturnError: &error] )
{
printf( "finderTrash failed\n" );
}
[appleScript release];
}
- (void) checkForUpdate: (id) sender
{
[self checkForUpdateAuto: NO];
}
- (void) checkForUpdateTimer: (NSTimer *) timer
{
NSString * check = [fDefaults stringForKey: @"VersionCheck"];
if( [check isEqualToString: @"Never"] )
return;
NSTimeInterval interval;
if( [check isEqualToString: @"Daily"] )
interval = 24 * 3600;
else if( [check isEqualToString: @"Weekly"] )
interval = 7 * 24 * 3600;
else
return;
id lastObject = [fDefaults objectForKey: @"VersionCheckLast"];
NSDate * lastDate = [lastObject isKindOfClass: [NSDate class]] ?
lastObject : nil;
if( lastDate )
{
NSTimeInterval actualInterval =
[[NSDate date] timeIntervalSinceDate: lastDate];
if( actualInterval > 0 && actualInterval < interval )
{
return;
}
}
[self checkForUpdateAuto: YES];
[fDefaults setObject: [NSDate date] forKey: @"VersionCheckLast"];
}
- (void) checkForUpdateAuto: (BOOL) automatic
{
fCheckIsAutomatic = automatic;
[[NSURL URLWithString: VERSION_PLIST_URL]
loadResourceDataNotifyingClient: self usingCache: NO];
}
- (void) URLResourceDidFinishLoading: (NSURL *) sender
{
NSDictionary * dict = [NSPropertyListSerialization
propertyListFromData: [sender resourceDataUsingCache: NO]
mutabilityOption: NSPropertyListImmutable
format: nil errorDescription: nil];
//check if plist was actually found and contains a version
NSString * webVersion = nil;
if (dict)
webVersion = [dict objectForKey: @"Version"];
if (!webVersion)
{
if (!fCheckIsAutomatic)
{
NSAlert * dialog = [[NSAlert alloc] init];
[dialog addButtonWithTitle: @"OK"];
[dialog setMessageText: @"Error checking for updates."];
[dialog setInformativeText:
@"Transmission was not able to check the latest version available."];
[dialog setAlertStyle: NSInformationalAlertStyle];
[dialog runModal];
[dialog release];
}
return;
}
NSString * currentVersion = [[[NSBundle mainBundle] infoDictionary]
objectForKey: (NSString *)kCFBundleVersionKey];
NSEnumerator * webEnum = [[webVersion componentsSeparatedByString: @"."] objectEnumerator],
* currentEnum = [[currentVersion componentsSeparatedByString: @"."] objectEnumerator];
NSString * webSub, * currentSub;
BOOL webGreater = NO;
NSComparisonResult result;
while ((webSub = [webEnum nextObject]))
{
if (!(currentSub = [currentEnum nextObject]))
{
webGreater = YES;
break;
}
result = [currentSub compare: webSub options: NSNumericSearch];
if (result != NSOrderedSame)
{
if (result == NSOrderedAscending)
webGreater = YES;
break;
}
}
if (webGreater)
{
NSAlert * dialog = [[NSAlert alloc] init];
[dialog addButtonWithTitle: @"Go to Website"];
[dialog addButtonWithTitle:@"Cancel"];
[dialog setMessageText: @"New version is available!"];
[dialog setInformativeText: [NSString stringWithFormat:
@"A newer version (%@) is available for download from the Transmission website.", webVersion]];
[dialog setAlertStyle: NSInformationalAlertStyle];
if ([dialog runModal] == NSAlertFirstButtonReturn)
[self linkHomepage: nil];
[dialog release];
}
else if (!fCheckIsAutomatic)
{
NSAlert * dialog = [[NSAlert alloc] init];
[dialog addButtonWithTitle: @"OK"];
[dialog setMessageText: @"No new versions are available."];
[dialog setInformativeText: [NSString stringWithFormat:
@"You are running the most current version of Transmission (%@).", currentVersion]];
[dialog setAlertStyle: NSInformationalAlertStyle];
[dialog runModal];
[dialog release];
}
else;
}
@end