source: trunk/macosx/PrefsController.m @ 6496

Last change on this file since 6496 was 6496, checked in by charles, 13 years ago

readability improvments #1, #2, #3, #4. (muks)

  • Property svn:keywords set to Date Rev Author Id
File size: 40.9 KB
Line 
1/******************************************************************************
2 * $Id: PrefsController.m 6496 2008-08-12 13:51:11Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "PrefsController.h"
26#import "BlocklistDownloader.h"
27#import "NSApplicationAdditions.h"
28#import "NSStringAdditions.h"
29#import "UKKQueue.h"
30
31#define DOWNLOAD_FOLDER     0
32#define DOWNLOAD_TORRENT    2
33
34#define PROXY_HTTP      0
35#define PROXY_SOCKS4    1
36#define PROXY_SOCKS5    2
37
38#define RPC_ACCESS_ALLOW    0
39#define RPC_ACCESS_DENY     1
40
41#define RPC_IP_ADD_TAG      0
42#define RPC_IP_REMOVE_TAG   1
43
44#define UPDATE_SECONDS 86400
45
46#define TOOLBAR_GENERAL     @"TOOLBAR_GENERAL"
47#define TOOLBAR_TRANSFERS   @"TOOLBAR_TRANSFERS"
48#define TOOLBAR_BANDWIDTH   @"TOOLBAR_BANDWIDTH"
49#define TOOLBAR_PEERS       @"TOOLBAR_PEERS"
50#define TOOLBAR_NETWORK     @"TOOLBAR_NETWORK"
51#define TOOLBAR_REMOTE      @"TOOLBAR_REMOTE"
52
53#define PROXY_KEYCHAIN_SERVICE  "Transmission:Proxy"
54#define PROXY_KEYCHAIN_NAME     "Proxy"
55
56#define RPC_KEYCHAIN_SERVICE    "Transmission:Remote"
57#define RPC_KEYCHAIN_NAME       "Remote"
58
59#define WEBUI_URL   @"http://localhost:%d/transmission/web/"
60
61@interface PrefsController (Private)
62
63- (void) setPrefView: (id) sender;
64
65- (void) folderSheetClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) info;
66- (void) incompleteFolderSheetClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) info;
67- (void) importFolderSheetClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) info;
68
69- (void) setKeychainPassword: (const char *) password forService: (const char *) service username: (const char *) username;
70
71@end
72
73@implementation PrefsController
74
75- (id) initWithHandle: (tr_handle *) handle
76{
77    if ((self = [super initWithWindowNibName: @"PrefsWindow"]))
78    {
79        fDefaults = [NSUserDefaults standardUserDefaults];
80        fHandle = handle;
81       
82        //checks for old version speeds of -1
83        if ([fDefaults integerForKey: @"UploadLimit"] < 0)
84        {
85            [fDefaults setInteger: 20 forKey: @"UploadLimit"];
86            [fDefaults setBool: NO forKey: @"CheckUpload"];
87        }
88        if ([fDefaults integerForKey: @"DownloadLimit"] < 0)
89        {
90            [fDefaults setInteger: 20 forKey: @"DownloadLimit"];
91            [fDefaults setBool: NO forKey: @"CheckDownload"];
92        }
93       
94        //check for old version download location (before 1.1)
95        NSString * choice;
96        if ((choice = [fDefaults stringForKey: @"DownloadChoice"]))
97        {
98            [fDefaults setBool: [choice isEqualToString: @"Constant"] forKey: @"DownloadLocationConstant"];
99            [fDefaults setBool: YES forKey: @"DownloadAsk"];
100           
101            [fDefaults removeObjectForKey: @"DownloadChoice"];
102        }
103       
104        //set check for update to right value
105        [self setCheckForUpdate: nil];
106       
107        //set auto import
108        NSString * autoPath;
109        if ([fDefaults boolForKey: @"AutoImport"] && (autoPath = [fDefaults stringForKey: @"AutoImportDirectory"]))
110            [[UKKQueue sharedFileWatcher] addPath: [autoPath stringByExpandingTildeInPath]];
111       
112        //set encryption
113        [self setEncryptionMode: nil];
114       
115        //actually set bandwidth limits
116        [self applySpeedSettings: nil];
117       
118        //set proxy type
119        [self updateProxyType];
120        [self updateProxyPassword];
121       
122        //update rpc access list
123        [self updateRPCPassword];
124       
125        fRPCAccessArray = [[fDefaults arrayForKey: @"RPCAccessList"] mutableCopy];
126        if (!fRPCAccessArray)
127            fRPCAccessArray = [[NSMutableArray arrayWithObject: [NSDictionary dictionaryWithObjectsAndKeys: @"127.0.0.1", @"IP",
128                                [NSNumber numberWithBool: YES], @"Allow", nil]] retain];
129        [self updateRPCAccessList];
130    }
131   
132    return self;
133}
134
135- (tr_handle *) handle
136{
137    return fHandle;
138}
139
140- (void) dealloc
141{
142    [fPortStatusTimer invalidate];
143    if (fPortChecker)
144    {
145        [fPortChecker cancelProbe];
146        [fPortChecker release];
147    }
148   
149    [fRPCAccessArray release];
150   
151    [super dealloc];
152}
153
154- (void) awakeFromNib
155{
156    fHasLoaded = YES;
157   
158    NSToolbar * toolbar = [[NSToolbar alloc] initWithIdentifier: @"Preferences Toolbar"];
159    [toolbar setDelegate: self];
160    [toolbar setAllowsUserCustomization: NO];
161    [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
162    [toolbar setSizeMode: NSToolbarSizeModeRegular];
163    [toolbar setSelectedItemIdentifier: TOOLBAR_GENERAL];
164    [[self window] setToolbar: toolbar];
165    [toolbar release];
166   
167    [self setPrefView: nil];
168   
169    if (![NSApp isOnLeopardOrBetter])
170    {
171        [fRPCAddRemoveControl sizeToFit];
172        [fRPCAddRemoveControl setLabel: @"+" forSegment: RPC_IP_ADD_TAG];
173        [fRPCAddRemoveControl setLabel: @"-" forSegment: RPC_IP_REMOVE_TAG];
174    }
175   
176    //set download folder
177    [fFolderPopUp selectItemAtIndex: [fDefaults boolForKey: @"DownloadLocationConstant"] ? DOWNLOAD_FOLDER : DOWNLOAD_TORRENT];
178   
179    //set stop ratio
180    [self updateRatioStopField];
181   
182    //set limits
183    [self updateLimitFields];
184   
185    //set speed limit
186    [fSpeedLimitUploadField setIntValue: [fDefaults integerForKey: @"SpeedLimitUploadLimit"]];
187    [fSpeedLimitDownloadField setIntValue: [fDefaults integerForKey: @"SpeedLimitDownloadLimit"]];
188   
189    //set port
190    [fPortField setIntValue: [fDefaults integerForKey: @"BindPort"]];
191    fNatStatus = -1;
192   
193    [self updatePortStatus];
194    fPortStatusTimer = [NSTimer scheduledTimerWithTimeInterval: 5.0 target: self
195                        selector: @selector(updatePortStatus) userInfo: nil repeats: YES];
196   
197    //set peer connections
198    [fPeersGlobalField setIntValue: [fDefaults integerForKey: @"PeersTotal"]];
199    [fPeersTorrentField setIntValue: [fDefaults integerForKey: @"PeersTorrent"]];
200   
201    //set queue values
202    [fQueueDownloadField setIntValue: [fDefaults integerForKey: @"QueueDownloadNumber"]];
203    [fQueueSeedField setIntValue: [fDefaults integerForKey: @"QueueSeedNumber"]];
204    [fStalledField setIntValue: [fDefaults integerForKey: @"StalledMinutes"]];
205   
206    //set proxy type
207    [fProxyAddressField setStringValue: [fDefaults stringForKey: @"ProxyAddress"]];
208    int proxyType;
209    switch(tr_sessionGetProxyType(fHandle))
210    {
211        case TR_PROXY_SOCKS4:
212            proxyType = PROXY_SOCKS4;
213            break;
214        case TR_PROXY_SOCKS5:
215            proxyType = PROXY_SOCKS5;
216            break;
217        case TR_PROXY_HTTP:
218            proxyType = PROXY_HTTP;
219    }
220    [fProxyTypePopUp selectItemAtIndex: proxyType];
221   
222    //set proxy password - does NOT need to be released
223    [fProxyPasswordField setStringValue: [NSString stringWithUTF8String: tr_sessionGetProxyPassword(fHandle)]];
224   
225    //set proxy port
226    [fProxyPortField setIntValue: [fDefaults integerForKey: @"ProxyPort"]];
227   
228    //set blocklist
229    [self updateBlocklistFields];
230   
231    //set rpc port
232    [fRPCPortField setIntValue: [fDefaults integerForKey: @"RPCPort"]];
233   
234    //set rpc password - has to be released
235    const char * rpcPassword = tr_sessionGetRPCPassword(fHandle);
236    [fRPCPasswordField setStringValue: [NSString stringWithUTF8String: rpcPassword]];
237    tr_free(rpcPassword);
238}
239
240- (void) setUpdater: (SUUpdater *) updater
241{
242    fUpdater = updater;
243}
244
245- (NSToolbarItem *) toolbar: (NSToolbar *) toolbar itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
246{
247    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
248
249    if ([ident isEqualToString: TOOLBAR_GENERAL])
250    {
251        [item setLabel: NSLocalizedString(@"General", "Preferences -> toolbar item title")];
252        [item setImage: [NSImage imageNamed: [NSApp isOnLeopardOrBetter] ? NSImageNamePreferencesGeneral : @"Preferences.png"]];
253        [item setTarget: self];
254        [item setAction: @selector(setPrefView:)];
255        [item setAutovalidates: NO];
256    }
257    else if ([ident isEqualToString: TOOLBAR_TRANSFERS])
258    {
259        [item setLabel: NSLocalizedString(@"Transfers", "Preferences -> toolbar item title")];
260        [item setImage: [NSImage imageNamed: @"Transfers.png"]];
261        [item setTarget: self];
262        [item setAction: @selector(setPrefView:)];
263        [item setAutovalidates: NO];
264    }
265    else if ([ident isEqualToString: TOOLBAR_BANDWIDTH])
266    {
267        [item setLabel: NSLocalizedString(@"Bandwidth", "Preferences -> toolbar item title")];
268        [item setImage: [NSImage imageNamed: @"Bandwidth.png"]];
269        [item setTarget: self];
270        [item setAction: @selector(setPrefView:)];
271        [item setAutovalidates: NO];
272    }
273    else if ([ident isEqualToString: TOOLBAR_PEERS])
274    {
275        [item setLabel: NSLocalizedString(@"Peers", "Preferences -> toolbar item title")];
276        [item setImage: [NSImage imageNamed: [NSApp isOnLeopardOrBetter] ? NSImageNameUserGroup : @"Peers.png"]];
277        [item setTarget: self];
278        [item setAction: @selector(setPrefView:)];
279        [item setAutovalidates: NO];
280    }
281    else if ([ident isEqualToString: TOOLBAR_NETWORK])
282    {
283        [item setLabel: NSLocalizedString(@"Network", "Preferences -> toolbar item title")];
284        [item setImage: [NSImage imageNamed: [NSApp isOnLeopardOrBetter] ? NSImageNameNetwork : @"Network.png"]];
285        [item setTarget: self];
286        [item setAction: @selector(setPrefView:)];
287        [item setAutovalidates: NO];
288    }
289    else if ([ident isEqualToString: TOOLBAR_REMOTE])
290    {
291        [item setLabel: NSLocalizedString(@"Remote", "Preferences -> toolbar item title")];
292        [item setImage: [NSImage imageNamed: @"Remote.png"]];
293        [item setTarget: self];
294        [item setAction: @selector(setPrefView:)];
295        [item setAutovalidates: NO];
296    }
297    else
298    {
299        [item release];
300        return nil;
301    }
302
303    return [item autorelease];
304}
305
306- (NSArray *) toolbarSelectableItemIdentifiers: (NSToolbar *) toolbar
307{
308    return [self toolbarDefaultItemIdentifiers: toolbar];
309}
310
311- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
312{
313    return [self toolbarAllowedItemIdentifiers: toolbar];
314}
315
316- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
317{
318    return [NSArray arrayWithObjects: TOOLBAR_GENERAL, TOOLBAR_TRANSFERS, TOOLBAR_BANDWIDTH,
319                                        TOOLBAR_PEERS, TOOLBAR_NETWORK, TOOLBAR_REMOTE, nil];
320}
321
322//used by ipc
323- (void) updatePortField
324{
325    [fPortField setIntValue: [fDefaults integerForKey: @"BindPort"]];
326}
327
328- (void) setPort: (id) sender
329{
330    int port = [sender intValue];
331    [fDefaults setInteger: port forKey: @"BindPort"];
332    tr_sessionSetPeerPort(fHandle, port);
333   
334    fPeerPort = -1;
335    [self updatePortStatus];
336}
337
338- (void) setNat: (id) sender
339{
340    tr_sessionSetPortForwardingEnabled(fHandle, [fDefaults boolForKey: @"NatTraversal"]);
341   
342    fNatStatus = -1;
343    [self updatePortStatus];
344}
345
346- (void) updatePortStatus
347{
348    const tr_port_forwarding fwd = tr_sessionGetPortForwarding(fHandle);
349    const int port = tr_sessionGetPeerPort(fHandle);
350
351    if (fNatStatus != fwd || fPeerPort != port )
352    {
353        fNatStatus = fwd;
354        fPeerPort = port;
355       
356        [fPortStatusField setStringValue: @""];
357        [fPortStatusImage setImage: nil];
358        [fPortStatusProgress startAnimation: self];
359       
360        if (fPortChecker)
361        {
362            [fPortChecker cancelProbe];
363            [fPortChecker release];
364        }
365        fPortChecker = [[PortChecker alloc] initForPort: fPeerPort withDelegate: self];
366    }
367}
368
369- (void) portCheckerDidFinishProbing: (PortChecker *) portChecker
370{
371    [fPortStatusProgress stopAnimation: self];
372    switch ([fPortChecker status])
373    {
374        case PORT_STATUS_OPEN:
375            [fPortStatusField setStringValue: NSLocalizedString(@"Port is open", "Preferences -> Network -> port status")];
376            [fPortStatusImage setImage: [NSImage imageNamed: @"GreenDot.png"]];
377            break;
378        case PORT_STATUS_CLOSED:
379            [fPortStatusField setStringValue: NSLocalizedString(@"Port is closed", "Preferences -> Network -> port status")];
380            [fPortStatusImage setImage: [NSImage imageNamed: @"RedDot.png"]];
381            break;
382        case PORT_STATUS_ERROR:
383            [fPortStatusField setStringValue: NSLocalizedString(@"Port check website is down", "Preferences -> Network -> port status")];
384            [fPortStatusImage setImage: [NSImage imageNamed: @"YellowDot.png"]];
385            break;
386    }
387    [fPortChecker release];
388    fPortChecker = nil;
389}
390
391- (NSArray *) sounds
392{
393    NSMutableArray * sounds = [NSMutableArray array];
394   
395    NSMutableArray * directories = [NSMutableArray arrayWithObjects: @"/System/Library/Sounds", @"/Library/Sounds", nil];
396    if ([NSApp isOnLeopardOrBetter])
397        [directories addObject: [NSHomeDirectory() stringByAppendingPathComponent: @"Library/Sounds"]];
398   
399    BOOL isDirectory;
400    NSString * directory;
401    NSEnumerator * enumerator = [directories objectEnumerator];
402    while ((directory = [enumerator nextObject]))
403        if ([[NSFileManager defaultManager] fileExistsAtPath: directory isDirectory: &isDirectory] && isDirectory)
404        {
405            NSString * sound;
406            NSEnumerator * soundEnumerator = [[[NSFileManager defaultManager] directoryContentsAtPath: directory] objectEnumerator];
407            while ((sound = [soundEnumerator nextObject]))
408            {
409                sound = [sound stringByDeletingPathExtension];
410                if ([NSSound soundNamed: sound])
411                    [sounds addObject: sound];
412            }
413        }
414   
415    return sounds;
416}
417
418- (void) setSound: (id) sender
419{
420    //play sound when selecting
421    NSSound * sound;
422    if ((sound = [NSSound soundNamed: [sender titleOfSelectedItem]]))
423        [sound play];
424}
425
426- (void) setPeersGlobal: (id) sender
427{
428    int count = [sender intValue];
429    [fDefaults setInteger: count forKey: @"PeersTotal"];
430    tr_sessionSetPeerLimit(fHandle, count);
431}
432
433- (void) setPeersTorrent: (id) sender
434{
435    int count = [sender intValue];
436    [fDefaults setInteger: count forKey: @"PeersTorrent"];
437}
438
439- (void) setPEX: (id) sender
440{
441    tr_sessionSetPexEnabled(fHandle, [fDefaults boolForKey: @"PEXGlobal"]);
442}
443
444- (void) setEncryptionMode: (id) sender
445{
446    tr_sessionSetEncryption(fHandle, [fDefaults boolForKey: @"EncryptionPrefer"] ?
447        ([fDefaults boolForKey: @"EncryptionRequire"] ? TR_ENCRYPTION_REQUIRED : TR_ENCRYPTION_PREFERRED) : TR_CLEAR_PREFERRED);
448}
449
450- (void) setBlocklistEnabled: (id) sender
451{
452    BOOL enable = [sender state] == NSOnState;
453    [fDefaults setBool: enable forKey: @"Blocklist"];
454    tr_blocklistSetEnabled(fHandle, enable);
455}
456
457- (void) updateBlocklist: (id) sender
458{
459    [BlocklistDownloader downloadWithPrefsController: self];
460}
461
462- (void) updateBlocklistFields
463{
464    BOOL exists = tr_blocklistExists(fHandle);
465   
466    if (exists)
467    {
468        NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init];
469        [numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
470        [numberFormatter setMaximumFractionDigits: 0];
471        NSString * countString = [numberFormatter stringFromNumber: [NSNumber numberWithInt: tr_blocklistGetRuleCount(fHandle)]];
472        [numberFormatter release];
473       
474        [fBlocklistMessageField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ IP address rules in list",
475            "Prefs -> blocklist -> message"), countString]];
476    }
477    else
478        [fBlocklistMessageField setStringValue: NSLocalizedString(@"A blocklist must first be downloaded",
479            "Prefs -> blocklist -> message")];
480   
481    [fBlocklistEnableCheck setEnabled: exists];
482    [fBlocklistEnableCheck setState: exists && [fDefaults boolForKey: @"Blocklist"]];
483}
484
485- (void) applySpeedSettings: (id) sender
486{
487    if ([fDefaults boolForKey: @"SpeedLimit"])
488    {
489        tr_sessionSetSpeedLimitEnabled(fHandle, TR_UP, 1);
490        tr_sessionSetSpeedLimit(fHandle, TR_UP, [fDefaults integerForKey: @"SpeedLimitUploadLimit"]);
491       
492        tr_sessionSetSpeedLimitEnabled(fHandle, TR_DOWN, 1);
493        tr_sessionSetSpeedLimit(fHandle, TR_DOWN, [fDefaults integerForKey: @"SpeedLimitDownloadLimit"]);
494    }
495    else
496    {
497        tr_sessionSetSpeedLimitEnabled(fHandle, TR_UP, [fDefaults boolForKey: @"CheckUpload"]);
498        tr_sessionSetSpeedLimit(fHandle, TR_UP, [fDefaults integerForKey: @"UploadLimit"]);
499       
500        tr_sessionSetSpeedLimitEnabled(fHandle, TR_DOWN, [fDefaults boolForKey: @"CheckDownload"]);
501        tr_sessionSetSpeedLimit(fHandle, TR_DOWN, [fDefaults integerForKey: @"DownloadLimit"]);
502    }
503}
504
505- (void) applyRatioSetting: (id) sender
506{
507    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
508}
509
510- (void) updateRatioStopField
511{
512    if (!fHasLoaded)
513        return;
514   
515    [fRatioStopField setFloatValue: [fDefaults floatForKey: @"RatioLimit"]];
516   
517    [self applyRatioSetting: nil];
518}
519
520- (void) setRatioStop: (id) sender
521{
522    [fDefaults setFloat: [sender floatValue] forKey: @"RatioLimit"];
523    [self applyRatioSetting: nil];
524}
525
526- (void) updateLimitFields
527{
528    if (!fHasLoaded)
529        return;
530   
531    [fUploadField setIntValue: [fDefaults integerForKey: @"UploadLimit"]];
532    [fDownloadField setIntValue: [fDefaults integerForKey: @"DownloadLimit"]];
533}
534
535- (void) setGlobalLimit: (id) sender
536{
537    [fDefaults setInteger: [sender intValue] forKey: sender == fUploadField ? @"UploadLimit" : @"DownloadLimit"];
538    [self applySpeedSettings: self];
539}
540
541- (void) setSpeedLimit: (id) sender
542{
543    [fDefaults setInteger: [sender intValue] forKey: sender == fSpeedLimitUploadField
544                                                        ? @"SpeedLimitUploadLimit" : @"SpeedLimitDownloadLimit"];
545    [self applySpeedSettings: self];
546}
547
548- (void) setAutoSpeedLimit: (id) sender
549{
550    [[NSNotificationCenter defaultCenter] postNotificationName: @"AutoSpeedLimitChange" object: self];
551}
552
553- (BOOL) control: (NSControl *) control textShouldBeginEditing: (NSText *) fieldEditor
554{
555    [fInitialString release];
556    fInitialString = [[control stringValue] retain];
557   
558    return YES;
559}
560
561- (BOOL) control: (NSControl *) control didFailToFormatString: (NSString *) string errorDescription: (NSString *) error
562{
563    NSBeep();
564    if (fInitialString)
565    {
566        [control setStringValue: fInitialString];
567        [fInitialString release];
568        fInitialString = nil;
569    }
570    return NO;
571}
572
573- (void) setBadge: (id) sender
574{
575    [[NSNotificationCenter defaultCenter] postNotificationName: @"DockBadgeChange" object: self];
576}
577
578- (void) resetWarnings: (id) sender
579{
580    [fDefaults setBool: YES forKey: @"WarningDuplicate"];
581    [fDefaults setBool: YES forKey: @"WarningRemainingSpace"];
582    [fDefaults setBool: YES forKey: @"WarningFolderDataSameName"];
583    [fDefaults setBool: YES forKey: @"WarningResetStats"];
584    [fDefaults setBool: YES forKey: @"WarningCreatorBlankAddress"];
585    [fDefaults setBool: YES forKey: @"WarningRemoveBuiltInTracker"];
586    [fDefaults setBool: YES forKey: @"WarningInvalidOpen"];
587}
588
589- (void) setCheckForUpdate: (id) sender
590{
591    NSTimeInterval seconds = [fDefaults boolForKey: @"CheckForUpdates"] ? UPDATE_SECONDS : 0;
592    [fDefaults setInteger: seconds forKey: @"SUScheduledCheckInterval"];
593    if (fUpdater)
594        [fUpdater scheduleCheckWithInterval: seconds];
595}
596
597- (void) setQueue: (id) sender
598{
599    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
600}
601
602- (void) setQueueNumber: (id) sender
603{
604    [fDefaults setInteger: [sender intValue] forKey: sender == fQueueDownloadField ? @"QueueDownloadNumber" : @"QueueSeedNumber"];
605    [self setQueue: nil];
606}
607
608- (void) setStalled: (id) sender
609{
610    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
611}
612
613- (void) setStalledMinutes: (id) sender
614{
615    [fDefaults setInteger: [sender intValue] forKey: @"StalledMinutes"];
616    [self setStalled: nil];
617}
618
619- (void) setDownloadLocation: (id) sender
620{
621    [fDefaults setBool: [fFolderPopUp indexOfSelectedItem] == DOWNLOAD_FOLDER forKey: @"DownloadLocationConstant"];
622}
623
624- (void) folderSheetShow: (id) sender
625{
626    NSOpenPanel * panel = [NSOpenPanel openPanel];
627
628    [panel setPrompt: NSLocalizedString(@"Select", "Preferences -> Open panel prompt")];
629    [panel setAllowsMultipleSelection: NO];
630    [panel setCanChooseFiles: NO];
631    [panel setCanChooseDirectories: YES];
632    [panel setCanCreateDirectories: YES];
633
634    [panel beginSheetForDirectory: nil file: nil types: nil
635        modalForWindow: [self window] modalDelegate: self didEndSelector:
636        @selector(folderSheetClosed:returnCode:contextInfo:) contextInfo: nil];
637}
638
639- (void) incompleteFolderSheetShow: (id) sender
640{
641    NSOpenPanel * panel = [NSOpenPanel openPanel];
642
643    [panel setPrompt: NSLocalizedString(@"Select", "Preferences -> Open panel prompt")];
644    [panel setAllowsMultipleSelection: NO];
645    [panel setCanChooseFiles: NO];
646    [panel setCanChooseDirectories: YES];
647    [panel setCanCreateDirectories: YES];
648
649    [panel beginSheetForDirectory: nil file: nil types: nil
650        modalForWindow: [self window] modalDelegate: self didEndSelector:
651        @selector(incompleteFolderSheetClosed:returnCode:contextInfo:) contextInfo: nil];
652}
653
654- (void) setAutoImport: (id) sender
655{
656    NSString * path;
657    if ((path = [fDefaults stringForKey: @"AutoImportDirectory"]))
658    {
659        path = [path stringByExpandingTildeInPath];
660        if ([fDefaults boolForKey: @"AutoImport"])
661            [[UKKQueue sharedFileWatcher] addPath: path];
662        else
663            [[UKKQueue sharedFileWatcher] removePathFromQueue: path];
664       
665        [[NSNotificationCenter defaultCenter] postNotificationName: @"AutoImportSettingChange" object: self];
666    }
667    else
668        [self importFolderSheetShow: nil];
669}
670
671- (void) importFolderSheetShow: (id) sender
672{
673    NSOpenPanel * panel = [NSOpenPanel openPanel];
674
675    [panel setPrompt: NSLocalizedString(@"Select", "Preferences -> Open panel prompt")];
676    [panel setAllowsMultipleSelection: NO];
677    [panel setCanChooseFiles: NO];
678    [panel setCanChooseDirectories: YES];
679    [panel setCanCreateDirectories: YES];
680
681    [panel beginSheetForDirectory: nil file: nil types: nil
682        modalForWindow: [self window] modalDelegate: self didEndSelector:
683        @selector(importFolderSheetClosed:returnCode:contextInfo:) contextInfo: nil];
684}
685
686- (void) setAutoSize: (id) sender
687{
688    [[NSNotificationCenter defaultCenter] postNotificationName: @"AutoSizeSettingChange" object: self];
689}
690
691- (void) setProxyEnabled: (id) sender
692{
693    tr_sessionSetProxyEnabled(fHandle, [fDefaults boolForKey: @"Proxy"]);
694}
695
696- (void) setProxyAddress: (id) sender
697{
698    NSString * address = [sender stringValue];
699    BOOL blank = [address isEqualToString: @""];
700   
701    if (!blank && [address rangeOfString: @"://"].location == NSNotFound)
702        address = [@"http://" stringByAppendingString: address];
703   
704    if (blank || tr_httpIsValidURL([address UTF8String]))
705    {
706        tr_sessionSetProxy(fHandle, [address UTF8String]);
707        [sender setStringValue: address];
708        [fDefaults setObject: address forKey: @"ProxyAddress"];
709    }
710    else
711    {
712        NSBeep();
713        [sender setStringValue: [fDefaults stringForKey: @"ProxyAddress"]];
714    }
715}
716
717- (void) setProxyPort: (id) sender
718{
719    int port = [sender intValue];
720    [fDefaults setInteger: port forKey: @"ProxyPort"];
721    tr_sessionSetProxyPort(fHandle, port);
722}
723
724- (void) setProxyType: (id) sender
725{
726    NSString * type;
727    switch ([sender indexOfSelectedItem])
728    {
729        case PROXY_HTTP:
730            type = @"HTTP";
731            break;
732        case PROXY_SOCKS4:
733            type = @"SOCKS4";
734            break;
735        case PROXY_SOCKS5:
736            type = @"SOCKS5";
737    }
738   
739    [fDefaults setObject: type forKey: @"ProxyType"];
740    [self updateProxyType];
741}
742
743- (void) updateProxyType
744{
745    NSString * typeString = [fDefaults stringForKey: @"ProxyType"];
746    tr_proxy_type type;
747    if ([typeString isEqualToString: @"SOCKS4"])
748        type = TR_PROXY_SOCKS4;
749    else if ([typeString isEqualToString: @"SOCKS5"])
750        type = TR_PROXY_SOCKS5;
751    else
752    {
753        //safety
754        if (![typeString isEqualToString: @"HTTP"])
755        {
756            typeString = @"HTTP";
757            [fDefaults setObject: typeString forKey: @"ProxyType"];
758        }
759        type = TR_PROXY_HTTP;
760    }
761   
762    tr_sessionSetProxyType(fHandle, type);
763}
764
765- (void) setProxyAuthorize: (id) sender
766{
767    BOOL enable = [fDefaults boolForKey: @"ProxyAuthorize"];
768    tr_sessionSetProxyAuthEnabled(fHandle, enable);
769}
770
771- (void) setProxyUsername: (id) sender
772{
773    tr_sessionSetProxyUsername(fHandle, [[fDefaults stringForKey: @"ProxyUsername"] UTF8String]);
774}
775
776- (void) setProxyPassword: (id) sender
777{
778    const char * password = [[sender stringValue] UTF8String];
779    [self setKeychainPassword: password forService: PROXY_KEYCHAIN_SERVICE username: PROXY_KEYCHAIN_NAME];
780   
781    tr_sessionSetProxyPassword(fHandle, password);
782}
783
784- (void) updateProxyPassword
785{
786    UInt32 passwordLength;
787    const char * password = nil;
788    SecKeychainFindGenericPassword(NULL, strlen(PROXY_KEYCHAIN_SERVICE), PROXY_KEYCHAIN_SERVICE,
789        strlen(PROXY_KEYCHAIN_NAME), PROXY_KEYCHAIN_NAME, &passwordLength, (void **)&password, NULL);
790   
791    if (password != NULL)
792    {
793        char fullPassword[passwordLength+1];
794        strncpy(fullPassword, password, passwordLength);
795        fullPassword[passwordLength] = '\0';
796        SecKeychainItemFreeContent(NULL, (void *)password);
797       
798        tr_sessionSetProxyPassword(fHandle, fullPassword);
799        [fProxyPasswordField setStringValue: [NSString stringWithUTF8String: fullPassword]];
800    }
801}
802
803- (void) setRPCEnabled: (id) sender
804{
805    tr_sessionSetRPCEnabled(fHandle, [fDefaults boolForKey: @"RPC"]);
806}
807
808- (void) linkWebUI: (id) sender
809{
810    NSString * urlString = [NSString stringWithFormat: WEBUI_URL, [fDefaults integerForKey: @"RPCPort"]];
811    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: urlString]];
812}
813
814- (void) setRPCAuthorize: (id) sender
815{
816    tr_sessionSetRPCPasswordEnabled(fHandle, [fDefaults boolForKey: @"RPCAuthorize"]);
817}
818
819- (void) setRPCUsername: (id) sender
820{
821    tr_sessionSetRPCUsername(fHandle, [[fDefaults stringForKey: @"RPCUsername"] UTF8String]);
822}
823
824- (void) setRPCPassword: (id) sender
825{
826    const char * password = [[sender stringValue] UTF8String];
827    [self setKeychainPassword: password forService: RPC_KEYCHAIN_SERVICE username: RPC_KEYCHAIN_NAME];
828   
829    tr_sessionSetRPCPassword(fHandle, password);
830}
831
832- (void) updateRPCPassword
833{
834    UInt32 passwordLength;
835    const char * password = nil;
836    SecKeychainFindGenericPassword(NULL, strlen(RPC_KEYCHAIN_SERVICE), RPC_KEYCHAIN_SERVICE,
837        strlen(RPC_KEYCHAIN_NAME), RPC_KEYCHAIN_NAME, &passwordLength, (void **)&password, NULL);
838   
839    if (password != NULL)
840    {
841        char fullPassword[passwordLength+1];
842        strncpy(fullPassword, password, passwordLength);
843        fullPassword[passwordLength] = '\0';
844        SecKeychainItemFreeContent(NULL, (void *)password);
845       
846        tr_sessionSetRPCPassword(fHandle, fullPassword);
847        [fRPCPasswordField setStringValue: [NSString stringWithUTF8String: fullPassword]];
848    }
849}
850
851- (void) setRPCPort: (id) sender
852{
853    int port = [sender intValue];
854    [fDefaults setInteger: port forKey: @"RPCPort"];
855    tr_sessionSetRPCPort(fHandle, port);
856}
857
858- (void) updateRPCAccessList
859{
860    NSMutableArray * components = [NSMutableArray arrayWithCapacity: [fRPCAccessArray count]];
861   
862    NSEnumerator * enumerator = [fRPCAccessArray objectEnumerator];
863    NSDictionary * dict;
864    while ((dict = [enumerator nextObject]))
865        [components addObject: [NSString stringWithFormat: @"%c%@", [[dict objectForKey: @"Allow"] boolValue] ? '+' : '-',
866                                [dict objectForKey: @"IP"]]];
867   
868    NSString * string = [components componentsJoinedByString: @","];
869   
870    char * error = NULL;
871    if (tr_sessionSetRPCACL(fHandle, [string UTF8String], &error))
872    {
873        NSLog([NSString stringWithUTF8String: error]);
874        tr_free(error);
875    }
876}
877
878- (void) addRemoveRPCIP: (id) sender
879{
880    //don't allow add/remove when currently adding - it leads to weird results
881    if ([fRPCAccessTable editedRow] != -1)
882        return;
883   
884    if ([[sender cell] tagForSegment: [sender selectedSegment]] == RPC_IP_REMOVE_TAG)
885    {
886        [fRPCAccessArray removeObjectsAtIndexes: [fRPCAccessTable selectedRowIndexes]];
887        [fRPCAccessTable deselectAll: self];
888        [fRPCAccessTable reloadData];
889       
890        [fDefaults setObject: fRPCAccessArray forKey: @"RPCAccessList"];
891        [self updateRPCAccessList];
892    }
893    else
894    {
895        [fRPCAccessArray addObject: [NSDictionary dictionaryWithObjectsAndKeys: @"", @"IP",
896                                        [NSNumber numberWithBool: YES], @"Allow", nil]];
897        [fRPCAccessTable reloadData];
898       
899        int row = [fRPCAccessArray count] - 1;
900        [fRPCAccessTable selectRow: row byExtendingSelection: NO];
901        [fRPCAccessTable editColumn: 0 row: row withEvent: nil select: YES];
902    }
903}
904
905- (NSInteger) numberOfRowsInTableView: (NSTableView *) tableView
906{
907    return [fRPCAccessArray count];
908}
909
910- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (NSInteger) row
911{
912    NSDictionary * dict = [fRPCAccessArray objectAtIndex: row];
913   
914    NSString * ident = [tableColumn identifier];
915    if ([ident isEqualToString: @"Permission"])
916    {
917        int allow = [[dict objectForKey: @"Allow"] boolValue] ? RPC_ACCESS_ALLOW : RPC_ACCESS_DENY;
918        return [NSNumber numberWithInt: allow];
919    }
920    else
921        return [dict objectForKey: @"IP"];
922}
923
924- (void) tableView: (NSTableView *) tableView setObjectValue: (id) object forTableColumn: (NSTableColumn *) tableColumn
925    row: (NSInteger) row
926{
927    NSDictionary * oldDict = [fRPCAccessArray objectAtIndex: row];
928   
929    NSString * ident = [tableColumn identifier];
930    if ([ident isEqualToString: @"Permission"])
931    {
932        NSNumber * allow = [NSNumber numberWithBool: [object intValue] == RPC_ACCESS_ALLOW];
933        NSDictionary * newDict = [NSDictionary dictionaryWithObjectsAndKeys: [oldDict objectForKey: @"IP"], @"IP", allow, @"Allow", nil];
934        [fRPCAccessArray replaceObjectAtIndex: row withObject: newDict];
935    }
936    else
937    {
938        NSArray * components = [object componentsSeparatedByString: @"."];
939        NSMutableArray * newComponents = [NSMutableArray arrayWithCapacity: 4];
940       
941        //create better-formatted ip string
942        if ([components count] == 4)
943        {
944            NSEnumerator * enumerator = [components objectEnumerator];
945            NSString * component;
946            while ((component = [enumerator nextObject]))
947            {
948                if ([component isEqualToString: @"*"])
949                    [newComponents addObject: component];
950                else
951                    [newComponents addObject: [[NSNumber numberWithInt: [component intValue]] stringValue]];
952            }
953        }
954       
955        NSString * newIP = [newComponents componentsJoinedByString: @"."];
956       
957        //verify ip string
958        if (!tr_sessionTestRPCACL(fHandle, [[@"+" stringByAppendingString: newIP] UTF8String], NULL))
959        {
960            NSDictionary * newDict = [NSDictionary dictionaryWithObjectsAndKeys: newIP, @"IP",
961                                        [oldDict objectForKey: @"Allow"], @"Allow", nil];
962            [fRPCAccessArray replaceObjectAtIndex: row withObject: newDict];
963           
964            NSSortDescriptor * descriptor = [[[NSSortDescriptor alloc] initWithKey: @"IP" ascending: YES
965                                                selector: @selector(compareNumeric:)] autorelease];
966            [fRPCAccessArray sortUsingDescriptors: [NSArray arrayWithObject: descriptor]];
967        }
968        else
969        {
970            NSBeep();
971           
972            if ([[oldDict objectForKey: @"IP"] isEqualToString: @""])
973                [fRPCAccessArray removeObjectAtIndex: row];
974        }
975       
976        [fRPCAccessTable deselectAll: self];
977        [fRPCAccessTable reloadData];
978    }
979   
980    [fDefaults setObject: fRPCAccessArray forKey: @"RPCAccessList"];
981    [self updateRPCAccessList];
982}
983
984- (void) tableViewSelectionDidChange: (NSNotification *) notification
985{
986    [fRPCAddRemoveControl setEnabled: [fRPCAccessTable numberOfSelectedRows] > 0 forSegment: RPC_IP_REMOVE_TAG];
987}
988
989- (void) helpForPeers: (id) sender
990{
991    [[NSHelpManager sharedHelpManager] openHelpAnchor: @"PeersPrefs"
992        inBook: [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"]];
993}
994
995- (void) helpForNetwork: (id) sender
996{
997    [[NSHelpManager sharedHelpManager] openHelpAnchor: @"NetworkPrefs"
998        inBook: [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"]];
999}
1000
1001- (void) helpForRemote: (id) sender
1002{
1003    [[NSHelpManager sharedHelpManager] openHelpAnchor: @"RemotePrefs"
1004        inBook: [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"]];
1005}
1006
1007- (void) rpcUpdatePrefs
1008{
1009    //encryption
1010    tr_encryption_mode encryptionMode = tr_sessionGetEncryption(fHandle);
1011    [fDefaults setBool: encryptionMode != TR_CLEAR_PREFERRED forKey: @"EncryptionPrefer"];
1012    [fDefaults setBool: encryptionMode == TR_ENCRYPTION_REQUIRED forKey: @"EncryptionRequire"];
1013   
1014    //download directory
1015    NSString * downloadLocation = [[NSString stringWithUTF8String: tr_sessionGetDownloadDir(fHandle)] stringByStandardizingPath];
1016    [fDefaults setObject: downloadLocation forKey: @"DownloadFolder"];
1017   
1018    //peers
1019    uint16_t peersTotal = tr_sessionGetPeerLimit(fHandle);
1020    [fDefaults setInteger: peersTotal forKey: @"PeersTotal"];
1021   
1022    //pex
1023    BOOL pex = tr_sessionIsPexEnabled(fHandle);
1024    [fDefaults setBool: pex forKey: @"PEXGlobal"];
1025   
1026    //port
1027    int port = tr_sessionGetPeerPort(fHandle);
1028    [fDefaults setInteger: port forKey: @"BindPort"];
1029   
1030    BOOL nat = tr_sessionIsPortForwardingEnabled(fHandle);
1031    [fDefaults setBool: nat forKey: @"NatTraversal"];
1032   
1033    fPeerPort = -1;
1034    fNatStatus = -1;
1035    [self updatePortStatus];
1036   
1037    //speed limit - down
1038    BOOL downLimitEnabled = tr_sessionIsSpeedLimitEnabled(fHandle, TR_DOWN);
1039    [fDefaults setBool: downLimitEnabled forKey: @"CheckDownload"];
1040   
1041    int downLimit = tr_sessionGetSpeedLimit(fHandle, TR_DOWN);
1042    [fDefaults setInteger: downLimit forKey: @"DownloadLimit"];
1043   
1044    //speed limit - up
1045    BOOL upLimitEnabled = tr_sessionIsSpeedLimitEnabled(fHandle, TR_UP);
1046    [fDefaults setBool: upLimitEnabled forKey: @"CheckUpload"];
1047   
1048    int upLimit = tr_sessionGetSpeedLimit(fHandle, TR_UP);
1049    [fDefaults setInteger: upLimit forKey: @"UploadLimit"];
1050   
1051    //update gui if loaded
1052    if (fHasLoaded)
1053    {
1054        //encryption handled by bindings
1055       
1056        //download directory handled by bindings
1057       
1058        [fPeersGlobalField setIntValue: peersTotal];
1059       
1060        //pex handled by bindings
1061       
1062        [fPortField setIntValue: port];
1063        //port forwarding (nat) handled by bindings
1064       
1065        //limit check handled by bindings
1066        [fDownloadField setIntValue: downLimit];
1067       
1068        //limit check handled by bindings
1069        [fUploadField setIntValue: upLimit];
1070    }
1071}
1072
1073@end
1074
1075@implementation PrefsController (Private)
1076
1077- (void) setPrefView: (id) sender
1078{
1079    NSView * view = fGeneralView;
1080    if (sender)
1081    {
1082        NSString * identifier = [sender itemIdentifier];
1083        if ([identifier isEqualToString: TOOLBAR_TRANSFERS])
1084            view = fTransfersView;
1085        else if ([identifier isEqualToString: TOOLBAR_BANDWIDTH])
1086            view = fBandwidthView;
1087        else if ([identifier isEqualToString: TOOLBAR_PEERS])
1088            view = fPeersView;
1089        else if ([identifier isEqualToString: TOOLBAR_NETWORK])
1090            view = fNetworkView;
1091        else if ([identifier isEqualToString: TOOLBAR_REMOTE])
1092            view = fRemoteView;
1093        else; //general view already selected
1094    }
1095   
1096    NSWindow * window = [self window];
1097    if ([window contentView] == view)
1098        return;
1099   
1100    NSRect windowRect = [window frame];
1101    float difference = ([view frame].size.height - [[window contentView] frame].size.height) * [window userSpaceScaleFactor];
1102    windowRect.origin.y -= difference;
1103    windowRect.size.height += difference;
1104   
1105    [view setHidden: YES];
1106    [window setContentView: view];
1107    [window setFrame: windowRect display: YES animate: YES];
1108    [view setHidden: NO];
1109   
1110    //set title label
1111    if (sender)
1112        [window setTitle: [sender label]];
1113    else
1114    {
1115        NSToolbar * toolbar = [window toolbar];
1116        NSString * itemIdentifier = [toolbar selectedItemIdentifier];
1117        NSEnumerator * enumerator = [[toolbar items] objectEnumerator];
1118        NSToolbarItem * item;
1119        while ((item = [enumerator nextObject]))
1120            if ([[item itemIdentifier] isEqualToString: itemIdentifier])
1121            {
1122                [window setTitle: [item label]];
1123                break;
1124            }
1125    }
1126   
1127    //for network view make sure progress indicator hides itself (get around a Tiger bug)
1128    if (![NSApp isOnLeopardOrBetter] && view == fNetworkView && [fPortStatusImage image])
1129        [fPortStatusProgress setDisplayedWhenStopped: NO];
1130}
1131
1132- (void) folderSheetClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) info
1133{
1134    if (code == NSOKButton)
1135    {
1136        [fFolderPopUp selectItemAtIndex: DOWNLOAD_FOLDER];
1137        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"DownloadFolder"];
1138        [fDefaults setObject: @"Constant" forKey: @"DownloadChoice"];
1139    }
1140    else
1141    {
1142        //reset if cancelled
1143        [fFolderPopUp selectItemAtIndex: [fDefaults boolForKey: @"DownloadLocationConstant"] ? DOWNLOAD_FOLDER : DOWNLOAD_TORRENT];
1144    }
1145}
1146
1147- (void) incompleteFolderSheetClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) info
1148{
1149    if (code == NSOKButton)
1150        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"IncompleteDownloadFolder"];
1151    [fIncompleteFolderPopUp selectItemAtIndex: 0];
1152}
1153
1154- (void) importFolderSheetClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) info
1155{
1156    NSString * path = [fDefaults stringForKey: @"AutoImportDirectory"];
1157    if (code == NSOKButton)
1158    {
1159        UKKQueue * sharedQueue = [UKKQueue sharedFileWatcher];
1160        if (path)
1161            [sharedQueue removePathFromQueue: [path stringByExpandingTildeInPath]];
1162       
1163        path = [[openPanel filenames] objectAtIndex: 0];
1164        [fDefaults setObject: path forKey: @"AutoImportDirectory"];
1165        [sharedQueue addPath: [path stringByExpandingTildeInPath]];
1166       
1167        [[NSNotificationCenter defaultCenter] postNotificationName: @"AutoImportSettingChange" object: self];
1168    }
1169    else if (!path)
1170        [fDefaults setBool: NO forKey: @"AutoImport"];
1171   
1172    [fImportFolderPopUp selectItemAtIndex: 0];
1173}
1174
1175- (void) setKeychainPassword: (const char *) password forService: (const char *) service username: (const char *) username
1176{
1177    SecKeychainItemRef item = NULL;
1178    NSUInteger passwordLength = strlen(password);
1179   
1180    OSStatus result = SecKeychainFindGenericPassword(NULL, strlen(service), service, strlen(username), username, NULL, NULL, &item);
1181    if (result == noErr && item)
1182    {
1183        if (passwordLength > 0) //found, so update
1184        {
1185            result = SecKeychainItemModifyAttributesAndData(item, NULL, passwordLength, (const void *)password);
1186            if (result != noErr)
1187                NSLog(@"Problem updating Keychain item: %s", GetMacOSStatusErrorString(result));
1188        }
1189        else //remove the item
1190        {
1191            result = SecKeychainItemDelete(item);
1192            if (result != noErr)
1193                NSLog(@"Problem removing Keychain item: %s", GetMacOSStatusErrorString(result));
1194        }
1195    }
1196    else if (result == errSecItemNotFound) //not found, so add
1197    {
1198        if (passwordLength > 0)
1199        {
1200            result = SecKeychainAddGenericPassword(NULL, strlen(service), service, strlen(username), username,
1201                        passwordLength, (const void *)password, NULL);
1202            if (result != noErr)
1203                NSLog(@"Problem adding Keychain item: %s", GetMacOSStatusErrorString(result));
1204        }
1205    }
1206    else
1207        NSLog(@"Problem accessing Keychain: %s", GetMacOSStatusErrorString(result));
1208}
1209
1210@end
Note: See TracBrowser for help on using the repository browser.