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 "AIAccountController.h"
18 #import "AIContactController.h"
19 #import "AIMenuController.h"
20 #import "AIToolbarController.h"
21 #import "ESContactAlertsController.h"
22 #import "ESFileTransferController.h"
23 #import "ESFileTransferPreferences.h"
24 #import "ESFileTransferProgressWindowController.h"
25 #import "ESFileTransferRequestPromptController.h"
26 #import <AIUtilities/AIDictionaryAdditions.h>
27 #import <AIUtilities/AIMenuAdditions.h>
28 #import <AIUtilities/AIToolbarUtilities.h>
29 #import <AIUtilities/CBObjectAdditions.h>
30 #import <AIUtilities/AIStringAdditions.h>
31 #import <AIUtilities/ESImageAdditions.h>
32 #import <AIUtilities/AIExceptionHandlingUtilities.h>
33 #import <Adium/AIAccount.h>
34 #import <Adium/AIListContact.h>
35 #import <Adium/AIListObject.h>
36 #import <Adium/ESFileTransfer.h>
38 #define SEND_FILE AILocalizedString(@"Send File",nil)
39 #define SEND_FILE_WITH_ELLIPSIS [SEND_FILE stringByAppendingEllipsis]
40 #define CONTACT AILocalizedString(@"Contact",nil)
42 #define SEND_FILE_IDENTIFIER @"SendFile"
44 #define FILE_TRANSFER_DEFAULT_PREFS @"FileTransferPrefs"
46 #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]
48 static ESFileTransferPreferences *preferences;
50 @interface ESFileTransferController (PRIVATE)
51 - (void)configureFileTransferProgressWindow;
52 - (void)showProgressWindow:(id)sender;
53 - (void)showProgressWindowIfNotOpen:(id)sender;
55 - (void)_finishReceiveRequestForFileTransfer:(ESFileTransfer *)fileTransfer localFilename:(NSString *)localFilename;
57 - (BOOL)shouldOpenCompleteFileTransfer:(ESFileTransfer *)fileTransfer;
58 - (void)_removeFileTransfer:(ESFileTransfer *)fileTransfer;
61 @implementation ESFileTransferController
64 - (void)initController
66 fileTransferArray = [[NSMutableArray alloc] init];
67 safeFileExtensions = nil;
69 //Add our get info contextual menu item
70 menuItem_sendFileContext = [[NSMenuItem alloc] initWithTitle:SEND_FILE
71 target:self action:@selector(contextualMenuSendFile:)
73 [[adium menuController] addContextualMenuItem:menuItem_sendFileContext toLocation:Context_Contact_Action];
75 //Register the events we generate
76 ESContactAlertsController *contactAlertsController = [adium contactAlertsController];
77 [contactAlertsController registerEventID:FILE_TRANSFER_REQUEST withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
78 [contactAlertsController registerEventID:FILE_TRANSFER_BEGAN withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
79 [contactAlertsController registerEventID:FILE_TRANSFER_CANCELED withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
80 [contactAlertsController registerEventID:FILE_TRANSFER_COMPLETE withHandler:self inGroup:AIFileTransferEventHandlerGroup globalOnly:YES];
82 //Install the Send File menu item
83 menuItem_sendFile = [[NSMenuItem alloc] initWithTitle:SEND_FILE
84 target:self action:@selector(sendFileToSelectedContact:)
86 [menuItem_sendFile setKeyEquivalentModifierMask:(NSCommandKeyMask | NSShiftKeyMask)];
87 [[adium menuController] addMenuItem:menuItem_sendFile toLocation:LOC_Contact_Action];
89 //Add our "Send File" toolbar item
90 NSToolbarItem *toolbarItem;
91 toolbarItem = [AIToolbarUtilities toolbarItemWithIdentifier:SEND_FILE_IDENTIFIER
93 paletteLabel:SEND_FILE
94 toolTip:AILocalizedString(@"Send a file",nil)
96 settingSelector:@selector(setImage:)
97 itemContent:[NSImage imageNamed:@"sendfile" forClass:[self class]]
98 action:@selector(sendFileToSelectedContact:)
100 [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"ListObject"];
102 AIPreferenceController *preferenceController = [adium preferenceController];
103 //Register our default preferences
104 [preferenceController registerDefaults:[NSDictionary dictionaryNamed:FILE_TRANSFER_DEFAULT_PREFS
105 forClass:[self class]]
106 forGroup:PREF_GROUP_FILE_TRANSFER];
108 //Observe pref changes
109 [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_FILE_TRANSFER];
110 preferences = [[ESFileTransferPreferences preferencePane] retain];
112 //Set up the file transfer progress window
113 [self configureFileTransferProgressWindow];
116 - (void)closeController
118 [[adium preferenceController] unregisterPreferenceObserver:self];
125 [safeFileExtensions release]; safeFileExtensions = nil;
126 [fileTransferArray release]; fileTransferArray = nil;
129 #pragma mark Access to file transfer objects
130 - (ESFileTransfer *)newFileTransferWithContact:(AIListContact *)inContact forAccount:(AIAccount *)inAccount
132 ESFileTransfer *fileTransfer;
134 fileTransfer = [ESFileTransfer fileTransferWithContact:inContact
135 forAccount:inAccount];
136 [fileTransferArray addObject:fileTransfer];
137 [fileTransfer setStatus:Not_Started_FileTransfer];
139 //Wait until the next run loop to inform observers of the new file transfer object;
140 //this way the code which requested a new ESFileTransfer has time to configure it before we
141 //dispaly information to the user
142 [[adium notificationCenter] performSelector:@selector(postNotificationName:object:)
143 withObject:FileTransfer_NewFileTransfer
144 withObject:fileTransfer
147 return(fileTransfer);
150 - (NSArray *)fileTransferArray
152 return(fileTransferArray);
155 //Remove a file transfer from our array.
156 - (void)_removeFileTransfer:(ESFileTransfer *)fileTransfer
158 [fileTransferArray removeObject:fileTransfer];
161 #pragma mark Sending and receiving
162 //Sent by an account when it gets a request for us to receive a file; prompt the user for a save location
163 - (NSWindowController *)receiveRequestForFileTransfer:(ESFileTransfer *)fileTransfer
165 AIListContact *listContact = [fileTransfer contact];
166 NSString *localFilename = nil;
167 NSWindowController *promptController = nil;
169 [fileTransfer setType:Incoming_FileTransfer];
171 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_REQUEST
172 forListObject:listContact
173 userInfo:fileTransfer
174 previouslyPerformedActionIDs:nil];
176 if((autoAcceptType == AutoAccept_All) ||
177 ((autoAcceptType == AutoAccept_FromContactList) && (![listContact isStranger]))){
178 NSString *preferredDownloadFolder = [[adium preferenceController] userPreferredDownloadFolder];
179 NSString *remoteFilename = [fileTransfer remoteFilename];
181 //If we should autoaccept, determine the local filename and proceed to accept the request
182 localFilename = [preferredDownloadFolder stringByAppendingPathComponent:remoteFilename];
184 /* If the file does not exist, immediately accept the receive request.
185 * If it does, display a Save As dialog.
187 if(![[NSFileManager defaultManager] fileExistsAtPath:localFilename]){
188 [self _finishReceiveRequestForFileTransfer:fileTransfer localFilename:localFilename];
191 //Prompt for a location to save; savePanelDidEnd::: will release the retained fileTransfer and the savePanel
192 [[[NSSavePanel savePanel] retain] beginSheetForDirectory:preferredDownloadFolder
196 didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:)
197 contextInfo:[fileTransfer retain]];
201 //Prompt to accept/deny
202 promptController = [ESFileTransferRequestPromptController displayPromptForFileTransfer:fileTransfer
204 selector:@selector(_finishReceiveRequestForFileTransfer:localFilename:)];
207 return promptController;
211 - (void)savePanelDidEnd:(NSSavePanel *)savePanel returnCode:(int)returnCode contextInfo:(ESFileTransfer *)fileTransfer
213 NSString *localFilename = nil;
215 if(returnCode == NSOKButton){
216 localFilename = [savePanel filename];
219 //Pass nil to cancel, if the user didn't press OK
220 [self _finishReceiveRequestForFileTransfer:fileTransfer
221 localFilename:localFilename];
223 //Match the retain made when invoking the save panel above
224 [fileTransfer release];
226 //release the save panel too
231 * @brief Finish the receive request process
233 * Called by either ESFileTransferRequestPromptController or self, this method is the last step in accepting or
234 * refusing a request to be sent a file.
236 * @param fileTransfer The file transfer in question
237 * @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.
239 - (void)_finishReceiveRequestForFileTransfer:(ESFileTransfer *)fileTransfer localFilename:(NSString *)localFilename
242 [fileTransfer setLocalFilename:localFilename];
243 [fileTransfer setStatus:Accepted_FileTransfer];
245 [(AIAccount<AIAccount_Files> *)[fileTransfer account] acceptFileTransferRequest:fileTransfer];
247 if(showProgressWindow){
248 [self showProgressWindowIfNotOpen:nil];
252 [(AIAccount<AIAccount_Files> *)[fileTransfer account] rejectFileReceiveRequest:fileTransfer];
253 [fileTransfer setStatus:Canceled_Local_FileTransfer];
257 //Prompt the user for the file to send via an Open File dialogue
258 - (void)requestForSendingFileToListContact:(AIListContact *)listContact
260 NSOpenPanel *openPanel = [NSOpenPanel openPanel];
261 [openPanel setTitle:[NSString stringWithFormat:AILocalizedString(@"Send File to %@",nil),[listContact displayName]]];
263 if([openPanel runModalForDirectory:nil file:nil types:nil] == NSOKButton){
264 [self sendFile:[openPanel filename] toListContact:listContact];
268 - (NSString *)pathToArchiveOfFolder:(NSString *)inPath
270 NSString *pathToArchive = nil;
271 NSFileManager *defaultManager = [NSFileManager defaultManager];
274 //Desired folder: /private/tmp/$UID/`uuidgen`
275 tmpDir = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
277 NSString *launchPath = [[[@"/" stringByAppendingPathComponent:@"usr"] stringByAppendingPathComponent:@"bin"] stringByAppendingPathComponent:@"zip"];
279 //Proceed only if /usr/bin/zip exists
280 if ([defaultManager fileExistsAtPath:launchPath]) {
281 NSString *folderName = [inPath lastPathComponent];
282 NSString *destinationName = folderName;
285 int uniqueNameCounter = 0;
288 //Ensure our temporary directory exists [it never will the first time this method is called]
289 [defaultManager createDirectoryAtPath:tmpDir attributes:nil];
291 //Get a unique name if necessary. This could happen if we are sending this folder multiple times.
292 while([defaultManager fileExistsAtPath:[tmpDir stringByAppendingPathComponent:destinationName]]){
293 destinationName = [NSString stringWithFormat:@"%@-%i",folderName,++uniqueNameCounter];
296 pathToArchive = [tmpDir stringByAppendingPathComponent:[destinationName stringByAppendingPathExtension:@"zip"]];
298 arguments = [NSArray arrayWithObjects:
299 @"-r", //we'll want to store recursively
300 @"-1", //use the fastest level of compression that isn't storage; the user can compress manually to do better
302 pathToArchive, //output to our destination name
303 folderName, //store the folder
306 zipTask = [[NSTask alloc] init];
307 [zipTask setLaunchPath:launchPath];
308 [zipTask setArguments:arguments];
309 [zipTask setCurrentDirectoryPath:[inPath stringByDeletingLastPathComponent]];
313 [zipTask waitUntilExit];
314 success = ([zipTask terminationStatus] == 0);
316 /* No exception handler needed */
320 if(!success) pathToArchive = nil;
324 return pathToArchive;
327 //Initiate sending a file at a specified path to listContact
328 - (void)sendFile:(NSString *)inPath toListContact:(AIListContact *)listContact
331 ESFileTransfer *fileTransfer;
333 if (account = [[adium accountController] preferredAccountForSendingContentType:FILE_TRANSFER_TYPE
334 toContact:listContact]){
335 NSFileManager *defaultManager = [NSFileManager defaultManager];
338 //Set up a fileTransfer object
339 fileTransfer = [self newFileTransferWithContact:listContact
341 [fileTransfer setType:Outgoing_FileTransfer];
343 if([defaultManager fileExistsAtPath:inPath isDirectory:&isDir]){
344 //If we get a directory, compress it first (this could be specified on a per-account basis later if we have services supporting folder transfer)
346 inPath = [self pathToArchiveOfFolder:inPath];
350 [fileTransfer setLocalFilename:inPath];
351 [fileTransfer setSize:[[[defaultManager fileAttributesAtPath:inPath
352 traverseLink:YES] objectForKey:NSFileSize] longValue]];
354 //The fileTransfer object should now have everything the account needs to begin transferring
355 [(AIAccount<AIAccount_Files> *)account beginSendOfFileTransfer:fileTransfer];
357 if(showProgressWindow){
358 [self showProgressWindowIfNotOpen:nil];
365 //Menu or context menu item for sending a file was selected - possible only when a listContact is selected
366 - (IBAction)sendFileToSelectedContact:(id)sender
368 //Get the "selected" list object (contact list or message window)
369 AIListObject *selectedObject;
370 AIListContact *listContact = nil;
372 selectedObject = [[adium contactController] selectedListObject];
373 if ([selectedObject isKindOfClass:[AIListContact class]]){
374 listContact = [[adium contactController] preferredContactForContentType:FILE_TRANSFER_TYPE
375 forListContact:(AIListContact *)selectedObject];
379 [self requestForSendingFileToListContact:listContact];
382 //Prompt for a new contact with the current tab's name
383 - (IBAction)contextualMenuSendFile:(id)sender
385 AIListObject *selectedObject = [[adium menuController] currentContextMenuObject];
386 AIListContact *listContact = [[adium contactController] preferredContactForContentType:FILE_TRANSFER_TYPE
387 forListContact:(AIListContact *)selectedObject];
389 [NSApp activateIgnoringOtherApps:YES];
390 [self requestForSendingFileToListContact:listContact];
393 #pragma mark Status updates
394 - (void)fileTransfer:(ESFileTransfer *)fileTransfer didSetStatus:(FileTransferStatus)status
397 case Accepted_FileTransfer:
399 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_BEGAN
400 forListObject:[fileTransfer contact]
401 userInfo:fileTransfer
402 previouslyPerformedActionIDs:nil];
404 if(showProgressWindow){
405 [self showProgressWindowIfNotOpen:nil];
410 case Complete_FileTransfer:
412 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_COMPLETE
413 forListObject:[fileTransfer contact]
414 userInfo:fileTransfer
415 previouslyPerformedActionIDs:nil];
417 //The file is complete; if we are supposed to automatically open safe files and this is one, open it
418 if([self shouldOpenCompleteFileTransfer:fileTransfer]){
419 [fileTransfer openFile];
422 if(autoClearCompletedTransfers){
423 [ESFileTransferProgressWindowController removeFileTransfer:fileTransfer];
424 [self _removeFileTransfer:fileTransfer];
428 case Canceled_Remote_FileTransfer:
430 [[adium contactAlertsController] generateEvent:FILE_TRANSFER_CANCELED
431 forListObject:[fileTransfer contact]
432 userInfo:fileTransfer
433 previouslyPerformedActionIDs:nil];
441 - (BOOL)shouldOpenCompleteFileTransfer:(ESFileTransfer *)fileTransfer
443 BOOL shouldOpen = NO;
446 ([fileTransfer type] == Incoming_FileTransfer)){
448 if(!safeFileExtensions) safeFileExtensions = [SAFE_FILE_EXTENSIONS_SET retain];
450 shouldOpen = [safeFileExtensions containsObject:[[[fileTransfer localFilename] pathExtension] lowercaseString]];
456 - (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
458 AIListContact *listContact = nil;
460 if(menuItem == menuItem_sendFile){
461 AIListObject *selectedObject = [[adium contactController] selectedListObject];
462 if (selectedObject && [selectedObject isKindOfClass:[AIListContact class]]){
463 listContact = [[adium contactController] preferredContactForContentType:FILE_TRANSFER_TYPE
464 forListContact:(AIListContact *)selectedObject];
467 return(listContact != nil);
469 }else if(menuItem == menuItem_sendFileContext){
470 AIListObject *selectedObject = [[adium menuController] currentContextMenuObject];
471 if (selectedObject && [selectedObject isKindOfClass:[AIListContact class]]){
472 listContact = [[adium contactController] preferredContactForContentType:FILE_TRANSFER_TYPE
473 forListContact:(AIListContact *)selectedObject];
476 return(listContact != nil);
478 }else if(menuItem == menuItem_showFileTransferProgress){
486 - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
488 AIListContact *listContact = nil;
490 AIListObject *selectedObject = [[adium contactController] selectedListObject];
491 if (selectedObject && [selectedObject isKindOfClass:[AIListContact class]]){
492 listContact = [[adium contactController] preferredContactForContentType:FILE_TRANSFER_TYPE
493 forListContact:(AIListContact *)selectedObject];
496 return(listContact != nil);
499 #pragma mark File transfer progress window
500 - (void)configureFileTransferProgressWindow
502 //Add the File Transfer Progress window menuItem
503 menuItem_showFileTransferProgress = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"File Transfers",nil)
505 action:@selector(showProgressWindow:)
507 [menuItem_showFileTransferProgress setKeyEquivalentModifierMask:(NSCommandKeyMask | NSAlternateKeyMask)];
508 [[adium menuController] addMenuItem:menuItem_showFileTransferProgress toLocation:LOC_Window_Auxiliary];
511 //Show the file transfer progress window
512 - (void)showProgressWindow:(id)sender
514 [ESFileTransferProgressWindowController showFileTransferProgressWindow];
517 - (void)showProgressWindowIfNotOpen:(id)sender
519 [ESFileTransferProgressWindowController showFileTransferProgressWindowIfNotOpen];
522 #pragma mark Preferences
523 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
524 object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
526 autoAcceptType = [[prefDict objectForKey:KEY_FT_AUTO_ACCEPT] intValue];
527 autoOpenSafe = [[prefDict objectForKey:KEY_FT_AUTO_OPEN_SAFE] boolValue];
528 autoClearCompletedTransfers = [[prefDict objectForKey:KEY_FT_AUTO_CLEAR_COMPLETED] boolValue];
530 //If we created a safe file extensions set and no longer need it, desroy it
531 if(!autoOpenSafe && safeFileExtensions){
532 [safeFileExtensions release]; safeFileExtensions = nil;
535 showProgressWindow = [[prefDict objectForKey:KEY_FT_SHOW_PROGRESS_WINDOW] boolValue];
538 #pragma mark AIEventHandler
540 - (NSString *)shortDescriptionForEventID:(NSString *)eventID { return @""; }
542 - (NSString *)globalShortDescriptionForEventID:(NSString *)eventID
544 NSString *description;
546 if([eventID isEqualToString:FILE_TRANSFER_REQUEST]){
547 description = AILocalizedString(@"File transfer requested",nil);
548 }else if([eventID isEqualToString:FILE_TRANSFER_BEGAN]){
549 description = AILocalizedString(@"File transfer begins",nil);
550 }else if([eventID isEqualToString:FILE_TRANSFER_CANCELED]){
551 description = AILocalizedString(@"File transfer canceled by the other side",nil);
552 }else if([eventID isEqualToString:FILE_TRANSFER_COMPLETE]){
553 description = AILocalizedString(@"File transfer completed successfully",nil);
561 //Evan: This exists because old X(tras) relied upon matching the description of event IDs, and I don't feel like making
562 //a converter for old packs. If anyone wants to fix this situation, please feel free :)
563 - (NSString *)englishGlobalShortDescriptionForEventID:(NSString *)eventID
565 NSString *description;
567 if([eventID isEqualToString:FILE_TRANSFER_REQUEST]){
568 description = @"File Transfer Request";
569 }else if([eventID isEqualToString:FILE_TRANSFER_BEGAN]){
570 description = @"File Transfer Began";
571 }else if([eventID isEqualToString:FILE_TRANSFER_CANCELED]){
572 description = @"File Transfer Canceled Remotely";
573 }else if([eventID isEqualToString:FILE_TRANSFER_COMPLETE]){
574 description = @"File Transfer Complete";
582 - (NSString *)longDescriptionForEventID:(NSString *)eventID forListObject:(AIListObject *)listObject
584 NSString *description;
586 if([eventID isEqualToString:FILE_TRANSFER_REQUEST]){
587 description = AILocalizedString(@"When a file transfer is requested",nil);
588 }else if([eventID isEqualToString:FILE_TRANSFER_BEGAN]){
589 description = AILocalizedString(@"When a file transfer begins",nil);
590 }else if([eventID isEqualToString:FILE_TRANSFER_CANCELED]){
591 description = AILocalizedString(@"When a file transfer is canceled remotely",nil);
592 }else if([eventID isEqualToString:FILE_TRANSFER_COMPLETE]){
593 description = AILocalizedString(@"When a file transfer is completed successfully",nil);
601 - (NSString *)naturalLanguageDescriptionForEventID:(NSString *)eventID
602 listObject:(AIListObject *)listObject
603 userInfo:(id)userInfo
604 includeSubject:(BOOL)includeSubject
606 NSString *description = nil;
607 NSString *displayName, *displayFilename;
608 ESFileTransfer *fileTransfer;
610 NSParameterAssert([userInfo isKindOfClass:[ESFileTransfer class]]);
611 fileTransfer = (ESFileTransfer *)userInfo;
613 displayName = [listObject displayName];
614 displayFilename = [fileTransfer displayFilename];
617 NSString *format = nil;
619 if([eventID isEqualToString:FILE_TRANSFER_REQUEST]){
620 //Should only happen for an incoming transfer
621 format = AILocalizedString(@"%@ requests to send you %@",nil);
623 }else if([eventID isEqualToString:FILE_TRANSFER_BEGAN]){
624 if([fileTransfer type] == Incoming_FileTransfer){
625 format = AILocalizedString(@"%@ began sending you %@",nil);
627 format = AILocalizedString(@"%@ began receiving %@",nil);
629 }else if([eventID isEqualToString:FILE_TRANSFER_CANCELED]){
630 format = AILocalizedString(@"%@ canceled the transfer of %@",nil);
631 }else if([eventID isEqualToString:FILE_TRANSFER_COMPLETE]){
632 if([fileTransfer type] == Incoming_FileTransfer){
633 format = AILocalizedString(@"%@ sent you %@",nil);
635 format = AILocalizedString(@"%@ received %@",nil);
640 description = [NSString stringWithFormat:format,displayName,displayFilename];
643 NSString *format = nil;
645 if([eventID isEqualToString:FILE_TRANSFER_REQUEST]){
646 //Should only happen for an incoming transfer
647 format = AILocalizedString(@"requests to send you %@",nil);
649 }else if([eventID isEqualToString:FILE_TRANSFER_BEGAN]){
650 if([fileTransfer type] == Incoming_FileTransfer){
651 format = AILocalizedString(@"began sending you %@",nil);
653 format = AILocalizedString(@"began receiving %@",nil);
655 }else if([eventID isEqualToString:FILE_TRANSFER_CANCELED]){
656 format = AILocalizedString(@"canceled the transfer of %@",nil);
657 }else if([eventID isEqualToString:FILE_TRANSFER_COMPLETE]){
658 if([fileTransfer type] == Incoming_FileTransfer){
659 format = AILocalizedString(@"sent you %@",nil);
661 format = AILocalizedString(@"received %@",nil);
666 description = [NSString stringWithFormat:format,displayFilename];
673 - (NSImage *)imageForEventID:(NSString *)eventID
675 static NSImage *eventImage = nil;
676 if(!eventImage) eventImage = [[NSImage imageNamed:@"pref-ft" forClass:[self class]] retain];
680 #pragma mark Strings for sizes
682 #define ZERO_BYTES AILocalizedString(@"Zero bytes", "no file size")
684 - (NSString *)stringForSize:(unsigned long long)inSize
688 if( inSize == 0. ) ret = ZERO_BYTES;
689 else if( inSize > 0. && inSize < 1024. ) ret = [NSString stringWithFormat:AILocalizedString( @"%lu bytes", "file size measured in bytes" ), inSize];
690 else if( inSize >= 1024. && inSize < pow( 1024., 2. ) ) ret = [NSString stringWithFormat:AILocalizedString( @"%.1f KB", "file size measured in kilobytes" ), ( inSize / 1024. )];
691 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. ) )];
692 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. ) )];
693 else if( inSize >= pow( 1024., 4. ) ) ret = [NSString stringWithFormat:AILocalizedString( @"%.4f TB", "file size measured in terabytes" ), ( inSize / pow( 1024., 4. ) )];
695 if(!ret) ret = ZERO_BYTES;
700 - (NSString *)stringForSize:(unsigned long long)inSize of:(unsigned long long)totalSize ofString:(NSString *)totalSizeString
706 }else if( inSize > 0. && inSize < 1024. ){
707 if( totalSize > 0. && totalSize < 1024. ){
708 ret = [NSString stringWithFormat:AILocalizedString( @"%lu of %lu bytes", "file sizes both measured in bytes" ), inSize, totalSize];
711 ret = [NSString stringWithFormat:AILocalizedString( @"%lu bytes of %@", "file size measured in bytes out of some other measurement" ), inSize, totalSizeString];
714 }else if( inSize >= 1024. && inSize < pow( 1024., 2. ) ){
715 if( totalSize >= 1024. && totalSize < pow( 1024., 2. ) ){
716 ret = [NSString stringWithFormat:AILocalizedString( @"%.1f of %.1f KB", "file sizes both measured in kilobytes" ), ( inSize / 1024. ), ( totalSize / 1024. )];
719 ret = [NSString stringWithFormat:AILocalizedString( @"%.1f KB of %@", "file size measured in kilobytes out of some other measurement" ), ( inSize / 1024. ), totalSizeString];
722 else if( inSize >= pow( 1024., 2. ) && inSize < pow( 1024., 3. ) ){
723 if( totalSize >= pow( 1024., 2. ) && totalSize < pow( 1024., 3. ) ){
724 ret = [NSString stringWithFormat:AILocalizedString( @"%.2f of %.2f MB", "file sizes both measured in megabytes" ), ( inSize / pow( 1024., 2. ) ), ( totalSize / pow( 1024., 2. ) )];
726 ret = [NSString stringWithFormat:AILocalizedString( @"%.2f MB of %@", "file size measured in megabytes out of some other measurement" ), ( inSize / pow( 1024., 2. ) ), totalSizeString];
729 else if( inSize >= pow( 1024., 3. ) && inSize < pow( 1024., 4. ) ){
730 if( totalSize >= pow( 1024., 3. ) && totalSize < pow( 1024., 4. ) ){
731 ret = [NSString stringWithFormat:AILocalizedString( @"%.3f of %.3f GB", "file sizes both measured in gigabytes" ), ( inSize / pow( 1024., 3. ) ), ( totalSize / pow( 1024., 3. ) )];
733 ret = [NSString stringWithFormat:AILocalizedString( @"%.3f GB of %@", "file size measured in gigabytes out of some other measurement" ), ( inSize / pow( 1024., 3. ) ), totalSizeString];
737 else if( inSize >= pow( 1024., 4. ) ){
738 if( totalSize >= pow( 1024., 4. ) ){
739 ret = [NSString stringWithFormat:AILocalizedString( @"%.4f of %.4f TB", "file sizes both measured in terabytes" ), ( inSize / pow( 1024., 4. ) ), ( totalSize / pow( 1024., 4. ) )];
741 ret = [NSString stringWithFormat:AILocalizedString( @"%.4f TB of %@", "file size measured in terabytes out of some other measurement" ), ( inSize / pow( 1024., 4. ) ), totalSizeString];
745 if(!ret) ret = ZERO_BYTES;