iPhoneレシピ6:zipファイルをダウンロードし、Documentsディレクトリに解凍する


下記4つのレシピを組み合わせて、zipファイルをダウンロードし、Documentsディレクトリに解凍する方法。

ポイント

基本的には上記のレシピの中にあるとおり。
ただしiPhone SDKレシピ3:UIProgressViewの使い方 - Random Noteにある、UIProgressViewのprogressプロパティを別スレッドで設定する、という部分の別スレッド呼び出しについては、NSURLConnectionとHetimaUnZipItemの中に隠蔽されている。

ソース

Downloader.zip

UIActionSheetを用意しておいて、

- (void)viewDidLoad {
    [super viewDidLoad];
	actionSheet = [[UIActionSheet alloc] initWithTitle:@"Please wait...\n\n\n\n" 
											  delegate:self 
									 cancelButtonTitle:NSLocalizedString(@"Cancel",nil) 
								destructiveButtonTitle:nil 
									 otherButtonTitles:nil];
	progressBar = [[UIProgressView alloc] initWithFrame:CGRectMake(30.0f, 40.0f, 240.0f, 90.0f)];
	progressBar.progressViewStyle = UIProgressViewStyleDefault;
	progressBar.progress = 0.0f;
	[actionSheet addSubview:progressBar];
	
	progressLabel = [[UILabel alloc] initWithFrame:CGRectMake(30.0f, 50.0f, 240.0f, 20.0f)];
	progressLabel.backgroundColor = [UIColor clearColor];
	progressLabel.textColor = [UIColor whiteColor];
	progressLabel.font = [UIFont systemFontOfSize:[UIFont systemFontSize]];
	progressLabel.text = NSLocalizedString(@"Downloading...", nil);
	[actionSheet addSubview:progressLabel];
	
	downloaderLock = [[NSLock alloc] init];
}

ダウンロードを開始し、

- (IBAction) download:(id)sender {
	[urlField resignFirstResponder];
	downloadedContentLength = 0;
	progressBar.progress = 0;
	NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:[urlField text]]];
	
	[[NSFileManager defaultManager] clearTmpDirectory];
	progressLabel.text = NSLocalizedString(@"Downloading...", nil);
	[actionSheet showInView:self.view];
	downloader = [[URLDownload alloc] initWithRequest:req directory:APPLICATION_TMP_DIR delegate:self];
}

ダウンロードが終わったら解凍する。だけ。

- (void)downloadDidFinish:(URLDownload *)download {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	LOG(download.filePath);
	progressLabel.text = NSLocalizedString(@"Extracting...", nil);
	
	HetimaUnZipContainer *unzipContainer = [[HetimaUnZipContainer alloc] initWithZipFile:download.filePath];
	[unzipContainer setListOnlyRealFile:YES];
	
	if ([[unzipContainer contents] count] == 0) {
		NSString *err = NSLocalizedString(@"No zip file is found.", nil);
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:err delegate:actionSheet cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
		[alert show];
		[alert release];
	} else {
		HetimaUnZipItem *item;
		NSEnumerator *contentsEnum = [[unzipContainer contents] objectEnumerator];
		expectedUmcompressedContentSize = 0;
		for (item in contentsEnum) {
			expectedUmcompressedContentSize += [item uncompressedSize];
			LOG(@"zip\tpath:%@\t%d", [item path], [item uncompressedSize]);
		}
		contentsEnum = [[unzipContainer contents] objectEnumerator];
		for (item in contentsEnum) {
			NSString *path = [[NSFileManager defaultManager] suggestFilePath:[APPLICATION_DOC_DIR stringByAppendingPathComponent:[item path]]];
			BOOL result = [item extractTo:path delegate:self];
			if (!result) {
				NSString *err = [NSString stringWithFormat:NSLocalizedString(@"Failed to extract %@.", nil), [item path]];
				UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:err delegate:actionSheet cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
				[alert show];
				[alert release];
			}
		}
	}
	
	
	[unzipContainer release];
	[pool release];
	[self dismissActionSheet];
}

ダウンロード中の進行状況はURLDownloadDelegateで取得。

- (void)download:(URLDownload *)download didReceiveResponse:(NSURLResponse *)response {
	expectedContentLength = [response expectedContentLength];
}
- (void)download:(URLDownload *)download didReceiveDataOfLength:(NSUInteger)length {
	progressBar.progress = progressBar.progress + ((long double)length / (long double)expectedContentLength) * 0.5f;
	LOG(@"Download: %f", progressBar.progress);
}

解凍中の進行状況はHetimaUnZipItemDeletegateで取得。

- (void)item:(HetimaUnZipItem *)item didExtractDataOfLength:(NSUInteger)length {
	progressBar.progress = progressBar.progress + ((long double)length / (long double)expectedUmcompressedContentSize) * 0.5f;
	LOG(@"Extracting %f", progressBar.progress);
}

終わったらdismissActionSheetを呼びだし、後始末。
UIActionSheetが非表示になった時にダウンロードに使ったURLDownloadをリリースしている。

- (void)dismissActionSheet {
	if (actionSheet) {
		[actionSheet dismissWithClickedButtonIndex:-1 animated:YES];
	}
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
	[self releaseDownloader];
}

- (void)releaseDownloader {
	[downloaderLock lock];
	if (downloader != nil) {
		[downloader release];
		downloader = nil;
	}
	[[NSFileManager defaultManager] clearTmpDirectory];
	[downloaderLock unlock];
}

途中キャンセルもできる。

// なぜかactionSheetCancelが呼ばれないので、こちらで代用
- (void)actionSheet:(UIActionSheet *)sheet clickedButtonAtIndex:(NSInteger)buttonIndex {
	if (buttonIndex == sheet.cancelButtonIndex) {
		[downloader cancel];
	}
}