Unescape the HREF attribute's text before passing it to NSURL which does not expect...
[adiumx.git] / Source / ESFileTransferProgressRow.m
blobbdaacf852ccf215a7264a6bd5e913a4ad4c1d9a3
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
17 #import "ESFileTransferProgressRow.h"
18 #import "ESFileTransferProgressView.h"
19 #import "ESFileTransferProgressWindowController.h"
20 #import <Adium/AIListObject.h>
21 #import <Adium/AIUserIcons.h>
22 #import "ESFileTransfer.h"
23 #import <AIUtilities/AIMenuAdditions.h>
24 #import <AIUtilities/AIStringAdditions.h>
26 #define BYTES_RECEIVED          [NSString stringWithFormat:AILocalizedString(@"%@ received","%@ will be replaced by a string such as '5 MB' in the file transfer window"),bytesString]
27 #define BYTES_SENT                      [NSString stringWithFormat:AILocalizedString(@"%@ sent","%@ will be replaced by a string such as '5 MB' in the file transfer window"),bytesString]
28 #define BUFFER_SIZE                     25
30 @interface ESFileTransferProgressRow (PRIVATE)
31 - (NSString *)stringForSize:(unsigned long long)size;
32 - (NSString *)stringForSize:(unsigned long long)inSize of:(unsigned long long)totalSize ofString:(NSString *)totalSizeString;
33 - (NSString *)readableTimeForSecs:(NSTimeInterval)secs inLongFormat:(BOOL)longFormat;
34 - (id)initForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)owner;
35 - (void)updateIconImage;
36 - (void)updateSourceAndDestination;
37 @end
39 @implementation ESFileTransferProgressRow
41 + (ESFileTransferProgressRow *)rowForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)inOwner
43         return [[[ESFileTransferProgressRow alloc] initForFileTransfer:inFileTransfer withOwner:inOwner] autorelease];
46 - (id)initForFileTransfer:(ESFileTransfer *)inFileTransfer withOwner:(id)inOwner
48         if ((self = [super init])) {
49                 sizeString = nil;
50                 forceUpdate = NO;
52                 fileTransfer = [inFileTransfer retain];
53                 [fileTransfer setDelegate:self];
55                 owner = inOwner;
56                 
57                 bytesSentQueue = [[NSMutableArray alloc] init];
58                 updateTickQueue = [[NSMutableArray alloc] init];
60                 [NSBundle loadNibNamed:@"ESFileTransferProgressView" owner:self];
61         }
62         
63         return self;
66 - (void)dealloc
68         owner = nil;
69         [fileTransfer setDelegate:nil];
70         [fileTransfer release];
71         [view release]; view = nil;
72         [sizeString release]; sizeString = nil;
73         
74         [bytesSentQueue release]; bytesSentQueue = nil;
75         [updateTickQueue release]; updateTickQueue = nil;
77         [super dealloc];
80 - (ESFileTransfer *)fileTransfer
82         return fileTransfer;
85 - (ESFileTransferProgressView *)view
86 {       
87         return view;
90 - (void)awakeFromNib
91 {       
92         //If we already know something about this file transfer, update since we missed delegate calls
93         [self fileTransfer:fileTransfer didSetSize:[fileTransfer size]];
94         [self fileTransfer:fileTransfer didSetLocalFilename:[fileTransfer localFilename]];
95         [self fileTransfer:fileTransfer didSetType:[fileTransfer fileTransferType]];
97         //This always calls gotUpdate and display, so do it last
98         [self fileTransfer:fileTransfer didSetStatus:[fileTransfer status]];
100         //Once we've set up some basic information, tell our owner it can add the view
101         [owner progressRowDidAwakeFromNib:self];
102         /*
103         [self performSelector:@selector(informOfAwakefromNib)
104                            withObject:nil
105                            afterDelay:0.000001];
106          */
109 - (void)informOfAwakefromNib
111         //Once we've set up some basic information, tell our owner it can add the view
112         [owner progressRowDidAwakeFromNib:self];
116 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetType:(AIFileTransferType)type
118         [self updateSourceAndDestination];
119         [self updateIconImage];
120         
121         [owner progressRowDidChangeType:self];
124 - (AIFileTransferType)type
126         return [fileTransfer fileTransferType];
129 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetSize:(unsigned long long)inSize
131         size = inSize;
132         
133         [sizeString release];
134         sizeString = [[[adium fileTransferController] stringForSize:size] retain];
137 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetLocalFilename:(NSString *)inLocalFilename
139         NSString        *filename = [inLocalFilename lastPathComponent];
140         
141         //If we don't have a local file name, try to use the remote file name.
142         if (!filename) filename = [[inFileTransfer remoteFilename] lastPathComponent];
143         
144         [view setFileName:filename];
146         [self updateIconImage];
149 - (void)fileTransfer:(ESFileTransfer *)inFileTransfer didSetStatus:(AIFileTransferStatus)inStatus
151         forceUpdate = YES;
152         [self gotUpdateForFileTransfer:inFileTransfer];
153         
154         [view setAllowsCancel:![inFileTransfer isStopped]];
155         [owner progressRowDidChangeStatus:self];
156         
157         [[view window] display];
158         forceUpdate = NO;
161 //Handle progress, bytes transferred/bytes total, rate, and time remaining
162 - (void)gotUpdateForFileTransfer:(ESFileTransfer *)inFileTransfer
164         UInt32                          updateTick = TickCount();
165         AIFileTransferStatus    status = [inFileTransfer status];
166         
167         //Don't update continously; on a LAN transfer, for instance, we'll get almost constant updates
168         if (lastUpdateTick && (((updateTick - lastUpdateTick) / 60.0) < 0.2) && (status == In_Progress_FileTransfer) && !forceUpdate) {
169                 return;
170         }
172         unsigned long long      bytesSent = [inFileTransfer bytesSent];
173         NSString                        *transferBytesStatus = nil, *transferSpeedStatus = nil, *transferRemainingStatus = nil;
174         AIFileTransferType      type = [inFileTransfer fileTransferType];
175         
176         if (!size) {
177                 size = [inFileTransfer size];
178                 
179                 [sizeString release];
180                 sizeString = [[[adium fileTransferController] stringForSize:size] retain];
181         }
183         switch (status) {
184                 case Unknown_Status_FileTransfer:
185                 case Not_Started_FileTransfer:
186                 case Accepted_FileTransfer:
187                 case Waiting_on_Remote_User_FileTransfer:
188                 case Connecting_FileTransfer:
189                         [view setProgressIndeterminate:YES];
190                         [view setProgressAnimation:YES];
191                         transferSpeedStatus = AILocalizedString(@"Waiting to start.","waiting to begin a file transfer status");
192                         break;
193                 case Checksumming_Filetransfer:
194                         [view setProgressIndeterminate:YES];
195                         [view setProgressAnimation:YES];
196                         transferSpeedStatus = [AILocalizedString(@"Preparing file","waiting to begin a file transfer status") stringByAppendingEllipsis];
197                         break;
198                 case In_Progress_FileTransfer:
199                         [view setProgressIndeterminate:NO];
200                         [view setProgressDoubleValue:[inFileTransfer percentDone]];
201                         break;
202                 case Complete_FileTransfer:
203                         [view setProgressVisible:NO];
204                         transferSpeedStatus = AILocalizedString(@"Complete",nil);
205                         break;
206                 case Cancelled_Local_FileTransfer:
207                 case Cancelled_Remote_FileTransfer:
208                         [view setProgressVisible:NO];
209                         transferSpeedStatus = AILocalizedString(@"Stopped",nil);
210                         break;
211                 case Failed_FileTransfer:
212                         [view setProgressVisible:NO];
213                         transferSpeedStatus = AILocalizedString(@"Failed",nil);
214                         break;
215         }
217         if (type == Unknown_FileTransfer) {
218                 transferBytesStatus = [AILocalizedString(@"Initiating file transfer",nil) stringByAppendingEllipsis];
220         } else {                
221                 switch (status) {
222                         case Unknown_Status_FileTransfer:
223                         case Not_Started_FileTransfer:
224                                 transferBytesStatus = [AILocalizedString(@"Initiating file transfer",nil) stringByAppendingEllipsis];
225                                 break;
226                         case Checksumming_Filetransfer:
227                                 transferBytesStatus = [AILocalizedString(@"Preparing file transfer","File transfer preparing status description") stringByAppendingEllipsis];
228                                 break;
229                         case Waiting_on_Remote_User_FileTransfer:
230                                 transferBytesStatus = [AILocalizedString(@"Waiting for transfer to be accepted","File transfer waiting on remote user status description") stringByAppendingEllipsis];
231                                 break;
232                         case Connecting_FileTransfer:
233                                 transferBytesStatus = [AILocalizedString(@"Establishing file transfer connection","File transfer connecting status description") stringByAppendingEllipsis];
234                                 break;
235                         case Accepted_FileTransfer:
236                                 transferBytesStatus = [AILocalizedString(@"Accepted file transfer",nil) stringByAppendingEllipsis];
237                         break;
238                         case In_Progress_FileTransfer:
239                         {
240                                 NSString                        *bytesString = [[adium fileTransferController] stringForSize:bytesSent
241                                                                                                                                                                                           of:size
242                                                                                                                                                                                 ofString:sizeString];
244                                 switch (type) {
245                                         case Incoming_FileTransfer:
246                                                 transferBytesStatus = BYTES_RECEIVED;
247                                                 break;
248                                         case Outgoing_FileTransfer:
249                                                 transferBytesStatus = BYTES_SENT;
250                                                 break;
251                                         default:
252                                                 break;
253                                 }
254                                 
255                                 break;
256                         }
257                         case Complete_FileTransfer:
258                         {
259                                 NSString                        *bytesString = sizeString;
260                                 switch (type) {
261                                         case Incoming_FileTransfer:
262                                                 transferBytesStatus = BYTES_RECEIVED;
263                                                 break;
264                                         case Outgoing_FileTransfer:
265                                                 transferBytesStatus = BYTES_SENT;
266                                                 break;
267                                         default:
268                                                 break;
269                                 }
270                                 
271                                 break;
272                         }
273                         case Cancelled_Local_FileTransfer:
274                                 transferBytesStatus = AILocalizedString(@"Cancelled","File transfer cancelled locally status description");
275                                 break;
276                         case Cancelled_Remote_FileTransfer:
277                                 transferBytesStatus = AILocalizedString(@"Remote contact cancelled","File transfer cancelled remotely status description");
278                                 break;
279                         case Failed_FileTransfer:
280                                 transferBytesStatus = AILocalizedString(@"Failed","File transfer failed status description");
281                                 break;
282                         default: 
283                                 break;
284                 }
285         }
286         
287         if ((status == In_Progress_FileTransfer) && lastUpdateTick && lastBytesSent) {
288                 if (updateTick != lastUpdateTick) {
289                         if ([bytesSentQueue count] == 0) {
290                                 [bytesSentQueue insertObject:[NSNumber numberWithUnsignedLongLong:lastBytesSent] atIndex:0];
291                                 [updateTickQueue insertObject:[NSNumber numberWithUnsignedLong:lastUpdateTick] atIndex:0];
292                         } else if ([bytesSentQueue count] >= BUFFER_SIZE) {
293                                 [bytesSentQueue removeObjectAtIndex:0];
294                                 [updateTickQueue removeObjectAtIndex:0];
295                         }
296                         
297                         [bytesSentQueue addObject:[NSNumber numberWithUnsignedLongLong:bytesSent]];
298                         [updateTickQueue addObject:[NSNumber numberWithUnsignedLong:updateTick]];
299                         
300                         unsigned long long      bytesDifference = bytesSent - [[bytesSentQueue objectAtIndex:0] unsignedLongLongValue];
301                         unsigned long           ticksDifference = updateTick - [[updateTickQueue objectAtIndex:0] unsignedLongValue];
302                         unsigned long long      rate = bytesDifference / (ticksDifference / 60.0);
303                         
304                         transferSpeedStatus = [NSString stringWithFormat:AILocalizedString(@"%@/sec","Rate of transfer phrase. %@ will be replaced by an abbreviated data amount such as 4 KB or 1 MB"),[[adium fileTransferController] stringForSize:rate]];
305                         
306                         if (rate > 0) {
307                                 unsigned long long secsRemaining = ((size - bytesSent) / rate);
308                                 transferRemainingStatus = [NSString stringWithFormat:AILocalizedString(@"%@ remaining","Time remaining for a file transfer to be completed phrase. %@ will be replaced by an amount of time such as '5 seconds' or '4 minutes and 30 seconds'."),[self readableTimeForSecs:secsRemaining inLongFormat:YES]];
309                         } else {
310                                 transferRemainingStatus = AILocalizedString(@"Stalled","file transfer is stalled status message");
311                         }
312                 }
313         }
314         
315         [view setTransferBytesStatus:transferBytesStatus
316                                  remainingStatus:transferRemainingStatus
317                                          speedStatus:transferSpeedStatus];
318         [view setNeedsDisplay:YES];
320         lastBytesSent = bytesSent;
321         lastUpdateTick = updateTick;
324 - (void)updateIconImage
326         NSImage *iconImage;
328         if ((iconImage = [fileTransfer iconImage])) {
329                 [view setIconImage:iconImage];          
330         }
333 - (void)updateSourceAndDestination
334 {       
335         AIListObject    *source = [fileTransfer source];
336         AIListObject    *destination = [fileTransfer destination];
337         
338         [view setSourceName:[source formattedUID]];
339         [view setSourceIcon:[AIUserIcons menuUserIconForObject:source]];
340         
341         [view setDestinationName:[destination formattedUID]];
342         [view setDestinationIcon:[AIUserIcons menuUserIconForObject:destination]];
345 //Button actions
346 #pragma mark Button actions
347 - (IBAction)stopResumeAction:(id)sender
349         [fileTransfer cancel];
351 - (IBAction)revealAction:(id)sender
353         [fileTransfer reveal];  
355 - (IBAction)openFileAction:(id)sender
357         if ([fileTransfer status] == Complete_FileTransfer) {
358                 [fileTransfer openFile];
359         }
361 - (void)removeRowAction:(id)sender
363         if ([fileTransfer isStopped]) {
364                 [owner _removeFileTransferRow:self];
365         }
368 #pragma mark Contextual menu
369 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
371         NSMenu          *contextualMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
372         NSMenuItem  *menuItem;
373         
374         //Allow open and show in finder on complete incoming transfers and all outgoing transfers
375         if (([fileTransfer status] == Complete_FileTransfer) ||
376            ([fileTransfer fileTransferType] == Outgoing_FileTransfer)) {
377                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Open",nil)
378                                                                                                                                                  target:self
379                                                                                                                                                  action:@selector(openFileAction:)
380                                                                                                                                   keyEquivalent:@""] autorelease];
381                 [contextualMenu addItem:menuItem];
383                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Show in Finder",nil)
384                                                                                                                                                  target:self
385                                                                                                                                                  action:@selector(revealAction:)
386                                                                                                                                   keyEquivalent:@""] autorelease];
387                 [contextualMenu addItem:menuItem];
388                 
389         }       
391         if ([fileTransfer isStopped]) {
392                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Remove from List",nil)
393                                                                                                                                                  target:self
394                                                                                                                                                  action:@selector(removeRowAction:)
395                                                                                                                                   keyEquivalent:@""] autorelease];
396                 [contextualMenu addItem:menuItem];      
397         } else {
398                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Cancel",nil)
399                                                                                                                                                  target:self
400                                                                                                                                                  action:@selector(stopResumeAction:)
401                                                                                                                                   keyEquivalent:@""] autorelease];
402                 [contextualMenu addItem:menuItem];
403         }       
404         
405         return [contextualMenu autorelease];
408 //Pass height change information on to our owner
409 - (void)fileTransferProgressView:(ESFileTransferProgressView *)inView
410                            heightChangedFrom:(float)oldHeight
411                                                          to:(float)newHeight
413         [owner fileTransferProgressRow:self
414                                  heightChangedFrom:oldHeight
415                                                                 to:newHeight];
418 #pragma mark Localized readable values
419 //From Colloquy
420 - (NSString *)readableTimeForSecs:(NSTimeInterval)secs inLongFormat:(BOOL)longFormat
422         unsigned int i = 0, stop = 0;
423         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];
424         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];
425         NSDictionary *use = nil;
426         NSMutableArray *breaks = nil;
427         unsigned int val = 0.;
428         NSString *retval = nil;
429         
430         if ( secs < 0 ) secs *= -1;
431         
432         breaks = [[[desc allKeys] mutableCopy] autorelease];
433         [breaks sortUsingSelector:@selector( compare: )];
434         
435         while ( i < [breaks count] && secs >= (NSTimeInterval) [[breaks objectAtIndex:i] unsignedIntValue] ) i++;
436         if ( i > 0 ) i--;
437         stop = [[breaks objectAtIndex:i] unsignedIntValue];
438         
439         val = (unsigned int) ( secs / stop );
440         use = ( val != 1 ? plural : desc );
441         retval = [NSString stringWithFormat:@"%d %@", val, [use objectForKey:[NSNumber numberWithUnsignedInt:stop]]];
442         if ( longFormat && i > 0 ) {
443                 unsigned int rest = (unsigned int) ( (unsigned int) secs % stop );
444                 stop = [[breaks objectAtIndex:--i] unsignedIntValue];
445                 rest = (unsigned int) ( rest / stop );
446                 if ( rest > 0 ) {
447                         use = ( rest > 1 ? plural : desc );
448                         retval = [retval stringByAppendingFormat:@" %d %@", rest, [use objectForKey:[breaks objectAtIndex:i]]];
449                 }
450         }
451         
452         return retval;
455 @end