« JNethack::倉庫番::1F | トップページ | 元に戻す »

2005年1月27日 (木)

ごみ箱から指定のファイルを「元に戻す」方法

ごみ箱にファイルを「元に戻せるように」捨てるのは

void	FileSystem::Delete( const FilePath& path )
{
	CountDownTimer	timer( 2000 );
	while( timer() )
	{
		//ゴミ箱に捨てる。
		MultiString	deletefilename;

		SHFILEOPSTRUCT shFileOp;
		shFileOp.hwnd = NULL;
		shFileOp.wFunc = FO_DELETE;
		shFileOp.pFrom = deletefilename.Make( vector< string >( 1, path() ) );
		shFileOp.pTo = NULL;
		shFileOp.fFlags = FOF_ALLOWUNDO|FOF_NOCONFIRMATION|FOF_NOERRORUI;
		shFileOp.fAnyOperationsAborted = TRUE;
		if( SHFileOperation( &shFileOp ) == 0 )
			return;
		//直前まで書き込みが行われていた場合など、エラーになるときがある。2秒まで再試行
	}
	TB_ENFORCE( TrueFail(), ("FileSystem::Deleteに失敗:" + path()).c_str() );
}

まぁなんかこんな感じでSHFileOperationとFOF_ALLOWUNDOを使えばよい、というのはググればすぐに出てくるが、
肝心の「元に戻す」方法はほとんど資料がない。どうも話によるとごみ箱のコンテキストメニューをIFolderとIContextMenuを使って取得して
InvorkCommandで元に戻すを実行すればできる…らしいという話はある。

が、それも直前の操作を元に戻すものでしかない。ごみ箱に捨てた○○というファイルを元に戻すにはどうすればいいのか。

まずごみ箱はたいていC:\RECYCLERの下にある。ちなみにNT系を前提とする。しかも今回紹介する方法は2k以降でしか使えない。
C:\RECYCLERの下に、SID(セキュリティ識別子)と呼ばれるたいてい"S-1-5-21"といった文字列から始まるIDのフォルダが切られて、その中にごみ箱のファイルがある。ちなみに"Dc"+番号.拡張子という名前にリネームされている。

元のファイル名の情報はどこにあるのかというと、同じフォルダに"INFO2"という隠しファイルがあってそれがデータベースとなっている。
INFO2のフォーマットは
ヘッダ(謎)+(元のファイル名(ASCII)+NULL領域+Dcの後に来る番号(2バイト)+NULL領域+マルチバイトに変換されたファイル名+NULL領域)*
というもののようだ。元に戻した場合ファイル名のレコードは削除されないが、元のファイル名の最初の一文字が削られることで無効フラグとなっているらしい。

と、いうわけでかなり怪しげなものの、以下の方法でごみ箱から指定のファイルを元に戻すことができる。
・ごみ箱のINFO2ファイルを開く。
・元の位置のパスでINFO2を検索。
・見つかったらそのファイル名の後ろのNULL領域の次に来る2バイトがごみ箱内のファイルの番号。
・ごみ箱のファイルを元の位置に移動する。

INFO2ファイルのパスはIShellFolderを利用することで取得できる。
データベースファイルはどの環境でも"INFO2"というファイル名なのか、中のフォーマットは本当に正しいのか、ごみ箱のリネーム後のファイル名のヘッダはどこでも"Dc"なのか、などこの辺は確かな情報はない模様。

とりあえずうちで動作する「元に戻す」関数。

