source: trunk/macosx/CreatorWindowController.m @ 7468

Last change on this file since 7468 was 7468, checked in by charles, 12 years ago

(trunk) patch from wereHamster to finish off tr_handle

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