515 lines
20 KiB
Objective-C
515 lines
20 KiB
Objective-C
/******************************************************************************
|
|
* $Id$
|
|
*
|
|
* Copyright (c) 2007-2008 Transmission authors and contributors
|
|
*
|
|
* 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 "CreatorWindowController.h"
|
|
#import "NSApplicationAdditions.h"
|
|
#import "NSStringAdditions.h"
|
|
#include "utils.h" //tr_httpIsValidURL
|
|
|
|
#define TRACKER_ADD_TAG 0
|
|
#define TRACKER_REMOVE_TAG 1
|
|
|
|
@interface CreatorWindowController (Private)
|
|
|
|
+ (NSString *) chooseFile;
|
|
- (void) updateEnableOpenCheckForTrackers;
|
|
- (void) locationSheetClosed: (NSSavePanel *) openPanel returnCode: (int) code contextInfo: (void *) info;
|
|
|
|
- (void) createBlankAddressAlertDidEnd: (NSAlert *) alert returnCode: (int) returnCode contextInfo: (void *) contextInfo;
|
|
- (void) createReal;
|
|
- (void) checkProgress;
|
|
- (void) failureSheetClosed: (NSAlert *) alert returnCode: (int) code contextInfo: (void *) info;
|
|
|
|
@end
|
|
|
|
@implementation CreatorWindowController
|
|
|
|
+ (void) createTorrentFile: (tr_handle *) handle
|
|
{
|
|
//get file/folder for torrent
|
|
NSString * path;
|
|
if (!(path = [CreatorWindowController chooseFile]))
|
|
return;
|
|
|
|
CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: path];
|
|
[creator showWindow: nil];
|
|
}
|
|
|
|
+ (void) createTorrentFile: (tr_handle *) handle forFile: (NSString *) file
|
|
{
|
|
CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: file];
|
|
[creator showWindow: nil];
|
|
}
|
|
|
|
- (id) initWithHandle: (tr_handle *) handle path: (NSString *) path
|
|
{
|
|
if ((self = [super initWithWindowNibName: @"Creator"]))
|
|
{
|
|
fStarted = NO;
|
|
|
|
fPath = [path retain];
|
|
fInfo = tr_metaInfoBuilderCreate(handle, [fPath UTF8String]);
|
|
|
|
if (fInfo->fileCount == 0)
|
|
{
|
|
NSAlert * alert = [[NSAlert alloc] init];
|
|
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> no files -> button")];
|
|
[alert setMessageText: NSLocalizedString(@"This folder contains no files.",
|
|
"Create torrent -> no files -> title")];
|
|
[alert setInformativeText: NSLocalizedString(@"There must be at least one file in a folder to create a torrent file.",
|
|
"Create torrent -> no files -> warning")];
|
|
[alert setAlertStyle: NSWarningAlertStyle];
|
|
|
|
[alert runModal];
|
|
[alert release];
|
|
|
|
[self release];
|
|
return nil;
|
|
}
|
|
if (fInfo->totalSize == 0)
|
|
{
|
|
NSAlert * alert = [[NSAlert alloc] init];
|
|
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> zero size -> button")];
|
|
[alert setMessageText: NSLocalizedString(@"The total file size is zero bytes.",
|
|
"Create torrent -> zero size -> title")];
|
|
[alert setInformativeText: NSLocalizedString(@"A torrent file cannot be created for files with no size.",
|
|
"Create torrent -> zero size -> warning")];
|
|
[alert setAlertStyle: NSWarningAlertStyle];
|
|
|
|
[alert runModal];
|
|
[alert release];
|
|
|
|
[self release];
|
|
return nil;
|
|
}
|
|
|
|
fDefaults = [NSUserDefaults standardUserDefaults];
|
|
|
|
//get list of trackers
|
|
if (!(fTrackers = [[fDefaults arrayForKey: @"CreatorTrackers"] mutableCopy]))
|
|
{
|
|
fTrackers = [[NSMutableArray alloc] initWithCapacity: 1];
|
|
|
|
//check for single tracker from versions before 1.3
|
|
NSString * tracker;
|
|
if ((tracker = [fDefaults stringForKey: @"CreatorTracker"]))
|
|
{
|
|
[fDefaults removeObjectForKey: @"CreatorTracker"];
|
|
if (![tracker isEqualToString: @""])
|
|
{
|
|
[fTrackers addObject: tracker];
|
|
[fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
|
|
}
|
|
}
|
|
}
|
|
|
|
//remove potentially invalid addresses
|
|
NSInteger i;
|
|
for (i = [fTrackers count]-1; i >= 0; i--)
|
|
{
|
|
if (!tr_httpIsValidURL([[fTrackers objectAtIndex: i] UTF8String]))
|
|
[fTrackers removeObjectAtIndex: i];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void) awakeFromNib
|
|
{
|
|
NSString * name = [fPath lastPathComponent];
|
|
|
|
[[self window] setTitle: name];
|
|
|
|
[fNameField setStringValue: name];
|
|
[fNameField setToolTip: fPath];
|
|
|
|
BOOL multifile = !fInfo->isSingleFile;
|
|
|
|
NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: multifile
|
|
? NSFileTypeForHFSTypeCode('fldr') : [fPath pathExtension]];
|
|
[icon setSize: [fIconView frame].size];
|
|
[fIconView setImage: icon];
|
|
|
|
NSString * statusString = [NSString stringForFileSize: fInfo->totalSize];
|
|
if (multifile)
|
|
{
|
|
NSString * fileString;
|
|
NSInteger count = fInfo->fileCount;
|
|
if (count != 1)
|
|
fileString = [NSString stringWithFormat: NSLocalizedString(@"%d files", "Create torrent -> info"), count];
|
|
else
|
|
fileString = NSLocalizedString(@"1 file", "Create torrent -> info");
|
|
statusString = [NSString stringWithFormat: @"%@, %@", fileString, statusString];
|
|
}
|
|
[fStatusField setStringValue: statusString];
|
|
|
|
NSString * piecesCountString;
|
|
int piecesCount = fInfo->pieceCount;
|
|
if (piecesCount == 1)
|
|
piecesCountString = NSLocalizedString(@"1 piece", "Create torrent -> info");
|
|
else
|
|
piecesCountString = [NSString stringWithFormat: NSLocalizedString(@"%d pieces", "Create torrent -> info"),
|
|
piecesCount];
|
|
[fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@, %@ each", "Create torrent -> info"),
|
|
piecesCountString, [NSString stringForFileSize: fInfo->pieceSize]]];
|
|
|
|
fLocation = [[[[fDefaults stringForKey: @"CreatorLocation"] stringByExpandingTildeInPath] stringByAppendingPathComponent:
|
|
[name stringByAppendingPathExtension: @"torrent"]] retain];
|
|
[fLocationField setStringValue: [fLocation stringByAbbreviatingWithTildeInPath]];
|
|
[fLocationField setToolTip: fLocation];
|
|
|
|
//set previously saved values
|
|
if ([fDefaults objectForKey: @"CreatorPrivate"])
|
|
[fPrivateCheck setState: [fDefaults boolForKey: @"CreatorPrivate"] ? NSOnState : NSOffState];
|
|
|
|
fOpenTorrent = [fDefaults boolForKey: @"CreatorOpen"];
|
|
[self updateEnableOpenCheckForTrackers];
|
|
|
|
if (![NSApp isOnLeopardOrBetter])
|
|
{
|
|
[fTrackerAddRemoveControl sizeToFit];
|
|
[fTrackerAddRemoveControl setLabel: @"+" forSegment: TRACKER_ADD_TAG];
|
|
[fTrackerAddRemoveControl setLabel: @"-" forSegment: TRACKER_REMOVE_TAG];
|
|
}
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[fPath release];
|
|
[fLocation release];
|
|
|
|
[fTrackers release];
|
|
|
|
if (fInfo)
|
|
tr_metaInfoBuilderFree(fInfo);
|
|
|
|
[fTimer invalidate];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void) toggleOpenCheck: (id) sender
|
|
{
|
|
fOpenTorrent = [fOpenCheck state] == NSOnState;
|
|
}
|
|
|
|
- (void) setLocation: (id) sender
|
|
{
|
|
NSSavePanel * panel = [NSSavePanel savePanel];
|
|
|
|
[panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> location sheet -> button")];
|
|
[panel setMessage: NSLocalizedString(@"Select the name and location for the torrent file.",
|
|
"Create torrent -> location sheet -> message")];
|
|
|
|
[panel setRequiredFileType: @"torrent"];
|
|
[panel setCanSelectHiddenExtension: YES];
|
|
|
|
[panel beginSheetForDirectory: [fLocation stringByDeletingLastPathComponent] file: [fLocation lastPathComponent]
|
|
modalForWindow: [self window] modalDelegate: self didEndSelector: @selector(locationSheetClosed:returnCode:contextInfo:)
|
|
contextInfo: nil];
|
|
}
|
|
|
|
- (void) create: (id) sender
|
|
{
|
|
if ([fTrackers count] == 0 && [fDefaults boolForKey: @"WarningCreatorBlankAddress"])
|
|
{
|
|
NSAlert * alert = [[NSAlert alloc] init];
|
|
[alert setMessageText: NSLocalizedString(@"There is no tracker address.", "Create torrent -> blank address -> title")];
|
|
[alert setInformativeText: NSLocalizedString(@"The torrent file will not be able to be opened."
|
|
" A torrent file with no tracker address is only useful when you plan to upload the file to a tracker website"
|
|
" that will add the address for you.", "Create torrent -> blank address -> message")];
|
|
[alert addButtonWithTitle: NSLocalizedString(@"Create", "Create torrent -> blank address -> button")];
|
|
[alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Create torrent -> blank address -> button")];
|
|
|
|
if ([NSApp isOnLeopardOrBetter])
|
|
[alert setShowsSuppressionButton: YES];
|
|
else
|
|
[alert addButtonWithTitle: NSLocalizedString(@"Don't Alert Again", "Create torrent -> blank address -> button")];
|
|
|
|
[alert beginSheetModalForWindow: [self window] modalDelegate: self
|
|
didEndSelector: @selector(createBlankAddressAlertDidEnd:returnCode:contextInfo:) contextInfo: nil];
|
|
}
|
|
else
|
|
[self createReal];
|
|
}
|
|
|
|
- (void) cancelCreateWindow: (id) sender
|
|
{
|
|
[[self window] close];
|
|
}
|
|
|
|
- (void) windowWillClose: (NSNotification *) notification
|
|
{
|
|
[self release];
|
|
}
|
|
|
|
- (void) cancelCreateProgress: (id) sender
|
|
{
|
|
fInfo->abortFlag = 1;
|
|
[fTimer fire];
|
|
}
|
|
|
|
- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
|
|
{
|
|
return [fTrackers count];
|
|
}
|
|
|
|
- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
|
|
{
|
|
return [fTrackers objectAtIndex: row];
|
|
}
|
|
|
|
- (void) addRemoveTracker: (id) sender
|
|
{
|
|
//don't allow add/remove when currently adding - it leads to weird results
|
|
if ([fTrackerTable editedRow] != -1)
|
|
return;
|
|
|
|
if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
|
|
{
|
|
[fTrackers removeObjectsAtIndexes: [fTrackerTable selectedRowIndexes]];
|
|
|
|
[fTrackerTable deselectAll: self];
|
|
[fTrackerTable reloadData];
|
|
|
|
[self updateEnableOpenCheckForTrackers];
|
|
}
|
|
else
|
|
{
|
|
[fTrackers addObject: @""];
|
|
[fTrackerTable reloadData];
|
|
|
|
int row = [fTrackers count] - 1;
|
|
[fTrackerTable selectRow: row byExtendingSelection: NO];
|
|
[fTrackerTable editColumn: 0 row: row withEvent: nil select: YES];
|
|
}
|
|
}
|
|
|
|
- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
|
|
row: (NSInteger) row
|
|
{
|
|
NSString * tracker = (NSString *)object;
|
|
|
|
tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
|
|
if ([tracker rangeOfString: @"://"].location == NSNotFound)
|
|
tracker = [@"http://" stringByAppendingString: tracker];
|
|
|
|
if (!tr_httpIsValidURL([tracker UTF8String]))
|
|
{
|
|
NSBeep();
|
|
[fTrackers removeObjectAtIndex: row];
|
|
}
|
|
else
|
|
{
|
|
[fTrackers replaceObjectAtIndex: row withObject: tracker];
|
|
[self updateEnableOpenCheckForTrackers];
|
|
}
|
|
|
|
[fTrackerTable deselectAll: self];
|
|
[fTrackerTable reloadData];
|
|
}
|
|
|
|
- (void) tableViewSelectionDidChange: (NSNotification *) notification
|
|
{
|
|
[fTrackerAddRemoveControl setEnabled: [fTrackerTable numberOfSelectedRows] > 0 forSegment: TRACKER_REMOVE_TAG];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CreatorWindowController (Private)
|
|
|
|
+ (NSString *) chooseFile
|
|
{
|
|
NSOpenPanel * panel = [NSOpenPanel openPanel];
|
|
|
|
[panel setTitle: NSLocalizedString(@"Create Torrent File", "Create torrent -> select file")];
|
|
[panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> select file")];
|
|
[panel setAllowsMultipleSelection: NO];
|
|
[panel setCanChooseFiles: YES];
|
|
[panel setCanChooseDirectories: YES];
|
|
[panel setCanCreateDirectories: NO];
|
|
|
|
[panel setMessage: NSLocalizedString(@"Select a file or folder for the torrent file.", "Create torrent -> select file")];
|
|
|
|
BOOL success = [panel runModal] == NSOKButton;
|
|
return success ? [[panel filenames] objectAtIndex: 0] : nil;
|
|
}
|
|
|
|
- (void) updateEnableOpenCheckForTrackers
|
|
{
|
|
BOOL hasTracker = [fTrackers count] > 0;
|
|
[fOpenCheck setEnabled: hasTracker];
|
|
[fOpenCheck setState: (fOpenTorrent && hasTracker) ? NSOnState : NSOffState];
|
|
}
|
|
|
|
- (void) locationSheetClosed: (NSSavePanel *) panel returnCode: (int) code contextInfo: (void *) info
|
|
{
|
|
if (code == NSOKButton)
|
|
{
|
|
[fLocation release];
|
|
fLocation = [[panel filename] retain];
|
|
|
|
[fLocationField setStringValue: [fLocation stringByAbbreviatingWithTildeInPath]];
|
|
[fLocationField setToolTip: fLocation];
|
|
}
|
|
}
|
|
|
|
- (void) createBlankAddressAlertDidEnd: (NSAlert *) alert returnCode: (int) returnCode contextInfo: (void *) contextInfo
|
|
{
|
|
if (([NSApp isOnLeopardOrBetter] ? [[alert suppressionButton] state] == NSOnState : returnCode == NSAlertThirdButtonReturn))
|
|
[[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningCreatorBlankAddress"];
|
|
|
|
[alert release];
|
|
|
|
if (returnCode == NSAlertFirstButtonReturn)
|
|
[self performSelectorOnMainThread: @selector(createReal) withObject: nil waitUntilDone: NO];
|
|
}
|
|
|
|
- (void) createReal
|
|
{
|
|
//check if a file with the same name and location already exists
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath: fLocation])
|
|
{
|
|
NSArray * pathComponents = [fLocation pathComponents];
|
|
int count = [pathComponents count];
|
|
|
|
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
|
|
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> file already exists warning -> button")];
|
|
[alert setMessageText: NSLocalizedString(@"A torrent file with this name and directory cannot be created.",
|
|
"Create torrent -> file already exists warning -> title")];
|
|
[alert setInformativeText: [NSString stringWithFormat:
|
|
NSLocalizedString(@"A file with the name \"%@\" already exists in the directory \"%@\". "
|
|
"Choose a new name or directory to create the torrent file.",
|
|
"Create torrent -> file already exists warning -> warning"),
|
|
[pathComponents objectAtIndex: count-1], [pathComponents objectAtIndex: count-2]]];
|
|
[alert setAlertStyle: NSWarningAlertStyle];
|
|
|
|
[alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
|
|
return;
|
|
}
|
|
|
|
//parse non-empty tracker strings
|
|
tr_tracker_info * trackerInfo = tr_new0(tr_tracker_info, [fTrackers count]);
|
|
|
|
NSUInteger i;
|
|
for (i = 0; i < [fTrackers count]; i++)
|
|
trackerInfo[i].announce = (char *)[[fTrackers objectAtIndex: i] UTF8String];
|
|
|
|
//store values
|
|
[fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
|
|
[fDefaults setBool: [fPrivateCheck state] == NSOnState forKey: @"CreatorPrivate"];
|
|
[fDefaults setBool: fOpenTorrent forKey: @"CreatorOpen"];
|
|
[fDefaults setObject: [fLocation stringByDeletingLastPathComponent] forKey: @"CreatorLocation"];
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName: @"BeginCreateTorrentFile" object: fLocation userInfo: nil];
|
|
tr_makeMetaInfo(fInfo, [fLocation UTF8String], trackerInfo, [fTrackers count], [[fCommentView string] UTF8String],
|
|
[fPrivateCheck state] == NSOnState);
|
|
tr_free(trackerInfo);
|
|
|
|
fTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(checkProgress)
|
|
userInfo: nil repeats: YES];
|
|
}
|
|
|
|
- (void) checkProgress
|
|
{
|
|
if (fInfo->isDone)
|
|
{
|
|
[fTimer invalidate];
|
|
fTimer = nil;
|
|
|
|
NSAlert * alert;
|
|
switch (fInfo->result)
|
|
{
|
|
case TR_MAKEMETA_OK:
|
|
if (fOpenTorrent && [fTrackers count] > 0)
|
|
{
|
|
NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys: fLocation, @"File",
|
|
[fPath stringByDeletingLastPathComponent], @"Path", nil];
|
|
[[NSNotificationCenter defaultCenter] postNotificationName: @"OpenCreatedTorrentFile" object: self userInfo: dict];
|
|
}
|
|
|
|
[[self window] close];
|
|
break;
|
|
|
|
case TR_MAKEMETA_CANCELLED:
|
|
[[self window] close];
|
|
break;
|
|
|
|
default:
|
|
alert = [[[NSAlert alloc] init] autorelease];
|
|
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> failed -> button")];
|
|
[alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Creation of \"%@\" failed.",
|
|
"Create torrent -> failed -> title"), [fLocation lastPathComponent]]];
|
|
[alert setAlertStyle: NSWarningAlertStyle];
|
|
|
|
if (fInfo->result == TR_MAKEMETA_IO_READ)
|
|
[alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not read \"%s\": %s.",
|
|
"Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
|
|
else if (fInfo->result == TR_MAKEMETA_IO_WRITE)
|
|
[alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not write \"%s\": %s.",
|
|
"Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
|
|
else; //invalid url should have been caught before creating
|
|
|
|
[alert beginSheetModalForWindow: [self window] modalDelegate: self
|
|
didEndSelector: @selector(failureSheetClosed:returnCode:contextInfo:) contextInfo: nil];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[fProgressIndicator setDoubleValue: (double)fInfo->pieceIndex / fInfo->pieceCount];
|
|
|
|
if (!fStarted)
|
|
{
|
|
fStarted = YES;
|
|
|
|
NSWindow * window = [self window];
|
|
|
|
NSRect windowRect = [window frame];
|
|
CGFloat difference = [fProgressView frame].size.height - [[window contentView] frame].size.height;
|
|
windowRect.origin.y -= difference;
|
|
windowRect.size.height += difference;
|
|
|
|
//don't allow vertical resizing
|
|
CGFloat height = windowRect.size.height;
|
|
[window setMinSize: NSMakeSize([window minSize].width, height)];
|
|
[window setMaxSize: NSMakeSize([window maxSize].width, height)];
|
|
|
|
[window setContentView: fProgressView];
|
|
[window setFrame: windowRect display: YES animate: YES];
|
|
[fProgressView setHidden: NO];
|
|
|
|
[[window standardWindowButton: NSWindowCloseButton] setEnabled: NO];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) failureSheetClosed: (NSAlert *) alert returnCode: (int) code contextInfo: (void *) info
|
|
{
|
|
[[alert window] orderOut: nil];
|
|
[[self window] close];
|
|
}
|
|
|
|
@end
|