2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 #import "AdiumAccounts.h"
18 #import <Adium/AIAccountControllerProtocol.h>
19 #import <Adium/AIPreferenceControllerProtocol.h>
20 #import <Adium/AIAccount.h>
21 #import <Adium/AIService.h>
22 #import <AIUtilities/AIArrayAdditions.h>
23 #import <AIUtilities/AIAttributedStringAdditions.h>
26 #define TOP_ACCOUNT_ID @"TopAccountID" //Highest account object ID
27 #define ACCOUNT_LIST @"Accounts" //Array of accounts
28 #define ACCOUNT_TYPE @"Type" //Account type
29 #define ACCOUNT_SERVICE @"Service" //Account service
30 #define ACCOUNT_UID @"UID" //Account UID
31 #define ACCOUNT_OBJECT_ID @"ObjectID" //Account object ID
33 @interface AdiumAccounts (PRIVATE)
34 - (void)_loadAccounts;
35 - (void)_saveAccounts;
36 - (NSString *)_generateUniqueInternalObjectID;
37 - (NSString *)_upgradeServiceID:(NSString *)serviceID forAccountDict:(NSDictionary *)accountDict;
38 - (void)upgradeAccounts;
42 * @class AdiumAccounts
43 * @brief Class to handle AIAccount access and creation
45 * This is a private class used by AIAccountController, its public interface.
47 @implementation AdiumAccounts
50 if ((self = [super init])) {
51 accounts = [[NSMutableArray alloc] init];
52 unloadableAccounts = [[NSMutableArray alloc] init];
60 [unloadableAccounts release];
66 * @brief Finish Initing
68 * Requires the all AIServices have registered
70 - (void)controllerDidLoad
74 [self upgradeAccounts];
78 //Accounts -------------------------------------------------------------------------------------------------------
81 * @brief Returns an array of all available accounts
83 * @return NSArray of AIAccount instances
91 * @brief Returns an array of accounts compatible with a service
93 * @param service AIService for compatible accounts
94 * @param NSArray of AIAccount instances
96 - (NSArray *)accountsCompatibleWithService:(AIService *)service
98 NSMutableArray *matchingAccounts = [NSMutableArray array];
99 NSEnumerator *enumerator = [accounts objectEnumerator];
101 NSString *serviceClass = [service serviceClass];
103 while ((account = [enumerator nextObject])) {
104 if ([account enabled] &&
105 [[[account service] serviceClass] isEqualToString:serviceClass]) {
106 [matchingAccounts addObject:account];
110 return matchingAccounts;
113 - (AIAccount *)accountWithInternalObjectID:(NSString *)objectID
115 NSEnumerator *enumerator = [accounts objectEnumerator];
116 AIAccount *account = nil;
118 //Some ancient preferences have NSNumbers instead of NSStrings. Work properly, silently.
119 if ([objectID isKindOfClass:[NSNumber class]]) objectID = [(NSNumber *)objectID stringValue];
121 while (objectID && (account = [enumerator nextObject])) {
122 if ([objectID isEqualToString:[account internalObjectID]]) break;
129 //Editing --------------------------------------------------------------------------------------------------------------
132 * @brief Create an account
134 * The account is not added to Adium's list of accounts, this must be done separately with addAccount:
135 * @param service AIService for the account
136 * @param inUID NSString userID for the account
137 * @return AIAccount instance that was created
139 - (AIAccount *)createAccountWithService:(AIService *)service UID:(NSString *)inUID
141 return [service accountWithUID:inUID internalObjectID:[self _generateUniqueInternalObjectID]];
145 * @brief Add an account
147 * @param inAccount AIAccount to add
149 - (void)addAccount:(AIAccount *)inAccount
151 [accounts addObject:inAccount];
152 [self _saveAccounts];
156 * @brief Delete an account
158 * @param inAccount AIAccount to delete
160 - (void)deleteAccount:(AIAccount *)inAccount
162 //Shut down the account in preparation for release
163 //XXX - Is this sufficient? Don't some accounts take a while to disconnect and all? -ai
164 [inAccount willBeDeleted];
165 [[adium accountController] forgetPasswordForAccount:inAccount];
167 //Remove from our array
168 [accounts removeObject:inAccount];
169 [self _saveAccounts];
173 * @brief Move an account
175 * @param inAccount AIAccount to move
176 * @param destIndex Index to place the account
177 * @return new index of the account
179 - (int)moveAccount:(AIAccount *)account toIndex:(int)destIndex
181 [accounts moveObject:account toIndex:destIndex];
182 [self _saveAccounts];
183 return [accounts indexOfObject:account];
187 * @brief An account's UID changed
189 * Save our account array, which stores the account's UID permanently
191 - (void)accountDidChangeUID:(AIAccount *)account
193 [self _saveAccounts];
197 * @brief Generate a unique account InternalObjectID
199 * @return NSString unique InternalObjectID
201 //XXX - This setup leaves the possibility that mangled preferences files would create multiple accounts with the same ID -ai
202 - (NSString *)_generateUniqueInternalObjectID
204 int topAccountID = [[[adium preferenceController] preferenceForKey:TOP_ACCOUNT_ID group:PREF_GROUP_ACCOUNTS] intValue];
205 NSString *internalObjectID = [NSString stringWithFormat:@"%i",topAccountID];
207 [[adium preferenceController] setPreference:[NSNumber numberWithInt:topAccountID + 1]
208 forKey:TOP_ACCOUNT_ID
209 group:PREF_GROUP_ACCOUNTS];
211 return internalObjectID;
215 //Storage --------------------------------------------------------------------------------------------------------------
218 * @brief Load accounts from disk
220 - (void)_loadAccounts
222 NSArray *accountList = [[adium preferenceController] preferenceForKey:ACCOUNT_LIST group:PREF_GROUP_ACCOUNTS];
223 NSEnumerator *enumerator;
224 NSDictionary *accountDict;
226 //Create an instance of every saved account
227 enumerator = [accountList objectEnumerator];
228 while ((accountDict = [enumerator nextObject])) {
229 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
230 NSString *serviceID = [self _upgradeServiceID:[accountDict objectForKey:ACCOUNT_TYPE] forAccountDict:accountDict];
231 AIAccount *newAccount;
233 //Fetch the account service, UID, and ID
234 AIService *service = [[adium accountController] serviceWithUniqueID:serviceID];
235 NSString *accountUID = [accountDict objectForKey:ACCOUNT_UID];
236 NSString *internalObjectID = [accountDict objectForKey:ACCOUNT_OBJECT_ID];
238 //Create the account and add it to our array
239 if (service && accountUID && [accountUID length]) {
240 if ((newAccount = [service accountWithUID:accountUID internalObjectID:internalObjectID])) {
241 [accounts addObject:newAccount];
243 NSLog(@"Could not load account %@",accountDict);
244 [unloadableAccounts addObject:accountDict];
247 if ([accountUID length]) {
248 NSLog(@"Available services are %@: could not load account %@ on service %@ (service %@)",
249 [[adium accountController] services], accountDict, serviceID, service);
250 [unloadableAccounts addObject:accountDict];
252 AILog(@"Ignored an account with a 0 length accountUID: %@", accountDict);
258 //Broadcast an account list changed notification
259 [[adium notificationCenter] postNotificationName:Account_ListChanged object:nil userInfo:nil];
263 * @brief ServiceID upgrade code (v0.63 -> v0.70 for libpurple, v0.70 -> v0.80 for bonjour, v1.0 -> v1.1 for libpurple)
265 * The changed name will only be saved if some other account change, such as adding an account, occurs,
266 * so this code should remain indefinitely to provide an upgrade path to people whose service IDs are in an
269 * @param serviceID NSString service ID (old or new)
270 * @param accountDict Dictionary of the saved account
271 * @return NSString service ID (new), or nil if unable to upgrade
273 - (NSString *)_upgradeServiceID:(NSString *)serviceID forAccountDict:(NSDictionary *)accountDict
276 if ([serviceID rangeOfString:@"libgaim" options:(NSLiteralSearch | NSAnchoredSearch)].location != NSNotFound) {
277 NSMutableString *newServiceID = [serviceID mutableCopy];
278 [newServiceID replaceOccurrencesOfString:@"libgaim"
279 withString:@"libpurple"
280 options:(NSLiteralSearch | NSAnchoredSearch)
281 range:NSMakeRange(0, [newServiceID length])];
282 serviceID = [newServiceID autorelease];
284 } else if ([serviceID rangeOfString:@"LIBGAIM" options:(NSLiteralSearch | NSAnchoredSearch | NSBackwardsSearch)].location != NSNotFound) {
285 if ([serviceID isEqualToString:@"AIM-LIBGAIM"]) {
286 NSString *uid = [accountDict objectForKey:ACCOUNT_UID];
287 if (uid && [uid length]) {
288 const char firstCharacter = [uid characterAtIndex:0];
290 if ([uid hasSuffix:@"@mac.com"]) {
291 serviceID = @"libpurple-oscar-Mac";
292 } else if (firstCharacter >= '0' && firstCharacter <= '9') {
293 serviceID = @"libpurple-oscar-ICQ";
295 serviceID = @"libpurple-oscar-AIM";
298 } else if ([serviceID isEqualToString:@"GaduGadu-LIBGAIM"]) {
299 serviceID = @"libpurple-Gadu-Gadu";
300 } else if ([serviceID isEqualToString:@"Jabber-LIBGAIM"]) {
301 serviceID = @"libpurple-Jabber";
302 } else if ([serviceID isEqualToString:@"MSN-LIBGAIM"]) {
303 serviceID = @"libpurple-MSN";
304 } else if ([serviceID isEqualToString:@"Napster-LIBGAIM"]) {
305 serviceID = @"libpurple-Napster";
306 } else if ([serviceID isEqualToString:@"Novell-LIBGAIM"]) {
307 serviceID = @"libpurple-GroupWise";
308 } else if ([serviceID isEqualToString:@"Sametime-LIBGAIM"]) {
309 serviceID = @"libpurple-Sametime";
310 } else if ([serviceID isEqualToString:@"Yahoo-LIBGAIM"]) {
311 serviceID = @"libpurple-Yahoo!";
312 } else if ([serviceID isEqualToString:@"Yahoo-Japan-LIBGAIM"]) {
313 serviceID = @"libpurple-Yahoo!-Japan";
315 } else if ([serviceID isEqualToString:@"rvous-libezv"])
316 serviceID = @"bonjour-libezv";
317 else if ([serviceID isEqualToString:@"joscar-OSCAR-AIM"])
318 serviceID = @"libpurple-oscar-AIM";
319 else if ([serviceID isEqualToString:@"joscar-OSCAR-dotMac"])
320 serviceID = @"libpurple-oscar-Mac";
326 * @brief Save accounts to disk
328 - (void)_saveAccounts
330 NSMutableArray *flatAccounts = [NSMutableArray array];
331 NSEnumerator *enumerator;
334 //Build a flattened array of the accounts
335 enumerator = [accounts objectEnumerator];
336 while ((account = [enumerator nextObject])) {
337 if (![account isTemporary]) {
338 NSMutableDictionary *flatAccount = [NSMutableDictionary dictionary];
339 AIService *service = [account service];
340 [flatAccount setObject:[service serviceCodeUniqueID] forKey:ACCOUNT_TYPE]; //Unique plugin ID
341 [flatAccount setObject:[service serviceID] forKey:ACCOUNT_SERVICE]; //Shared service ID
342 [flatAccount setObject:[account UID] forKey:ACCOUNT_UID]; //Account UID
343 [flatAccount setObject:[account internalObjectID] forKey:ACCOUNT_OBJECT_ID]; //Account Object ID
345 [flatAccounts addObject:flatAccount];
349 //Add any unloadable accounts so they're not lost
350 [flatAccounts addObjectsFromArray:unloadableAccounts];
352 //Save and broadcast an account list changed notification
353 [[adium preferenceController] setPreference:flatAccounts forKey:ACCOUNT_LIST group:PREF_GROUP_ACCOUNTS];
354 [[adium notificationCenter] postNotificationName:Account_ListChanged object:nil userInfo:nil];
358 * @brief Perform upgrades for a new version
360 * 1.0: KEY_ACCOUNT_DISPLAY_NAME and @"TextProfile" cleared if @"" and moved to global if identical on all accounts
362 - (void)upgradeAccounts
364 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
365 NSNumber *upgradedAccounts = [userDefaults objectForKey:@"Adium:Account Prefs Upgraded for 1.0"];
367 if (!upgradedAccounts || ![upgradedAccounts boolValue]) {
368 [userDefaults setObject:[NSNumber numberWithBool:YES] forKey:@"Adium:Account Prefs Upgraded for 1.0"];
371 NSEnumerator *enumerator, *keyEnumerator;
374 //Adium 0.8x would store @"" in preferences which we now want to be able to inherit global values if they don't have a value.
375 NSSet *keysWeNowUseGlobally = [NSSet setWithObjects:KEY_ACCOUNT_DISPLAY_NAME, @"TextProfile", nil];
377 NSCharacterSet *whitespaceAndNewlineCharacterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
379 keyEnumerator = [keysWeNowUseGlobally objectEnumerator];
380 while ((key = [keyEnumerator nextObject])) {
381 NSAttributedString *firstAttributedString = nil;
382 BOOL allOnThisKeyAreTheSame = YES;
384 enumerator = [[self accounts] objectEnumerator];
385 while ((account = [enumerator nextObject])) {
386 NSAttributedString *attributedString = [[account preferenceForKey:key
387 group:GROUP_ACCOUNT_STATUS
388 ignoreInheritedValues:YES] attributedString];
389 if (attributedString && ![attributedString length]) {
390 [account setPreference:nil
392 group:GROUP_ACCOUNT_STATUS];
393 attributedString = nil;
396 if (attributedString) {
397 if (firstAttributedString) {
398 /* If this string is not the same as the first one we found, all are not the same.
399 * Only need to check if thus far they all have been the same
401 if (allOnThisKeyAreTheSame &&
402 ![[[attributedString string] stringByTrimmingCharactersInSet:whitespaceAndNewlineCharacterSet] isEqualToString:
403 [[firstAttributedString string] stringByTrimmingCharactersInSet:whitespaceAndNewlineCharacterSet]]) {
404 allOnThisKeyAreTheSame = NO;
407 //Note the first one we find, which will be our reference
408 firstAttributedString = attributedString;
413 if (allOnThisKeyAreTheSame && firstAttributedString) {
414 //All strings on this key are the same. Set the preference globally...
415 [[adium preferenceController] setPreference:[firstAttributedString dataRepresentation]
417 group:GROUP_ACCOUNT_STATUS];
419 //And remove it from all accounts
420 enumerator = [[self accounts] objectEnumerator];
421 while ((account = [enumerator nextObject])) {
422 [account setPreference:nil
424 group:GROUP_ACCOUNT_STATUS];
429 [userDefaults synchronize];