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 "ESFileTransferProgressRow.h"
18 #import "ESFileTransferProgressView.h"
19 #import "ESFileTransferProgressWindowController.h"
20 #import <AIUtilities/AIMenuAdditions.h>
21 #import <Adium/AIListObject.h>
22 #import <Adium/AIUserIcons.h>
23 #import <Adium/ESFileTransfer.h>
25 #define BYTES_RECEIVED [NSString stringWithFormat:AILocalizedString(@"%@ received","(a bytes string) received"),bytesString]
26 #define BYTES_SENT [NSString stringWithFormat:AILocalizedString(@"%@ sent","(a bytes string) sent"),bytesString]
28 @interface ESFileTransferProgressRow (PRIVATE)
29 - (NSString *)stringForSize:(unsigned long long)size;
30 - (NSString *)stringForSize:(unsigned long long)inSize of:(unsigned long long)totalSize ofString:(NSString *)totalSizeString;
31 - (NSString *)readableTimeForSecs:(NSTimeInterval)secs inLongFormat:(BOOL)longFormat;
32 - (id)initForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)owner;
33 - (void)updateIconImage;
34 - (void)updateSourceAndDestination;
37 @implementation ESFileTransferProgressRow
39 + (ESFileTransferProgressRow *)rowForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)inOwner
41 return([[[ESFileTransferProgressRow alloc] initForFileTransfer:inFileTransfer withOwner:inOwner] autorelease]);
44 - (id)initForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)inOwner
46 if((self = [super init])) {
50 fileTransfer = [inFileTransfer retain];
51 [fileTransfer setDelegate:self];
55 [NSBundle loadNibNamed:@"ESFileTransferProgressView" owner:self];
64 [fileTransfer setDelegate:nil];
65 [fileTransfer release];
66 [view release]; view = nil;
67 [sizeString release]; sizeString = nil;
72 - (ESFileTransfer *)fileTransfer
77 - (ESFileTransferProgressView *)view
84 //If we already know something about this file transfer, update since we missed delegate calls
85 [self fileTransfer:fileTransfer didSetSize:[fileTransfer size]];
86 [self fileTransfer:fileTransfer didSetLocalFilename:[fileTransfer localFilename]];
87 [self fileTransfer:fileTransfer didSetType:[fileTransfer type]];
89 //This always calls gotUpdate and display, so do it last
90 [self fileTransfer:fileTransfer didSetStatus:[fileTransfer status]];
92 //Once we've set up some basic information, tell our owner it can add the view
93 [owner progressRowDidAwakeFromNib:self];
95 [self performSelector:@selector(informOfAwakefromNib)
101 - (void)informOfAwakefromNib
103 //Once we've set up some basic information, tell our owner it can add the view
104 [owner progressRowDidAwakeFromNib:self];
108 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetType:(FileTransferType)type
110 [self updateSourceAndDestination];
111 [self updateIconImage];
113 [owner progressRowDidChangeType:self];
116 - (FileTransferType)type
118 return([fileTransfer type]);
121 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetSize:(unsigned long long)inSize
125 [sizeString release];
126 sizeString = [[[adium fileTransferController] stringForSize:size] retain];
129 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetLocalFilename:(NSString *)inLocalFilename
131 NSString *filename = [inLocalFilename lastPathComponent];
133 //If we don't have a local file name, try to use the remote file name.
134 if(!filename) filename = [[inFileTransfer remoteFilename] lastPathComponent];
136 [view setFileName:filename];
138 [self updateIconImage];
141 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetStatus:(FileTransferStatus)inStatus
144 [self gotUpdateForFileTransfer:inFileTransfer];
146 [view setAllowsCancel:![inFileTransfer isStopped]];
147 [owner progressRowDidChangeStatus:self];
149 [[view window] display];
153 //Handle progress, bytes transferred/bytes total, rate, and time remaining
154 - (void)gotUpdateForFileTransfer:(ESFileTransfer *)inFileTransfer
156 UInt32 updateTick = TickCount();
157 FileTransferStatus status = [inFileTransfer status];
159 //Don't update continously; on a LAN transfer, for instance, we'll get almost constant updates
160 if(lastUpdateTick && (((updateTick - lastUpdateTick) / 60.0) < 0.2) && (status == In_Progress_FileTransfer) && !forceUpdate){
164 unsigned long long bytesSent = [inFileTransfer bytesSent];
165 NSString *transferBytesStatus = nil, *transferSpeedStatus = nil, *transferRemainingStatus = nil;
166 FileTransferType type = [inFileTransfer type];
169 size = [inFileTransfer size];
171 [sizeString release];
172 sizeString = [[[adium fileTransferController] stringForSize:size] retain];
176 case Unknown_Status_FileTransfer:
177 case Not_Started_FileTransfer:
178 case Accepted_FileTransfer:
179 [view setProgressIndeterminate:YES];
180 [view setProgressAnimation:YES];
181 transferSpeedStatus = AILocalizedString(@"Waiting to start.","waiting to begin a file transfer status");
184 case In_Progress_FileTransfer:
185 [view setProgressIndeterminate:NO];
186 [view setProgressDoubleValue:[inFileTransfer percentDone]];
188 case Complete_FileTransfer:
189 [view setProgressVisible:NO];
190 transferSpeedStatus = AILocalizedString(@"Complete",nil);
192 case Canceled_Local_FileTransfer:
193 case Canceled_Remote_FileTransfer:
194 [view setProgressVisible:NO];
195 transferSpeedStatus = AILocalizedString(@"Stopped",nil);
199 if(type == Unknown_FileTransfer || status == Unknown_Status_FileTransfer || status == Not_Started_FileTransfer){
200 transferBytesStatus = [NSString stringWithFormat:AILocalizedString(@"Initiating file transfer...",nil)];
203 case Accepted_FileTransfer:
204 transferBytesStatus = [NSString stringWithFormat:AILocalizedString(@"Accepted file transfer...",nil)];
206 case In_Progress_FileTransfer:
208 NSString *bytesString = [[adium fileTransferController] stringForSize:bytesSent
210 ofString:sizeString];
213 case Incoming_FileTransfer:
214 transferBytesStatus = BYTES_RECEIVED;
216 case Outgoing_FileTransfer:
217 transferBytesStatus = BYTES_SENT;
225 case Complete_FileTransfer:
227 NSString *bytesString = [[adium fileTransferController] stringForSize:bytesSent];
229 case Incoming_FileTransfer:
230 transferBytesStatus = BYTES_RECEIVED;
232 case Outgoing_FileTransfer:
233 transferBytesStatus = BYTES_SENT;
241 case Canceled_Local_FileTransfer:
242 transferBytesStatus = AILocalizedString(@"Canceled","File transfer canceled locally status description");
244 case Canceled_Remote_FileTransfer:
245 transferBytesStatus = AILocalizedString(@"Remote contact canceled","File transfer canceled remotely status description");
252 if((status == In_Progress_FileTransfer) && lastUpdateTick && lastBytesSent){
253 if(updateTick != lastUpdateTick){
254 unsigned long long rate;
256 rate = ((bytesSent - lastBytesSent) / ((updateTick - lastUpdateTick) / 60.0));
257 transferSpeedStatus = [NSString stringWithFormat:AILocalizedString(@"%@ per sec.",nil),[[adium fileTransferController] stringForSize:rate]];
260 unsigned long long secsRemaining = ((size - bytesSent) / rate);
261 transferRemainingStatus = [NSString stringWithFormat:AILocalizedString(@"%@ remaining.",nil),[self readableTimeForSecs:secsRemaining inLongFormat:YES]];
264 transferRemainingStatus = AILocalizedString(@"Stalled","file transfer is stalled status message");
269 [view setTransferBytesStatus:transferBytesStatus
270 remainingStatus:transferRemainingStatus
271 speedStatus:transferSpeedStatus];
272 [view setNeedsDisplay:YES];
274 lastBytesSent = bytesSent;
275 lastUpdateTick = updateTick;
278 - (void)updateIconImage
282 if(iconImage = [fileTransfer iconImage]){
283 [view setIconImage:iconImage];
287 - (void)updateSourceAndDestination
289 AIListObject *source = [fileTransfer source];
290 AIListObject *destination = [fileTransfer destination];
292 [view setSourceName:[source formattedUID]];
293 [view setSourceIcon:[AIUserIcons menuUserIconForObject:source]];
295 [view setDestinationName:[destination formattedUID]];
296 [view setDestinationIcon:[AIUserIcons menuUserIconForObject:destination]];
300 #pragma mark Button actions
301 - (IBAction)stopResumeAction:(id)sender
303 [fileTransfer cancel];
305 - (IBAction)revealAction:(id)sender
307 [fileTransfer reveal];
309 - (IBAction)openFileAction:(id)sender
311 if([fileTransfer status] == Complete_FileTransfer){
312 [fileTransfer openFile];
315 - (void)removeRowAction:(id)sender
317 if([fileTransfer isStopped]){
318 [owner _removeFileTransferRow:self];
322 #pragma mark Contextual menu
323 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
325 NSMenu *contextualMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
326 NSMenuItem *menuItem;
328 //Allow open and show in finder on complete incoming transfers and all outgoing transfers
329 if(([fileTransfer status] == Complete_FileTransfer) ||
330 ([fileTransfer type] == Outgoing_FileTransfer)){
331 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Open",nil)
333 action:@selector(openFileAction:)
334 keyEquivalent:@""] autorelease];
335 [contextualMenu addItem:menuItem];
337 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Show in Finder",nil)
339 action:@selector(revealAction:)
340 keyEquivalent:@""] autorelease];
341 [contextualMenu addItem:menuItem];
345 if([fileTransfer isStopped]){
346 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Remove from List",nil)
348 action:@selector(removeRowAction:)
349 keyEquivalent:@""] autorelease];
350 [contextualMenu addItem:menuItem];
352 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Cancel",nil)
354 action:@selector(stopResumeAction:)
355 keyEquivalent:@""] autorelease];
356 [contextualMenu addItem:menuItem];
359 return([contextualMenu autorelease]);
362 //Pass height change information on to our owner
363 - (void)fileTransferProgressView:(ESFileTransferProgressView *)inView
364 heightChangedFrom:(float)oldHeight
367 [owner fileTransferProgressRow:self
368 heightChangedFrom:oldHeight
372 #pragma mark Localized readable values
374 - (NSString *)readableTimeForSecs:(NSTimeInterval)secs inLongFormat:(BOOL)longFormat
376 unsigned int i = 0, stop = 0;
377 NSDictionary *desc = [NSDictionary dictionaryWithObjectsAndKeys:AILocalizedString( @"second", "singular second" ), [NSNumber numberWithUnsignedInt:1], AILocalizedString( @"minute", "singular minute" ), [NSNumber numberWithUnsignedInt:60], AILocalizedString( @"hour", "singular hour" ), [NSNumber numberWithUnsignedInt:3600], AILocalizedString( @"day", "singular day" ), [NSNumber numberWithUnsignedInt:86400], AILocalizedString( @"week", "singular week" ), [NSNumber numberWithUnsignedInt:604800], AILocalizedString( @"month", "singular month" ), [NSNumber numberWithUnsignedInt:2628000], AILocalizedString( @"year", "singular year" ), [NSNumber numberWithUnsignedInt:31536000], nil];
378 NSDictionary *plural = [NSDictionary dictionaryWithObjectsAndKeys:AILocalizedString( @"seconds", "plural seconds" ), [NSNumber numberWithUnsignedInt:1], AILocalizedString( @"minutes", "plural minutes" ), [NSNumber numberWithUnsignedInt:60], AILocalizedString( @"hours", "plural hours" ), [NSNumber numberWithUnsignedInt:3600], AILocalizedString( @"days", "plural days" ), [NSNumber numberWithUnsignedInt:86400], AILocalizedString( @"weeks", "plural weeks" ), [NSNumber numberWithUnsignedInt:604800], AILocalizedString( @"months", "plural months" ), [NSNumber numberWithUnsignedInt:2628000], AILocalizedString( @"years", "plural years" ), [NSNumber numberWithUnsignedInt:31536000], nil];
379 NSDictionary *use = nil;
380 NSMutableArray *breaks = nil;
381 unsigned int val = 0.;
382 NSString *retval = nil;
384 if( secs < 0 ) secs *= -1;
386 breaks = [[[desc allKeys] mutableCopy] autorelease];
387 [breaks sortUsingSelector:@selector( compare: )];
389 while( i < [breaks count] && secs >= (NSTimeInterval) [[breaks objectAtIndex:i] unsignedIntValue] ) i++;
391 stop = [[breaks objectAtIndex:i] unsignedIntValue];
393 val = (unsigned int) ( secs / stop );
394 use = ( val > 1 ? plural : desc );
395 retval = [NSString stringWithFormat:@"%d %@", val, [use objectForKey:[NSNumber numberWithUnsignedInt:stop]]];
396 if( longFormat && i > 0 ) {
397 unsigned int rest = (unsigned int) ( (unsigned int) secs % stop );
398 stop = [[breaks objectAtIndex:--i] unsignedIntValue];
399 rest = (unsigned int) ( rest / stop );
401 use = ( rest > 1 ? plural : desc );
402 retval = [retval stringByAppendingFormat:@" %d %@", rest, [use objectForKey:[breaks objectAtIndex:i]]];