1/******************************************************************************
2 * Copyright (c) 2008-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#import "AddWindowController.h"
24#import "Controller.h"
25#import "ExpandedPathToIconTransformer.h"
26#import "FileOutlineController.h"
27#import "GroupsController.h"
28#import "NSStringAdditions.h"
29#import "Torrent.h"
30
31#define UPDATE_SECONDS 1.0
32
33#define POPUP_PRIORITY_HIGH 0
34#define POPUP_PRIORITY_NORMAL 1
35#define POPUP_PRIORITY_LOW 2
36
37@interface AddWindowController (Private)
38
39- (void) updateFiles;
40
41- (void) confirmAdd;
42
43- (void) setDestinationPath: (NSString *) destination determinationType: (TorrentDeterminationType) determinationType;
44
45- (void) setGroupsMenu;
46- (void) changeGroupValue: (id) sender;
47
48- (void) sameNameAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo;
49
50@end
51
52@implementation AddWindowController
53
54- (id) initWithTorrent: (Torrent *) torrent destination: (NSString *) path lockDestination: (BOOL) lockDestination
55    controller: (Controller *) controller torrentFile: (NSString *) torrentFile
56    deleteTorrentCheckEnableInitially: (BOOL) deleteTorrent canToggleDelete: (BOOL) canToggleDelete
57{
58    if ((self = [super initWithWindowNibName: @"AddWindow"]))
59    {
60        fTorrent = torrent;
61        fDestination = [path stringByExpandingTildeInPath];
62        fLockDestination = lockDestination;
63
64        fController = controller;
65
66        fTorrentFile = [torrentFile stringByExpandingTildeInPath];
67
68        fDeleteTorrentEnableInitially = deleteTorrent;
69        fCanToggleDelete = canToggleDelete;
70
71        fGroupValue = [torrent groupValue];
72        fGroupValueDetermination = TorrentDeterminationAutomatic;
73
74        [fVerifyIndicator setUsesThreadedAnimation: YES];
75    }
76    return self;
77}
78
79- (void) awakeFromNib
80{
81    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateCheckButtons:) name: @"TorrentFileCheckChange" object: fTorrent];
82
83    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateGroupMenu:) name: @"UpdateGroups" object: nil];
84
85    [fFileController setTorrent: fTorrent];
86
87    NSString * name = [fTorrent name];
88    [[self window] setTitle: name];
89    [fNameField setStringValue: name];
90    [fNameField setToolTip: name];
91
92    [fIconView setImage: [fTorrent icon]];
93
94    if (![fTorrent isFolder])
95    {
96        [fFileFilterField setHidden: YES];
97        [fCheckAllButton setHidden: YES];
98        [fUncheckAllButton setHidden: YES];
99
100        NSRect scrollFrame = [fFileScrollView frame];
101        const CGFloat diff = NSMinY([fFileScrollView frame]) - NSMinY([fFileFilterField frame]);
102        scrollFrame.origin.y -= diff;
103        scrollFrame.size.height += diff;
104        [fFileScrollView setFrame: scrollFrame];
105    }
106    else
107        [self updateCheckButtons: nil];
108
109    [self setGroupsMenu];
110    [fGroupPopUp selectItemWithTag: fGroupValue];
111
112    NSInteger priorityIndex;
113    switch ([fTorrent priority])
114    {
115        case TR_PRI_HIGH: priorityIndex = POPUP_PRIORITY_HIGH; break;
116        case TR_PRI_NORMAL: priorityIndex = POPUP_PRIORITY_NORMAL; break;
117        case TR_PRI_LOW: priorityIndex = POPUP_PRIORITY_LOW; break;
118        default:
119            NSAssert1(NO, @"Unknown priority for adding torrent: %d", [fTorrent priority]);
120            priorityIndex = POPUP_PRIORITY_NORMAL;
121    }
122    [fPriorityPopUp selectItemAtIndex: priorityIndex];
123
124    [fStartCheck setState: [[NSUserDefaults standardUserDefaults] boolForKey: @"AutoStartDownload"] ? NSOnState : NSOffState];
125
126    [fDeleteCheck setState: fDeleteTorrentEnableInitially ? NSOnState : NSOffState];
127    [fDeleteCheck setEnabled: fCanToggleDelete];
128
129    if (fDestination)
130        [self setDestinationPath: fDestination determinationType: (fLockDestination ? TorrentDeterminationUserSpecified : TorrentDeterminationAutomatic)];
131    else
132    {
133        [fLocationField setStringValue: @""];
134        [fLocationImageView setImage: nil];
135    }
136
137    fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self
138                selector: @selector(updateFiles) userInfo: nil repeats: YES];
139    [self updateFiles];
140}
141
142- (void) windowDidLoad
143{
144    //if there is no destination, prompt for one right away
145    if (!fDestination)
146        [self setDestination: nil];
147}
148
149- (void) dealloc
150{
151    [[NSNotificationCenter defaultCenter] removeObserver: self];
152
153    [fTimer invalidate];
154}
155
156- (Torrent *) torrent
157{
158    return fTorrent;
159}
160
161- (void) setDestination: (id) sender
162{
163    NSOpenPanel * panel = [NSOpenPanel openPanel];
164
165    [panel setPrompt: NSLocalizedString(@"Select", "Open torrent -> prompt")];
166    [panel setAllowsMultipleSelection: NO];
167    [panel setCanChooseFiles: NO];
168    [panel setCanChooseDirectories: YES];
169    [panel setCanCreateDirectories: YES];
170
171    [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
172                        "Add -> select destination folder"), [fTorrent name]]];
173
174    [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
175        if (result == NSFileHandlingPanelOKButton)
176        {
177            fLockDestination = YES;
178            [self setDestinationPath: [[panel URLs][0] path] determinationType: TorrentDeterminationUserSpecified];
179        }
180        else
181        {
182            if (!fDestination)
183                [self performSelectorOnMainThread: @selector(cancelAdd:) withObject: nil waitUntilDone: NO];
184        }
185    }];
186}
187
188- (void) add: (id) sender
189{
190    if ([[fDestination lastPathComponent] isEqualToString: [fTorrent name]]
191        && [[NSUserDefaults standardUserDefaults] boolForKey: @"WarningFolderDataSameName"])
192    {
193        NSAlert * alert = [[NSAlert alloc] init];
194        [alert setMessageText: NSLocalizedString(@"The destination directory and root data directory have the same name.",
195                                "Add torrent -> same name -> title")];
196        [alert setInformativeText: NSLocalizedString(@"If you are attempting to use already existing data,"
197            " the root data directory should be inside the destination directory.", "Add torrent -> same name -> message")];
198        [alert setAlertStyle: NSWarningAlertStyle];
199        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Add torrent -> same name -> button")];
200        [alert addButtonWithTitle: NSLocalizedString(@"Add", "Add torrent -> same name -> button")];
201        [alert setShowsSuppressionButton: YES];
202
203        [alert beginSheetModalForWindow: [self window] modalDelegate: self
204            didEndSelector: @selector(sameNameAlertDidEnd:returnCode:contextInfo:) contextInfo: nil];
205    }
206    else
207        [self confirmAdd];
208}
209
210- (void) cancelAdd: (id) sender
211{
212    [[self window] performClose: sender];
213}
214
215//only called on cancel
216- (BOOL) windowShouldClose: (id) window
217{
218    [fTimer invalidate];
219    fTimer = nil;
220
221    [fFileController setTorrent: nil]; //avoid a crash when window tries to update
222
223    [fController askOpenConfirmed: self add: NO];
224    return YES;
225}
226
227- (void) setFileFilterText: (id) sender
228{
229    [fFileController setFilterText: [sender stringValue]];
230}
231
232- (IBAction) checkAll: (id) sender
233{
234    [fFileController checkAll];
235}
236
237- (IBAction) uncheckAll: (id) sender
238{
239    [fFileController uncheckAll];
240}
241
242- (void) verifyLocalData: (id) sender
243{
244    [fTorrent resetCache];
245    [self updateFiles];
246}
247
248- (void) changePriority: (id) sender
249{
250    tr_priority_t priority;
251    switch ([sender indexOfSelectedItem])
252    {
253        case POPUP_PRIORITY_HIGH: priority = TR_PRI_HIGH; break;
254        case POPUP_PRIORITY_NORMAL: priority = TR_PRI_NORMAL; break;
255        case POPUP_PRIORITY_LOW: priority = TR_PRI_LOW; break;
256        default:
257            NSAssert1(NO, @"Unknown priority tag for adding torrent: %ld", [sender tag]);
258            priority = TR_PRI_NORMAL;
259    }
260    [fTorrent setPriority: priority];
261}
262
263- (void) updateCheckButtons: (NSNotification *) notification
264{
265    NSString * statusString = [NSString stringForFileSize: [fTorrent size]];
266    if ([fTorrent isFolder])
267    {
268        //check buttons
269        //keep synced with identical code in InfoFileViewController.m
270        const NSInteger filesCheckState = [fTorrent checkForFiles: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])]];
271        [fCheckAllButton setEnabled: filesCheckState != NSOnState]; //if anything is unchecked
272        [fUncheckAllButton setEnabled: ![fTorrent allDownloaded]]; //if there are any checked files that aren't finished
273
274        //status field
275        NSString * fileString;
276        NSInteger count = [fTorrent fileCount];
277        if (count != 1)
278            fileString = [NSString stringWithFormat: NSLocalizedString(@"%@ files", "Add torrent -> info"),
279                            [NSString formattedUInteger: count]];
280        else
281            fileString = NSLocalizedString(@"1 file", "Add torrent -> info");
282
283        NSString * selectedString = [NSString stringWithFormat: NSLocalizedString(@"%@ selected", "Add torrent -> info"),
284                                        [NSString stringForFileSize: [fTorrent totalSizeSelected]]];
285
286        statusString = [NSString stringWithFormat: @"%@, %@ (%@)", fileString, statusString, selectedString];
287    }
288
289    [fStatusField setStringValue: statusString];
290}
291
292- (void) updateGroupMenu: (NSNotification *) notification
293{
294    [self setGroupsMenu];
295    if (![fGroupPopUp selectItemWithTag: fGroupValue])
296    {
297        fGroupValue = -1;
298        fGroupValueDetermination = TorrentDeterminationAutomatic;
299        [fGroupPopUp selectItemWithTag: fGroupValue];
300    }
301}
302
303@end
304
305@implementation AddWindowController (Private)
306
307- (void) updateFiles
308{
309    [fTorrent update];
310
311    [fFileController refresh];
312
313    [self updateCheckButtons: nil]; //call in case button state changed by checking
314
315    if ([fTorrent isChecking])
316    {
317        const BOOL waiting = [fTorrent isCheckingWaiting];
318        [fVerifyIndicator setIndeterminate: waiting];
319        if (waiting)
320            [fVerifyIndicator startAnimation: self];
321        else
322            [fVerifyIndicator setDoubleValue: [fTorrent checkingProgress]];
323    }
324    else {
325        [fVerifyIndicator setIndeterminate: YES]; //we want to hide when stopped, which only applies when indeterminate
326        [fVerifyIndicator stopAnimation: self];
327    }
328}
329
330- (void) confirmAdd
331{
332    [fTimer invalidate];
333    fTimer = nil;
334    [fTorrent setGroupValue: fGroupValue  determinationType: fGroupValueDetermination];
335
336    if (fTorrentFile && fCanToggleDelete && [fDeleteCheck state] == NSOnState)
337        [Torrent trashFile: fTorrentFile error: nil];
338
339    if ([fStartCheck state] == NSOnState)
340        [fTorrent startTransfer];
341
342    [fFileController setTorrent: nil]; //avoid a crash when window tries to update
343
344    [self close];
345    [fController askOpenConfirmed: self add: YES];
346}
347
348- (void) setDestinationPath: (NSString *) destination determinationType: (TorrentDeterminationType) determinationType
349{
350    destination = [destination stringByExpandingTildeInPath];
351    if (!fDestination || ![fDestination isEqualToString: destination])
352    {
353        fDestination = destination;
354
355        [fTorrent changeDownloadFolderBeforeUsing: fDestination determinationType: determinationType];
356    }
357
358    [fLocationField setStringValue: [fDestination stringByAbbreviatingWithTildeInPath]];
359    [fLocationField setToolTip: fDestination];
360
361    ExpandedPathToIconTransformer * iconTransformer = [[ExpandedPathToIconTransformer alloc] init];
362    [fLocationImageView setImage: [iconTransformer transformedValue: fDestination]];
363}
364
365- (void) setGroupsMenu
366{
367    NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(changeGroupValue:) isSmall: NO];
368    [fGroupPopUp setMenu: groupMenu];
369}
370
371- (void) changeGroupValue: (id) sender
372{
373    NSInteger previousGroup = fGroupValue;
374    fGroupValue = [sender tag];
375    fGroupValueDetermination = TorrentDeterminationUserSpecified;
376
377    if (!fLockDestination)
378    {
379        if ([[GroupsController groups] usesCustomDownloadLocationForIndex: fGroupValue])
380            [self setDestinationPath: [[GroupsController groups] customDownloadLocationForIndex: fGroupValue] determinationType: TorrentDeterminationAutomatic];
381        else if ([fDestination isEqualToString: [[GroupsController groups] customDownloadLocationForIndex: previousGroup]])
382            [self setDestinationPath: [[NSUserDefaults standardUserDefaults] stringForKey: @"DownloadFolder"] determinationType: TorrentDeterminationAutomatic];
383        else;
384    }
385}
386
387- (void) sameNameAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo
388{
389    if ([[alert suppressionButton] state] == NSOnState)
390        [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningFolderDataSameName"];
391
392
393    if (returnCode == NSAlertSecondButtonReturn)
394        [self performSelectorOnMainThread: @selector(confirmAdd) withObject: nil waitUntilDone: NO];
395}
396
397@end
398