ドロップしたファイルを日付でリネームする(その2)

その1に続きApplicatioin Delegateクラスを実装してファイルのリネーム処理を完成させます。

●追加メンバー変数

以下のメンバー変数を追加します。

@private
	BOOL				mbCanceled;		// キャンセルフラグ
	double				mdFileCnt;		// リネーム対象ファイル数
	double				mdComplete;		// リネーム済みファイル数
	NSArray				*marrFiles;		// リネーム対象ファイルリスト
	

●キャンセルボタンハンドラ

キャンセルボタンハンドラを以下のように実装します。

//
// キャンセルボタンハンドラ
//
- (IBAction)onBtnCancel:(id)sender
{
	mbCanceled = YES;
}
 

Interface Builder上でボタンとハンドラを結合します。具体的な操作はまずボタンの上でコントロールキーを押しながらマウスをドラッグしてMainMenu.xibのRename By Dateオブジェクトの上まで持ってきて、ボタンを離した時に表示されるハンドラリストからonBtnCancelを選択します。

●リネームAPI

ファイルのリネームのAPIにはrenameシステムコールを用います。

     #include <stdio.h>

     int
     rename(const char *old, const char *new);

 

このシステムコールで注意するところはnewにはフルパスを指定しないとカレントディレクトリにファイルをリネームして移動してしまうことです(mvコマンドと同様の振る舞い)
そこで以下のメソッドを実装してnewパラメータとして元ファイルと同じディレクトリにリネームファイル名がくるようにします。

//
// リネーム後のファイルパス名生成
//
- (NSString *)createNewNameOrg:(NSString *)strOrg prefix:(NSString *)strPrefix date:(NSDate *)oModDate seq:(int)nSeq
{
	NSString *strDir, *strExt, *strModDate, *strFileName;
	strDir = [strOrg stringByDeletingLastPathComponent];
	strExt = [strOrg pathExtension];
	strModDate = [oModDate descriptionWithCalendarFormat:@"%Y%m%d%H%M%S" timeZone:nil locale:nil];
	strFileName = [[[strPrefix stringByAppendingString:strModDate] stringByAppendingString:[NSString stringWithFormat:@"-%03d", nSeq]] stringByAppendingPathExtension:strExt];
	return [strDir stringByAppendingPathComponent:strFileName];
}
 

●リネームスレッド

ドロップされたファイルをリネームするに当たって、単純にメインスレッド上でループで回してしまうとキャンセルできなくなるという難点があります。そこで、リネーム処理を別スレッドで処理し、メインスレッドとの間ではキャンセルフラグによりインターフェースを取るようにします。
スレッドのソースを以下に示します。

//
// リネームスレッド
//
- (void)renameThreadWithFiles:(id)arrFiles
{
	NSFileManager	*oFM = [NSFileManager defaultManager];
	BOOL			bDir;
	
	for ( NSString *strPath in arrFiles ) {
		if ( mbCanceled == YES ) {
			[self performSelectorOnMainThread:@selector(renameCanceled:)
								   withObject:nil
								waitUntilDone:YES];
			break;
		}

		[oFM fileExistsAtPath:strPath isDirectory:&bDir];
		NSDictionary *dicAttr = [oFM attributesOfItemAtPath:strPath error:NULL];
		NSDate	*oModDate = [dicAttr objectForKey:NSFileModificationDate];

		if ( bDir == NO ) {
			NSString *strNewPath;
			
			for (int i = 1;; i++ ) {
				strNewPath = [self createNewNameOrg:strPath prefix:[outPrefix stringValue] date:oModDate seq:i];
				if ( [oFM fileExistsAtPath:strNewPath isDirectory:&bDir] == NO ) {
					rename( [strPath UTF8String], [strNewPath UTF8String] );
					break;
				}
			}
		}
		
		[self performSelectorOnMainThread:@selector(renameComplete:)
							   withObject:nil
							waitUntilDone:YES];
		
	}
}

このスレッドはNSApplicationのdetachDrawingThread:toTarget:withObject:メソッドで起動するのでAutoReleasePoolは自動的に作成されます。
また、AppKitはスレッドセーフではないためプログレスバーの更新などの処理はperformSelectorOnMainThread:withObject:waitUntilDone:メソッドで行っています。
メインスレッドで実行されるメソッドは以下の通りです。

//
// リネーム完了通知
//
- (void)renameComplete:(id)arg
{
	mdComplete++;
	[outProgInd setDoubleValue:mdComplete];
	
	if ( mdComplete == mdFileCnt ) {
		[outBtnCancel setEnabled:NO];
		[marrFiles release];
		NSBeep();
	}
}

//
// キャンセル通知
//
- (void)renameCanceled:(id)arg
{
	[outProgInd setDoubleValue:0.0];
	[outBtnCancel setEnabled:NO];
	[marrFiles release];
	NSBeep();
}

●delegateメソッド

UCDropViewからのdelegateメソッドは以下のようになります。

//
// UCDropViewからのコールバック(delegate)
//
- (void)droppedFiles:(NSArray *)arrFileList
{
	// ファイルリストの保持
	[marrFiles release];
	marrFiles = arrFileList;
	[marrFiles retain];

	// 制御情報の設定
	mbCanceled	= NO;
	mdFileCnt	= (double)[arrFileList count];
	mdComplete	= 0.0;

	// ProgressIndicatorの設定
	[outProgInd setMinValue:mdComplete];
	[outProgInd setMaxValue:mdFileCnt];
	[outProgInd setDoubleValue:mdComplete];

	// Cencelボタンを有効にする
	[outBtnCancel setEnabled:YES];
	
	
	// rename thread起動
	[NSApplication detachDrawingThread:@selector(renameThreadWithFiles:) toTarget:self withObject:arrFileList];
}

以上でリネーム処理は完成です。プログラムの中にはウィンドウを閉じた時にアプリを終了するなどの細かい処理も入っていますがご自身でお調べください。

サンプルプログラムのダウンロード(37KB)

【作成・確認環境】
MacOS X v10.6.4
Xcode v3.2.2

一覧に戻る