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 "ESFileTransferController.h"
19 #import <Adium/AIAccountControllerProtocol.h>
20 #import <Adium/AIContactControllerProtocol.h>
21 #import <Adium/AIInterfaceControllerProtocol.h>
22 #import <Adium/AIMenuControllerProtocol.h>
23 #import <Adium/AIToolbarControllerProtocol.h>
24 #import <Adium/AIContactAlertsControllerProtocol.h>
25 #import "ESFileTransferPreferences.h"
26 #import "ESFileTransferProgressWindowController.h"
27 #import "ESFileTransferRequestPromptController.h"
28 #import <AIUtilities/AIDictionaryAdditions.h>
29 #import <AIUtilities/AIFileManagerAdditions.h>
30 #import <AIUtilities/AIMenuAdditions.h>
31 #import <AIUtilities/AIStringAdditions.h>
32 #import <AIUtilities/AIToolbarUtilities.h>
33 #import <AIUtilities/AIObjectAdditions.h>
34 #import <AIUtilities/AIImageAdditions.h>
35 #import <Adium/AIAccount.h>
36 #import <Adium/AIListContact.h>
37 #import <Adium/AIListObject.h>
38 #import <Adium/AIListGroup.h>
39 #import "ESFileTransfer.h"
40 #import <Adium/AIWindowController.h>
42 #define SEND_FILE AILocalizedString(@"Send File",nil)
43 #define SEND_FILE_WITH_ELLIPSIS [SEND_FILE stringByAppendingEllipsis]
44 #define CONTACT AILocalizedString(@"Contact",nil)
46 #define SEND_FILE_IDENTIFIER @"SendFile"
48 #define FILE_TRANSFER_DEFAULT_PREFS @"FileTransferPrefs"
50 #define SAFE_FILE_EXTENSIONS_SET [NSSet setWithObjects:@"jpg",@"jpeg",@"gif",@"png",@"tif",@"tiff",@"psd",@"pdf",@"txt",@"rtf",@"html",@"htm",@"swf",@"mp3",@"wma",@"wmv",@"ogg",@"ogm",@"mov",@"mpg",@"mpeg",@"m1v",@"m2v",@"mp4",@"avi",@"vob",@"avi",@"asx",@"asf",@"pls",@"m3u",@"rmp",@"aif",@"aiff",@"aifc",@"wav",@"wave",@"m4a",@"m4p",@"m4b",@"dmg",@"udif",@"ndif",@"dart",@"sparseimage",@"cdr",@"dvdr",@"iso",@"img",@"toast",@"rar",@"sit",@"sitx",@"bin",@"hqx",@"zip",@"gz",@"tgz",@"tar",@"bz",@"bz2",@"tbz",@"z",@"taz",@"uu",@"uue",@"colloquytranscript",@"torrent",@"AdiumIcon",@"AdiumSoundset",@"AdiumEmoticon",@"AdiumMessageStyle",nil]
52 static ESFileTransferPreferences *preferences;
54 @interface ESFileTransferController (PRIVATE)
55 - (void)configureFileTransferProgressWindow;
56 - (void)showProgressWindow:(id)sender;
57 - (void)showProgressWindowIfNotOpen:(id)sender;
59 - (void)_finishReceiveRequestForFileTransfer:(ESFileTransfer *)fileTransfer localFilename:(NSString *)localFilename;
61 - (BOOL)shouldOpenCompleteFileTransfer:(ESFileTransfer *)fileTransfer;
64 @implementation ESFileTransferController
69 if ((self = [super init])) {
70 fileTransferArray = [[NSMutableArray alloc] init];
71 safeFileExtensions = nil;
77 - (void)controllerDidLoad
79 //Add our get info contextual menu item
80 menuItem_sendFileContext = [[NSMenuItem alloc] initWithTitle:SEND_FILE_WITH_ELLIPSIS
81 target:self action:@selector(contextualMenuSendFile:)
83 [[adium menuController] addContextualMenuItem:menuItem_sendFileContext toLocation:Context_Contact_Action];
85 //Register the events we generate
86 NSObject <AIContactAlertsController> *contactAlertsController = [adium contactAlertsController];
87 [contactAlertsController registerEventID:FILE_TRANSFER_REQUEST withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
88 [contactAlertsController registerEventID:FILE_TRANSFER_WAITING_REMOTE withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
89 [contactAlertsController registerEventID:FILE_TRANSFER_BEGAN withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
90 [contactAlertsController registerEventID:FILE_TRANSFER_CANCELLED withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
91 [contactAlertsController registerEventID:FILE_TRANSFER_COMPLETE withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
92 [contactAlertsController registerEventID:FILE_TRANSFER_FAILED withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:NO];
94 //Install the Send File menu item
95 menuItem_sendFile = [[NSMenuItem alloc] initWithTitle:SEND_FILE_WITH_ELLIPSIS
96 target:self action:@selector(sendFileToSelectedContact:)
98 [menuItem_sendFile setKeyEquivalentModifierMask:(NSCommandKeyMask | NSShiftKeyMask)];
99 [[adium menuController] addMenuItem:menuItem_sendFile toLocation:LOC_Contact_Action];
101 //Add our "Send File" toolbar item
102 NSToolbarItem *toolbarItem;
103 toolbarItem = [AIToolbarUtilities toolbarItemWithIdentifier:SEND_FILE_IDENTIFIER
105 paletteLabel:SEND_FILE
106 toolTip:AILocalizedString(@"Send a file","Tooltip for the Send File toolbar item")
108 settingSelector:@selector(setImage:)
109 itemContent:[NSImage imageNamed:@"sendfile" forClass:[self class] loadLazily:YES]
110 action:@selector(sendFileToSelectedContact:)
112 [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"ListObject"];
114 //Register our default preferences
115 [[adium preferenceController] registerDefaults:[NSDictionary dictionaryNamed:FILE_TRANSFER_DEFAULT_PREFS
116 forClass:[self class]]
117 forGroup:PREF_GROUP_FILE_TRANSFER];
119 //Observe pref changes
120 [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_FILE_TRANSFER];
121 preferences = [[ESFileTransferPreferences preferencePane] retain];
123 //Set up the file transfer progress window
124 [self configureFileTransferProgressWindow];
127 - (void)controllerWillClose
129 [[adium preferenceController] unregisterPreferenceObserver:self];
136 [safeFileExtensions release]; safeFileExtensions = nil;
137 [fileTransferArray release]; fileTransferArray = nil;
140 #pragma mark Access to file transfer objects
141 - (ESFileTransfer *)newFileTransferWithContact:(AIListContact *)inContact forAccount:(AIAccount *)inAccount type:(AIFileTransferType)t
143 ESFileTransfer *fileTransfer;
145 fileTransfer = [ESFileTransfer fileTransferWithContact:inContact
148 [fileTransferArray addObject:fileTransfer];
149 [fileTransfer setStatus:Not_Started_FileTransfer];
151 //Wait until the next run loop to inform observers of the new file transfer object;
152 //this way the code which requested a new ESFileTransfer has time to configure it before we
153 //dispaly information to the user
154 [[adium notificationCenter] performSelector:@selector(postNotificationName:object:)
155 withObject:FileTransfer_NewFileTransfer
156 withObject:fileTransfer
162 - (int)activeTransferCount
166 NSEnumerator * fts = [fileTransferArray objectEnumerator];
168 while ((t = [fts nextObject])) {
169 AIFileTransferStatus status = [t status];
171 if ((status == Unknown_Status_FileTransfer) ||
172 (status == Not_Started_FileTransfer) ||
173 (status == Checksumming_Filetransfer) ||
174 (status == Waiting_on_Remote_User_FileTransfer) ||
175 (status == Connecting_FileTransfer) ||
176 (status == Accepted_FileTransfer) ||
177 (status == In_Progress_FileTransfer))
184 - (NSArray *)fileTransferArray
186 return fileTransferArray;
189 //Remove a file transfer from our array.
190 - (void)_removeFileTransfer:(ESFileTransfer *)fileTransfer
192 [fileTransferArray removeObject:fileTransfer];
195 #pragma mark Sending and receiving
196 //Sent by an account when it gets a request for us to receive a file; prompt the user for a save location
197 - (void)receiveRequestForFileTransfer:(ESFileTransfer *)fileTransfer
199 AIListContact *listContact = [fileTransfer contact];
200 NSString *localFilename = nil;
202 [fileTransfer setFileTransferType:Incoming_FileTransfer];
204 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_REQUEST
205 forListObject:listContact
206 userInfo:fileTransfer
207 previouslyPerformedActionIDs:nil];
209 if ((autoAcceptType == AutoAccept_All) ||
210 ((autoAcceptType == AutoAccept_FromContactList) && [listContact isIntentionallyNotAStranger])) {
211 NSString *preferredDownloadFolder = [[adium preferenceController] userPreferredDownloadFolder];
212 NSString *remoteFilename = [fileTransfer remoteFilename];
214 //If the incoming file would become hidden, prefix it with an underscore so it is visible.
215 if ([remoteFilename hasPrefix:@"."]) remoteFilename = [@"_" stringByAppendingString:remoteFilename ];
217 //If we should autoaccept, determine the local filename and proceed to accept the request.
218 localFilename = [preferredDownloadFolder stringByAppendingPathComponent:remoteFilename];
220 [self _finishReceiveRequestForFileTransfer:fileTransfer
221 localFilename:[[NSFileManager defaultManager] uniquePathForPath:localFilename]];
224 //Prompt to accept/deny
225 [ESFileTransferRequestPromptController displayPromptForFileTransfer:fileTransfer
227 selector:@selector(_finishReceiveRequestForFileTransfer:localFilename:)];
232 * @brief Finish the receive request process
234 * Called by either ESFileTransferRequestPromptController or self, this method is the last step in accepting or
235 * refusing a request to be sent a file.
237 * @param fileTransfer The file transfer in question
238 * @param localFilename Full path at which to save the file. If anything exists at this path it will be overwritten without further confirmation. Pass nil to deny the transfer.
240 - (void)_finishReceiveRequestForFileTransfer:(ESFileTransfer *)fileTransfer localFilename:(NSString *)localFilename
242 if([fileTransfer isStopped]) //if it's been canceled while we were busy asking the user stuff, ignore it
245 [fileTransfer setLocalFilename:localFilename];
246 [fileTransfer setStatus:Accepted_FileTransfer];
248 [(AIAccount<AIAccount_Files> *)[fileTransfer account] acceptFileTransferRequest:fileTransfer];
250 if (showProgressWindow) {
251 [self showProgressWindowIfNotOpen:nil];
255 [(AIAccount<AIAccount_Files> *)[fileTransfer account] rejectFileReceiveRequest:fileTransfer];
256 [fileTransfer setStatus:Cancelled_Local_FileTransfer];
260 //Prompt the user for the file to send via an Open File dialogue
261 - (void)requestForSendingFileToListContact:(AIListContact *)listContact
263 NSOpenPanel *openPanel = [NSOpenPanel openPanel];
264 [openPanel setTitle:[NSString stringWithFormat:AILocalizedString(@"Send File to %@",nil),[listContact displayName]]];
265 [openPanel setCanChooseDirectories:YES];
266 [openPanel setResolvesAliases:YES];
267 [openPanel setAllowsMultipleSelection:YES];
268 [openPanel setPrompt:AILocalizedStringFromTable(@"Send", @"Buttons", nil)];
270 if ([openPanel runModalForDirectory:nil file:nil types:nil] == NSOKButton) {
271 NSEnumerator *enumerator = [[openPanel filenames] objectEnumerator];
274 while ((filePath = [enumerator nextObject])) {
275 [self sendFile:filePath toListContact:listContact];
280 - (NSString *)pathToArchiveOfFolder:(NSString *)inPath
282 NSString *pathToArchive = nil;
283 NSFileManager *defaultManager = [NSFileManager defaultManager];
286 //Desired folder: /private/tmp/$UID/`uuidgen`
287 tmpDir = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
289 NSString *launchPath = [[[@"/" stringByAppendingPathComponent:@"usr"] stringByAppendingPathComponent:@"bin"] stringByAppendingPathComponent:@"zip"];
291 //Proceed only if /usr/bin/zip exists
292 if ([defaultManager fileExistsAtPath:launchPath]) {
293 NSString *folderName = [inPath lastPathComponent];
295 NSTask *zipTask = nil;
299 //Ensure our temporary directory exists [it never will the first time this method is called]
300 [defaultManager createDirectoryAtPath:tmpDir attributes:nil];
302 pathToArchive = [[NSFileManager defaultManager] uniquePathForPath:[tmpDir stringByAppendingPathComponent:[folderName stringByAppendingPathExtension:@"zip"]]];
304 arguments = [NSArray arrayWithObjects:
305 @"-r", //we'll want to store recursively
306 @"-1", //use the fastest level of compression that isn't storage; the user can compress manually to do better
308 pathToArchive, //output to our destination name
309 folderName, //store the folder
311 AILog(@"-[ESFileTransferController pathToArchiveOfFolder:]: Will launch %@ with arguments %@ in directory %@",
312 launchPath, arguments, [inPath stringByDeletingLastPathComponent]);
315 zipTask = [[NSTask alloc] init];
316 [zipTask setLaunchPath:launchPath];
317 [zipTask setArguments:arguments];
318 [zipTask setCurrentDirectoryPath:[inPath stringByDeletingLastPathComponent]];
320 [zipTask waitUntilExit];
327 success = (([zipTask terminationStatus] == -1) || ([zipTask terminationStatus] == 0));
330 if (!success) pathToArchive = nil;
331 AILog(@"-[ESFileTransferController pathToArchiveOfFolder:]: Success %i (%i), so pathToArchive is %@",
332 success, [zipTask terminationStatus], pathToArchive);
337 return pathToArchive;
340 //Initiate sending a file at a specified path to listContact
341 - (void)sendFile:(NSString *)inPath toListContact:(AIListContact *)listContact
345 if ((account = [[adium accountController] preferredAccountForSendingContentType:CONTENT_FILE_TRANSFER_TYPE
346 toContact:listContact]) &&
347 [account conformsToProtocol:@protocol(AIAccount_Files)]) {
348 NSFileManager *defaultManager = [NSFileManager defaultManager];
351 //Resolve any alias we're passed if necessary
352 inPath = [defaultManager pathByResolvingAlias:inPath];
354 if ([defaultManager fileExistsAtPath:inPath isDirectory:&isDir]) {
355 //If we get a directory and the account we're sending from doesn't support folder transfers
357 ![(AIAccount<AIAccount_Files> *)account canSendFolders]) {
358 inPath = [self pathToArchiveOfFolder:inPath];
362 long fileSize = [[[defaultManager fileAttributesAtPath:inPath
363 traverseLink:YES] objectForKey:NSFileSize] longValue];
365 ESFileTransfer *fileTransfer;
367 //Set up a fileTransfer object
368 fileTransfer = [self newFileTransferWithContact:listContact
370 type:Outgoing_FileTransfer];
372 [fileTransfer setLocalFilename:inPath];
373 [fileTransfer setSize:fileSize];
375 //The fileTransfer object should now have everything the account needs to begin transferring
376 [(AIAccount<AIAccount_Files> *)account beginSendOfFileTransfer:fileTransfer];
378 if (showProgressWindow) {
379 [self showProgressWindowIfNotOpen:nil];
382 //XXX Show a warning message rather than just beeping
390 //Menu or context menu item for sending a file was selected - possible only when a listContact is selected
391 - (IBAction)sendFileToSelectedContact:(id)sender
393 //Get the "selected" list object (contact list or message window)
394 AIListObject *selectedObject;
395 AIListContact *listContact = nil;
397 selectedObject = [[adium interfaceController] selectedListObject];
398 if ([selectedObject isKindOfClass:[AIListContact class]]) {
399 listContact = [[adium contactController] preferredContactForContentType:CONTENT_FILE_TRANSFER_TYPE
400 forListContact:(AIListContact *)selectedObject];
404 [self requestForSendingFileToListContact:listContact];
407 //Prompt for a new contact with the current tab's name
408 - (IBAction)contextualMenuSendFile:(id)sender
410 AIListObject *selectedObject = [[adium menuController] currentContextMenuObject];
411 AIListContact *listContact = [[adium contactController] preferredContactForContentType:CONTENT_FILE_TRANSFER_TYPE
412 forListContact:(AIListContact *)selectedObject];
414 [NSApp activateIgnoringOtherApps:YES];
415 [self requestForSendingFileToListContact:listContact];
418 #pragma mark Status updates
419 - (void)fileTransfer:(ESFileTransfer *)fileTransfer didSetStatus:(AIFileTransferStatus)status
422 case Checksumming_Filetransfer:
423 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_CHECKSUMMING
424 forListObject:[fileTransfer contact]
425 userInfo:fileTransfer
426 previouslyPerformedActionIDs:nil];
428 if (showProgressWindow) {
429 [self showProgressWindowIfNotOpen:nil];
432 case Waiting_on_Remote_User_FileTransfer:
433 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_WAITING_REMOTE
434 forListObject:[fileTransfer contact]
435 userInfo:fileTransfer
436 previouslyPerformedActionIDs:nil];
438 if (showProgressWindow) {
439 [self showProgressWindowIfNotOpen:nil];
443 case Accepted_FileTransfer:
444 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_BEGAN
445 forListObject:[fileTransfer contact]
446 userInfo:fileTransfer
447 previouslyPerformedActionIDs:nil];
449 if (showProgressWindow) {
450 [self showProgressWindowIfNotOpen:nil];
454 case Complete_FileTransfer:
455 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_COMPLETE
456 forListObject:[fileTransfer contact]
457 userInfo:fileTransfer
458 previouslyPerformedActionIDs:nil];
460 //The file is complete; if we are supposed to automatically open safe files and this is one, open it
461 if ([self shouldOpenCompleteFileTransfer:fileTransfer]) {
462 [fileTransfer openFile];
466 case Cancelled_Remote_FileTransfer:
467 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_CANCELLED
468 forListObject:[fileTransfer contact]
469 userInfo:fileTransfer
470 previouslyPerformedActionIDs:nil];
472 case Failed_FileTransfer:
473 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_FAILED
474 forListObject:[fileTransfer contact]
475 userInfo:fileTransfer
476 previouslyPerformedActionIDs:nil];
483 - (BOOL)shouldOpenCompleteFileTransfer:(ESFileTransfer *)fileTransfer
485 BOOL shouldOpen = NO;
488 ([fileTransfer fileTransferType] == Incoming_FileTransfer)) {
490 if (!safeFileExtensions) safeFileExtensions = [SAFE_FILE_EXTENSIONS_SET retain];
492 shouldOpen = [safeFileExtensions containsObject:[[[fileTransfer localFilename] pathExtension] lowercaseString]];
498 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
500 AIListContact *listContact = nil;
502 if (menuItem == menuItem_sendFile) {
503 AIListObject *selectedObject = [[adium interfaceController] selectedListObject];
504 if (selectedObject && [selectedObject isKindOfClass:[AIListContact class]]) {
505 listContact = [[adium contactController] preferredContactForContentType:CONTENT_FILE_TRANSFER_TYPE
506 forListContact:(AIListContact *)selectedObject];
509 return listContact != nil;
511 } else if (menuItem == menuItem_sendFileContext) {
512 AIListObject *selectedObject = [[adium menuController] currentContextMenuObject];
513 if (selectedObject && [selectedObject isKindOfClass:[AIListContact class]]) {
514 listContact = [[adium contactController] preferredContactForContentType:CONTENT_FILE_TRANSFER_TYPE
515 forListContact:(AIListContact *)selectedObject];
518 return listContact != nil;
520 } else if (menuItem == menuItem_showFileTransferProgress) {
528 - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
530 AIListContact *listContact = nil;
532 AIListObject *selectedObject = [[adium interfaceController] selectedListObject];
533 if (selectedObject && [selectedObject isKindOfClass:[AIListContact class]]) {
534 listContact = [[adium contactController] preferredContactForContentType:CONTENT_FILE_TRANSFER_TYPE
535 forListContact:(AIListContact *)selectedObject];
538 return listContact != nil;
541 #pragma mark File transfer progress window
542 - (void)configureFileTransferProgressWindow
544 //Add the File Transfer Progress window menuItem
545 menuItem_showFileTransferProgress = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"File Transfers",nil)
547 action:@selector(showProgressWindow:)
549 [menuItem_showFileTransferProgress setKeyEquivalentModifierMask:(NSCommandKeyMask | NSAlternateKeyMask)];
550 [[adium menuController] addMenuItem:menuItem_showFileTransferProgress toLocation:LOC_Window_Auxiliary];
553 //Show the file transfer progress window
554 - (void)showProgressWindow:(id)sender
556 [ESFileTransferProgressWindowController showFileTransferProgressWindow];
559 - (void)showProgressWindowIfNotOpen:(id)sender
561 [ESFileTransferProgressWindowController showFileTransferProgressWindowIfNotOpen];
564 #pragma mark Preferences
565 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
566 object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
568 autoAcceptType = [[prefDict objectForKey:KEY_FT_AUTO_ACCEPT] intValue];
569 autoOpenSafe = [[prefDict objectForKey:KEY_FT_AUTO_OPEN_SAFE] boolValue];
571 //If we created a safe file extensions set and no longer need it, desroy it
572 if (!autoOpenSafe && safeFileExtensions) {
573 [safeFileExtensions release]; safeFileExtensions = nil;
576 showProgressWindow = [[prefDict objectForKey:KEY_FT_SHOW_PROGRESS_WINDOW] boolValue];
579 #pragma mark AIEventHandler
581 - (NSString *)shortDescriptionForEventID:(NSString *)eventID
583 NSString *description;
585 if ([eventID isEqualToString:FILE_TRANSFER_FAILED]) {
586 description = AILocalizedString(@"File transfer fails",nil);
594 - (NSString *)globalShortDescriptionForEventID:(NSString *)eventID
596 NSString *description;
598 if ([eventID isEqualToString:FILE_TRANSFER_REQUEST]) {
599 description = AILocalizedString(@"File transfer requested",nil);
600 } else if ([eventID isEqualToString:FILE_TRANSFER_CHECKSUMMING]) {
601 description = AILocalizedString(@"File is checksummed before sending",nil);
602 } else if ([eventID isEqualToString:FILE_TRANSFER_WAITING_REMOTE]) {
603 description = AILocalizedString(@"File transfer being offered to other side",nil);
604 } else if ([eventID isEqualToString:FILE_TRANSFER_BEGAN]) {
605 description = AILocalizedString(@"File transfer begins",nil);
606 } else if ([eventID isEqualToString:FILE_TRANSFER_CANCELLED]) {
607 description = AILocalizedString(@"File transfer cancelled by the other side",nil);
608 } else if ([eventID isEqualToString:FILE_TRANSFER_COMPLETE]) {
609 description = AILocalizedString(@"File transfer completed successfully",nil);
610 } else if ([eventID isEqualToString:FILE_TRANSFER_FAILED]) {
611 description = AILocalizedString(@"File transfer failed",nil);
619 //Evan: This exists because old X(tras) relied upon matching the description of event IDs, and I don't feel like making
620 //a converter for old packs. If anyone wants to fix this situation, please feel free :)
621 //XXX-fix this for the above comment.
622 - (NSString *)englishGlobalShortDescriptionForEventID:(NSString *)eventID
624 NSString *description;
626 if ([eventID isEqualToString:FILE_TRANSFER_REQUEST]) {
627 description = @"File Transfer Request";
628 } else if ([eventID isEqualToString:FILE_TRANSFER_CHECKSUMMING]) {
629 description = @"File Checksumming for Sending";
630 } else if ([eventID isEqualToString:FILE_TRANSFER_WAITING_REMOTE]) {
631 description = @"File Transfer Being Offered to Remote User";
632 } else if ([eventID isEqualToString:FILE_TRANSFER_BEGAN]) {
633 description = @"File Transfer Began";
634 } else if ([eventID isEqualToString:FILE_TRANSFER_CANCELLED]) {
635 //Canceled, not Cancelled as we use elsewhere in Adium, for historical reasons. Both are valid spellings.
636 description = @"File Transfer Canceled Remotely";
637 } else if ([eventID isEqualToString:FILE_TRANSFER_COMPLETE]) {
638 description = @"File Transfer Complete";
639 } else if ([eventID isEqualToString:FILE_TRANSFER_FAILED]) {
640 description = @"File transfer failed";
648 - (NSString *)longDescriptionForEventID:(NSString *)eventID forListObject:(AIListObject *)listObject
650 NSString *description;
655 if ([eventID isEqualToString:FILE_TRANSFER_FAILED]) {
656 format = AILocalizedString(@"When a file transfer with %@ fails",nil);
663 name = ([listObject isKindOfClass:[AIListGroup class]] ?
664 [NSString stringWithFormat:AILocalizedString(@"a member of %@",nil),[listObject displayName]] :
665 [listObject displayName]);
667 description = [NSString stringWithFormat:format, name];
674 if ([eventID isEqualToString:FILE_TRANSFER_REQUEST]) {
675 description = AILocalizedString(@"When a file transfer is requested",nil);
676 } else if ([eventID isEqualToString:FILE_TRANSFER_CHECKSUMMING]) {
677 description = AILocalizedString(@"When a file is checksummed prior to sending",nil);
678 } else if ([eventID isEqualToString:FILE_TRANSFER_WAITING_REMOTE]) {
679 description = AILocalizedString(@"When a file transfer is offered to a remote user",nil);
680 } else if ([eventID isEqualToString:FILE_TRANSFER_BEGAN]) {
681 description = AILocalizedString(@"When a file transfer begins",nil);
682 } else if ([eventID isEqualToString:FILE_TRANSFER_CANCELLED]) {
683 description = AILocalizedString(@"When a file transfer is cancelled remotely",nil);
684 } else if ([eventID isEqualToString:FILE_TRANSFER_COMPLETE]) {
685 description = AILocalizedString(@"When a file transfer is completed successfully",nil);
686 } else if ([eventID isEqualToString:FILE_TRANSFER_FAILED]) {
687 description = AILocalizedString(@"When a file transfer fails",nil);
696 - (NSString *)naturalLanguageDescriptionForEventID:(NSString *)eventID
697 listObject:(AIListObject *)listObject
698 userInfo:(id)userInfo
699 includeSubject:(BOOL)includeSubject
701 NSString *description = nil;
702 NSString *displayName, *displayFilename;
703 ESFileTransfer *fileTransfer;
705 NSParameterAssert([userInfo isKindOfClass:[ESFileTransfer class]]);
706 fileTransfer = (ESFileTransfer *)userInfo;
708 displayName = [listObject displayName];
709 displayFilename = [fileTransfer displayFilename];
711 if (includeSubject) {
712 NSString *format = nil;
714 if ([eventID isEqualToString:FILE_TRANSFER_REQUEST]) {
715 //Should only happen for an incoming transfer
716 format = AILocalizedString(@"%@ requests to send you %@","A person is wanting to send you a file. The first %@ is a name; the second %@ is the filename of the file being sent.");
718 } else if ([eventID isEqualToString:FILE_TRANSFER_WAITING_REMOTE]) {
719 //Should only happen for outgoing file transfers
720 format = AILocalizedString(@"Offering to send %@ to %@","You are offering to send a file to a remote user. The first %@ is the filename of the file being sent; the second %@ is the recipient of the file being sent.");
722 } else if ([eventID isEqualToString:FILE_TRANSFER_BEGAN]) {
723 if ([fileTransfer fileTransferType] == Incoming_FileTransfer) {
724 format = AILocalizedString(@"%@ began sending you %@","A person began sending you a file. The first %@ is a name; the second %@ is the filename of the file being sent.");
726 format = AILocalizedString(@"%@ began receiving %@","A person began receiving a file from you. The first %@ is the recipient of the file; the second %@ is the filename of the file being sent.");
728 } else if ([eventID isEqualToString:FILE_TRANSFER_CANCELLED]) {
729 format = AILocalizedString(@"%@ cancelled the transfer of %@","The other contact cancelled a file transfer in progress. The first %@ is the recipient of the file; the second %@ is the filename of the file being sent.");
730 } else if ([eventID isEqualToString:FILE_TRANSFER_COMPLETE]) {
731 if ([fileTransfer fileTransferType] == Incoming_FileTransfer) {
732 format = AILocalizedString(@"%@ sent you %@","First placeholder is a name; second is a filename");
734 format = AILocalizedString(@"%@ received %@","First placeholder is a name; second is a filename");
736 } else if ([eventID isEqualToString:FILE_TRANSFER_FAILED]) {
737 if ([fileTransfer fileTransferType] == Incoming_FileTransfer) {
738 format = AILocalizedString(@"%@'s transfer of %@ failed","First placeholder is a name; second is a filename");
741 format = AILocalizedString(@"Your transfer to %@ of %@ failed","First placeholder is a name; second is a filename");
746 description = [NSString stringWithFormat:format,displayName,displayFilename];
749 NSString *format = nil;
751 if ([eventID isEqualToString:FILE_TRANSFER_REQUEST]) {
752 //Should only happen for an incoming transfer
753 format = AILocalizedString(@"requests to send you %@","%@ is a filename of a file being sent");
755 }else if ([eventID isEqualToString:FILE_TRANSFER_WAITING_REMOTE]) {
756 //Should only happen for an outgoing transfer
757 format = AILocalizedString(@"offers to send %@","%@ is a filename of a file being sent");
759 } else if ([eventID isEqualToString:FILE_TRANSFER_BEGAN]) {
760 if ([fileTransfer fileTransferType] == Incoming_FileTransfer) {
761 format = AILocalizedString(@"began sending you %@","%@ is a filename of a file being sent");
763 format = AILocalizedString(@"began receiving %@","%@ is a filename of a file being sent");
765 } else if ([eventID isEqualToString:FILE_TRANSFER_CANCELLED]) {
766 format = AILocalizedString(@"cancelled the transfer of %@","%@ is a filename of a file being sent");
767 } else if ([eventID isEqualToString:FILE_TRANSFER_COMPLETE]) {
768 if ([fileTransfer fileTransferType] == Incoming_FileTransfer) {
769 format = AILocalizedString(@"sent you %@","%@ is a filename of a file being sent");
771 format = AILocalizedString(@"received %@","%@ is a filename of a file being sent");
773 } else if ([eventID isEqualToString:FILE_TRANSFER_FAILED]) {
774 if ([fileTransfer fileTransferType] == Incoming_FileTransfer) {
775 format = AILocalizedString(@"failed to send you %@","%@ is a filename of a file being sent");
777 format = AILocalizedString(@"failed to receive %@","%@ is a filename of a file being sent");
782 description = [NSString stringWithFormat:format,displayFilename];
789 - (NSImage *)imageForEventID:(NSString *)eventID
791 static NSImage *eventImage = nil;
792 if (!eventImage) eventImage = [[NSImage imageNamed:@"pref-ft" forClass:[self class]] retain];
796 #pragma mark Strings for sizes
798 #define ZERO_BYTES AILocalizedString(@"Zero bytes", "no file size")
800 - (NSString *)stringForSize:(unsigned long long)inSize
804 if ( inSize == 0. ) ret = ZERO_BYTES;
805 else if ( inSize > 0. && inSize < 1024. ) ret = [NSString stringWithFormat:AILocalizedString( @"%llu bytes", "file size measured in bytes" ), inSize];
806 else if ( inSize >= 1024. && inSize < pow( 1024., 2. ) ) ret = [NSString stringWithFormat:AILocalizedString( @"%.1f KB", "file size measured in kilobytes" ), ( inSize / 1024. )];
807 else if ( inSize >= pow( 1024., 2. ) && inSize < pow( 1024., 3. ) ) ret = [NSString stringWithFormat:AILocalizedString( @"%.2f MB", "file size measured in megabytes" ), ( inSize / pow( 1024., 2. ) )];
808 else if ( inSize >= pow( 1024., 3. ) && inSize < pow( 1024., 4. ) ) ret = [NSString stringWithFormat:AILocalizedString( @"%.3f GB", "file size measured in gigabytes" ), ( inSize / pow( 1024., 3. ) )];
809 else if ( inSize >= pow( 1024., 4. ) ) ret = [NSString stringWithFormat:AILocalizedString( @"%.4f TB", "file size measured in terabytes" ), ( inSize / pow( 1024., 4. ) )];
811 if (!ret) ret = ZERO_BYTES;
816 - (NSString *)stringForSize:(unsigned long long)inSize of:(unsigned long long)totalSize ofString:(NSString *)totalSizeString
820 if ( inSize == 0. ) {
822 } else if ( inSize > 0. && inSize < 1024. ) {
823 if ( totalSize > 0. && totalSize < 1024. ) {
824 ret = [NSString stringWithFormat:AILocalizedString( @"%llu of %llu bytes", "file sizes both measured in bytes" ), inSize, totalSize];
827 ret = [NSString stringWithFormat:AILocalizedString( @"%llu bytes of %@", "file size measured in bytes out of some other measurement" ), inSize, totalSizeString];
830 } else if ( inSize >= 1024. && inSize < pow( 1024., 2. ) ) {
831 if ( totalSize >= 1024. && totalSize < pow( 1024., 2. ) ) {
832 ret = [NSString stringWithFormat:AILocalizedString( @"%.1f of %.1f KB", "file sizes both measured in kilobytes" ), ( inSize / 1024. ), ( totalSize / 1024. )];
835 ret = [NSString stringWithFormat:AILocalizedString( @"%.1f KB of %@", "file size measured in kilobytes out of some other measurement" ), ( inSize / 1024. ), totalSizeString];
838 else if ( inSize >= pow( 1024., 2. ) && inSize < pow( 1024., 3. ) ) {
839 if ( totalSize >= pow( 1024., 2. ) && totalSize < pow( 1024., 3. ) ) {
840 ret = [NSString stringWithFormat:AILocalizedString( @"%.2f of %.2f MB", "file sizes both measured in megabytes" ), ( inSize / pow( 1024., 2. ) ), ( totalSize / pow( 1024., 2. ) )];
842 ret = [NSString stringWithFormat:AILocalizedString( @"%.2f MB of %@", "file size measured in megabytes out of some other measurement" ), ( inSize / pow( 1024., 2. ) ), totalSizeString];
845 else if ( inSize >= pow( 1024., 3. ) && inSize < pow( 1024., 4. ) ) {
846 if ( totalSize >= pow( 1024., 3. ) && totalSize < pow( 1024., 4. ) ) {
847 ret = [NSString stringWithFormat:AILocalizedString( @"%.3f of %.3f GB", "file sizes both measured in gigabytes" ), ( inSize / pow( 1024., 3. ) ), ( totalSize / pow( 1024., 3. ) )];
849 ret = [NSString stringWithFormat:AILocalizedString( @"%.3f GB of %@", "file size measured in gigabytes out of some other measurement" ), ( inSize / pow( 1024., 3. ) ), totalSizeString];
853 else if ( inSize >= pow( 1024., 4. ) ) {
854 if ( totalSize >= pow( 1024., 4. ) ) {
855 ret = [NSString stringWithFormat:AILocalizedString( @"%.4f of %.4f TB", "file sizes both measured in terabytes" ), ( inSize / pow( 1024., 4. ) ), ( totalSize / pow( 1024., 4. ) )];
857 ret = [NSString stringWithFormat:AILocalizedString( @"%.4f TB of %@", "file size measured in terabytes out of some other measurement" ), ( inSize / pow( 1024., 4. ) ), totalSizeString];
861 if (!ret) ret = ZERO_BYTES;