Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 590 lines (518 sloc) 16.081 kB
76d2a5b @mstroeck
mstroeck authored
1 //
2 // DownloadManager.m
3 // Vienna
4 //
5 // Created by Steve on 10/7/05.
6 // Copyright (c) 2004-2005 Steve Palmer. All rights reserved.
7 //
8 // Licensed under the Apache License, Version 2.0 (the "License");
9 // you may not use this file except in compliance with the License.
10 // You may obtain a copy of the License at
11 //
12 // http://www.apache.org/licenses/LICENSE-2.0
13 //
14 // Unless required by applicable law or agreed to in writing, software
15 // distributed under the License is distributed on an "AS IS" BASIS,
16 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 // See the License for the specific language governing permissions and
18 // limitations under the License.
19 //
20
21 #import "DownloadManager.h"
22 #import "AppController.h"
23 #import "Constants.h"
24 #import "Preferences.h"
25
26 // There's just one database and we manage access to it through a
27 // singleton object.
28 static DownloadManager * _sharedDownloadManager = nil;
29
30 // Private functions
31 @interface DownloadManager (Private)
32 -(void)archiveDownloadsList;
33 -(void)unarchiveDownloadsList;
34 -(void)notifyDownloadItemChange:(DownloadItem *)item;
35 @end
36
37 @implementation DownloadItem
38
39 /* init
40 * Initialise a new DownloadItem object
41 */
42 -(id)init
43 {
44 if ((self = [super init]) != nil)
45 {
46 state = DOWNLOAD_INIT;
47 expectedSize = 0;
48 fileSize = 0;
49 filename = nil;
50 download = nil;
51 image = nil;
52 startTime = nil;
53 }
54 return self;
55 }
56
57 /* initWithCoder
58 * Initalises a decoded object. All decoded objects are assumed to be
59 * completed downloads.
60 */
61 -(id)initWithCoder:(NSCoder *)coder
62 {
63 if ((self = [super init]) != nil)
64 {
65 [self setFilename:[coder decodeObject]];
66 [coder decodeValueOfObjCType:@encode(long long) at:&fileSize];
67 state = DOWNLOAD_COMPLETED;
68 }
69 return self;
70 }
71
72 /* encodeWithCoder
73 * Encodes a single DownloadItem object for archiving.
74 */
75 -(void)encodeWithCoder:(NSCoder *)coder
76 {
77 [coder encodeObject:filename];
78 [coder encodeValueOfObjCType:@encode(long long) at:&fileSize];
79 }
80
81 /* setState
82 * Sets the download state.
83 */
84 -(void)setState:(int)newState
85 {
86 state = newState;
87 }
88
89 /* state
90 * Returns the download state.
91 */
92 -(int)state
93 {
94 return state;
95 }
96
97 /* setExpectedSize
98 * Sets the expected file size.
99 */
100 -(void)setExpectedSize:(long long)newExpectedSize
101 {
102 expectedSize = newExpectedSize;
103 fileSize = 0;
104 }
105
106 /* expectedSize
107 * Returns the expected total size of the item.
108 */
109 -(long long)expectedSize
110 {
111 return expectedSize;
112 }
113
114 /* setSize
115 * Updates the file size.
116 */
117 -(void)setSize:(long long)newSize
118 {
119 fileSize = newSize;
120 }
121
122 /* size
123 * Returns the file size.
124 */
125 -(long long)size
126 {
127 return fileSize;
128 }
129
130 /* setDownload
131 * Sets the NSURLDownload object associated with this download.
132 */
133 -(void)setDownload:(NSURLDownload *)theDownload
134 {
135 [theDownload retain];
136 [download release];
137 download = theDownload;
138 }
139
140 /* download
141 * Returns the NSURLDownload object associated with this download.
142 */
143 -(NSURLDownload *)download
144 {
145 return download;
146 }
147
148 /* setFilename
149 * Sets the filename associated with this download item. The specified filename
150 * may contain a path but only the last path component is stored.
151 */
152 -(void)setFilename:(NSString *)theFilename
153 {
154 [filename release];
155 [theFilename retain];
156 filename = theFilename;
157
158 // Force the image to be recached.
159 [image release];
160 image = nil;
161 }
162
163 /* filename
164 * Returns the download item filename.
165 */
166 -(NSString *)filename
167 {
168 return filename;
169 }
170
171 /* image
172 * Return the file image. This is always computed here.
173 */
174 -(NSImage *)image
175 {
176 if (image == nil)
177 {
178 image = [[NSWorkspace sharedWorkspace] iconForFileType:[[self filename] pathExtension]];
179 if (![image isValid])
180 image = nil;
181 else
182 {
183 [image retain];
184 [image setScalesWhenResized:YES];
185 [image setSize:NSMakeSize(32, 32)];
186 }
187 }
188 return image;
189 }
190
191 /* setStartTime
192 * Set the date/time when this file download started.
193 */
194 -(void)setStartTime:(NSDate *)newStartTime
195 {
196 [newStartTime retain];
197 [startTime release];
198 startTime = newStartTime;
199 }
200
201 /* startTime
202 * Returns the date/time when this file download started.
203 */
204 -(NSDate *)startTime
205 {
206 return startTime;
207 }
208
209 /* dealloc
210 * Clean up behind ourself.
211 */
212 -(void)dealloc
213 {
214 [filename release];
215 [download release];
216 [image release];
217 [super dealloc];
218 }
219 @end
220
221 @implementation DownloadManager
222
223 /* sharedInstance
224 * There's just one download manager, so return the single shared instance
225 * that controls access to it.
226 */
227 +(DownloadManager *)sharedInstance
228 {
229 if (_sharedDownloadManager == nil)
230 {
231 _sharedDownloadManager = [[DownloadManager alloc] init];
232 [_sharedDownloadManager unarchiveDownloadsList];
233 }
234 return _sharedDownloadManager;
235 }
236
237 /* init
238 * Initialise the DownloadManager object.
239 */
240 -(id)init
241 {
242 if ((self = [super init]) != nil)
243 {
244 downloadsList = [[NSMutableArray alloc] init];
245 activeDownloads = 0;
246 }
247 return self;
248 }
249
250 /* downloadsList
251 * Return the array of DownloadItems.
252 */
253 -(NSArray *)downloadsList
254 {
255 return downloadsList;
256 }
257
258 /* activeDownloads
259 * Return the number of downloads in progress.
260 */
261 -(int)activeDownloads
262 {
263 return activeDownloads;
264 }
265
266 /* clearList
267 * Remove all completed items from the list.
268 */
269 -(void)clearList
270 {
271 int index = [downloadsList count] - 1;
272 while (index >= 0)
273 {
274 DownloadItem * item = [downloadsList objectAtIndex:index--];
275 if ([item state] != DOWNLOAD_STARTED)
276 [downloadsList removeObject:item];
277 }
278 [self notifyDownloadItemChange:nil];
279 [self archiveDownloadsList];
280 }
281
282 /* archiveDownloadsList
283 * Archive the downloads list to the preferences.
284 */
285 -(void)archiveDownloadsList
286 {
287 NSMutableArray * listArray = [[NSMutableArray alloc] initWithCapacity:[downloadsList count]];
288
289 for (DownloadItem * item in downloadsList)
290 [listArray addObject:[NSArchiver archivedDataWithRootObject:item]];
291
292 [[Preferences standardPreferences] setArray:listArray forKey:MAPref_DownloadsList];
293 [listArray release];
294 }
295
296 /* unarchiveDownloadsList
297 * Unarchive the downloads list from the preferences.
298 */
299 -(void)unarchiveDownloadsList
300 {
301 NSArray * listArray = [[Preferences standardPreferences] arrayForKey:MAPref_DownloadsList];
302 if (listArray != nil)
303 {
304 for (NSData * dataItem in listArray)
305 [downloadsList addObject:[NSUnarchiver unarchiveObjectWithData:dataItem]];
306 }
307 }
308
309 /* removeItem
310 * Remove the specified item from the list.
311 */
312 -(void)removeItem:(DownloadItem *)item
313 {
314 [downloadsList removeObject:item];
315 [self archiveDownloadsList];
316 }
317
318 /* cancelItem
319 * Abort the specified item and remove it from the list
320 */
321 -(void)cancelItem:(DownloadItem *)item
322 {
323 [[item download] cancel];
324 [item setState:DOWNLOAD_CANCELLED];
325 NSAssert(activeDownloads > 0, @"cancelItem called with zero activeDownloads count!");
326 --activeDownloads;
327 [self notifyDownloadItemChange:item];
328 [downloadsList removeObject:item];
329 [self archiveDownloadsList];
330 }
331
332 /* downloadFile
333 * Downloads a file from the specified URL.
334 */
335 -(void)downloadFile:(NSString *)filename fromURL:(NSString *)url
336 {
337 NSURLRequest * theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
2627497 @barijaona Fix warnings related to format strings in sources directly related to…
barijaona authored
338 NSURLDownload * theDownload = [[NSURLDownload alloc] initWithRequest:theRequest delegate:(id)self];
76d2a5b @mstroeck
mstroeck authored
339 if (theDownload)
340 {
341 DownloadItem * newItem = [[DownloadItem alloc] init];
342 [newItem setState:DOWNLOAD_INIT];
343 [newItem setDownload:theDownload];
344 [newItem setFilename:filename];
345 [downloadsList addObject:newItem];
346 [newItem release];
347
348 // The following line will stop us getting decideDestinationWithSuggestedFilename.
349 [theDownload setDestination:filename allowOverwrite:YES];
350
351 [theDownload release];
352 }
353 }
354
355 /* itemForDownload
356 * Retrieves the DownloadItem for the given NSURLDownload object. We scan from the
357 * last item since the odds are that the one we want will be at the end given that
358 * new items are always appended to the list.
359 */
360 -(DownloadItem *)itemForDownload:(NSURLDownload *)download
361 {
362 int index = [downloadsList count] - 1;
363 while (index >= 0)
364 {
365 DownloadItem * item = [downloadsList objectAtIndex:index--];
366 if ([item download] == download)
367 return item;
368 }
369 return nil;
370 }
371
372 /* fullDownloadPath
373 * Given a filename, returns the fully qualified path to where the file will be downloaded by
374 * using the user's preferred download folder. If that folder is absent then we default to
375 * downloading to the desktop instead.
376 */
377 +(NSString *)fullDownloadPath:(NSString *)filename
378 {
379 NSString * downloadPath = [[Preferences standardPreferences] downloadFolder];
380 NSFileManager * fileManager = [NSFileManager defaultManager];
381 BOOL isDir = YES;
382
383 if (![fileManager fileExistsAtPath:downloadPath isDirectory:&isDir] || !isDir)
384 downloadPath = @"~/Desktop";
385
386 return [[downloadPath stringByExpandingTildeInPath] stringByAppendingPathComponent:filename];
387 }
388
389 /* isFileDownloaded
390 * Looks up the specified file in the download list to determine if it is being downloaded. If
391 * not, then it looks up the file in the workspace.
392 */
393 +(BOOL)isFileDownloaded:(NSString *)filename
394 {
395 DownloadManager * downloadManager = [DownloadManager sharedInstance];
396 int count = [[downloadManager downloadsList] count];
397 int index;
398
399 NSString * firstFile = [filename stringByStandardizingPath];
400
401 for (index = 0; index < count; ++index)
402 {
403 DownloadItem * item = [[downloadManager downloadsList] objectAtIndex:index];
404 NSString * secondFile = [[item filename] stringByStandardizingPath];
405
406 if ([firstFile compare:secondFile options:NSCaseInsensitiveSearch] == NSOrderedSame)
407 {
408 if ([item state] != DOWNLOAD_COMPLETED)
409 return NO;
410
411 // File completed download but possibly moved or deleted after download
412 // so check the file system.
413 return [[NSFileManager defaultManager] fileExistsAtPath:secondFile];
414 }
415 }
416 return NO;
417 }
418
419 /* notifyDownloadItemChange
420 * Send a notification that the specified download item has changed.
421 */
422 -(void)notifyDownloadItemChange:(DownloadItem *)item
423 {
424 NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
425 [nc postNotificationName:@"MA_Notify_DownloadsListChange" object:item];
426 }
427
428 /* downloadDidBegin
429 * A download has started.
430 */
431 -(void)downloadDidBegin:(NSURLDownload *)download
432 {
433 DownloadItem * theItem = [self itemForDownload:download];
434 if (theItem == nil)
435 {
436 theItem = [[[DownloadItem alloc] init] autorelease];
437 [theItem setDownload:download];
438 [downloadsList addObject:theItem];
439 }
440 [theItem setState:DOWNLOAD_STARTED];
441 if ([theItem filename] == nil)
442 [theItem setFilename:[[[download request] URL] path]];
443
444 // Keep count of active downloads
445 ++activeDownloads;
446
447 // Record the time we started. We'll need this to work out the remaining
448 // time and the number of KBytes/second we're getting
449 [theItem setStartTime:[NSDate date]];
450 [self notifyDownloadItemChange:theItem];
451
452 // If there's no download window visible, display one now.
453 [[NSApp delegate] conditionalShowDownloadsWindow:self];
454 }
455
456 /* downloadDidFinish
457 * A download has completed. Mark the associated DownloadItem as completed.
458 */
459 -(void)downloadDidFinish:(NSURLDownload *)download
460 {
461 DownloadItem * theItem = [self itemForDownload:download];
462 [theItem setState:DOWNLOAD_COMPLETED];
463 NSAssert(activeDownloads > 0, @"downloadDidFinish called with zero activeDownloads count!");
464 --activeDownloads;
465 [self notifyDownloadItemChange:theItem];
466 [self archiveDownloadsList];
467
468 NSString * filename = [[theItem filename] lastPathComponent];
469 if (filename == nil)
470 filename = [theItem filename];
471
472 NSMutableDictionary * contextDict = [NSMutableDictionary dictionary];
473 [contextDict setValue:[NSNumber numberWithInt:MA_GrowlContext_DownloadCompleted] forKey:@"ContextType"];
474 [contextDict setValue:[theItem filename] forKey:@"ContextData"];
475
476 [[NSApp delegate] growlNotify:contextDict
477 title:NSLocalizedString(@"Download completed", nil)
478 description:[NSString stringWithFormat:NSLocalizedString(@"File %@ downloaded", nil), filename]
479 notificationName:NSLocalizedString(@"Growl download completed", nil)];
480
481 // Post a notification when the download completes.
482 [[NSNotificationCenter defaultCenter] postNotificationName:@"MA_Notify_DownloadCompleted" object:filename];
483 }
484
485 /* didFailWithError
486 * A download failed with an error. Mark the associated DownloadItem as broken.
487 */
488 -(void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
489 {
490 DownloadItem * theItem = [self itemForDownload:download];
491 [theItem setState:DOWNLOAD_FAILED];
492 NSAssert(activeDownloads > 0, @"didFailWithError called with zero activeDownloads count!");
493 --activeDownloads;
494 [self notifyDownloadItemChange:theItem];
495 [self archiveDownloadsList];
496
497 NSString * filename = [[theItem filename] lastPathComponent];
498 if (filename == nil)
499 filename = [theItem filename];
500
501 NSMutableDictionary * contextDict = [NSMutableDictionary dictionary];
502 [contextDict setValue:[NSNumber numberWithInt:MA_GrowlContext_DownloadFailed] forKey:@"ContextType"];
503 [contextDict setValue:[theItem filename] forKey:@"ContextData"];
504
505 [[NSApp delegate] growlNotify:contextDict
506 title:NSLocalizedString(@"Download failed", nil)
507 description:[NSString stringWithFormat:NSLocalizedString(@"File %@ failed to download", nil), filename]
508 notificationName:NSLocalizedString(@"Growl download failed", nil)];
509 }
510
511 /* didReceiveDataOfLength
512 * The download received additional data of the specified size.
513 */
fbe040c Fixed some errors warning, deprecated functions and 64 bits declarations
Salvatore Ansani authored
514 -(void)download:(NSURLDownload *)download didReceiveDataOfLength:(NSUInteger)length
76d2a5b @mstroeck
mstroeck authored
515 {
516 DownloadItem * theItem = [self itemForDownload:download];
517 [theItem setSize:[theItem size] + length];
518
519 // TODO: How many bytes are we getting each second?
520
521 // TODO: And the elapsed time until we're done?
522
523 [self notifyDownloadItemChange:theItem];
524 }
525
526 /* didReceiveResponse
527 * Called once after we have the initial response from the server. Get and save the
528 * expected file size.
529 */
530 -(void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response
531 {
532 DownloadItem * theItem = [self itemForDownload:download];
533 [theItem setExpectedSize:[response expectedContentLength]];
534 [self notifyDownloadItemChange:theItem];
535 }
536
537 /* willResumeWithResponse
538 * The download is about to resume from the specified position.
539 */
540 -(void)download:(NSURLDownload *)download willResumeWithResponse:(NSURLResponse *)response fromByte:(long long)startingByte
541 {
542 DownloadItem * theItem = [self itemForDownload:download];
543 [theItem setSize:startingByte];
544 [self notifyDownloadItemChange:theItem];
545 }
546
547 /* shouldDecodeSourceDataOfMIMEType
548 * Returns whether NSURLDownload should decode a download with a given MIME type. We ask for MacBinary, BinHex
549 * and GZip files to be automatically decoded. Any other type is left alone.
550 */
551 -(BOOL)download:(NSURLDownload *)download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType
552 {
553 if ([encodingType isEqualToString:@"application/macbinary"])
554 return YES;
555 if ([encodingType isEqualToString:@"application/mac-binhex40"])
556 return YES;
557 return NO;
558 }
559
560 /* decideDestinationWithSuggestedFilename
561 * The delegate receives this message when download has determined a suggested filename for the downloaded file.
562 * The suggested filename is specified in filename and is either derived from the last path component of the URL
563 * and the MIME type or if the download was encoded, from the encoding. Once the delegate has decided a path,
564 * it should send setDestination:allowOverwrite: to download.
565 */
566 -(void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename
567 {
568 NSString * destPath = [DownloadManager fullDownloadPath:filename];
569
570 // Hack for certain compression types that are converted to .txt extension when
571 // downloaded. SITX is the only one I know about.
572 DownloadItem * theItem = [self itemForDownload:download];
573 if ([[[theItem filename] pathExtension] isEqualToString:@"sitx"] && [[filename pathExtension] isEqualToString:@"txt"])
574 destPath = [destPath stringByDeletingPathExtension];
575
576 // Save the filename
577 [download setDestination:destPath allowOverwrite:NO];
578 [theItem setFilename:destPath];
579 }
580
581 /* dealloc
582 * Clean up at the end.
583 */
584 -(void)dealloc
585 {
586 [downloadsList release];
587 [super dealloc];
588 }
589 @end
Something went wrong with that request. Please try again.