//! ごみ箱から指定のファイルを検索して復元
void	FileSystem::Restore( const FilePath& restoreFile )
{
	//ごみ箱内のファイルをリストアップ
	std::vector< FilePath >	paths;
	{
		//ShellAPIを使う。
		IMalloc*	pMalloc;
		TB_ENFORCE( NotEqualFail( S_OK ), CoGetMalloc( 1, &pMalloc ) );
		LPITEMIDLIST	pIdList;
		TB_ENFORCE( NotEqualFail( S_OK ), SHGetFolderLocation( NULL/*hwnd*/, CSIDL_BITBUCKET, NULL, NULL, &pIdList ) );
		IShellFolder*	pDesktop;
		TB_ENFORCE( NotEqualFail( NOERROR ), SHGetDesktopFolder( &pDesktop ) );
		IShellFolder*	pTrash;
		TB_ENFORCE( NotEqualFail( S_OK ), pDesktop->BindToObject( pIdList, NULL, IID_IShellFolder, reinterpret_cast< void ** >( &pTrash ) ) );
		LPENUMIDLIST	pEnumIDList;
		TB_ENFORCE( NotEqualFail( NOERROR ), pTrash->EnumObjects( NULL/*hwnd*/, SHCONTF_NONFOLDERS |SHCONTF_INCLUDEHIDDEN | SHCONTF_FOLDERS, &pEnumIDList ) );
		LPITEMIDLIST	pFileIDList;
		ULONG			result;
		while( pEnumIDList->Next( 1, &pFileIDList, &result ) == NOERROR )
		{
			// ファイルパスの取得。
			STRRET		stFileName;
			TB_ENFORCE( NotEqualFail( NOERROR ), pTrash->GetDisplayNameOf( pFileIDList, SHGDN_FORPARSING, &stFileName ) );
			paths.push_back( String::ToAscii( stFileName.pOleStr ) );
			pMalloc->Free( stFileName.pOleStr );
			pMalloc->Free( pFileIDList );
		}
		pMalloc->Free( pEnumIDList );
		pTrash->Release();
		pDesktop->Release();
		pMalloc->Free( pIdList );
		pMalloc->Release();
	}
	//ごみ箱が空なら帰る。
	TB_ENFORCE( TrueFail(), paths.empty() );
	//ごみ箱の元のファイル名データベースを開く。
	FilePath	info2Path( paths[0].GetFolder(), "INFO2" );
	TB_ENFORCE( FalseFail(), FileSystem::IsExist( info2Path ) );
	//INFO2を開く。
	BinaryFile	info2( info2Path );
	//検索
	std::string	restoreString = restoreFile();
	char*	pInfo2 = reinterpret_cast< char* >( info2() );
	char*	pInfo2End = pInfo2 + info2.GetSize();
	char*	pFilePosition = std::search( pInfo2, pInfo2End, restoreString.begin(), restoreString.end() );
	TB_ENFORCE( TrueFail(), pFilePosition == pInfo2End );
	//見つかった。パスの後にNULLが連続していて、その後にファイルの番号が来る。
	pFilePosition += restoreString.length();
	while( pFilePosition < pInfo2End && '\0' == pFilePosition[0] )
		++pFilePosition;
	//ごみ箱内からIDを検索する
	std::string	id = String( *reinterpret_cast< Uint16* >( pFilePosition ) )();
	FilePath	source;
	TB_FOR_EACH( std::vector< FilePath >, paths )
	{

		std::string	basename = it->GetBaseName();
		std::string::size_type	index = basename.find( id );
		if( std::string::npos == index )
			continue;
		//Dc198.lzhで、9番がヒットするのを防ぐ。
		//見つけた位置にIDの長さを加えたらベース名の長さになること。
		if( basename.length() != index + id.length() )
			continue;
		//ありえるのかわからないけど、ファイル名部分がIDと一緒ならいいとする。
		//198.lzhみたいな。
		if( basename.length() != id.length() )
		{
			//IDが見つかった位置はベース名の最後。
			//サイズが違うのならindexは0以上。
			//index-1が数字だったらやり直す。
			//98のときに198.lzhみたいな。
			if( '0' <= basename[index-1] && basename[index-1] <= '9' )
				continue;
		}
		//ここまでくれば見つかったことになる。
		source = *it;
		break;
	}
	//元に戻す
	FileSystem::Move( source, restoreFile );
}

|

« JNethack::倉庫番::1F | トップページ | 元に戻す »

コメント

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/61222/972621

この記事へのトラックバック一覧です: ごみ箱から指定のファイルを「元に戻す」方法:

« JNethack::倉庫番::1F | トップページ | 元に戻す »