1/******************************************************************************
2 * Copyright (c) 2007-2012 Transmission authors and contributors
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 *****************************************************************************/
22
23#include <libtransmission/transmission.h>
24#include <libtransmission/utils.h> // tr_urlIsValidTracker()
25
26#import "CreatorWindowController.h"
27#import "Controller.h"
28#import "NSApplicationAdditions.h"
29#import "NSStringAdditions.h"
30
31#define TRACKER_ADD_TAG 0
32#define TRACKER_REMOVE_TAG 1
33
34@interface CreatorWindowController (Private)
35
36+ (NSURL *) chooseFile;
37
38- (void) updateLocationField;
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
46NSMutableSet *creatorWindowControllerSet = nil;
47@implementation CreatorWindowController
48
49+ (CreatorWindowController *) createTorrentFile: (tr_session *) handle
50{
51    //get file/folder for torrent
52    NSURL * path;
53    if (!(path = [CreatorWindowController chooseFile]))
54        return nil;
55
56    CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: path];
57    [creator showWindow: nil];
58    return creator;
59}
60
61+ (CreatorWindowController *) createTorrentFile: (tr_session *) handle forFile: (NSURL *) file
62{
63    CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: file];
64    [creator showWindow: nil];
65    return creator;
66}
67
68- (id) initWithHandle: (tr_session *) handle path: (NSURL *) path
69{
70    if ((self = [super initWithWindowNibName: @"Creator"]))
71    {
72        if (!creatorWindowControllerSet) {
73            creatorWindowControllerSet = [NSMutableSet set];
74        }
75
76        fStarted = NO;
77
78        fPath = path;
79        fInfo = tr_metaInfoBuilderCreate([[fPath path] UTF8String]);
80
81        if (fInfo->fileCount == 0)
82        {
83            NSAlert * alert = [[NSAlert alloc] init];
84            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> no files -> button")];
85            [alert setMessageText: NSLocalizedString(@"This folder contains no files.",
86                                                    "Create torrent -> no files -> title")];
87            [alert setInformativeText: NSLocalizedString(@"There must be at least one file in a folder to create a torrent file.",
88                                                        "Create torrent -> no files -> warning")];
89            [alert setAlertStyle: NSWarningAlertStyle];
90
91            [alert runModal];
92
93            return nil;
94        }
95        if (fInfo->totalSize == 0)
96        {
97            NSAlert * alert = [[NSAlert alloc] init];
98            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> zero size -> button")];
99            [alert setMessageText: NSLocalizedString(@"The total file size is zero bytes.",
100                                                    "Create torrent -> zero size -> title")];
101            [alert setInformativeText: NSLocalizedString(@"A torrent file cannot be created for files with no size.",
102                                                        "Create torrent -> zero size -> warning")];
103            [alert setAlertStyle: NSWarningAlertStyle];
104
105            [alert runModal];
106
107            return nil;
108        }
109
110        fDefaults = [NSUserDefaults standardUserDefaults];
111
112        //get list of trackers
113        if (!(fTrackers = [[fDefaults arrayForKey: @"CreatorTrackers"] mutableCopy]))
114        {
115            fTrackers = [[NSMutableArray alloc] init];
116
117            //check for single tracker from versions before 1.3
118            NSString * tracker;
119            if ((tracker = [fDefaults stringForKey: @"CreatorTracker"]))
120            {
121                [fDefaults removeObjectForKey: @"CreatorTracker"];
122                if (![tracker isEqualToString: @""])
123                {
124                    [fTrackers addObject: tracker];
125                    [fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
126                }
127            }
128        }
129
130        //remove potentially invalid addresses
131        for (NSInteger i = [fTrackers count]-1; i >= 0; i--)
132        {
133            if (!tr_urlIsValidTracker([fTrackers[i] UTF8String]))
134                [fTrackers removeObjectAtIndex: i];
135        }
136
137        [creatorWindowControllerSet addObject:self];
138    }
139    return self;
140}
141
142- (void) awakeFromNib
143{
144    [[self window] setRestorationClass: [self class]];
145
146    NSString * name = [fPath lastPathComponent];
147
148    [[self window] setTitle: name];
149
150    [fNameField setStringValue: name];
151    [fNameField setToolTip: [fPath path]];
152
153    const BOOL multifile = fInfo->isFolder;
154
155    NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: multifile
156                        ? NSFileTypeForHFSTypeCode(kGenericFolderIcon) : [fPath pathExtension]];
157    [icon setSize: [fIconView frame].size];
158    [fIconView setImage: icon];
159
160    NSString * statusString = [NSString stringForFileSize: fInfo->totalSize];
161    if (multifile)
162    {
163        NSString * fileString;
164        NSInteger count = fInfo->fileCount;
165        if (count != 1)
166            fileString = [NSString stringWithFormat: NSLocalizedString(@"%@ files", "Create torrent -> info"),
167                            [NSString formattedUInteger: count]];
168        else
169            fileString = NSLocalizedString(@"1 file", "Create torrent -> info");
170        statusString = [NSString stringWithFormat: @"%@, %@", fileString, statusString];
171    }
172    [fStatusField setStringValue: statusString];
173
174    if (fInfo->pieceCount == 1)
175        [fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"1 piece, %@", "Create torrent -> info"),
176                                                            [NSString stringForFileSize: fInfo->pieceSize]]];
177    else
178        [fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d pieces, %@ each", "Create torrent -> info"),
179                                                            fInfo->pieceCount, [NSString stringForFileSize: fInfo->pieceSize]]];
180
181    fLocation = [[fDefaults URLForKey: @"CreatorLocationURL"] URLByAppendingPathComponent: [name stringByAppendingPathExtension: @"torrent"]];
182    if (!fLocation)
183    {
184        //for 2.5 and earlier
185        #warning we still store "CreatorLocation" in Defaults.plist, and not "CreatorLocationURL"
186        NSString * location = [fDefaults stringForKey: @"CreatorLocation"];
187        fLocation = [[NSURL alloc] initFileURLWithPath: [[location stringByExpandingTildeInPath] stringByAppendingPathComponent: [name stringByAppendingPathExtension: @"torrent"]]];
188    }
189    [self updateLocationField];
190
191    //set previously saved values
192    if ([fDefaults objectForKey: @"CreatorPrivate"])
193        [fPrivateCheck setState: [fDefaults boolForKey: @"CreatorPrivate"] ? NSOnState : NSOffState];
194
195    [fOpenCheck setState: [fDefaults boolForKey: @"CreatorOpen"] ? NSOnState : NSOffState];
196}
197
198- (void) dealloc
199{
200    if (fInfo)
201        tr_metaInfoBuilderFree(fInfo);
202
203    [fTimer invalidate];
204}
205
206+ (void) restoreWindowWithIdentifier: (NSString *) identifier state: (NSCoder *) state completionHandler: (void (^)(NSWindow *, NSError *)) completionHandler
207{
208    NSURL * path = [state decodeObjectForKey: @"TRCreatorPath"];
209    if (!path || ![path checkResourceIsReachableAndReturnError: nil])
210    {
211        completionHandler(nil, [NSError errorWithDomain: NSURLErrorDomain code: NSURLErrorCannotOpenFile userInfo: nil]);
212        return;
213    }
214
215    NSWindow * window = [[self createTorrentFile: [(Controller *)[NSApp delegate] sessionHandle] forFile: path] window];
216    completionHandler(window, nil);
217}
218
219- (void) window: (NSWindow *) window willEncodeRestorableState: (NSCoder *) state
220{
221    [state encodeObject: fPath forKey: @"TRCreatorPath"];
222    [state encodeObject: fLocation forKey: @"TRCreatorLocation"];
223    [state encodeObject: fTrackers forKey: @"TRCreatorTrackers"];
224    [state encodeInteger: [fOpenCheck state] forKey: @"TRCreatorOpenCheck"];
225    [state encodeInteger: [fPrivateCheck state] forKey: @"TRCreatorPrivateCheck"];
226    [state encodeObject: [fCommentView string] forKey: @"TRCreatorPrivateComment"];
227}
228
229- (void) window: (NSWindow *) window didDecodeRestorableState: (NSCoder *) coder
230{
231    fLocation = [coder decodeObjectForKey: @"TRCreatorLocation"];
232    [self updateLocationField];
233
234    fTrackers = [coder decodeObjectForKey: @"TRCreatorTrackers"];
235    [fTrackerTable reloadData];
236
237    [fOpenCheck setState: [coder decodeIntegerForKey: @"TRCreatorOpenCheck"]];
238    [fPrivateCheck setState: [coder decodeIntegerForKey: @"TRCreatorPrivateCheck"]];
239    [fCommentView setString: [coder decodeObjectForKey: @"TRCreatorPrivateComment"]];
240}
241
242- (IBAction) setLocation: (id) sender
243{
244    NSSavePanel * panel = [NSSavePanel savePanel];
245
246    [panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> location sheet -> button")];
247    [panel setMessage: NSLocalizedString(@"Select the name and location for the torrent file.",
248                                        "Create torrent -> location sheet -> message")];
249
250    [panel setAllowedFileTypes: @[@"org.bittorrent.torrent", @"torrent"]];
251    [panel setCanSelectHiddenExtension: YES];
252
253    [panel setDirectoryURL: [fLocation URLByDeletingLastPathComponent]];
254    [panel setNameFieldStringValue: [fLocation lastPathComponent]];
255
256    [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
257        if (result == NSFileHandlingPanelOKButton)
258        {
259            fLocation = [panel URL];
260            [self updateLocationField];
261        }
262    }];
263}
264
265- (IBAction) create: (id) sender
266{
267    //make sure the trackers are no longer being verified
268    if ([fTrackerTable editedRow] != -1)
269        [[self window] endEditingFor: fTrackerTable];
270
271    const BOOL isPrivate = [fPrivateCheck state] == NSOnState;
272    if ([fTrackers count] == 0
273        && [fDefaults boolForKey: isPrivate ? @"WarningCreatorPrivateBlankAddress" : @"WarningCreatorBlankAddress"])
274    {
275        NSAlert * alert = [[NSAlert alloc] init];
276        [alert setMessageText: NSLocalizedString(@"There are no tracker addresses.", "Create torrent -> blank address -> title")];
277
278        NSString * infoString = isPrivate
279                    ? NSLocalizedString(@"A transfer marked as private with no tracker addresses will be unable to connect to peers."
280                        " The torrent file will only be useful if you plan to upload the file to a tracker website"
281                        " that will add the addresses for you.", "Create torrent -> blank address -> message")
282                    : NSLocalizedString(@"The transfer will not contact trackers for peers, and will have to rely solely on"
283                        " non-tracker peer discovery methods such as PEX and DHT to download and seed.",
284                        "Create torrent -> blank address -> message");
285
286        [alert setInformativeText: infoString];
287        [alert addButtonWithTitle: NSLocalizedString(@"Create", "Create torrent -> blank address -> button")];
288        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Create torrent -> blank address -> button")];
289        [alert setShowsSuppressionButton: YES];
290
291        [alert beginSheetModalForWindow: [self window] modalDelegate: self
292            didEndSelector: @selector(createBlankAddressAlertDidEnd:returnCode:contextInfo:) contextInfo: nil];
293    }
294    else
295        [self createReal];
296}
297
298- (IBAction) cancelCreateWindow: (id) sender
299{
300    [[self window] close];
301}
302
303- (void) windowWillClose: (NSNotification *) notification
304{
305    [creatorWindowControllerSet removeObject:self];
306}
307
308- (IBAction) cancelCreateProgress: (id) sender
309{
310    fInfo->abortFlag = 1;
311    [fTimer fire];
312}
313
314- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
315{
316    return [fTrackers count];
317}
318
319- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
320{
321    return fTrackers[row];
322}
323
324- (IBAction) addRemoveTracker: (id) sender
325{
326    //don't allow add/remove when currently adding - it leads to weird results
327    if ([fTrackerTable editedRow] != -1)
328        return;
329
330    if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
331    {
332        [fTrackers removeObjectsAtIndexes: [fTrackerTable selectedRowIndexes]];
333
334        [fTrackerTable deselectAll: self];
335        [fTrackerTable reloadData];
336    }
337    else
338    {
339        [fTrackers addObject: @""];
340        [fTrackerTable reloadData];
341
342        const NSInteger row = [fTrackers count] - 1;
343        [fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
344        [fTrackerTable editColumn: 0 row: row withEvent: nil select: YES];
345    }
346}
347
348- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
349    row: (NSInteger) row
350{
351    NSString * tracker = (NSString *)object;
352
353    tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
354
355    if ([tracker rangeOfString: @"://"].location == NSNotFound)
356        tracker = [@"http://" stringByAppendingString: tracker];
357
358    if (!tr_urlIsValidTracker([tracker UTF8String]))
359    {
360        NSBeep();
361        [fTrackers removeObjectAtIndex: row];
362    }
363    else
364        fTrackers[row] = tracker;
365
366    [fTrackerTable deselectAll: self];
367    [fTrackerTable reloadData];
368}
369
370- (void) tableViewSelectionDidChange: (NSNotification *) notification
371{
372    [fTrackerAddRemoveControl setEnabled: [fTrackerTable numberOfSelectedRows] > 0 forSegment: TRACKER_REMOVE_TAG];
373}
374
375- (void) copy: (id) sender
376{
377    NSArray * addresses = [fTrackers objectsAtIndexes: [fTrackerTable selectedRowIndexes]];
378    NSString * text = [addresses componentsJoinedByString: @"\n"];
379
380    NSPasteboard * pb = [NSPasteboard generalPasteboard];
381    [pb clearContents];
382    [pb writeObjects: @[text]];
383}
384
385- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
386{
387    const SEL action = [menuItem action];
388
389    if (action == @selector(copy:))
390        return [[self window] firstResponder] == fTrackerTable && [fTrackerTable numberOfSelectedRows] > 0;
391
392    if (action == @selector(paste:))
393        return [[self window] firstResponder] == fTrackerTable
394            && [[NSPasteboard generalPasteboard] canReadObjectForClasses: @[[NSString class]] options: nil];
395
396    return YES;
397}
398
399- (void) paste: (id) sender
400{
401    NSMutableArray * tempTrackers = [NSMutableArray array];
402
403    NSArray * items = [[NSPasteboard generalPasteboard] readObjectsForClasses: @[[NSString class]] options: nil];
404    NSAssert(items != nil, @"no string items to paste; should not be able to call this method");
405
406    for (NSString * pbItem in items)
407    {
408        for (NSString * tracker in [pbItem componentsSeparatedByString: @"\n"])
409            [tempTrackers addObject: tracker];
410    }
411
412    BOOL added = NO;
413
414    for (__strong NSString * tracker in tempTrackers)
415    {
416        tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
417
418        if ([tracker rangeOfString: @"://"].location == NSNotFound)
419            tracker = [@"http://" stringByAppendingString: tracker];
420
421        if (tr_urlIsValidTracker([tracker UTF8String]))
422        {
423            [fTrackers addObject: tracker];
424            added = YES;
425        }
426    }
427
428    if (added)
429    {
430        [fTrackerTable deselectAll: self];
431        [fTrackerTable reloadData];
432    }
433    else
434        NSBeep();
435}
436
437@end
438
439@implementation CreatorWindowController (Private)
440
441- (void) updateLocationField
442{
443    NSString * pathString = [fLocation path];
444    [fLocationField setStringValue: [pathString stringByAbbreviatingWithTildeInPath]];
445    [fLocationField setToolTip: pathString];
446}
447
448+ (NSURL *) chooseFile
449{
450    NSOpenPanel * panel = [NSOpenPanel openPanel];
451
452    [panel setTitle: NSLocalizedString(@"Create Torrent File", "Create torrent -> select file")];
453    [panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> select file")];
454    [panel setAllowsMultipleSelection: NO];
455    [panel setCanChooseFiles: YES];
456    [panel setCanChooseDirectories: YES];
457    [panel setCanCreateDirectories: NO];
458
459    [panel setMessage: NSLocalizedString(@"Select a file or folder for the torrent file.", "Create torrent -> select file")];
460
461    BOOL success = [panel runModal] == NSOKButton;
462    return success ? [panel URLs][0] : nil;
463}
464
465- (void) createBlankAddressAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo
466{
467    if ([[alert suppressionButton] state] == NSOnState)
468    {
469        [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningCreatorBlankAddress"]; //set regardless of private/public
470        if ([fPrivateCheck state] == NSOnState)
471            [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningCreatorPrivateBlankAddress"];
472    }
473
474
475    if (returnCode == NSAlertFirstButtonReturn)
476        [self performSelectorOnMainThread: @selector(createReal) withObject: nil waitUntilDone: NO];
477}
478
479- (void) createReal
480{
481    //check if the location currently exists
482    if (![[fLocation URLByDeletingLastPathComponent] checkResourceIsReachableAndReturnError: NULL])
483    {
484        NSAlert * alert = [[NSAlert alloc] init];
485        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> directory doesn't exist warning -> button")];
486        [alert setMessageText: NSLocalizedString(@"The chosen torrent file location does not exist.",
487                                                "Create torrent -> directory doesn't exist warning -> title")];
488        [alert setInformativeText: [NSString stringWithFormat:
489                NSLocalizedString(@"The directory \"%@\" does not currently exist. "
490                    "Create this directory or choose a different one to create the torrent file.",
491                    "Create torrent -> directory doesn't exist warning -> warning"),
492                    [[fLocation URLByDeletingLastPathComponent] path]]];
493        [alert setAlertStyle: NSWarningAlertStyle];
494
495        [alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
496        return;
497    }
498
499    //check if a file with the same name and location already exists
500    if ([fLocation checkResourceIsReachableAndReturnError: NULL])
501    {
502        NSArray * pathComponents = [fLocation pathComponents];
503        NSInteger count = [pathComponents count];
504
505        NSAlert * alert = [[NSAlert alloc] init];
506        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> file already exists warning -> button")];
507        [alert setMessageText: NSLocalizedString(@"A torrent file with this name and directory cannot be created.",
508                                                "Create torrent -> file already exists warning -> title")];
509        [alert setInformativeText: [NSString stringWithFormat:
510                NSLocalizedString(@"A file with the name \"%@\" already exists in the directory \"%@\". "
511                    "Choose a new name or directory to create the torrent file.",
512                    "Create torrent -> file already exists warning -> warning"),
513                    pathComponents[count-1], pathComponents[count-2]]];
514        [alert setAlertStyle: NSWarningAlertStyle];
515
516        [alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
517        return;
518    }
519
520    //parse non-empty tracker strings
521    tr_tracker_info * trackerInfo = tr_new0(tr_tracker_info, [fTrackers count]);
522
523    for (NSUInteger i = 0; i < [fTrackers count]; i++)
524    {
525        trackerInfo[i].announce = (char *)[fTrackers[i] UTF8String];
526        trackerInfo[i].tier = i;
527    }
528
529    //store values
530    [fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
531    [fDefaults setBool: [fPrivateCheck state] == NSOnState forKey: @"CreatorPrivate"];
532    [fDefaults setBool: [fOpenCheck state] == NSOnState forKey: @"CreatorOpen"];
533    fOpenWhenCreated = [fOpenCheck state] == NSOnState; //need this since the check box might not exist, and value in prefs might have changed from another creator window
534    [fDefaults setURL: [fLocation URLByDeletingLastPathComponent] forKey: @"CreatorLocationURL"];
535
536    [[self window] setRestorable: NO];
537
538    [[NSNotificationCenter defaultCenter] postNotificationName: @"BeginCreateTorrentFile" object: fLocation userInfo: nil];
539    tr_makeMetaInfo(fInfo, [[fLocation path] UTF8String], trackerInfo, [fTrackers count], [[fCommentView string] UTF8String], [fPrivateCheck state] == NSOnState);
540    tr_free(trackerInfo);
541
542    fTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(checkProgress) userInfo: nil repeats: YES];
543}
544
545- (void) checkProgress
546{
547    if (fInfo->isDone)
548    {
549        [fTimer invalidate];
550        fTimer = nil;
551
552        NSAlert * alert;
553        switch (fInfo->result)
554        {
555            case TR_MAKEMETA_OK:
556                if (fOpenWhenCreated)
557                {
558                    NSDictionary * dict = [[NSDictionary alloc] initWithObjects: @[
559                        [fLocation path],
560                        [[fPath URLByDeletingLastPathComponent] path]]
561                        forKeys: @[@"File", @"Path"]];
562                    [[NSNotificationCenter defaultCenter] postNotificationName: @"OpenCreatedTorrentFile" object: self userInfo: dict];
563                }
564
565                [[self window] close];
566                break;
567
568            case TR_MAKEMETA_CANCELLED:
569                [[self window] close];
570                break;
571
572            default:
573                alert = [[NSAlert alloc] init];
574                [alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> failed -> button")];
575                [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Creation of \"%@\" failed.",
576                                                "Create torrent -> failed -> title"), [fLocation lastPathComponent]]];
577                [alert setAlertStyle: NSWarningAlertStyle];
578
579                if (fInfo->result == TR_MAKEMETA_IO_READ)
580                    [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not read \"%s\": %s.",
581                        "Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
582                else if (fInfo->result == TR_MAKEMETA_IO_WRITE)
583                    [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not write \"%s\": %s.",
584                        "Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
585                else //invalid url should have been caught before creating
586                    [alert setInformativeText: [NSString stringWithFormat: @"%@ (%d)",
587                        NSLocalizedString(@"An unknown error has occurred.", "Create torrent -> failed -> warning"), fInfo->result]];
588
589                [alert beginSheetModalForWindow: [self window] modalDelegate: self
590                    didEndSelector: @selector(failureSheetClosed:returnCode:contextInfo:) contextInfo: nil];
591        }
592    }
593    else
594    {
595        [fProgressIndicator setDoubleValue: (double)fInfo->pieceIndex / fInfo->pieceCount];
596
597        if (!fStarted)
598        {
599            fStarted = YES;
600
601            [fProgressView setHidden: YES];
602
603            NSWindow * window = [self window];
604            [window setFrameAutosaveName: @""];
605
606            NSRect windowRect = [window frame];
607            CGFloat difference = [fProgressView frame].size.height - [[window contentView] frame].size.height;
608            windowRect.origin.y -= difference;
609            windowRect.size.height += difference;
610
611            //don't allow vertical resizing
612            CGFloat height = windowRect.size.height;
613            [window setMinSize: NSMakeSize([window minSize].width, height)];
614            [window setMaxSize: NSMakeSize([window maxSize].width, height)];
615
616            [window setContentView: fProgressView];
617            [window setFrame: windowRect display: YES animate: YES];
618            [fProgressView setHidden: NO];
619
620            [[window standardWindowButton: NSWindowCloseButton] setEnabled: NO];
621        }
622    }
623}
624
625- (void) failureSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info
626{
627    [[alert window] orderOut: nil];
628    [[self window] close];
629}
630
631@end
632