1/******************************************************************************
2 * $Id: PrefsController.m 13492 2012-09-10 02:37:29Z livings124 $
3 *
4 * Copyright (c) 2005-2012 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 "PrefsController.h"
26#import "BlocklistDownloaderViewController.h"
27#import "BlocklistScheduler.h"
28#import "Controller.h"
29#import "PortChecker.h"
30#import "BonjourController.h"
31#import "NSApplicationAdditions.h"
32#import "NSStringAdditions.h"
33#import "UKKQueue.h"
34
35#import "transmission.h"
36#import "utils.h"
37
38#import <Growl/Growl.h>
39#import <Sparkle/Sparkle.h>
40
41#define DOWNLOAD_FOLDER     0
42#define DOWNLOAD_TORRENT    2
43
44#define RPC_IP_ADD_TAG      0
45#define RPC_IP_REMOVE_TAG   1
46
47#define TOOLBAR_GENERAL     @"TOOLBAR_GENERAL"
48#define TOOLBAR_TRANSFERS   @"TOOLBAR_TRANSFERS"
49#define TOOLBAR_GROUPS      @"TOOLBAR_GROUPS"
50#define TOOLBAR_BANDWIDTH   @"TOOLBAR_BANDWIDTH"
51#define TOOLBAR_PEERS       @"TOOLBAR_PEERS"
52#define TOOLBAR_NETWORK     @"TOOLBAR_NETWORK"
53#define TOOLBAR_REMOTE      @"TOOLBAR_REMOTE"
54
55#define RPC_KEYCHAIN_SERVICE    "Transmission:Remote"
56#define RPC_KEYCHAIN_NAME       "Remote"
57
58#define WEBUI_URL   @"http://localhost:%ld/"
59
60@interface PrefsController (Private)
61
62- (void) setPrefView: (id) sender;
63
64- (void) updateGrowlButton;
65
66- (void) setKeychainPassword: (const char *) password forService: (const char *) service username: (const char *) username;
67
68@end
69
70@implementation PrefsController
71
72- (id) initWithHandle: (tr_session *) handle
73{
74    if ((self = [super initWithWindowNibName: @"PrefsWindow"]))
75    {
76        fHandle = handle;
77        
78        fDefaults = [NSUserDefaults standardUserDefaults];
79        
80        //check for old version download location (before 1.1)
81        NSString * choice;
82        if ((choice = [fDefaults stringForKey: @"DownloadChoice"]))
83        {
84            [fDefaults setBool: [choice isEqualToString: @"Constant"] forKey: @"DownloadLocationConstant"];
85            [fDefaults setBool: YES forKey: @"DownloadAsk"];
86            
87            [fDefaults removeObjectForKey: @"DownloadChoice"];
88        }
89        
90        //check for old version blocklist (before 2.12)
91        NSDate * blocklistDate;
92        if ((blocklistDate = [fDefaults objectForKey: @"BlocklistLastUpdate"]))
93        {
94            [fDefaults setObject: blocklistDate forKey: @"BlocklistNewLastUpdateSuccess"];
95            [fDefaults setObject: blocklistDate forKey: @"BlocklistNewLastUpdate"];
96            [fDefaults removeObjectForKey: @"BlocklistLastUpdate"];
97            
98            NSURL * blocklistDir = [[[[NSFileManager defaultManager] URLsForDirectory: NSApplicationDirectory inDomains: NSUserDomainMask] objectAtIndex: 0] URLByAppendingPathComponent: @"Transmission/blocklists/"];
99            [[NSFileManager defaultManager] moveItemAtURL: [blocklistDir URLByAppendingPathComponent: @"level1.bin"]
100                toURL: [blocklistDir URLByAppendingPathComponent: [NSString stringWithUTF8String: DEFAULT_BLOCKLIST_FILENAME]]
101                error: nil];
102        }
103        
104        //save a new random port
105        if ([fDefaults boolForKey: @"RandomPort"])
106            [fDefaults setInteger: tr_sessionGetPeerPort(fHandle) forKey: @"BindPort"];
107        
108        //set auto import
109        NSString * autoPath;
110        if ([fDefaults boolForKey: @"AutoImport"] && (autoPath = [fDefaults stringForKey: @"AutoImportDirectory"]))
111            [[UKKQueue sharedFileWatcher] addPath: [autoPath stringByExpandingTildeInPath]];
112        
113        //set blocklist scheduler
114        [[BlocklistScheduler scheduler] updateSchedule];
115        
116        //set encryption
117        [self setEncryptionMode: nil];
118        
119        //update rpc whitelist
120        [self updateRPCPassword];
121        
122        fRPCWhitelistArray = [[fDefaults arrayForKey: @"RPCWhitelist"] mutableCopy];
123        if (!fRPCWhitelistArray)
124            fRPCWhitelistArray = [[NSMutableArray arrayWithObject: @"127.0.0.1"] retain];
125        [self updateRPCWhitelist];
126        
127        //reset old Sparkle settings from previous versions
128        [fDefaults removeObjectForKey: @"SUScheduledCheckInterval"];
129        if ([fDefaults objectForKey: @"CheckForUpdates"])
130        {
131            [[SUUpdater sharedUpdater] setAutomaticallyChecksForUpdates: [fDefaults boolForKey: @"CheckForUpdates"]];
132            [fDefaults removeObjectForKey: @"CheckForUpdates"];
133        }
134        
135        //set built-in Growl
136        [GrowlApplicationBridge setShouldUseBuiltInNotifications: ![NSApp isOnMountainLionOrBetter] && [fDefaults boolForKey: @"DisplayNotifications"]];
137        
138        [self setAutoUpdateToBeta: nil];
139    }
140    
141    return self;
142}
143
144- (void) dealloc
145{
146    [[NSNotificationCenter defaultCenter] removeObserver: self];
147    
148    [fPortStatusTimer invalidate];
149    [fPortStatusTimer release];
150    if (fPortChecker)
151    {
152        [fPortChecker cancelProbe];
153        [fPortChecker release];
154    }
155    
156    [fRPCWhitelistArray release];
157    
158    [fRPCPassword release];
159    
160    [super dealloc];
161}
162
163- (void) awakeFromNib
164{
165    fHasLoaded = YES;
166    
167    if ([NSApp isOnLionOrBetter])
168        [[self window] setRestorationClass: [self class]];
169    
170    NSToolbar * toolbar = [[NSToolbar alloc] initWithIdentifier: @"Preferences Toolbar"];
171    [toolbar setDelegate: self];
172    [toolbar setAllowsUserCustomization: NO];
173    [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
174    [toolbar setSizeMode: NSToolbarSizeModeRegular];
175    [toolbar setSelectedItemIdentifier: TOOLBAR_GENERAL];
176    [[self window] setToolbar: toolbar];
177    [toolbar release];
178    
179    [self setPrefView: nil];
180    
181    //make sure proper notification settings are shown
182    [self updateGrowlButton];
183    
184    //set download folder
185    [fFolderPopUp selectItemAtIndex: [fDefaults boolForKey: @"DownloadLocationConstant"] ? DOWNLOAD_FOLDER : DOWNLOAD_TORRENT];
186    
187    //set stop ratio
188    [fRatioStopField setFloatValue: [fDefaults floatForKey: @"RatioLimit"]];
189    
190    //set idle seeding minutes
191    [fIdleStopField setIntegerValue: [fDefaults integerForKey: @"IdleLimitMinutes"]];
192    
193    //set limits
194    [self updateLimitFields];
195    
196    //set speed limit
197    [fSpeedLimitUploadField setIntValue: [fDefaults integerForKey: @"SpeedLimitUploadLimit"]];
198    [fSpeedLimitDownloadField setIntValue: [fDefaults integerForKey: @"SpeedLimitDownloadLimit"]];
199    
200    //set port
201    [fPortField setIntValue: [fDefaults integerForKey: @"BindPort"]];
202    fNatStatus = -1;
203    
204    [self updatePortStatus];
205    fPortStatusTimer = [[NSTimer scheduledTimerWithTimeInterval: 5.0 target: self selector: @selector(updatePortStatus) userInfo: nil repeats: YES] retain];
206    
207    //set peer connections
208    [fPeersGlobalField setIntValue: [fDefaults integerForKey: @"PeersTotal"]];
209    [fPeersTorrentField setIntValue: [fDefaults integerForKey: @"PeersTorrent"]];
210    
211    //set queue values
212    [fQueueDownloadField setIntValue: [fDefaults integerForKey: @"QueueDownloadNumber"]];
213    [fQueueSeedField setIntValue: [fDefaults integerForKey: @"QueueSeedNumber"]];
214    [fStalledField setIntValue: [fDefaults integerForKey: @"StalledMinutes"]];
215    
216    //set blocklist
217    NSString * blocklistURL = [fDefaults stringForKey: @"BlocklistURL"];
218    if (blocklistURL)
219        [fBlocklistURLField setStringValue: blocklistURL];
220    
221    [self updateBlocklistButton];
222    [self updateBlocklistFields];
223    
224    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateLimitFields)
225                                                 name: @"UpdateSpeedLimitValuesOutsidePrefs" object: nil];
226    
227    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateRatioStopField)
228                                                 name: @"UpdateRatioStopValueOutsidePrefs" object: nil];
229    
230    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateLimitStopField)
231                                                 name: @"UpdateIdleStopValueOutsidePrefs" object: nil];
232    
233    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateBlocklistFields)
234        name: @"BlocklistUpdated" object: nil];
235    
236    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateBlocklistURLField)
237        name: NSControlTextDidChangeNotification object: fBlocklistURLField];
238    
239    //set rpc port
240    [fRPCPortField setIntValue: [fDefaults integerForKey: @"RPCPort"]];
241    
242    //set rpc password
243    if (fRPCPassword)
244        [fRPCPasswordField setStringValue: fRPCPassword];
245}
246
247- (NSToolbarItem *) toolbar: (NSToolbar *) toolbar itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
248{
249    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
250
251    if ([ident isEqualToString: TOOLBAR_GENERAL])
252    {
253        [item setLabel: NSLocalizedString(@"General", "Preferences -> toolbar item title")];
254        [item setImage: [NSImage imageNamed: NSImageNamePreferencesGeneral]];
255        [item setTarget: self];
256        [item setAction: @selector(setPrefView:)];
257        [item setAutovalidates: NO];
258    }
259    else if ([ident isEqualToString: TOOLBAR_TRANSFERS])
260    {
261        [item setLabel: NSLocalizedString(@"Transfers", "Preferences -> toolbar item title")];
262        [item setImage: [NSImage imageNamed: @"Transfers"]];
263        [item setTarget: self];
264        [item setAction: @selector(setPrefView:)];
265        [item setAutovalidates: NO];
266    }
267    else if ([ident isEqualToString: TOOLBAR_GROUPS])
268    {
269        [item setLabel: NSLocalizedString(@"Groups", "Preferences -> toolbar item title")];
270        [item setImage: [NSImage imageNamed: @"Groups"]];
271        [item setTarget: self];
272        [item setAction: @selector(setPrefView:)];
273        [item setAutovalidates: NO];
274    }
275    else if ([ident isEqualToString: TOOLBAR_BANDWIDTH])
276    {
277        [item setLabel: NSLocalizedString(@"Bandwidth", "Preferences -> toolbar item title")];
278        [item setImage: [NSImage imageNamed: @"Bandwidth"]];
279        [item setTarget: self];
280        [item setAction: @selector(setPrefView:)];
281        [item setAutovalidates: NO];
282    }
283    else if ([ident isEqualToString: TOOLBAR_PEERS])
284    {
285        [item setLabel: NSLocalizedString(@"Peers", "Preferences -> toolbar item title")];
286        [item setImage: [NSImage imageNamed: NSImageNameUserGroup]];
287        [item setTarget: self];
288        [item setAction: @selector(setPrefView:)];
289        [item setAutovalidates: NO];
290    }
291    else if ([ident isEqualToString: TOOLBAR_NETWORK])
292    {
293        [item setLabel: NSLocalizedString(@"Network", "Preferences -> toolbar item title")];
294        [item setImage: [NSImage imageNamed: NSImageNameNetwork]];
295        [item setTarget: self];
296        [item setAction: @selector(setPrefView:)];
297        [item setAutovalidates: NO];
298    }
299    else if ([ident isEqualToString: TOOLBAR_REMOTE])
300    {
301        [item setLabel: NSLocalizedString(@"Remote", "Preferences -> toolbar item title")];
302        [item setImage: [NSImage imageNamed: @"Remote"]];
303        [item setTarget: self];
304        [item setAction: @selector(setPrefView:)];
305        [item setAutovalidates: NO];
306    }
307    else
308    {
309        [item release];
310        return nil;
311    }
312
313    return [item autorelease];
314}
315
316- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
317{
318    return [NSArray arrayWithObjects: TOOLBAR_GENERAL, TOOLBAR_TRANSFERS, TOOLBAR_GROUPS, TOOLBAR_BANDWIDTH,
319                                        TOOLBAR_PEERS, TOOLBAR_NETWORK, TOOLBAR_REMOTE, nil];
320}
321
322- (NSArray *) toolbarSelectableItemIdentifiers: (NSToolbar *) toolbar
323{
324    return [self toolbarAllowedItemIdentifiers: toolbar];
325}
326
327- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
328{
329    return [self toolbarAllowedItemIdentifiers: toolbar];
330}
331
332- (void) windowDidBecomeMain: (NSNotification *) notification
333{
334    //this is a good place to see if Growl was quit/launched
335    [self updateGrowlButton];
336}
337
338+ (void) restoreWindowWithIdentifier: (NSString *) identifier state: (NSCoder *) state completionHandler: (void (^)(NSWindow *, NSError *)) completionHandler
339{
340    NSWindow * window = [[(Controller *)[NSApp delegate] prefsController] window];
341    completionHandler(window, nil);
342}
343
344//for a beta release, always use the beta appcast
345#if defined(TR_BETA_RELEASE)
346#define SPARKLE_TAG YES
347#else
348#define SPARKLE_TAG [fDefaults boolForKey: @"AutoUpdateBeta"]
349#endif
350- (void) setAutoUpdateToBeta: (id) sender
351{
352    [[SUUpdater sharedUpdater] setAllowedTags: SPARKLE_TAG ? [NSSet setWithObject: @"beta"] : nil];
353}
354
355- (void) setPort: (id) sender
356{
357    const tr_port port = [sender intValue];
358    [fDefaults setInteger: port forKey: @"BindPort"];
359    tr_sessionSetPeerPort(fHandle, port);
360    
361    fPeerPort = -1;
362    [self updatePortStatus];
363}
364
365- (void) randomPort: (id) sender
366{
367    const tr_port port = tr_sessionSetPeerPortRandom(fHandle);
368    [fDefaults setInteger: port forKey: @"BindPort"];
369    [fPortField setIntValue: port];
370    
371    fPeerPort = -1;
372    [self updatePortStatus];
373}
374
375- (void) setRandomPortOnStart: (id) sender
376{
377    tr_sessionSetPeerPortRandomOnStart(fHandle, [(NSButton *)sender state] == NSOnState);
378}
379
380- (void) setNat: (id) sender
381{
382    tr_sessionSetPortForwardingEnabled(fHandle, [fDefaults boolForKey: @"NatTraversal"]);
383    
384    fNatStatus = -1;
385    [self updatePortStatus];
386}
387
388- (void) updatePortStatus
389{
390    const tr_port_forwarding fwd = tr_sessionGetPortForwarding(fHandle);
391    const int port = tr_sessionGetPeerPort(fHandle);
392    BOOL natStatusChanged = (fNatStatus != fwd);
393    BOOL peerPortChanged = (fPeerPort != port);
394
395    if (natStatusChanged || peerPortChanged)
396    {
397        fNatStatus = fwd;
398        fPeerPort = port;
399        
400        [fPortStatusField setStringValue: @""];
401        [fPortStatusImage setImage: nil];
402        [fPortStatusProgress startAnimation: self];
403        
404        if (fPortChecker)
405        {
406            [fPortChecker cancelProbe];
407            [fPortChecker release];
408        }
409        BOOL delay = natStatusChanged || tr_sessionIsPortForwardingEnabled(fHandle);
410        fPortChecker = [[PortChecker alloc] initForPort: fPeerPort delay: delay withDelegate: self];
411    }
412}
413
414- (void) portCheckerDidFinishProbing: (PortChecker *) portChecker
415{
416    [fPortStatusProgress stopAnimation: self];
417    switch ([fPortChecker status])
418    {
419        case PORT_STATUS_OPEN:
420            [fPortStatusField setStringValue: NSLocalizedString(@"Port is open", "Preferences -> Network -> port status")];
421            [fPortStatusImage setImage: [NSImage imageNamed: @"GreenDot"]];
422            break;
423        case PORT_STATUS_CLOSED:
424            [fPortStatusField setStringValue: NSLocalizedString(@"Port is closed", "Preferences -> Network -> port status")];
425            [fPortStatusImage setImage: [NSImage imageNamed: @"RedDot"]];
426            break;
427        case PORT_STATUS_ERROR:
428            [fPortStatusField setStringValue: NSLocalizedString(@"Port check site is down", "Preferences -> Network -> port status")];
429            [fPortStatusImage setImage: [NSImage imageNamed: @"YellowDot"]];
430            break;
431        default:
432            NSAssert1(NO, @"Port checker returned invalid status: %d", [fPortChecker status]);
433            break;
434    }
435    [fPortChecker release];
436    fPortChecker = nil;
437}
438
439- (NSArray *) sounds
440{
441    NSMutableArray * sounds = [NSMutableArray array];
442    
443    NSArray * directories = NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, NSUserDomainMask | NSLocalDomainMask | NSSystemDomainMask, YES);
444    
445    for (NSString * directory in directories)
446    {
447        directory = [directory stringByAppendingPathComponent: @"Sounds"];
448        
449        BOOL isDirectory;
450        if ([[NSFileManager defaultManager] fileExistsAtPath: directory isDirectory: &isDirectory] && isDirectory)
451        {
452            NSArray * directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: directory error: NULL];
453            for (NSString * sound in directoryContents)
454            {
455                sound = [sound stringByDeletingPathExtension];
456                if ([NSSound soundNamed: sound])
457                    [sounds addObject: sound];
458            }
459        }
460    }
461    
462    return sounds;
463}
464
465- (void) setSound: (id) sender
466{
467    //play sound when selecting
468    NSSound * sound;
469    if ((sound = [NSSound soundNamed: [sender titleOfSelectedItem]]))
470        [sound play];
471}
472
473- (void) setUTP: (id) sender
474{
475    tr_sessionSetUTPEnabled(fHandle, [fDefaults boolForKey: @"UTPGlobal"]);
476}
477
478- (void) setPeersGlobal: (id) sender
479{
480    const int count = [sender intValue];
481    [fDefaults setInteger: count forKey: @"PeersTotal"];
482    tr_sessionSetPeerLimit(fHandle, count);
483}
484
485- (void) setPeersTorrent: (id) sender
486{
487    const int count = [sender intValue];
488    [fDefaults setInteger: count forKey: @"PeersTorrent"];
489    tr_sessionSetPeerLimitPerTorrent(fHandle, count);
490}
491
492- (void) setPEX: (id) sender
493{
494    tr_sessionSetPexEnabled(fHandle, [fDefaults boolForKey: @"PEXGlobal"]);
495}
496
497- (void) setDHT: (id) sender
498{
499    tr_sessionSetDHTEnabled(fHandle, [fDefaults boolForKey: @"DHTGlobal"]);
500}
501
502- (void) setLPD: (id) sender
503{
504    tr_sessionSetLPDEnabled(fHandle, [fDefaults boolForKey: @"LocalPeerDiscoveryGlobal"]);
505}
506
507- (void) setEncryptionMode: (id) sender
508{
509    const tr_encryption_mode mode = [fDefaults boolForKey: @"EncryptionPrefer"] ? 
510        ([fDefaults boolForKey: @"EncryptionRequire"] ? TR_ENCRYPTION_REQUIRED : TR_ENCRYPTION_PREFERRED) : TR_CLEAR_PREFERRED;
511    tr_sessionSetEncryption(fHandle, mode);
512}
513
514- (void) setBlocklistEnabled: (id) sender
515{
516    tr_blocklistSetEnabled(fHandle, [fDefaults boolForKey: @"BlocklistNew"]);
517    
518    [[BlocklistScheduler scheduler] updateSchedule];
519    
520    [self updateBlocklistButton];
521}
522
523- (void) updateBlocklist: (id) sender
524{
525    [BlocklistDownloaderViewController downloadWithPrefsController: self];
526}
527
528- (void) setBlocklistAutoUpdate: (id) sender
529{
530    [[BlocklistScheduler scheduler] updateSchedule];
531}
532
533- (void) updateBlocklistFields
534{
535    const BOOL exists = tr_blocklistExists(fHandle);
536    
537    if (exists)
538    {
539        NSString * countString = [NSString formattedUInteger: tr_blocklistGetRuleCount(fHandle)];
540        [fBlocklistMessageField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ IP address rules in list",
541            "Prefs -> blocklist -> message"), countString]];
542    }
543    else 
544        [fBlocklistMessageField setStringValue: NSLocalizedString(@"A blocklist must first be downloaded",
545            "Prefs -> blocklist -> message")];
546    
547    NSString * updatedDateString;
548    if (exists)
549    {
550        NSDate * updatedDate = [fDefaults objectForKey: @"BlocklistNewLastUpdateSuccess"];
551        
552        if (updatedDate)
553            updatedDateString = [NSDateFormatter localizedStringFromDate: updatedDate dateStyle: NSDateFormatterFullStyle timeStyle: NSDateFormatterShortStyle];
554        else
555            updatedDateString = NSLocalizedString(@"N/A", "Prefs -> blocklist -> message");
556    }
557    else
558        updatedDateString = NSLocalizedString(@"Never", "Prefs -> blocklist -> message");
559    
560    [fBlocklistDateField setStringValue: [NSString stringWithFormat: @"%@: %@",
561        NSLocalizedString(@"Last updated", "Prefs -> blocklist -> message"), updatedDateString]];
562}
563
564- (void) updateBlocklistURLField
565{
566    NSString * blocklistString = [fBlocklistURLField stringValue];
567    
568    [fDefaults setObject: blocklistString forKey: @"BlocklistURL"];
569    tr_blocklistSetURL(fHandle, [blocklistString UTF8String]);
570    
571    [self updateBlocklistButton];
572}
573
574- (void) updateBlocklistButton
575{
576    NSString * blocklistString = [fDefaults objectForKey: @"BlocklistURL"];
577    const BOOL enable = (blocklistString && ![blocklistString isEqualToString: @""])
578                            && [fDefaults boolForKey: @"BlocklistNew"];
579    [fBlocklistButton setEnabled: enable];
580}
581
582- (void) setAutoStartDownloads: (id) sender
583{
584    tr_sessionSetPaused(fHandle, ![fDefaults boolForKey: @"AutoStartDownload"]);
585}
586
587- (void) applySpeedSettings: (id) sender
588{
589    tr_sessionLimitSpeed(fHandle, TR_UP, [fDefaults boolForKey: @"CheckUpload"]);
590    tr_sessionSetSpeedLimit_KBps(fHandle, TR_UP, [fDefaults integerForKey: @"UploadLimit"]);
591    
592    tr_sessionLimitSpeed(fHandle, TR_DOWN, [fDefaults boolForKey: @"CheckDownload"]);
593    tr_sessionSetSpeedLimit_KBps(fHandle, TR_DOWN, [fDefaults integerForKey: @"DownloadLimit"]);
594    
595    [[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
596}
597
598- (void) applyAltSpeedSettings
599{
600    tr_sessionSetAltSpeed_KBps(fHandle, TR_UP, [fDefaults integerForKey: @"SpeedLimitUploadLimit"]);
601    tr_sessionSetAltSpeed_KBps(fHandle, TR_DOWN, [fDefaults integerForKey: @"SpeedLimitDownloadLimit"]);
602        
603    [[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
604}
605
606- (void) applyRatioSetting: (id) sender
607{
608    tr_sessionSetRatioLimited(fHandle, [fDefaults boolForKey: @"RatioCheck"]);
609    tr_sessionSetRatioLimit(fHandle, [fDefaults floatForKey: @"RatioLimit"]);
610    
611    //reload main table for seeding progress
612    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
613    
614    //reload global settings in inspector
615    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
616}
617
618- (void) setRatioStop: (id) sender
619{
620    [fDefaults setFloat: [sender floatValue] forKey: @"RatioLimit"];
621    
622    [self applyRatioSetting: nil];
623}
624
625- (void) updateRatioStopField
626{
627    if (fHasLoaded)
628        [fRatioStopField setFloatValue: [fDefaults floatForKey: @"RatioLimit"]];
629}
630
631- (void) updateRatioStopFieldOld
632{
633    [self updateRatioStopField];
634    
635    [self applyRatioSetting: nil];
636}
637
638- (void) applyIdleStopSetting: (id) sender
639{
640    tr_sessionSetIdleLimited(fHandle, [fDefaults boolForKey: @"IdleLimitCheck"]);
641    tr_sessionSetIdleLimit(fHandle, [fDefaults integerForKey: @"IdleLimitMinutes"]);
642    
643    //reload main table for remaining seeding time
644    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
645    
646    //reload global settings in inspector
647    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
648}
649
650- (void) setIdleStop: (id) sender
651{
652    [fDefaults setInteger: [sender integerValue] forKey: @"IdleLimitMinutes"];
653    
654    [self applyIdleStopSetting: nil];
655}
656
657- (void) updateLimitStopField
658{
659    if (fHasLoaded)
660        [fIdleStopField setIntegerValue: [fDefaults integerForKey: @"IdleLimitMinutes"]];
661}
662
663- (void) updateLimitFields
664{
665    if (!fHasLoaded)
666        return;
667    
668    [fUploadField setIntValue: [fDefaults integerForKey: @"UploadLimit"]];
669    [fDownloadField setIntValue: [fDefaults integerForKey: @"DownloadLimit"]];
670}
671
672- (void) setGlobalLimit: (id) sender
673{
674    [fDefaults setInteger: [sender intValue] forKey: sender == fUploadField ? @"UploadLimit" : @"DownloadLimit"];
675    [self applySpeedSettings: self];
676}
677
678- (void) setSpeedLimit: (id) sender
679{
680    [fDefaults setInteger: [sender intValue] forKey: sender == fSpeedLimitUploadField
681                                                        ? @"SpeedLimitUploadLimit" : @"SpeedLimitDownloadLimit"];
682    [self applyAltSpeedSettings];
683}
684
685- (void) setAutoSpeedLimit: (id) sender
686{
687    tr_sessionUseAltSpeedTime(fHandle, [fDefaults boolForKey: @"SpeedLimitAuto"]);
688}
689
690- (void) setAutoSpeedLimitTime: (id) sender
691{
692    tr_sessionSetAltSpeedBegin(fHandle, [PrefsController dateToTimeSum: [fDefaults objectForKey: @"SpeedLimitAutoOnDate"]]);
693    tr_sessionSetAltSpeedEnd(fHandle, [PrefsController dateToTimeSum: [fDefaults objectForKey: @"SpeedLimitAutoOffDate"]]);
694}
695
696- (void) setAutoSpeedLimitDay: (id) sender
697{
698    tr_sessionSetAltSpeedDay(fHandle, [[sender selectedItem] tag]);
699}
700
701+ (NSInteger) dateToTimeSum: (NSDate *) date
702{
703    NSCalendar * calendar = [NSCalendar currentCalendar];
704    NSDateComponents * components = [calendar components: NSHourCalendarUnit | NSMinuteCalendarUnit fromDate: date];
705    return [components hour] * 60 + [components minute];
706}
707
708+ (NSDate *) timeSumToDate: (NSInteger) sum
709{
710    NSDateComponents * comps = [[[NSDateComponents alloc] init] autorelease];
711    [comps setHour: sum / 60];
712    [comps setMinute: sum % 60];
713    
714    return [[NSCalendar currentCalendar] dateFromComponents: comps];
715}
716
717- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
718{
719    [fInitialString release];
720    fInitialString = [[control stringValue] retain];
721    
722    return YES;
723}
724
725- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
726{
727    NSBeep();
728    if (fInitialString)
729    {
730        [control setStringValue: fInitialString];
731        [fInitialString release];
732        fInitialString = nil;
733    }
734    return NO;
735}
736
737- (void) setBadge: (id) sender
738{
739    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: self];
740}
741
742- (IBAction) setBuiltInGrowlEnabled: (id) sender
743{
744    const BOOL enable = [(NSButton *)sender state] == NSOnState;
745    [fDefaults setBool: enable forKey: @"DisplayNotifications"];
746    [GrowlApplicationBridge setShouldUseBuiltInNotifications: enable];
747}
748
749- (IBAction) openGrowlApp: (id) sender
750{
751    [GrowlApplicationBridge openGrowlPreferences: YES];
752}
753
754- (void) openNotificationSystemPrefs: (id) sender
755{
756    [[NSWorkspace sharedWorkspace] openURL: [NSURL fileURLWithPath:@"/System/Library/PreferencePanes/Notifications.prefPane"]];
757}
758
759- (void) resetWarnings: (id) sender
760{
761    [fDefaults removeObjectForKey: @"WarningDuplicate"];
762    [fDefaults removeObjectForKey: @"WarningRemainingSpace"];
763    [fDefaults removeObjectForKey: @"WarningFolderDataSameName"];
764    [fDefaults removeObjectForKey: @"WarningResetStats"];
765    [fDefaults removeObjectForKey: @"WarningCreatorBlankAddress"];
766    [fDefaults removeObjectForKey: @"WarningCreatorPrivateBlankAddress"];
767    [fDefaults removeObjectForKey: @"WarningRemoveTrackers"];
768    [fDefaults removeObjectForKey: @"WarningInvalidOpen"];
769    [fDefaults removeObjectForKey: @"WarningRemoveCompleted"];
770    [fDefaults removeObjectForKey: @"WarningDonate"];
771    //[fDefaults removeObjectForKey: @"WarningLegal"];
772}
773
774- (void) setDefaultForMagnets: (id) sender
775{
776    NSString * bundleID = [[NSBundle mainBundle] bundleIdentifier];
777    const OSStatus result = LSSetDefaultHandlerForURLScheme((CFStringRef)@"magnet", (CFStringRef)bundleID);
778    if (result != noErr)
779        NSLog(@"Failed setting default magnet link handler");
780}
781
782- (void) setQueue: (id) sender
783{
784    //let's just do both - easier that way
785    tr_sessionSetQueueEnabled(fHandle, TR_DOWN, [fDefaults boolForKey: @"Queue"]);
786    tr_sessionSetQueueEnabled(fHandle, TR_UP, [fDefaults boolForKey: @"QueueSeed"]);
787    
788    //handle if any transfers switch from queued to paused
789    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
790}
791
792- (void) setQueueNumber: (id) sender
793{
794    const NSInteger number = [sender intValue];
795    const BOOL seed = sender == fQueueSeedField;
796    
797    [fDefaults setInteger: number forKey: seed ? @"QueueSeedNumber" : @"QueueDownloadNumber"];
798    
799    tr_sessionSetQueueSize(fHandle, seed ? TR_UP : TR_DOWN, number);
800}
801
802- (void) setStalled: (id) sender
803{
804    tr_sessionSetQueueStalledEnabled(fHandle, [fDefaults boolForKey: @"CheckStalled"]);
805    
806    //reload main table for stalled status
807    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
808}
809
810- (void) setStalledMinutes: (id) sender
811{
812    const NSInteger min = [sender intValue];
813    [fDefaults setInteger: min forKey: @"StalledMinutes"];
814    tr_sessionSetQueueStalledMinutes(fHandle, min);
815    
816    //reload main table for stalled status
817    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: self];
818}
819
820- (void) setDownloadLocation: (id) sender
821{
822    [fDefaults setBool: [fFolderPopUp indexOfSelectedItem] == DOWNLOAD_FOLDER forKey: @"DownloadLocationConstant"];
823}
824
825- (void) folderSheetShow: (id) sender
826{
827    NSOpenPanel * panel = [NSOpenPanel openPanel];
828
829    [panel setPrompt: NSLocalizedString(@"Select", "Preferences -> Open panel prompt")];
830    [panel setAllowsMultipleSelection: NO];
831    [panel setCanChooseFiles: NO];
832    [panel setCanChooseDirectories: YES];
833    [panel setCanCreateDirectories: YES];
834    
835    [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
836        if (result == NSFileHandlingPanelOKButton)
837        {
838            [fFolderPopUp selectItemAtIndex: DOWNLOAD_FOLDER];
839            
840            NSString * folder = [[[panel URLs] objectAtIndex: 0] path];
841            [fDefaults setObject: folder forKey: @"DownloadFolder"];
842            [fDefaults setObject: @"Constant" forKey: @"DownloadChoice"];
843            
844            tr_sessionSetDownloadDir(fHandle, [folder UTF8String]);
845        }
846        else
847        {
848            //reset if cancelled
849            [fFolderPopUp selectItemAtIndex: [fDefaults boolForKey: @"DownloadLocationConstant"] ? DOWNLOAD_FOLDER : DOWNLOAD_TORRENT];
850        }
851    }];
852}
853
854- (void) incompleteFolderSheetShow: (id) sender
855{
856    NSOpenPanel * panel = [NSOpenPanel openPanel];
857
858    [panel setPrompt: NSLocalizedString(@"Select", "Preferences -> Open panel prompt")];
859    [panel setAllowsMultipleSelection: NO];
860    [panel setCanChooseFiles: NO];
861    [panel setCanChooseDirectories: YES];
862    [panel setCanCreateDirectories: YES];
863    
864    [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
865        if (result == NSFileHandlingPanelOKButton)
866        {
867            NSString * folder = [[[panel URLs] objectAtIndex: 0] path];
868            [fDefaults setObject: folder forKey: @"IncompleteDownloadFolder"];
869            
870            tr_sessionSetIncompleteDir(fHandle, [folder UTF8String]);
871        }
872        [fIncompleteFolderPopUp selectItemAtIndex: 0];
873    }];
874}
875
876- (void) doneScriptSheetShow:(id)sender
877{
878    NSOpenPanel * panel = [NSOpenPanel openPanel];
879    
880    [panel setPrompt: NSLocalizedString(@"Select", "Preferences -> Open panel prompt")];
881    [panel setAllowsMultipleSelection: NO];
882    [panel setCanChooseFiles: YES];
883    [panel setCanChooseDirectories: NO];
884    [panel setCanCreateDirectories: NO];
885    
886    [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
887        if (result == NSFileHandlingPanelOKButton)
888        {
889            NSString * filePath = [[[panel URLs] objectAtIndex: 0] path];
890            
891            [fDefaults setObject: filePath forKey: @"DoneScriptPath"];
892            tr_sessionSetTorrentDoneScript(fHandle, [filePath UTF8String]);
893            
894            [fDefaults setBool: YES forKey: @"DoneScriptEnabled"];
895            tr_sessionSetTorrentDoneScriptEnabled(fHandle, YES);
896        }
897        [fDoneScriptPopUp selectItemAtIndex: 0];
898    }];
899}
900
901- (void) setUseIncompleteFolder: (id) sender
902{
903    tr_sessionSetIncompleteDirEnabled(fHandle, [fDefaults boolForKey: @"UseIncompleteDownloadFolder"]);
904}
905
906- (void) setRenamePartialFiles: (id) sender
907{
908    tr_sessionSetIncompleteFileNamingEnabled(fHandle, [fDefaults boolForKey: @"RenamePartialFiles"]);
909}
910
911- (void) setDoneScriptEnabled: (id) sender
912{
913    if ([fDefaults boolForKey: @"DoneScriptEnabled"] && ![[NSFileManager defaultManager] fileExistsAtPath: [fDefaults stringForKey:@"DoneScriptPath"]])
914    {
915        // enabled is set but script file doesn't exist, so prompt for one and disable until they pick one
916        [fDefaults setBool: NO forKey: @"DoneScriptEnabled"];
917        [self doneScriptSheetShow: sender];
918    }
919    tr_sessionSetTorrentDoneScriptEnabled(fHandle, [fDefaults boolForKey: @"DoneScriptEnabled"]);
920}
921
922- (void) setAutoImport: (id) sender
923{
924    NSString * path;
925    if ((path = [fDefaults stringForKey: @"AutoImportDirectory"]))
926    {
927        path = [path stringByExpandingTildeInPath];
928        if ([fDefaults boolForKey: @"AutoImport"])
929            [[UKKQueue sharedFileWatcher] addPath: path];
930        else
931            [[UKKQueue sharedFileWatcher] removePathFromQueue: path];
932        
933        [[NSNotificationCenter defaultCenter] postNotificationName: @"AutoImportSettingChange" object: self];
934    }
935    else
936        [self importFolderSheetShow: nil];
937}
938
939- (void) importFolderSheetShow: (id) sender
940{
941    NSOpenPanel * panel = [NSOpenPanel openPanel];
942
943    [panel setPrompt: NSLocalizedString(@"Select", "Preferences -> Open panel prompt")];
944    [panel setAllowsMultipleSelection: NO];
945    [panel setCanChooseFiles: NO];
946    [panel setCanChooseDirectories: YES];
947    [panel setCanCreateDirectories: YES];
948
949    [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
950        NSString * path = [fDefaults stringForKey: @"AutoImportDirectory"];
951        if (result == NSFileHandlingPanelOKButton)
952        {
953            UKKQueue * sharedQueue = [UKKQueue sharedFileWatcher];
954            if (path)
955                [sharedQueue removePathFromQueue: [path stringByExpandingTildeInPath]];
956            
957            path = [[[panel URLs] objectAtIndex: 0] path];
958            [fDefaults setObject: path forKey: @"AutoImportDirectory"];
959            [sharedQueue addPath: [path stringByExpandingTildeInPath]];
960            
961            [[NSNotificationCenter defaultCenter] postNotificationName: @"AutoImportSettingChange" object: self];
962        }
963        else if (!path)
964            [fDefaults setBool: NO forKey: @"AutoImport"];
965        
966        [fImportFolderPopUp selectItemAtIndex: 0];
967    }];
968}
969
970- (void) setAutoSize: (id) sender
971{
972    [[NSNotificationCenter defaultCenter] postNotificationName: @"AutoSizeSettingChange" object: self];
973}
974
975- (void) setRPCEnabled: (id) sender
976{
977    BOOL enable = [fDefaults boolForKey: @"RPC"];
978    tr_sessionSetRPCEnabled(fHandle, enable);
979    
980    [self setRPCWebUIDiscovery: nil];
981}
982
983- (void) linkWebUI: (id) sender
984{
985    NSString * urlString = [NSString stringWithFormat: WEBUI_URL, [fDefaults integerForKey: @"RPCPort"]];
986    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: urlString]];
987}
988
989- (void) setRPCAuthorize: (id) sender
990{
991    tr_sessionSetRPCPasswordEnabled(fHandle, [fDefaults boolForKey: @"RPCAuthorize"]);
992}
993
994- (void) setRPCUsername: (id) sender
995{
996    tr_sessionSetRPCUsername(fHandle, [[fDefaults stringForKey: @"RPCUsername"] UTF8String]);
997}
998
999- (void) setRPCPassword: (id) sender
1000{
1001    [fRPCPassword release];
1002    fRPCPassword = [[sender stringValue] copy];
1003    
1004    const char * password = [[sender stringValue] UTF8String];
1005    [self setKeychainPassword: password forService: RPC_KEYCHAIN_SERVICE username: RPC_KEYCHAIN_NAME];
1006    
1007    tr_sessionSetRPCPassword(fHandle, password);
1008}
1009
1010- (void) updateRPCPassword
1011{
1012    UInt32 passwordLength;
1013    const char * password = nil;
1014    SecKeychainFindGenericPassword(NULL, strlen(RPC_KEYCHAIN_SERVICE), RPC_KEYCHAIN_SERVICE,
1015        strlen(RPC_KEYCHAIN_NAME), RPC_KEYCHAIN_NAME, &passwordLength, (void **)&password, NULL);
1016    
1017    [fRPCPassword release];
1018    if (password != NULL)
1019    {
1020        char fullPassword[passwordLength+1];
1021        strncpy(fullPassword, password, passwordLength);
1022        fullPassword[passwordLength] = '\0';
1023        SecKeychainItemFreeContent(NULL, (void *)password);
1024        
1025        tr_sessionSetRPCPassword(fHandle, fullPassword);
1026        
1027        fRPCPassword = [[NSString alloc] initWithUTF8String: fullPassword];
1028        [fRPCPasswordField setStringValue: fRPCPassword];
1029    }
1030    else
1031        fRPCPassword = nil;
1032}
1033
1034- (void) setRPCPort: (id) sender
1035{
1036    int port = [sender intValue];
1037    [fDefaults setInteger: port forKey: @"RPCPort"];
1038    tr_sessionSetRPCPort(fHandle, port);
1039    
1040    [self setRPCWebUIDiscovery: nil];
1041}
1042
1043- (void) setRPCUseWhitelist: (id) sender
1044{
1045    tr_sessionSetRPCWhitelistEnabled(fHandle, [fDefaults boolForKey: @"RPCUseWhitelist"]);
1046}
1047
1048- (void) setRPCWebUIDiscovery: (id) sender
1049{
1050    if ([fDefaults boolForKey:@"RPC"] && [fDefaults boolForKey: @"RPCWebDiscovery"])
1051        [[BonjourController defaultController] startWithPort: [fDefaults integerForKey: @"RPCPort"]];
1052    else
1053    {
1054        if ([BonjourController defaultControllerExists])
1055            [[BonjourController defaultController] stop];
1056    }
1057}
1058
1059- (void) updateRPCWhitelist
1060{
1061    NSString * string = [fRPCWhitelistArray componentsJoinedByString: @","];
1062    tr_sessionSetRPCWhitelist(fHandle, [string UTF8String]);
1063}
1064
1065- (void) addRemoveRPCIP: (id) sender
1066{
1067    //don't allow add/remove when currently adding - it leads to weird results
1068    if ([fRPCWhitelistTable editedRow] != -1)
1069        return;
1070    
1071    if ([[sender cell] tagForSegment: [sender selectedSegment]] == RPC_IP_REMOVE_TAG)
1072    {
1073        [fRPCWhitelistArray removeObjectsAtIndexes: [fRPCWhitelistTable selectedRowIndexes]];
1074        [fRPCWhitelistTable deselectAll: self];
1075        [fRPCWhitelistTable reloadData];
1076        
1077        [fDefaults setObject: fRPCWhitelistArray forKey: @"RPCWhitelist"];
1078        [self updateRPCWhitelist];
1079    }
1080    else
1081    {
1082        [fRPCWhitelistArray addObject: @""];
1083        [fRPCWhitelistTable reloadData];
1084        
1085        const int row = [fRPCWhitelistArray count] - 1;
1086        [fRPCWhitelistTable selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
1087        [fRPCWhitelistTable editColumn: 0 row: row withEvent: nil select: YES];
1088    }
1089}
1090
1091- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
1092{
1093    return [fRPCWhitelistArray count];
1094}
1095
1096- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
1097{
1098    return [fRPCWhitelistArray objectAtIndex: row];
1099}
1100
1101- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
1102    row: (NSInteger) row
1103{
1104    NSArray * components = [object componentsSeparatedByString: @"."];
1105    NSMutableArray * newComponents = [NSMutableArray arrayWithCapacity: 4];
1106        
1107    //create better-formatted ip string
1108    BOOL valid = false;
1109    if ([components count] == 4)
1110    {
1111        valid = true;
1112        for (NSString * component in components)
1113        {
1114            if ([component isEqualToString: @"*"])
1115                [newComponents addObject: component];
1116            else
1117            {
1118                int num = [component intValue];
1119                if (num >= 0 && num < 256)
1120                    [newComponents addObject: [[NSNumber numberWithInt: num] stringValue]];
1121                else
1122                {
1123                    valid = false;
1124                    break;
1125                }
1126            }
1127        }
1128    }
1129    
1130    NSString * newIP;
1131    if (valid)
1132    {
1133        newIP = [newComponents componentsJoinedByString: @"."];
1134        
1135        //don't allow the same ip address
1136        if ([fRPCWhitelistArray containsObject: newIP] && ![[fRPCWhitelistArray objectAtIndex: row] isEqualToString: newIP])
1137            valid = false;
1138    }
1139    
1140    if (valid)
1141    {
1142        [fRPCWhitelistArray replaceObjectAtIndex: row withObject: newIP];
1143        [fRPCWhitelistArray sortUsingSelector: @selector(compareNumeric:)];
1144    }
1145    else
1146    {
1147        NSBeep();
1148        if ([[fRPCWhitelistArray objectAtIndex: row] isEqualToString: @""])
1149            [fRPCWhitelistArray removeObjectAtIndex: row];
1150    }
1151        
1152    [fRPCWhitelistTable deselectAll: self];
1153    [fRPCWhitelistTable reloadData];
1154    
1155    [fDefaults setObject: fRPCWhitelistArray forKey: @"RPCWhitelist"];
1156    [self updateRPCWhitelist];
1157}
1158
1159- (void) tableViewSelectionDidChange: (NSNotification *) notification
1160{
1161    [fRPCAddRemoveControl setEnabled: [fRPCWhitelistTable numberOfSelectedRows] > 0 forSegment: RPC_IP_REMOVE_TAG];
1162}
1163
1164- (void) helpForScript: (id) sender
1165{
1166    [[NSHelpManager sharedHelpManager] openHelpAnchor: @"script"
1167        inBook: [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"]];
1168}
1169
1170- (void) helpForPeers: (id) sender
1171{
1172    [[NSHelpManager sharedHelpManager] openHelpAnchor: @"peers"
1173        inBook: [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"]];
1174}
1175
1176- (void) helpForNetwork: (id) sender
1177{
1178    [[NSHelpManager sharedHelpManager] openHelpAnchor: @"network"
1179        inBook: [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"]];
1180}
1181
1182- (void) helpForRemote: (id) sender
1183{
1184    [[NSHelpManager sharedHelpManager] openHelpAnchor: @"remote"
1185        inBook: [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"]];
1186}
1187
1188- (void) rpcUpdatePrefs
1189{
1190    //encryption
1191    const tr_encryption_mode encryptionMode = tr_sessionGetEncryption(fHandle);
1192    [fDefaults setBool: encryptionMode != TR_CLEAR_PREFERRED forKey: @"EncryptionPrefer"];
1193    [fDefaults setBool: encryptionMode == TR_ENCRYPTION_REQUIRED forKey: @"EncryptionRequire"];
1194    
1195    //download directory
1196    NSString * downloadLocation = [[NSString stringWithUTF8String: tr_sessionGetDownloadDir(fHandle)] stringByStandardizingPath];
1197    [fDefaults setObject: downloadLocation forKey: @"DownloadFolder"];
1198    
1199    NSString * incompleteLocation = [[NSString stringWithUTF8String: tr_sessionGetIncompleteDir(fHandle)] stringByStandardizingPath];
1200    [fDefaults setObject: incompleteLocation forKey: @"IncompleteDownloadFolder"];
1201    
1202    const BOOL useIncomplete = tr_sessionIsIncompleteDirEnabled(fHandle);
1203    [fDefaults setBool: useIncomplete forKey: @"UseIncompleteDownloadFolder"];
1204    
1205    const BOOL usePartialFileRanaming = tr_sessionIsIncompleteFileNamingEnabled(fHandle);
1206    [fDefaults setBool: usePartialFileRanaming forKey: @"RenamePartialFiles"];
1207    
1208    //utp
1209    const BOOL utp = tr_sessionIsUTPEnabled(fHandle);
1210    [fDefaults setBool: utp forKey: @"UTPGlobal"];
1211    
1212    //peers
1213    const uint16_t peersTotal = tr_sessionGetPeerLimit(fHandle);
1214    [fDefaults setInteger: peersTotal forKey: @"PeersTotal"];
1215    
1216    const uint16_t peersTorrent = tr_sessionGetPeerLimitPerTorrent(fHandle);
1217    [fDefaults setInteger: peersTorrent forKey: @"PeersTorrent"];
1218    
1219    //pex
1220    const BOOL pex = tr_sessionIsPexEnabled(fHandle);
1221    [fDefaults setBool: pex forKey: @"PEXGlobal"];
1222    
1223    //dht
1224    const BOOL dht = tr_sessionIsDHTEnabled(fHandle);
1225    [fDefaults setBool: dht forKey: @"DHTGlobal"];
1226    
1227    //lpd
1228    const BOOL lpd = tr_sessionIsLPDEnabled(fHandle);
1229    [fDefaults setBool: lpd forKey: @"LocalPeerDiscoveryGlobal"];
1230    
1231    //auto start
1232    const BOOL autoStart = !tr_sessionGetPaused(fHandle);
1233    [fDefaults setBool: autoStart forKey: @"AutoStartDownload"];
1234    
1235    //port
1236    const tr_port port = tr_sessionGetPeerPort(fHandle);
1237    [fDefaults setInteger: port forKey: @"BindPort"];
1238    
1239    const BOOL nat = tr_sessionIsPortForwardingEnabled(fHandle);
1240    [fDefaults setBool: nat forKey: @"NatTraversal"];
1241    
1242    fPeerPort = -1;
1243    fNatStatus = -1;
1244    [self updatePortStatus];
1245    
1246    const BOOL randomPort = tr_sessionGetPeerPortRandomOnStart(fHandle);
1247    [fDefaults setBool: randomPort forKey: @"RandomPort"];
1248    
1249    //speed limit - down
1250    const BOOL downLimitEnabled = tr_sessionIsSpeedLimited(fHandle, TR_DOWN);
1251    [fDefaults setBool: downLimitEnabled forKey: @"CheckDownload"];
1252    
1253    const int downLimit = tr_sessionGetSpeedLimit_KBps(fHandle, TR_DOWN);
1254    [fDefaults setInteger: downLimit forKey: @"DownloadLimit"];
1255    
1256    //speed limit - up
1257    const BOOL upLimitEnabled = tr_sessionIsSpeedLimited(fHandle, TR_UP);
1258    [fDefaults setBool: upLimitEnabled forKey: @"CheckUpload"];
1259    
1260    const int upLimit = tr_sessionGetSpeedLimit_KBps(fHandle, TR_UP);
1261    [fDefaults setInteger: upLimit forKey: @"UploadLimit"];
1262    
1263    //alt speed limit enabled
1264    const BOOL useAltSpeed = tr_sessionUsesAltSpeed(fHandle);
1265    [fDefaults setBool: useAltSpeed forKey: @"SpeedLimit"];
1266    
1267    //alt speed limit - down
1268    const int downLimitAlt = tr_sessionGetAltSpeed_KBps(fHandle, TR_DOWN);
1269    [fDefaults setInteger: downLimitAlt forKey: @"SpeedLimitDownloadLimit"];
1270    
1271    //alt speed limit - up
1272    const int upLimitAlt = tr_sessionGetAltSpeed_KBps(fHandle, TR_UP);
1273    [fDefaults setInteger: upLimitAlt forKey: @"SpeedLimitUploadLimit"];
1274    
1275    //alt speed limit schedule
1276    const BOOL useAltSpeedSched = tr_sessionUsesAltSpeedTime(fHandle);
1277    [fDefaults setBool: useAltSpeedSched forKey: @"SpeedLimitAuto"];
1278    
1279    NSDate * limitStartDate = [PrefsController timeSumToDate: tr_sessionGetAltSpeedBegin(fHandle)];
1280    [fDefaults setObject: limitStartDate forKey: @"SpeedLimitAutoOnDate"];
1281    
1282    NSDate * limitEndDate = [PrefsController timeSumToDate: tr_sessionGetAltSpeedEnd(fHandle)];
1283    [fDefaults setObject: limitEndDate forKey: @"SpeedLimitAutoOffDate"];
1284    
1285    const int limitDay = tr_sessionGetAltSpeedDay(fHandle);
1286    [fDefaults setInteger: limitDay forKey: @"SpeedLimitAutoDay"];
1287    
1288    //blocklist
1289    const BOOL blocklist = tr_blocklistIsEnabled(fHandle);
1290    [fDefaults setBool: blocklist forKey: @"BlocklistNew"];
1291    
1292    NSString * blocklistURL = [NSString stringWithUTF8String: tr_blocklistGetURL(fHandle)];
1293    [fDefaults setObject: blocklistURL forKey: @"BlocklistURL"];
1294    
1295    //seed ratio
1296    const BOOL ratioLimited = tr_sessionIsRatioLimited(fHandle);
1297    [fDefaults setBool: ratioLimited forKey: @"RatioCheck"];
1298    
1299    const float ratioLimit = tr_sessionGetRatioLimit(fHandle);
1300    [fDefaults setFloat: ratioLimit forKey: @"RatioLimit"];
1301    
1302    //idle seed limit
1303    const BOOL idleLimited = tr_sessionIsIdleLimited(fHandle);
1304    [fDefaults setBool: idleLimited forKey: @"IdleLimitCheck"];
1305    
1306    const NSUInteger idleLimitMin = tr_sessionGetIdleLimit(fHandle);
1307    [fDefaults setInteger: idleLimitMin forKey: @"IdleLimitMinutes"];
1308    
1309    //queue
1310    const BOOL downloadQueue = tr_sessionGetQueueEnabled(fHandle, TR_DOWN);
1311    [fDefaults setBool: downloadQueue forKey: @"Queue"];
1312    
1313    const int downloadQueueNum = tr_sessionGetQueueSize(fHandle, TR_DOWN);
1314    [fDefaults setInteger: downloadQueueNum forKey: @"QueueDownloadNumber"];
1315    
1316    const BOOL seedQueue = tr_sessionGetQueueEnabled(fHandle, TR_UP);
1317    [fDefaults setBool: seedQueue forKey: @"QueueSeed"];
1318    
1319    const int seedQueueNum = tr_sessionGetQueueSize(fHandle, TR_UP);
1320    [fDefaults setInteger: seedQueueNum forKey: @"QueueSeedNumber"];
1321    
1322    const BOOL checkStalled = tr_sessionGetQueueStalledEnabled(fHandle);
1323    [fDefaults setBool: checkStalled forKey: @"CheckStalled"];
1324    
1325    const int stalledMinutes = tr_sessionGetQueueStalledMinutes(fHandle);
1326    [fDefaults setInteger: stalledMinutes forKey: @"StalledMinutes"];
1327    
1328    //done script
1329    const BOOL doneScriptEnabled = tr_sessionIsTorrentDoneScriptEnabled(fHandle);
1330    [fDefaults setBool: doneScriptEnabled forKey: @"DoneScriptEnabled"];
1331    
1332    NSString * doneScriptPath = [NSString stringWithUTF8String: tr_sessionGetTorrentDoneScript(fHandle)];
1333    [fDefaults setObject: doneScriptPath forKey: @"DoneScriptPath"];
1334    
1335    //update gui if loaded
1336    if (fHasLoaded)
1337    {
1338        //encryption handled by bindings
1339        
1340        //download directory handled by bindings
1341        
1342        //utp handled by bindings
1343        
1344        [fPeersGlobalField setIntValue: peersTotal];
1345        [fPeersTorrentField setIntValue: peersTorrent];
1346        
1347        //pex handled by bindings
1348        
1349        //dht handled by bindings
1350        
1351        //lpd handled by bindings
1352        
1353        [fPortField setIntValue: port];
1354        //port forwarding (nat) handled by bindings
1355        //random port handled by bindings
1356        
1357        //limit check handled by bindings
1358        [fDownloadField setIntValue: downLimit];
1359        
1360        //limit check handled by bindings
1361        [fUploadField setIntValue: upLimit];
1362        
1363        [fSpeedLimitDownloadField setIntValue: downLimitAlt];
1364        
1365        [fSpeedLimitUploadField setIntValue: upLimitAlt];
1366        
1367        //speed limit schedule handled by bindings
1368        
1369        //speed limit schedule times and day handled by bindings
1370        
1371        [fBlocklistURLField setStringValue: blocklistURL];
1372        [self updateBlocklistButton];
1373        [self updateBlocklistFields];
1374        
1375        //ratio limit enabled handled by bindings
1376        [fRatioStopField setFloatValue: ratioLimit];
1377        
1378        //idle limit enabled handled by bindings
1379        [fIdleStopField setIntegerValue: idleLimitMin];
1380        
1381        //queues enabled handled by bindings
1382        [fQueueDownloadField setIntValue: downloadQueueNum];
1383        [fQueueSeedField setIntValue: seedQueueNum];
1384        
1385        //check stalled handled by bindings
1386        [fStalledField setIntValue: stalledMinutes];
1387    }
1388    
1389    [[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
1390    
1391    //reload global settings in inspector
1392    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
1393}
1394
1395@end
1396
1397@implementation PrefsController (Private)
1398
1399- (void) setPrefView: (id) sender
1400{
1401    NSString * identifier;
1402    if (sender)
1403    {
1404        identifier = [sender itemIdentifier];
1405        [[NSUserDefaults standardUserDefaults] setObject: identifier forKey: @"SelectedPrefView"];
1406    }
1407    else
1408        identifier = [[NSUserDefaults standardUserDefaults] stringForKey: @"SelectedPrefView"];
1409    
1410    NSView * view;
1411    if ([identifier isEqualToString: TOOLBAR_TRANSFERS])
1412        view = fTransfersView;
1413    else if ([identifier isEqualToString: TOOLBAR_GROUPS])
1414        view = fGroupsView;
1415    else if ([identifier isEqualToString: TOOLBAR_BANDWIDTH])
1416        view = fBandwidthView;
1417    else if ([identifier isEqualToString: TOOLBAR_PEERS])
1418        view = fPeersView;
1419    else if ([identifier isEqualToString: TOOLBAR_NETWORK])
1420        view = fNetworkView;
1421    else if ([identifier isEqualToString: TOOLBAR_REMOTE])
1422        view = fRemoteView;
1423    else
1424    {
1425        identifier = TOOLBAR_GENERAL; //general view is the default selected
1426        view = fGeneralView;
1427    }
1428    
1429    [[[self window] toolbar] setSelectedItemIdentifier: identifier];
1430    
1431    NSWindow * window = [self window];
1432    if ([window contentView] == view)
1433        return;
1434    
1435    NSRect windowRect = [window frame];
1436    const CGFloat difference = (NSHeight([view frame]) - NSHeight([[window contentView] frame])) * [window userSpaceScaleFactor];
1437    windowRect.origin.y -= difference;
1438    windowRect.size.height += difference;
1439    
1440    [view setHidden: YES];
1441    [window setContentView: view];
1442    [window setFrame: windowRect display: YES animate: YES];
1443    [view setHidden: NO];
1444    
1445    //set title label
1446    if (sender)
1447        [window setTitle: [sender label]];
1448    else
1449    {
1450        NSToolbar * toolbar = [window toolbar];
1451        NSString * itemIdentifier = [toolbar selectedItemIdentifier];
1452        for (NSToolbarItem * item in [toolbar items])
1453            if ([[item itemIdentifier] isEqualToString: itemIdentifier])
1454            {
1455                [window setTitle: [item label]];
1456                break;
1457            }
1458    }
1459}
1460
1461- (void) updateGrowlButton
1462{
1463    if ([GrowlApplicationBridge isGrowlRunning])
1464    {
1465        [fBuiltInGrowlButton setHidden: YES];
1466        [fGrowlAppButton setHidden: NO];
1467        
1468#warning remove NO
1469        [fGrowlAppButton setEnabled: NO && [GrowlApplicationBridge isGrowlURLSchemeAvailable]];
1470        [fGrowlAppButton setTitle: NSLocalizedString(@"Configure In Growl", "Prefs -> Notifications")];
1471        [fGrowlAppButton sizeToFit];
1472        
1473        [fGrowlAppButton setTarget: self];
1474        [fGrowlAppButton setAction: @selector(openGrowlApp:)];
1475    }
1476    else if ([NSApp isOnMountainLionOrBetter])
1477    {
1478        [fBuiltInGrowlButton setHidden: YES];
1479        [fGrowlAppButton setHidden: NO];
1480        
1481        [fGrowlAppButton setEnabled: YES];
1482        [fGrowlAppButton setTitle: NSLocalizedString(@"Configure In System Preferences", "Prefs -> Notifications")];
1483        [fGrowlAppButton sizeToFit];
1484        
1485        [fGrowlAppButton setTarget: self];
1486        [fGrowlAppButton setAction: @selector(openNotificationSystemPrefs:)];
1487    }
1488    else
1489    {
1490        [fBuiltInGrowlButton setHidden: NO];
1491        [fGrowlAppButton setHidden: YES];
1492        
1493        [fBuiltInGrowlButton setState: [fDefaults boolForKey: @"DisplayNotifications"]];
1494    }
1495}
1496
1497- (void) setKeychainPassword: (const char *) password forService: (const char *) service username: (const char *) username
1498{
1499    SecKeychainItemRef item = NULL;
1500    NSUInteger passwordLength = strlen(password);
1501    
1502    OSStatus result = SecKeychainFindGenericPassword(NULL, strlen(service), service, strlen(username), username, NULL, NULL, &item);
1503    if (result == noErr && item)
1504    {
1505        if (passwordLength > 0) //found, so update
1506        {
1507            result = SecKeychainItemModifyAttributesAndData(item, NULL, passwordLength, (const void *)password);
1508            if (result != noErr)
1509                NSLog(@"Problem updating Keychain item: %s", GetMacOSStatusErrorString(result));
1510        }
1511        else //remove the item
1512        {
1513            result = SecKeychainItemDelete(item);
1514            if (result != noErr)
1515                NSLog(@"Problem removing Keychain item: %s", GetMacOSStatusErrorString(result));
1516        }
1517    }
1518    else if (result == errSecItemNotFound) //not found, so add
1519    {
1520        if (passwordLength > 0)
1521        {
1522            result = SecKeychainAddGenericPassword(NULL, strlen(service), service, strlen(username), username,
1523                        passwordLength, (const void *)password, NULL);
1524            if (result != noErr)
1525                NSLog(@"Problem adding Keychain item: %s", GetMacOSStatusErrorString(result));
1526        }
1527    }
1528    else
1529        NSLog(@"Problem accessing Keychain: %s", GetMacOSStatusErrorString(result));
1530}
1531
1532@end
1533