source: branches/1.7x/macosx/CreatorWindowController.m @ 9374

Last change on this file since 9374 was 9374, checked in by livings124, 13 years ago

#2486 In the Mac's "create torrent" window, place each tracker into its own tier
and the previous commit (the last two comments need to be shifted over, i probably shouldn't do this when I'm tired)

  • Property Id set to /Users/mitchell/Desktop/Transmission/macosx/ActionMenuRatioToDisplayRatioTransformer.h
  • Property svn:keywords set to Date Rev Author Id
File size: 20.2 KB
Line 
1/******************************************************************************
2 * $Id: CreatorWindowController.m 9374 2009-10-22 03:31:59Z livings124 $
3 *
4 * Copyright (c) 2007-2009 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "CreatorWindowController.h"
26#import "NSStringAdditions.h"
27#import "utils.h" //tr_httpIsValidURL
28
29#define TRACKER_ADD_TAG 0
30#define TRACKER_REMOVE_TAG 1
31
32@interface CreatorWindowController (Private)
33
34+ (NSString *) chooseFile;
35- (void) updateEnableOpenCheckForTrackers;
36- (void) locationSheetClosed: (NSSavePanel *) openPanel returnCode: (NSInteger) code contextInfo: (void *) info;
37
38- (void) createBlankAddressAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo;
39- (void) createReal;
40- (void) checkProgress;
41- (void) failureSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info;
42
43@end
44
45@implementation CreatorWindowController
46
47+ (void) createTorrentFile: (tr_session *) handle
48{
49    //get file/folder for torrent
50    NSString * path;
51    if (!(path = [CreatorWindowController chooseFile]))
52        return;
53   
54    CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: path];
55    [creator showWindow: nil];
56}
57
58+ (void) createTorrentFile: (tr_session *) handle forFile: (NSString *) file
59{
60    CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: file];
61    [creator showWindow: nil];
62}
63
64- (id) initWithHandle: (tr_session *) handle path: (NSString *) path
65{
66    if ((self = [super initWithWindowNibName: @"Creator"]))
67    {
68        fStarted = NO;
69       
70        fPath = [path retain];
71        fInfo = tr_metaInfoBuilderCreate([fPath UTF8String]);
72       
73        if (fInfo->fileCount == 0)
74        {
75            NSAlert * alert = [[NSAlert alloc] init];
76            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> no files -> button")];
77            [alert setMessageText: NSLocalizedString(@"This folder contains no files.",
78                                                    "Create torrent -> no files -> title")];
79            [alert setInformativeText: NSLocalizedString(@"There must be at least one file in a folder to create a torrent file.",
80                                                        "Create torrent -> no files -> warning")];
81            [alert setAlertStyle: NSWarningAlertStyle];
82           
83            [alert runModal];
84            [alert release];
85           
86            [self release];
87            return nil;
88        }
89        if (fInfo->totalSize == 0)
90        {
91            NSAlert * alert = [[NSAlert alloc] init];
92            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> zero size -> button")];
93            [alert setMessageText: NSLocalizedString(@"The total file size is zero bytes.",
94                                                    "Create torrent -> zero size -> title")];
95            [alert setInformativeText: NSLocalizedString(@"A torrent file cannot be created for files with no size.",
96                                                        "Create torrent -> zero size -> warning")];
97            [alert setAlertStyle: NSWarningAlertStyle];
98           
99            [alert runModal];
100            [alert release];
101           
102            [self release];
103            return nil;
104        }
105       
106        fDefaults = [NSUserDefaults standardUserDefaults];
107       
108        //get list of trackers
109        if (!(fTrackers = [[fDefaults arrayForKey: @"CreatorTrackers"] mutableCopy]))
110        {
111            fTrackers = [[NSMutableArray alloc] initWithCapacity: 1];
112           
113            //check for single tracker from versions before 1.3
114            NSString * tracker;
115            if ((tracker = [fDefaults stringForKey: @"CreatorTracker"]))
116            {
117                [fDefaults removeObjectForKey: @"CreatorTracker"];
118                if (![tracker isEqualToString: @""])
119                {
120                    [fTrackers addObject: tracker];
121                    [fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
122                }
123            }
124        }
125       
126        //remove potentially invalid addresses
127        for (NSInteger i = [fTrackers count]-1; i >= 0; i--)
128        {
129            if (!tr_httpIsValidURL([[fTrackers objectAtIndex: i] UTF8String]))
130                [fTrackers removeObjectAtIndex: i];
131        }
132    }
133    return self;
134}
135
136- (void) awakeFromNib
137{
138    NSString * name = [fPath lastPathComponent];
139   
140    [[self window] setTitle: name];
141   
142    [fNameField setStringValue: name];
143    [fNameField setToolTip: fPath];
144   
145    BOOL multifile = !fInfo->isSingleFile;
146   
147    NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: multifile
148                        ? NSFileTypeForHFSTypeCode('fldr') : [fPath pathExtension]];
149    [icon setSize: [fIconView frame].size];
150    [fIconView setImage: icon];
151   
152    NSString * statusString = [NSString stringForFileSize: fInfo->totalSize];
153    if (multifile)
154    {
155        NSString * fileString;
156        NSInteger count = fInfo->fileCount;
157        if (count != 1)
158            fileString = [NSString stringWithFormat: NSLocalizedString(@"%d files", "Create torrent -> info"), count];
159        else
160            fileString = NSLocalizedString(@"1 file", "Create torrent -> info");
161        statusString = [NSString stringWithFormat: @"%@, %@", fileString, statusString];
162    }
163    [fStatusField setStringValue: statusString];
164   
165    NSString * piecesCountString;
166    int piecesCount = fInfo->pieceCount;
167    if (piecesCount == 1)
168        piecesCountString = NSLocalizedString(@"1 piece", "Create torrent -> info");
169    else
170        piecesCountString = [NSString stringWithFormat: NSLocalizedString(@"%d pieces", "Create torrent -> info"),
171                                                                piecesCount];
172    [fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@, %@ each", "Create torrent -> info"),
173                                        piecesCountString, [NSString stringForFileSize: fInfo->pieceSize]]];
174   
175    fLocation = [[[[fDefaults stringForKey: @"CreatorLocation"] stringByExpandingTildeInPath] stringByAppendingPathComponent:
176                    [name stringByAppendingPathExtension: @"torrent"]] retain];
177    [fLocationField setStringValue: [fLocation stringByAbbreviatingWithTildeInPath]];
178    [fLocationField setToolTip: fLocation];
179   
180    //set previously saved values
181    if ([fDefaults objectForKey: @"CreatorPrivate"])
182        [fPrivateCheck setState: [fDefaults boolForKey: @"CreatorPrivate"] ? NSOnState : NSOffState];
183   
184    fOpenTorrent = [fDefaults boolForKey: @"CreatorOpen"];
185    [self updateEnableOpenCheckForTrackers];
186}
187
188- (void) dealloc
189{
190    [fPath release];
191    [fLocation release];
192   
193    [fTrackers release];
194   
195    if (fInfo)
196        tr_metaInfoBuilderFree(fInfo);
197   
198    [fTimer invalidate];
199   
200    [super dealloc];
201}
202
203- (void) toggleOpenCheck: (id) sender
204{
205    fOpenTorrent = [fOpenCheck state] == NSOnState;
206}
207
208- (void) setLocation: (id) sender
209{
210    NSSavePanel * panel = [NSSavePanel savePanel];
211
212    [panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> location sheet -> button")];
213    [panel setMessage: NSLocalizedString(@"Select the name and location for the torrent file.",
214                                        "Create torrent -> location sheet -> message")];
215   
216    [panel setAllowedFileTypes: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]];
217    [panel setCanSelectHiddenExtension: YES];
218   
219    [panel beginSheetForDirectory: [fLocation stringByDeletingLastPathComponent] file: [fLocation lastPathComponent]
220            modalForWindow: [self window] modalDelegate: self didEndSelector: @selector(locationSheetClosed:returnCode:contextInfo:)
221            contextInfo: nil];
222}
223
224- (void) create: (id) sender
225{
226    //make sure the trackers are no longer being verified
227    if ([fTrackerTable editedRow] != -1)
228        [[self window] endEditingFor: fTrackerTable];
229   
230    if ([fTrackers count] == 0 && [fDefaults boolForKey: @"WarningCreatorBlankAddress"])
231    {
232        NSAlert * alert = [[NSAlert alloc] init];
233        [alert setMessageText: NSLocalizedString(@"There is no tracker address.", "Create torrent -> blank address -> title")];
234        [alert setInformativeText: NSLocalizedString(@"The torrent file will not be able to be opened."
235            " A torrent file with no tracker address is only useful when you plan to upload the file to a tracker website"
236            " that will add the address for you.", "Create torrent -> blank address -> message")];
237        [alert addButtonWithTitle: NSLocalizedString(@"Create", "Create torrent -> blank address -> button")];
238        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Create torrent -> blank address -> button")];
239        [alert setShowsSuppressionButton: YES];
240
241        [alert beginSheetModalForWindow: [self window] modalDelegate: self
242            didEndSelector: @selector(createBlankAddressAlertDidEnd:returnCode:contextInfo:) contextInfo: nil];
243    }
244    else
245        [self createReal];
246}
247
248- (void) cancelCreateWindow: (id) sender
249{
250    [[self window] close];
251}
252
253- (void) windowWillClose: (NSNotification *) notification
254{
255    [self release];
256}
257
258- (void) cancelCreateProgress: (id) sender
259{
260    fInfo->abortFlag = 1;
261    [fTimer fire];
262}
263
264- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
265{
266    return [fTrackers count];
267}
268
269- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
270{
271    return [fTrackers objectAtIndex: row];
272}
273
274- (void) addRemoveTracker: (id) sender
275{
276    //don't allow add/remove when currently adding - it leads to weird results
277    if ([fTrackerTable editedRow] != -1)
278        return;
279   
280    if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
281    {
282        [fTrackers removeObjectsAtIndexes: [fTrackerTable selectedRowIndexes]];
283       
284        [fTrackerTable deselectAll: self];
285        [fTrackerTable reloadData];
286       
287        [self updateEnableOpenCheckForTrackers];
288    }
289    else
290    {
291        [fTrackers addObject: @""];
292        [fTrackerTable reloadData];
293       
294        const NSInteger row = [fTrackers count] - 1;
295        [fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
296        [fTrackerTable editColumn: 0 row: row withEvent: nil select: YES];
297    }
298}
299
300- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
301    row: (NSInteger) row
302{
303    NSString * tracker = (NSString *)object;
304   
305    tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
306   
307    if ([tracker rangeOfString: @"://"].location == NSNotFound)
308        tracker = [@"http://" stringByAppendingString: tracker];
309   
310    if (!tr_httpIsValidURL([tracker UTF8String]))
311    {
312        NSBeep();
313        [fTrackers removeObjectAtIndex: row];
314    }
315    else
316    {
317        [fTrackers replaceObjectAtIndex: row withObject: tracker];
318        [self updateEnableOpenCheckForTrackers];
319    }
320   
321    [fTrackerTable deselectAll: self];
322    [fTrackerTable reloadData];
323}
324
325- (void) tableViewSelectionDidChange: (NSNotification *) notification
326{
327    [fTrackerAddRemoveControl setEnabled: [fTrackerTable numberOfSelectedRows] > 0 forSegment: TRACKER_REMOVE_TAG];
328}
329
330@end
331
332@implementation CreatorWindowController (Private)
333
334+ (NSString *) chooseFile
335{
336    NSOpenPanel * panel = [NSOpenPanel openPanel];
337   
338    [panel setTitle: NSLocalizedString(@"Create Torrent File", "Create torrent -> select file")];
339    [panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> select file")];
340    [panel setAllowsMultipleSelection: NO];
341    [panel setCanChooseFiles: YES];
342    [panel setCanChooseDirectories: YES];
343    [panel setCanCreateDirectories: NO];
344
345    [panel setMessage: NSLocalizedString(@"Select a file or folder for the torrent file.", "Create torrent -> select file")];
346   
347    BOOL success = [panel runModal] == NSOKButton;
348    return success ? [[panel filenames] objectAtIndex: 0] : nil;
349}
350
351- (void) updateEnableOpenCheckForTrackers
352{
353    BOOL hasTracker = [fTrackers count] > 0;
354    [fOpenCheck setEnabled: hasTracker];
355    [fOpenCheck setState: (fOpenTorrent && hasTracker) ? NSOnState : NSOffState];
356}
357
358- (void) locationSheetClosed: (NSSavePanel *) panel returnCode: (NSInteger) code contextInfo: (void *) info
359{
360    if (code == NSOKButton)
361    {
362        [fLocation release];
363        fLocation = [[panel filename] retain];
364       
365        [fLocationField setStringValue: [fLocation stringByAbbreviatingWithTildeInPath]];
366        [fLocationField setToolTip: fLocation];
367    }
368}
369
370- (void) createBlankAddressAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo
371{
372    if ([[alert suppressionButton] state] == NSOnState)
373        [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningCreatorBlankAddress"];
374   
375    [alert release];
376   
377    if (returnCode == NSAlertFirstButtonReturn)
378        [self performSelectorOnMainThread: @selector(createReal) withObject: nil waitUntilDone: NO];
379}
380
381- (void) createReal
382{
383    //check if a file with the same name and location already exists
384    if ([[NSFileManager defaultManager] fileExistsAtPath: fLocation])
385    {
386        NSArray * pathComponents = [fLocation pathComponents];
387        NSInteger count = [pathComponents count];
388       
389        NSAlert * alert = [[[NSAlert alloc] init] autorelease];
390        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> file already exists warning -> button")];
391        [alert setMessageText: NSLocalizedString(@"A torrent file with this name and directory cannot be created.",
392                                                "Create torrent -> file already exists warning -> title")];
393        [alert setInformativeText: [NSString stringWithFormat:
394                NSLocalizedString(@"A file with the name \"%@\" already exists in the directory \"%@\". "
395                    "Choose a new name or directory to create the torrent file.",
396                    "Create torrent -> file already exists warning -> warning"),
397                    [pathComponents objectAtIndex: count-1], [pathComponents objectAtIndex: count-2]]];
398        [alert setAlertStyle: NSWarningAlertStyle];
399       
400        [alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
401        return;
402    }
403   
404    //parse non-empty tracker strings
405    tr_tracker_info * trackerInfo = tr_new0(tr_tracker_info, [fTrackers count]);
406   
407    for (NSUInteger i = 0; i < [fTrackers count]; i++)
408    {
409        trackerInfo[i].announce = (char *)[[fTrackers objectAtIndex: i] UTF8String];
410        trackerInfo[i].tier = i;
411    }
412   
413    //store values
414    [fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
415    [fDefaults setBool: [fPrivateCheck state] == NSOnState forKey: @"CreatorPrivate"];
416    [fDefaults setBool: fOpenTorrent forKey: @"CreatorOpen"];
417    [fDefaults setObject: [fLocation stringByDeletingLastPathComponent] forKey: @"CreatorLocation"];
418   
419    [[NSNotificationCenter defaultCenter] postNotificationName: @"BeginCreateTorrentFile" object: fLocation userInfo: nil];
420    tr_makeMetaInfo(fInfo, [fLocation UTF8String], trackerInfo, [fTrackers count], [[fCommentView string] UTF8String],
421                    [fPrivateCheck state] == NSOnState);
422    tr_free(trackerInfo);
423   
424    fTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(checkProgress)
425                userInfo: nil repeats: YES];
426}
427
428- (void) checkProgress
429{
430    if (fInfo->isDone)
431    {
432        [fTimer invalidate];
433        fTimer = nil;
434       
435        NSAlert * alert;
436        switch (fInfo->result)
437        {
438            case TR_MAKEMETA_OK:
439                if (fOpenTorrent && [fTrackers count] > 0)
440                {
441                    NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys: fLocation, @"File",
442                                            [fPath stringByDeletingLastPathComponent], @"Path", nil];
443                    [[NSNotificationCenter defaultCenter] postNotificationName: @"OpenCreatedTorrentFile" object: self userInfo: dict];
444                }
445               
446                [[self window] close];
447                break;
448           
449            case TR_MAKEMETA_CANCELLED:
450                [[self window] close];
451                break;
452           
453            default:
454                alert = [[[NSAlert alloc] init] autorelease];
455                [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> failed -> button")];
456                [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Creation of \"%@\" failed.",
457                                                "Create torrent -> failed -> title"), [fLocation lastPathComponent]]];
458                [alert setAlertStyle: NSWarningAlertStyle];
459               
460                if (fInfo->result == TR_MAKEMETA_IO_READ)
461                    [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not read \"%s\": %s.",
462                        "Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
463                else if (fInfo->result == TR_MAKEMETA_IO_WRITE)
464                    [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not write \"%s\": %s.",
465                        "Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
466                else //invalid url should have been caught before creating
467                    [alert setInformativeText: [NSString stringWithFormat: @"%@ (%d)",
468                        NSLocalizedString(@"An unknown error has occurred.", "Create torrent -> failed -> warning"), fInfo->result]];
469               
470                [alert beginSheetModalForWindow: [self window] modalDelegate: self
471                    didEndSelector: @selector(failureSheetClosed:returnCode:contextInfo:) contextInfo: nil];
472        }
473    }
474    else
475    {
476        [fProgressIndicator setDoubleValue: (double)fInfo->pieceIndex / fInfo->pieceCount];
477       
478        if (!fStarted)
479        {
480            fStarted = YES;
481           
482            [fProgressView setHidden: YES];
483           
484            NSWindow * window = [self window];
485            [window setFrameAutosaveName: @""];
486           
487            NSRect windowRect = [window frame];
488            CGFloat difference = [fProgressView frame].size.height - [[window contentView] frame].size.height;
489            windowRect.origin.y -= difference;
490            windowRect.size.height += difference;
491           
492            //don't allow vertical resizing
493            CGFloat height = windowRect.size.height;
494            [window setMinSize: NSMakeSize([window minSize].width, height)];
495            [window setMaxSize: NSMakeSize([window maxSize].width, height)];
496           
497            [window setContentView: fProgressView];
498            [window setFrame: windowRect display: YES animate: YES];
499            [fProgressView setHidden: NO];
500           
501            [[window standardWindowButton: NSWindowCloseButton] setEnabled: NO];
502        }
503    }
504}
505
506- (void) failureSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info
507{
508    [[alert window] orderOut: nil];
509    [[self window] close];
510}
511
512@end
Note: See TracBrowser for help on using the repository browser.