耇合ファむルの操䜜

私は、15幎以䞊もの間、耇合ファむルを扱っおきたした。 垞に、耇合ファむルの長所ず短所に関する十分な情報を蓄積しおきたした。

䞀方で、それらは実際に非垞に䟿利な情報のストレヌゞであり、その堎でデヌタを倉曎できたす。他方では、この利䟿性はデヌタぞのアクセス速床によっお郚分的に平準化されたす。



䞀般的に、通垞は耇合ファむルは䜕に䜿甚されたすか

特定のコンテナNoSQLサブセットに保存する必芁があるすべおのもの。

たずえば、97〜2003の叀いバヌゞョンのMicrosoft Officeのファむル実際には数十個のファむルで構成されおいたすは、耇合ファむルに保存されおいたした。 これで、これらも保存され、ZIPのみがコンテナずしお䜿甚されたす。



MSIむンストヌルパッケヌゞも耇合ファむルであり、Thumbs.dbフォルダヌのサムネむルキャッシュファむルもこの圢匏を䜿甚したす。



確かに、同じWordには、砎損したドキュメントを回埩する、たたは少なくずも回埩しようずするナヌティリティWordの回埩、Word回埩ツヌルボックス、Munsoft Easy Word回埩がありたす。 独自の結論を匕き出すこずができたす。

ただし、耇合ファむルを適切に凊理すれば、それらの損傷の問題を解決できたすそしお、その方法を瀺したす。



そしお、もちろん、この圢匏の間違いない利点は、ファむルずフォルダヌを備えた本栌的なファむルシステムがストレヌゞ内で゚ミュレヌトされるこずです。



ずころで、ニュアンス。 蚘事を始める前に、いく぀かのフォヌラムで調査を実斜したしたが、ほずんどの開発者は耇合ファむルを䜿甚せず、単玔な理由で、それが䜕であるかを聞きたせんでした。

今、このギャップを埋めたす。



1.耇合ファむルずその䜜成に関する䞀般情報



耇合ファむルの構造ず内郚圢匏に぀いおはすぐに説明したせんが、これは䞍芁です。

最初に、あなたはそれを「感じる」必芁がありたす-圌が䜕に぀いおなのか。



それでは、StgCreateDocfileを呌び出しお新しい耇合ファむルを䜜成するこずから始めたしょう。

甚途では、このActiveXずAxCtrlsのカップルを接続したす䟿利になりたす。

そしお今、私たちは曞きたす



procedure CheckHResult(Code: HRESULT); begin if not Succeeded(Code) then RaiseLastOSError; end; var TestFilePath: string; WideBuff: WideString; Root: IStorage; begin TestFilePath := ExtractFilePath(ParamStr(0)) + '..\data\simple.bin'; ForceDirectories(ExtractFilePath(TestFilePath)); WideBuff := TestFilePath; CheckHResult(StgCreateDocfile(@WideBuff[1], STGM_CREATE or STGM_WRITE or STGM_SHARE_EXCLUSIVE, 0, Root));
      
      





たず、旗に泚意を払いたす。

STGM_CREATEおよびSTGM_WRITE-これら2぀のフラグは新しい耇合ファむルを䜜成するために䜿甚され、この堎合のSTGM_WRITEフラグの存圚は必須ですそうでない堎合、フォヌカスは機胜したせん。



重芁

しかし、3番目のフラグSTGM_SHARE_EXCLUSIVEを䜿甚するず、すべおが非垞に耇雑になりたす。 2番目の章で説明されおいるように、読み取り専甚モヌドでファむルを開く堎合を陀き、その存圚は垞に必芁です。

IDA Pro Freewareで自分で確認できたす。

StgCreateDocfileは、VerifyPermsが呌び出されるDfOpenDocfile関数を呌び出したす。この関数では、次のようなチェックが行われたす。









アドレス72554E62では、このフラグの存圚がチェックされ、突然怜出されない堎合は、゚ラヌが開かれたす。 したがっお、耇数回蚘録するために耇合ファむルを同時に開くこずは犁止されおいたす。



3番目のリングでこのようなチェックを芋るのは少し驚きで、実隓的にすくい䞊げた埌、同時に2回録音するためにファむルを開くこずができたした。 しかし-もちろん、䞡方のファむルに正しく曞き蟌むこずはできたせんでした。 :)



実際、これはデヌタストレヌゞ圢匏そのもののため、かなり有胜な決定ですが、蚘事の終わり近くで、少し埌で説明したす。



すべおのチェックが成功し、戻りコヌドStgCreateDocfileがS_OKである堎合、この関数の4番目のパラメヌタヌで、IStorageむンタヌフェむスは、耇合ファむルのルヌト芁玠を指し瀺しお戻りたす。

次に䜕ができたすか



たずえば、ルヌトに新しいファむルを䜜成しただファむルシステムがありたす、デヌタブロックを曞き蟌みたす。

この関数を曞きたす



 procedure WriteFile(Storage: IStorage; AName: WideString; Data: AnsiString); var Stream: IStream; OS: TOleStream; begin CheckHResult(Storage.CreateStream(@AName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE, 0, 0, Stream)); OS := TOleStream.Create(Stream); try OS.WriteBuffer(Data[1], Length(Data)); finally OS.Free; end; end;
      
      





最初に、Storage.CreateStream関数を呌び出しお新しい「ファむル」を䜜成したす。 これは、以前に怜蚎したStgCreateDocfileずほが同じですが、結果ずしお、ファむルのコンテンツが凊理されるIStreamむンタヌフェむスを返したす 。



フラグに泚意しおくださいSTGM_SHARE_EXCLUSIVEを指定する必芁があり、2番目䜜成する堎合はSTGM_WRITEたたはSTGM_READWRITEのいずれかでなければなりたせんが、 耇合ファむルはSTGM_WRITEフラグを䜿甚しお䜜成されたした-䜿甚されおいたす。



䟿宜䞊、IStreamの凊理は、デヌタを蚘録するTOleStreamレむダヌクラスを介しお実行されたす。



もちろん、これは原理的な問題ではなく、ISequentialStreamむンタヌフェむスのWrite関数の呌び出しを䜿甚できたす。ISequentialStreamむンタヌフェむスの子孫はIStreamですが、TOleStreamクラスの操䜜は簡単です。

前に実装した関数を呌び出したす



 WriteFile(Root, 'RootFile', 'First file data');
      
      





その結果、RootFileずいう名前ず「最初のファむルデヌタ」の内容を持぀ファむルがルヌトに衚瀺されたす。



重芁

ここで1぀の泚意点がありたす耇合ファむル内のファむルずフォルダヌの名前は、31 Unicode文字の長さを超えるこずはできたせん実際、32文字以䞋ですが、れロの終了を忘れおはなりたせん。



はい、そのように、フォルダたたはファむルを「123」ず呌ぶこずができたすが、「私の長いファむル名ずそれ以䞊の数字」を呌び出すこずはできたせん。 さらに、仕様䞊、名前に䜿甚できない文字のセットがありたす0〜0x1F。



おそらくあなたは蚀うだろう-なぜそのような制限があるのか​​、そしおネストの巚倧な深さを持぀巚倧な分岐ファむルシステムを展開したい堎合はどうするのか

したがっお、暙準のファむル制限ずは異なり、MAX_PATH定数は圱響したせん。

「マむビッグネヌム」ずいう名前の500個のサブフォルダヌ

簡単ですが、仮想ファむルシステムを䜿甚しお䜜業したす-必芁なこずを行いたす。 :)



ラムに戻りたしょう。ルヌトにフォルダヌを䜜成したす。



 CheckHResult(Root.CreateStorage('SubFolder', STGM_WRITE or STGM_SHARE_EXCLUSIVE, 0, 0, Folder));
      
      





コヌドはStorage.CreateStreamの呌び出しずほずんど同じですが、今回は新しく䜜成されたフォルダヌを指す別のIStorageむンタヌフェむスを取埗したす。



すぐに新しいファむルを䜜成できたす



 WriteFile(Folder, 'SubFolderFile', 'Second file data');
      
      





これを行うために、最初のパラメヌタヌはルヌトではなく、ルヌトを参照したすが、䜜成されたばかりのForderです。



重芁

そしお今、ニュアンスは、今すぐアプリケヌションを閉じるず、デヌタが保存されない堎合がありたす。

ここでは、実際、すべおがそれほど単玔ではありたせん。たずえば、自宅のマシンではこの動䜜が再珟されるこずが保蚌されおいたすが、動䜜しおいるマシンではたったく逆です。



デヌタの保存を保蚌するには、次のコヌドを実行する必芁がありたす。



 CheckHResult(Root.Commit(STGC_DEFAULT));
      
      





このコヌドを実行するず、すべおのデヌタがディスク䞊のファむルに保存されるこずが保蚌されたす。 さお、突然 "突然"気が倉わった堎合は、次のコヌドを呌び出すこずで、前回のコミット以降に発生したすべおの倉曎を取り消すこずができたす。



 CheckHResult(Root.Revert);
      
      





ずころで、ファむルを閉じるこずに぀いお。

これは、ルヌトの通垞のブロック解陀によっお行われたす。その埌、@ IntfClearがルヌト倉数のむンタヌフェむスに察しお呌び出されるず、他のすべおのむンタヌフェむスが階局順に砎棄されたす。

他に䜕が残っおいたすか



ええ、もっずCopyTo / MoveElementTo / EnumElementsメ゜ッドなど...

それらに぀いおは少し埌で説明したすが、今のずころは、蚘事に添付されたアヌカむブを開き、ファむル ".. \ simple \ StorageCreateDemo.dpr"で䞊蚘のコヌドの実装を確認できたす。



今、私たちはこのすべおの問題を読み蟌もうずしおいたす。



2.耇合ファむルの読み取り



新しいプロゞェクトを䜜成し、ActiveXずAxCtrlsを再床接続しお、開始コヌドを蚘述したしょう。



 var TestFilePath: string; WideBuff: WideString; Root: IStorage; begin TestFilePath := ExtractFilePath(ParamStr(0)) + '..\data\simple.bin'; WideBuff := TestFilePath; CheckHResult(StgOpenStorage(@WideBuff[1], nil, STGM_READ or STGM_SHARE_DENY_WRITE, nil, 0, Root));
      
      





曞き蟌みアクセスが必芁ないため、STGM_READフラグを䜿甚したす。ここでは、STGM_SHARE_DENY_WRITEを䜿甚するか、STGM_SHARE_EXCLUSIVEのたたにするかを遞択できたす2぀のフラグのいずれかが必芁です。



コヌド実行の結果は、ルヌトを指すIStorageクラスのルヌト倉数です。



ディスク䞊の指定されたフォルダヌ内のファむルをどのように怜玢したすか

圓然、FindFirstFileを䜿甚した再垰的なディレクトリトラバヌサル。

この堎合、䌌たようなものがありたす。これはIStorageむンタヌフェむスのEnumElementsメ゜ッドで、呌び出しは次のようになりたす。



 var Enum: IEnumStatStg; begin CheckHResult(Storage.EnumElements(0, nil, 0, Enum));
      
      





倧たかに蚀うず、これはFindFirstFile呌び出しに類䌌しおいたすが、ここではさらに凊理するためのハンドルではなく、IEnumStatStgむンタヌフェむスを取埗したす。



泚目する䟡倀がある興味深い点が1぀ありたす。

このむンタヌフェむス䜿甚する堎合はTStatStg構造を返したす。そのフィヌルドの1぀は、タむプがPOleStrであるpwcsNameパラメヌタヌになりたす。



シムスの状況を理解しおいたすか



もちろん、OLEはネむティブメモリマネヌゞャヌの存圚を認識せず、IMallocむンタヌフェむスを介しお独自の手段でこの行を栌玍するためのブロックを割り圓おるため、これは朜圚的なメモリです。



この状況を凊理しない堎合、アプリケヌションメモリはビクトリアフォヌルズのように流れたすが、メモリ消費カりンタを芋るのは楜しいでしょう。 :)



したがっお、最初にこのむンタヌフェむスのむンスタンスぞのリンクを取埗する必芁がありたす



 if (CoGetMalloc(1, ShellMalloc) <> S_OK) or (ShellMalloc = nil) then raise Exception.Create('CoGetMalloc failed.');
      
      





割り圓おられたメモリを解攟するために必芁ではありたせん。

このようなもの



 ShellMalloc.Free(TmpElement.pwcsName);
      
      





さらに、もう1぀のニュアンス

返されるTStatStgのデヌタ型は、次の倀を取るこずができたす。





他のオプションはすべお玔粋にサヌビスであり、興味はありたせん。



これがどのように起こるかを芋おみたしょう。



 procedure Enumerate(const Root: string; Storage: IStorage); var Enum: IEnumStatStg; TmpElement: TStatStg; ShellMalloc: IMalloc; Fetched: Int64; Folder: IStorage; AFile: IStream; begin // ..   OLE,     IMalloc if (CoGetMalloc(1, ShellMalloc) <> S_OK) or (ShellMalloc = nil) then raise Exception.Create('CoGetMalloc failed.'); //        CheckHResult(Storage.EnumElements(0, nil, 0, Enum)); //       Fetched := 1; while Fetched > 0 do if Enum.Next(1, TmpElement, @Fetched) = S_OK then //  ( ) if ShellMalloc.DidAlloc(TmpElement.pwcsName) = 1 then begin //    Write('Found: ', Root, '\', AnsiString(TmpElement.pwcsName)); //    case TmpElement.dwType of //   -       STGTY_STREAM: begin Writeln(' - file: ', sLineBreak); CheckHResult(Storage.OpenStream(TmpElement.pwcsName, nil, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, AFile)); ShowFileData(AFile); Writeln; end; //   -           STGTY_STORAGE: begin Writeln(' - folder'); CheckHResult(Storage.OpenStorage(TmpElement.pwcsName, nil, STGM_READ or STGM_SHARE_EXCLUSIVE, nil, 0, Folder)); Enumerate(Root + '\' + string(TmpElement.pwcsName), Folder); end; else Writeln('Unsupported type: ', TmpElement.dwType); end; // ,      -    ShellMalloc.Free(TmpElement.pwcsName); end; end;
      
      





そしお、最初の章で䜜成されたファむルを読んだずきに䜕が起こるか芋おみたしょう









実際、これはたさに最初の章で蚘録したデヌタです。



この䟋のコヌドは蚘事のアヌカむブにあり、パス「.. \ simple \ StorageReadDemo.dpr」に埓っおいたす。



次に、これをもう少し䟿利に䜿甚する方法を芋おみたしょう。



3.ラッパヌクラス



か぀お、私は小さなモゞュヌルコメント付きの1000行を開発したした。このモゞュヌルは、耇合ファむルを操䜜する際のすべおのニュアンスを考慮し、より䟿利な䜜業メカニズムを提䟛するいく぀かのクラスを実装したす。

アヌカむブの「.. \ StorageReader \ FWStorage.pas」フォルダにありたす 。



いく぀かの欠陥がありたす。 実際、私はその開発をかなり前に攟棄したため、Delphiのナニコヌドバヌゞョンでは、文字列の操䜜に関連する䞍満を発行したす。

[dcc32è­Šå‘Š] FWStorage.pas860W1057「AnsiString」から「string」ぞの暗黙的な文字列キャスト

[dcc32è­Šå‘Š] uStgReader.pas102W1057「ShortString」から「string」ぞの暗黙的な文字列キャスト


しかし同時に、それは非垞に機胜的であり、これらの倉動はパフォヌマンスに圱響を䞎えたせん。 正盎に蚀うず、それらをずかすには面倒です。



このモゞュヌルは、次の予玄で自由裁量で䜿甚できたす。



クラスコヌドを突然倉曎しものを远加し、芋぀かった堎合ぱラヌを修正しお、それをむンタヌネットにアップロヌドするず、モゞュヌルの䜜成者の名前がヘッダヌに保存されたす。

私はもうこのモゞュヌルに同行しおいたせん私にずっおは時代遅れですので、すぐに完了するためのリク゚ストを拒吊したす。

したがっお、このモゞュヌルでは、耇合ファむルを操䜜するTFWStorageクラスず、IStorageのラッパヌであるTFWStorageCursorクラスに興味がありたす。

最初に、これらのクラスのメ゜ッドをリストし、次にそれらを操䜜する䟋を瀺したす。

したがっお、TFWStorageクラスは、ファむルの操䜜のみを目的ずしおおり、いく぀かの実甚的なメ゜ッドを提䟛したす。





぀たり 原則ずしお、その䞻なタスクは、TFWStorageCursorクラスのむンスタンスを提䟛するこずです。これにより、耇合ファむルの䞻な䜜業が行われたす。



圌の方法は次のずおりです。





ご芧のずおり、IStreamにはラッパヌはありたせん。このむンタヌフェむスでの䜜業は、CreateStream、ReadStream、WriteStreamメ゜ッドに委任されたす。



Enumerateメ゜ッドが返すTFWStorageEnum配列では、pacsNameに割り圓おられたメモリを解攟する必芁はありたせん。これは既に完了しおおり、ネむティブメモリマネヌゞャによっお割り圓おられたメモリに栌玍されおいるデヌタのコピヌを䜿甚しおいたす。

Backwardメ゜ッドで発生する可胜性がある唯䞀の質問は、それがどのように発生するかずいうこずです。



そしお今、私はそれが本圓に䟿利であるこずを瀺したす。

ここで、たずえば、そのようなパスを開く必芁がある堎合「ファむル\ Subfolder1 \ subfolder2 \ subsubfolderぞのパス」。これは、第2章の通垞のむンタヌフェむスを䜿甚しお実行する必芁がありたす。

ファむル自䜓を開き、ルヌトを指すIStorageむンタヌフェヌスを取埗しおから、最初のフォルダヌのIStorageを取埗し、次に2番目ず3番目のフォルダヌ「サブサブフォルダヌ」のIStorageを取埗したす。

これらはどこかに栌玍する必芁がある最倧4぀の芁玠です。



TFWStorageを䜿甚するず、物事がずっず簡単になりたす。



 procedure TForm1.Button1Click(Sender: TObject); var Path: string; Storage: TFWStorage; Root, Folder: TFWStorageCursor; Data: TStringStream; begin Storage := TFWStorage.Create; try //     Path := ExpandFileName(ExtractFilePath(ParamStr(0)) + '..\data\test.bin'); //    Storage.OpenFile(Path, True, Root); //          Storage.ForceStorage(Path + '\Subfolder1\subloder2\subsubfolder', Folder); Data := TStringStream.Create; try //      Data.WriteString('new file data.'); //             while Folder <> Root do begin Folder.WriteStream(Folder.GetName + '_new_file.txt', Data); //       Folder.Backward(Folder); end; //   Root.FlushBuffer; finally Data.Free; end; finally Storage.Free; end; end;
      
      





プログラミングの芳点から芋るず、非垞に䟿利でした。



それでは、もっず深刻なもの、぀たり耇合ファむルの内容の゚ディタヌを䜜成したしょう。



新しいプロゞェクトを開き、その䞭に次のようなものを䜜成したす。









プラむベヌトでは、3぀の倉数を远加したす。
 private FCurrentFileName: string; FStorage: TFWStorage; FRoot: TFWStorageCursor;
      
      







フォヌムコンストラクタヌで、次のコヌドを蚘述したす。
 procedure TForm1.FormCreate(Sender: TObject); begin //     FCurrentFileName := ExpandFileName(ExtractFilePath(ParamStr(0)) + '..\data\simple.bin'); //   FStorage := TFWStorage.Create; //   OpenFile(False); end;
      
      







次に、ファむル自䜓を開く手順を蚘述したす。これは簡単です。



 procedure TForm1.OpenFile(CreateNew: Boolean); begin //  ,      FStorage.CloseFile; //    FStorage.OpenFile(FCurrentFileName, CreateNew or not FileExists(FCurrentFileName), FRoot); Caption := FCurrentFileName; //       ShowStorageData(FRoot); end;
      
      





これたでのずころずおも簡単ですよね 原則ずしお、残りのコヌドは単玔です。



次に、画面にフォルダの内容を衚瀺する手順を蚘述したす。



 procedure TForm1.ShowStorageData(AStorage: TFWStorageCursor); procedure AddItem(const ACaption: string; AIndex: Integer); begin with ListView1.Items.Add do begin Caption := ACaption; case AIndex of -1: ImageIndex := -1; 1: begin ImageIndex := 0; SubItems.Add('Folder'); end else ImageIndex := 1; SubItems.Add('File'); end; //      Data, : // -1 -     // 0 -  // 1 -  //       Data := Pointer(AIndex); end; end; var AData: TFWStorageEnum; I: Integer; begin ListView1.Items.BeginUpdate; try ListView1.Items.Clear; //  ,         // (   - ) if not AStorage.IsRoot then AddItem('..', -1); //     AStorage.Enumerate(AData); //     ListView for I := 0 to AData.Count - 1 do AddItem( string(AData.ElementEnum[I].pacsName), Byte(AData.ElementEnum[I].dwType = STGTY_STORAGE)); finally ListView1.Items.EndUpdate; end; end;
      
      





すべおが正垞に完了したら、プロゞェクトを開始するず、最初の章で䜜成したファむル「.. \ data \ simple.bin」が開き、すべおが次のようになりたす。









それでは、リポゞトリをナビゲヌトしたしょう。

そのロゞックは簡単です





これを行うには、ListViewのOnDblClickむベントハンドラヌで次のコヌドを蚘述したす。



 procedure TForm1.ListView1DblClick(Sender: TObject); begin //     -  if ListView1.Selected = nil then Exit; //    Data   case Integer(ListView1.Selected.Data) of -1: //     begin //       FRoot.Backward(FRoot); //    ShowStorageData(FRoot); end; 0: //   EditFile; 1: //   begin //      FRoot.OpenStorage(AnsiString(ListView1.Selected.Caption), FRoot); //    ShowStorageData(FRoot); end; end; end;
      
      





これで、ダブルクリックでストアを歩き回るこずができたす。 :)



次のようにファむルを線集したす。 新しいフォヌムをプロゞェクトに接続し、保存ボタンずキャンセルボタンを远加したす。さらに、ファむルの内容が衚瀺されるTMemoを远加し、その埌、次のコヌドを蚘述したす。



 procedure TForm1.EditFile; var Buff: TMemoryStream; Data: AnsiString; begin Buff := TMemoryStream.Create; try //    FRoot.ReadStream(AnsiString(ListView1.Selected.Caption), Buff); //     if Buff.Size > 0 then begin SetLength(Data, Buff.Size); Buff.Read(Data[1], Buff.Size); end; //    frmEdit := TfrmEdit.Create(Self); try //   Memo   frmEdit.Memo1.Text := string(Data); //   if frmEdit.ShowModal <> mrOk then Exit; //    Memo Buff.Clear; Data := AnsiString(frmEdit.Memo1.Text); if Length(Data) > 0 then Buff.Write(Data[1], Length(Data)); //    FRoot.WriteStream(AnsiString(ListView1.Selected.Caption), Buff); //    FRoot.FlushBuffer; finally frmEdit.Release; end; finally Buff.Free; end; end;
      
      





さお、ここにはほが本栌的な゚ディタヌがありたすが、フォヌムの䞊郚にあるボタンの機胜を远加するこずは残っおいたす。



新しい耇合ファむルを䜜成し、既存のファむルを開くためのハンドラヌは次のようになりたす。
 procedure TForm1.btnCreateDFaseClick(Sender: TObject); begin if SaveDialog1.Execute then begin FCurrentFileName := SaveDialog1.FileName; OpenFile(True); end; end; procedure TForm1.btnOpenDBaseClick(Sender: TObject); begin if OpenDialog1.Execute then begin FCurrentFileName := OpenDialog1.FileName; OpenFile(False); end; end;
      
      







これは、新しいフォルダヌを䜜成し、既存のフォルダヌを削陀するためのボタンコヌドになりたす。
 procedure TForm1.btnAddFolderClick(Sender: TObject); var NewFolderName: string; Tmp: TFWStorageCursor; begin if InputQuery('New folder', 'Enter folder name', NewFolderName) then begin FRoot.CreateStorage(AnsiString(NewFolderName), Tmp); FRoot.FlushBuffer; end; ShowStorageData(FRoot); end; procedure TForm1.btnDelFolderClick(Sender: TObject); begin if Application.MessageBox( PChar(Format('Delete folder: "%s"?', [ListView1.Selected.Caption])), 'Delete folder', MB_ICONQUESTION or MB_YESNO) = ID_YES then begin FRoot.DeleteStorage(AnsiString(ListView1.Selected.Caption)); FRoot.FlushBuffer; ShowStorageData(FRoot); end; end;
      
      







同じこず、ファむルを開いお削陀するボタンのみ
 procedure TForm1.btnAddFileClick(Sender: TObject); var NewFileName: string; begin if InputQuery('New file', 'Enter file name', NewFileName) then begin FRoot.CreateStream(AnsiString(NewFileName)); FRoot.FlushBuffer; end; ShowStorageData(FRoot); end; procedure TForm1.btnDelFileClick(Sender: TObject); begin if Application.MessageBox( PChar(Format('Delete file: "%s"?', [ListView1.Selected.Caption])), 'Delete file', MB_ICONQUESTION or MB_YESNO) = ID_YES then begin FRoot.DeleteStream(AnsiString(ListView1.Selected.Caption)); FRoot.FlushBuffer; ShowStorageData(FRoot); end; end;
      
      







それだけです、私の意芋では簡単



です:) これで芋るこずができたすが、DOCファむル内に保存されおいるものは䜕ですか :)









おそらくこれで停止し、耇合ファむルが匕き起こすさたざたなトラブルの説明に移るでしょう。そしお、アヌカむブ内のこのサンプルの゜ヌスコヌドをパスに埓っお取埗できたす ".. \ StorageReader \"



4.耇合ファむルの短所



, , , .



— .



.

— .

, . :)

, , .



, , :





実際、これらのすべおの建蚭プロゞェクト、オブゞェクトなど掚定を陀くは階局オブゞェクトにすぎたせん-基本的にフォルダヌですが、これらのフォルダヌは厳密に定矩された順序で移動する必芁がありたす。



ファむルシステムツヌルを䜿甚しおこの階局を䜜成し、フォルダヌの皮類階局のレベルが「ビルド/オブゞェクト/キュヌ」が属するを実装するこずを想像するず、たずえば、ルヌトla thumbs.dbにある 、それから指揮者の䞭でこの構造党䜓を台無しにするこずができる過床に遊び心のあるペンを持っおいるナヌザヌで䜕をすべきでしょうか



これらの考慮事項から、䜕幎も前に、デヌタりェアハりスずしお耇合ファむルを遞択したのは、ナヌザヌがそこに䟵入しおすべおを壊すこずができないからです。

このストレヌゞ圢匏を䜿甚しお、フォルダ䜜成の目的の階局を制埡し、ナヌザヌが足で撮圱するのを防ぐこずができたす。



しかし、ニュアンスが出おきたした。圓瀟の゜フトりェアで新しい芋積もりを䜜成するずき、䜕らかの理由で、ナヌザヌは、芋積もりの​​名前で芳察したこずに関する完党な情報を衚瀺しようずしたす。

䟋「旧玠材のトラックのオヌバヌホヌル。 Selegvozh-Chimセクション、片道、142 pc1-163 pc10、長さ22.0 km”。



私たちは思い出したす-ファむル名の長さの制限はわずか31文字であり、これは極端な堎合です「叀いパスぞのオヌバヌホヌル」。

いいえ、「りェむの修理」ず名付けたす。



そしおもちろん、パスに沿っおファむル名を虐殺するず、ナヌザヌはひどく気分を害するので、それに぀いお䜕かしなければなりたせんでした。



:

, ( — ), . «Properties», ( , , ) ( 1024 — , , ).

— — «Data», .



:









, ?

GUID «» 31 . :)



31 , - .



, 5 ?

-, , 5 , . , , — . :)



「デザむン研究所」ず呌ばれるそのようなクラむアントがありたす-ロシア䞭にはそれらの倚くがあり、そこでは膚倧な数の芋積り者が働いおいたす。圌らはすべおプロであり、したがっお、単に「巚倧な」䞀連の掚定倀で垞に動䜜したす-これが仕事です。



そしおある日、次のようなバグレポヌトが寄せられたした。「皆さん、゜フトりェアの起動を埅぀のにうんざりしおいたすが、毎日䜕をしおいるのですか」



たた、これらのオフィスの䞀郚は、サむドに提䟛できないデヌタを凊理しおいるため䞀郚の政府機関は芳察しおいたす、たったく理解できたせんでした-野生のブレヌキはどこから来たのですか!!!



しかし、幞運なこずに、デヌタは機密ではなく、私たちに提䟛されたした。

そしお今、私のフォルダにはほが2ギガバむトのファむルがありたす玄20䞇件の芋積もりを船内で郚門もちろん、私はそのようなボリュヌムに倢䞭でしたが...

しかし、本圓に-必芁かどうかにかかわらず、このサむズのファむルは5分以䞊開かれたせんでした次の章でその理由を理解したす。



圌らは速床のテストを開始し、実隓的に確立されたしたすでに50メガバむトのサむズの耇合ファむルのボリュヌムで䜜業するのは気に入らないでしょう。



テストはもちろんテストですが、䜕かする必芁がありたす。

できるだけ早く、耇合ファむルではなくデヌタベヌスに保存されたデヌタを凊理するネットワヌクサヌビスが実装されたした。さらに、圌らはすぐに、通垞のFirebird / Interbaseずより深刻なデヌタベヌスの䞡方MS SQL / Olracle、およびADOがヒヌプにねじ蟌たれたのサポヌトを远加したした。耇合ファむルからデヌタベヌスにデヌタを倉換する小さなナヌティリティも䜜成されたした。



私たちはテストしたす-それは飛ぶ、私の母は心配したせんが、ニュアンスがありたす。



2ギガファむルからすべおのデヌタを取埗しおデヌタベヌスに転送するこずはできたせん。

ある時点で、OpenStorageを介しお次のフォルダヌを開くず、開く゚ラヌが発生する可胜性がありたす。この堎合、これ以䞊心配する必芁はありたせん。呌び出しぱラヌになりたす。



これはたさにその目的のためであり、TFWStorageに2぀のメ゜ッドが远加されたした。ReConnect-耇合ファむルを再怜出できる方法ず、゚ラヌが発生したフォルダヌをすぐに開くForceStorageメ゜ッドです。



ただし、その時点では、ただ十分なコヌンが埗られず、別の補品で耇合ファむルを䜿甚しおいたした。このプロゞェクトは、情報照䌚システムISSであり、掚定者に圌に必芁なドキュメントMSDNの偏狭な類䌌物ぞのアクセスを提䟛したす。



そしお今がきお、ベヌスを埋める責任のある技術者がやっお来お、「ベヌスが開かないので、次のアップデヌトをリリヌスできたせん」ず蚀いたす。



私はチェックし始めおいたす。

, , StgOpenStorage.






— - 4 , .

, . .



, — , ( ) — MSDN , . , .



, :

たずえば、ファむルストリヌムに䜕かを曞き蟌み、EnumElementsを呌び出しお、ファむルが存圚するかどうかを確認しようずしたす。たた、この呌び出しによっお返されるIEnumStatStgは、そのようなストリヌムを認識したせん。次に、CreateStreamを呌び出しお䜜成したすが、゚ラヌが発生したす。



この堎合、このポむントを回避するのは非垞に簡単です。指定されたストリヌムに察しおDestroyElementを呌び出し、CreateStreamを呌び出しお再䜜成するだけで十分ですが、これは非垞に玠晎らしい呌び出しです。「耇合ファむルの問題は本圓に問題です」。



そしお、圌にずっおは本圓に悪いので、私たちは圌からただ利甚可胜なデヌタを取埗する方法を孊ぶ必芁がありたす。



5. RAWモヌドでのデヌタの読み取り



あなたは今、この意芋を持っおいるず思うたあ、あなた自身、nifiga、耇合ファむルを䜿甚するずき、いく぀の問題があるでしょうかなぜ著者はそれらに぀いお話すのでしょうか



これは正しい意芋ではありたせん。珟圚䜿甚されおいるさたざたなテクノロゞヌに倧量の゚ラヌを投げるこずができたすが、これは、これが最初に倱敗したこずを意味するものではありたせん。



たずえば、察応するAPIを呌び出すために誀ったパラメヌタヌを䜿甚するだけで、3番目のリングから盎接、NTFSファむルシステムに基本的に削陀できないフォルダヌを盎接䜜成できるこずがわかっおいる堎合、ファむルシステムの䜿甚を拒吊したせんか :)



耇合ファむルは本圓に優れおいたすが、正しく調理する方法を孊ぶ必芁がありたす。



䞀般的には、MS は技術の説明を公開圢匏で公開しおおり、この圢匏で䜜業を開始したずき、Java POIFSからの圢匏の説明、Wikiからの短い抜粋、およびPOIFSの構造の詳现な説明を含む別のファむルしか取埗できたせんでした特に省庁によるず、しかし私は今それを芋぀けるこずができたせん䜕幎も経ちたした。



だから、私が必芁なずきにフォヌマットを開かなかったのです。 :)

したがっお、私は自分ですべおを遞択する必芁がありたした。



耇合ファむルを構成するもの、぀たりヘッダヌを調べたす。



 TPoifsFileHeader = packed record // .   (0 x E011CFD0, 0 x E11AB1A1) _abSig: array [0..7] of Byte; // Class ID.  WriteClassStg,  GetClassFile/ReadClassStg. //  Excel   = 0 _clid: TGUID; //    . _uMinorVersion: USHORT; //    Dll/ _uDllVersion: USHORT; // 0 x FFFE ,   Intel  _uByteOrder: USHORT; //  .   9,     512  (2 ^ 9) _uSectorShift: USHORT; //  -.   6,     64  (2 ^ 6) _uMiniSectorShift: USHORT; // ,    0 _usReserved: USHORT; // ,    0 _ulReserved1: ULONG; // ,    0 _ulReserved2: ULONG; //  ,    FAT. //    7,   1,  ,   1   DIF . _csectFat: ULONG; //   ,    Property Set Storage // (  FAT Directory  Root Directory Entry) _sectDirStart: ULONG; //   . _signature: ULONG; //   -.  4096 _ulMiniSectorCutoff: ULONG; //   -FAT. //  (-2),  - . _sectMiniFatStart: ULONG; //     -FAT. 0,  -  _csectMiniFat: ULONG; //    DIF . //    7,  DIF      (-2) _sectDifStart: ULONG; //    DIF .0,   < 7 _csectDif: ULONG; //   109 ,    FAT. //    7,   ,    (-1). _sectFat: array [0..108] of ULONG; end;
      
      





さお、ここではすべおが明確で、すべおのkamentyが添付されおいるず思いたす-最初にこの構造を怜蚎する必芁がありたす。



_uSectorShiftフィヌルドず_uMiniSectorShiftフィヌルドの唯䞀のものは2の环乗であるため、それらを通垞に戻す必芁がありたす。



 procedure TPoifsFile.InitHeader; begin FStream.ReadBuffer(FHeader, SizeOf(TPoifsFileHeader)); FHeader._uSectorShift := Round(IntPower(2, FHeader._uSectorShift)); FHeader._uMiniSectorShift := Round(IntPower(2, FHeader._uMiniSectorShift)); end;
      
      





次に、FATを読み取る必芁がありたす。FATは、ヘッダヌの_ulMiniSectorCutoffフィヌルドの倀以䞊のサむズのファむルに関するデヌタを栌玍したす。
 procedure TPoifsFile.ComposeFAT; var I, J, X, FatLength: Integer; FatBlock: TPoifsFatBlock; CurrentFat, Offset: Integer; XFat: array of Integer; begin //  -  FAT (   128 ) //   DIF ,  _csectFat   FatLength := FHeader._csectFat * 128; //      FAT SetLength(FFat, FatLength); //        SetLength(FFatOffset, FatLength); //   DIF ,  FAT     109  //     DIF  for I := 0 to IfThen(FHeader._csectDif > 0, 108, FHeader._csectFat - 1) do begin //  FAT    128  FatBlock := TPoifsFatBlock(GetBlock(FHeader._sectFat[I])); for J := 0 to 127 do begin FFat[I * 128 + J] := FatBlock[J]; //        ,  FFatOffset[I * 128 + J] := FStream.Position - SizeOf(TPoifsBlock); end; end; // ,   DIF  if FHeader._sectDifStart = 0 then Exit; //  ,      FAT   Offset := FHeader._sectDifStart; //   XFAT     FAT  SetLength(XFat, 128); //     FAT  CurrentFat := 13951; //109 * 128 - 1 BAT for X := 0 to FHeader._csectDif - 1 do begin //     ( _uSectorShift  ) //     FStream.Position := GetBlockOffset(Offset); //    FAT  FStream.ReadBuffer(XFat[0], 128 * SizeOf(DWORD)); //      //        //        for I := 0 to 126 do begin //     ,   , //  FAT   if XFat[I] < 0 then Exit; //  FAT    128  FatBlock := TPoifsFatBlock(GetBlock(XFat[I])); for J := 0 to 127 do begin Inc(CurrentFat); FFat[CurrentFat] := FatBlock[J]; FFatOffset[CurrentFat] := FStream.Position - SizeOf(TPoifsBlock); end; end; //       Offset := XFat[127]; end; end;
      
      







ここではTPoifsFatBlock構造が䜿甚されたす。これは、128個の敎数の配列です。

GetBlockOffsetおよびGetBlock関数ず同様に。



それらは本質的に単玔です。
 function TPoifsFile.GetBlockOffset(BlockIndex: Integer): Int64; begin Result := HEADER_SIZE + FHeader._uSectorShift * BlockIndex; end; function TPoifsFile.GetBlock(Adress: Integer): TPoifsBlock; begin FStream.Position := GetBlockOffset(Adress); FStream.ReadBuffer(Result, SizeOf(TPoifsBlock)); end;
      
      







次の手順では、サむズが_ulMiniSectorCutoffフィヌルドの倀より小さいファむルに関するデヌタを保存するミニファットを怜蚎したす。



 procedure TPoifsFile.ComposeMiniFat; var I, CurrChain: Integer; TmpPosition: int64; begin //        CurrChain := FHeader._sectMiniFatStart; //     (-    128 ) SetLength(FMiniFat, FHeader._csectMiniFat * 128); I := 0; while Integer(CurrChain) >= 0 do begin //    TmpPosition := GetBlockOffset(CurrChain); //  ,    if TmpPosition < 0 then Exit; //if TmpPosition > FStream.Size then Exit; FStream.Position := TmpPosition; //   FStream.ReadBuffer(FMiniFat[I], 512 {128 * SizeOf(DWORD)}); Inc(I, 128); //      FAT CurrChain := FFat[CurrChain]; end; end;
      
      





最埌の手順は、すべおのファむルずフォルダヌのプロパティを読み取るこずです。



これらは、これらの構造の配列に保存されたす。



 TPoifsProperty = packed record // 128 length //  / Caption: array[0..31] of WChar; //   CaptionSize: Word; //   STGTY_ PropertyType: Byte; //   ( TPoifsProperty    Red-Black-Tree) NodeColor: Byte; // 0 (red) or 1 (black) //      PreviousProp: Integer; //      NextProp: Integer; //      ChildProp: Integer; Reserved1: TGUID; UserFlags: DWORD; //  ATime: array [0..1] of Int64; //   FAT        StartBlock: Integer; //   Size: Integer; Reserved2: DWORD; end; TPoifsPropsBlock = array[0..3] of TPoifsProperty;
      
      





そしお、次のコヌドでそれらを読みたす。
 function TPoifsFile.ReadPropsArray: Boolean; var I, J, Len: Integer; PropsBlock: TPoifsPropsBlock; begin Result := True; //     Len := 0; //    ,    Property Set Storage J := FHeader._sectDirStart; repeat //     4  Inc(Len, 4); SetLength(FPropsArray, Len); PropsBlock := TPoifsPropsBlock(GetBlock(J)); for I := 0 to 3 do FPropsArray[Len - 4 + I] := PropsBlock[I]; //      FAT J := FFat[J]; until J = ENDOFCHAIN; end;
      
      







その埌、手元にありたす

  1. FAT倀の配列。各倀にはデヌタ付きのセクション番号が含たれたす。
  2. ファむル内のデヌタぞのオフセットの配列
  3. MiniFAT倀の配列
  4. すべおのファむルのプロパティの配列


FATおよびMiniFATずは䜕ですか

倧たかに蚀うず、耇合ファむルはヘッダヌであり、他のすべおが存圚するFHeader._uSectorShiftのサむズのデヌタ​​セクタヌの配列です。

FATには、これらのセクタヌにデヌタを保存する手順が含たれおいたすファむルの内容ず、ナヌザヌがアクセスできない厳密なサヌビスブロックの䞡方。

たずえば、サむズが1メガバむトのファむルがあり、正確に2048セクタヌが割り圓おられたす。各セクタヌのサむズは512バむトですデフォルト。

断片化により、このファむルのデヌタは垞に連続しお送信されるずは限らず、最初の10セクタヌにファむルの終わりが含たれ、残りがその始たりになる可胜性がありたす。

, ( ) FAT StartBlock TPoifsProperty, , , ( FAT).

, , .



, .

. , TPoifsProperty, PreviousProp, NextProp ChildProp, NodeColor. Red-Black-Tree.

, .



, .



, :









: ( TreeView), .



, Extract:



 begin FileStream := TFileStream.Create(edSrc.Text, fmOpenReadWrite); try AFile := TPoifsFile.Create(FileStream); try //      AFile.LoadFromStream; ATree := TStorageTree.Create; try //   for I := 0 to AFile.PropertiesCount - 1 do ATree.AddNode(I).Data := AFile[I]; //    FillAllChilds(0, ATree.GetNode(0).Data.ChildProp); //    TreeView1.Items.Clear; FillTree(nil, 0); //    DebugLog := TStringList.Create; try Extract(IncludeTrailingPathDelimiter(edDst.Text), 0); if DebugLog.Count > 0 then DebugLog.SaveToFile(IncludeTrailingPathDelimiter(edDst.Text) + 'cannotread.log'); finally DebugLog.Free; end; finally ATree.Free; end; finally AFile.Free; end; finally FileStream.Free; end; end;
      
      





最初の段階ファむルからのデヌタの読み取りは既に実装されおいたす。

2番目ず3番目に進みたしょう。



考えすぎなかったので、ツリヌの構造を埩元するために、゜リュヌションの基瀎ずしおグラフを䜿甚したした。



考え方は簡単です。たず、グラフにN個のノヌドを远加したす。各ノヌドはTPoifsProperty配列の芁玠の1぀を担圓したすこれは実際にコヌドで衚瀺される「ノヌドを埋める」ブロックです。

そしお次のステップは、ノヌド、誰が、䜕を参照するかの間のクロスリンクを構築するこずです。



䞀般に、ツリヌ自䜓は非垞に単玔に構築されたす。䞻なこずは、いく぀かの単玔なルヌルに埓うこずです。





より明確にするために、ここにあなたのための写真がありたす









䞋矢印はChildProp、右はNextProp、巊はPreviousPropです。

その結果、耇合ファむルのルヌト内に2぀のファむルず1぀のフォルダヌがあり、その䞭にさらに3぀のファむルがあるこずがすぐに明らかになりたす。



ただし、第3段階がどのように芋えるか、぀たり、グラフノヌド間のリンクの構築を芋おみたしょう。
 var ATree: TStorageTree; ... procedure FillAllChilds(RootIndex, CurrentIndex: Integer); var SubChildIndex: Integer; RootNode, CurrNode, ChildNode: TStorageElement; begin if CurrentIndex < 0 then Exit; //      RootNode := ATree.GetNode(RootIndex); //        CurrNode := ATree.GetNode(CurrentIndex); if CurrNode = nil then Exit; //     -  if CurrNode.Added then Exit; CurrNode.Added := True; //       ATree.AddVector(RootNode, CurrNode); //         FillAllChilds(CurrNode.ID, CurrNode.Data.ChildProp); //          SubChildIndex := CurrNode.Data.PreviousProp; while SubChildIndex >= 0 do begin //  ,     FillAllChilds(RootIndex, SubChildIndex); ChildNode := ATree.GetNode(SubChildIndex); if ChildNode <> nil then SubChildIndex := ChildNode.Data.PreviousProp else SubChildIndex := -1; end; //       ,      SubChildIndex := CurrNode.Data.NextProp; while SubChildIndex >= 0 do begin FillAllChilds(RootIndex, SubChildIndex); ChildNode := ATree.GetNode(SubChildIndex); if ChildNode <> nil then SubChildIndex := ChildNode.Data.NextProp else SubChildIndex := -1; end; end;
      
      







TStorageTreeクラスで衚されるグラフクラスの実装は、蚘事のトピックずは関係がないため、考慮したせん。このクラスのコヌドはsourceに衚瀺されたす。

珟時点では、むンデックスによっおグラフノヌドを返すGetNodeメ゜ッドDataプロパティを介しお制埡されるTPoifsProperty配列芁玠ぞの参照を含むず、グラフの2぀のノヌド間のリンクを䜜成するAddVectorメ゜ッドを知っおいるだけで十分です。



次に、第4段階-グラフに基づいお、フォルダヌずファむルのツリヌを䜜成したす。
 procedure FillTree(Node: TTreeNode; RootNodeIndex: Integer); var W: WideString; TreeNode: TTreeNode; I: Integer; RootStorageNode, ChildStorageNode: TStorageElement; begin //    RootStorageNode := ATree.GetNode(RootNodeIndex); //     (   ) W := RootStorageNode.Data.Caption; TreeNode := TreeView1.Items.AddChildFirst(Node, W); case RootStorageNode.Data.PropertyType of STGTY_STORAGE: TreeNode.ImageIndex := 0; STGTY_STREAM: TreeNode.ImageIndex := 1; end; //      for I := 0 to RootStorageNode.VectorCount - 1 do begin // ,       (   ?) ChildStorageNode := TStorageElement(RootStorageNode.GetVector(I).SlaveNode); if ChildStorageNode = nil then Continue; //   ,      ,    if ChildStorageNode.ID <> RootNodeIndex then FillTree(TreeNode, ChildStorageNode.ID); end; end;
      
      







䞀般的な再垰。

ルヌトから開始しおグラフのノヌドを倧たかに実行し、埓属ノヌドぞのリンクグラフの゚ッゞGetVectorI。SlaveNodeから取埗が珟圚のフォルダヌに栌玍されたす。



NodeColorフィヌルドを考慮しお、「Red-Black-Tree」に構造ツリヌを構築しなかった理由を尋ねたすか

しかし、道化垫は圌を知っおいたす。私はこのアルゎリズムを十数幎前に曞きたしたが、「うたくいきたす-䜕も觊れないでください」ずいう黄金のルヌルがありたす。 :)



次に、5番目の段階に進みたす-デヌタを倖郚フォルダヌに抜出したす。

ただし、このためには、ファむルに関連付けられたデヌタセット党䜓を取埗する方法を正確に把握する必芁がありたす。



FATセクションに぀いお述べたこずを思い出しおください-デヌタは垞に連続しお送信されるずは限らず、FATで蚘述されたセクションのむンデックスに焊点を圓おお取埗する必芁がありたす。



「倧きなファむル」ヘッダヌの_ulMiniSectorCutoffフィヌルドに保存されおいる倀以䞊のサむズのデヌタを取埗する方法を参照しおください。



 procedure TPoifsFile.GetDataFromStream(ChainStart: ULONG; NeedLength: DWORD; const Stream: TStream); begin Stream.Size := 0; while (Integer(ChainStart) >= 0) and (Stream.Size < NeedLength) do begin //      FStream.Position := GetBlockOffset(ChainStart); //      ChainStart := FFat[ChainStart]; //    Stream.CopyFrom(FStream, FHeader._uSectorShift); end; //   if Stream.Size > NeedLength then Stream.Size := NeedLength; end;
      
      





セクタヌ内のデヌタはブロック単䜍で保存され、実際のファむルサむズは垞にそのサむズの倍数ではないため、最終線集が必芁です。

同意したす。最終改蚂版を削陀するには、少し曞き盎す䟡倀がありたすが、必芁ですか :)



ただし、パラメヌタヌの点では、秘密が䜕もないChainStartに興味がありたす。これは、TPoifsProperty構造䜓のStartBlockフィヌルドの倀です。



そしお、_ulMiniSectorCutoffよりも小さなファむルのデヌタを取埗したす。



 procedure TPoifsFile.GetDataFromMiniStream(ChainStart: ULONG; NeedLength: DWORD; const Stream: TStream); var MiniStreamOffset: DWORD; RealMiniStreamSector, TmpChain: Integer; begin Stream.Size := 0; while (Integer(ChainStart) >= 0) and (Stream.Size < NeedLength) do begin //        Ministream TmpChain := ChainStart; RealMiniStreamSector := Properties[0].StartBlock; while TmpChain >= 8 do begin Dec(TmpChain, 8); RealMiniStreamSector := FFat[RealMiniStreamSector]; end; //    MiniStreamOffset := GetBlockOffset(RealMiniStreamSector); //      FStream.Position := MiniStreamOffset + (ChainStart mod 8) * FHeader._uMiniSectorShift; //       ChainStart := FMiniFat[ChainStart]; //    Stream.CopyFrom(FStream, FHeader._uMiniSectorShift); end; //   if Stream.Size > NeedLength then Stream.Size := NeedLength; end;
      
      





少しトリッキヌですよね

確かに、よく芋るず、ここでの倉曎は、TmpChainず同じFATを介したオフセットの蚈算のみです。セクタヌが蚱容倀8を超えおノックアりトされた堎合、ミニファットぞのリンクがルヌトにあるため、TmpChainが蚱容倀より小さくなるたで、ルヌトからFATチェヌンに沿っお進みたす。



これで、指定したファむルからデヌタを抜出する手順を䜜成できたす。
 procedure GetStorageData(ANode: TStorageElement; const Stream: TStream); begin if ANode.Data.Size < Integer(AFile.Header._ulMiniSectorCutoff) then AFile.GetDataFromMiniStream(ANode.Data.StartBlock, ANode.Data.Size, Stream) else AFile.GetDataFromStream(ANode.Data.StartBlock, ANode.Data.Size, Stream); end;
      
      







この手順では、耇合ファむルからフォルダヌに抜出されたグラフのノヌドを転送し、そのサむズに基づいお、䞊蚘で実装したメ゜ッドの1぀を呌び出したす。



ほずんどすべおのように芋えたすが、最埌の5番目のステヌゞは残りたす-耇合ファむルの内容党䜓をフォルダヌに解凍したす。
 procedure Extract(Path: string; RootNodeIndex: Integer); var W: WideString; I: Integer; RootStorageNode, ChildStorageNode: TStorageElement; F: TFileStream; begin RootStorageNode := ATree.GetNode(RootNodeIndex); W := RootStorageNode.Data.Caption; case RootStorageNode.Data.PropertyType of STGTY_STORAGE: Path := Path + W + '\'; STGTY_STREAM: begin try ForceDirectories(Path); F := TFileStream.Create(Path + W, fmCreate); try GetStorageData(RootStorageNode, F); finally F.Free; end; except DebugLog.Add(Path + W); end; end; end; for I := 0 to RootStorageNode.VectorCount - 1 do begin ChildStorageNode := TStorageElement(RootStorageNode.GetVector(I).SlaveNode); if ChildStorageNode = nil then Continue; if ChildStorageNode.ID <> RootNodeIndex then Extract(Path, ChildStorageNode.ID); end; end;
      
      







さお、ここでコメントなしですでに。私たちはすべお以前に芋たした-通垞のアルゎリズム。



䜜成したプロゞェクトを起動しお、Wordドキュメントに蚭定するず、次のようになりたす。









そしお、私たち党員がそれを手に入れたパパには、これがありたす









すべおのファむルではありたせんか

ずおも簡単です。ツリヌの名前を芋るず、「|」の圢でダッシュが衚瀺されたす。 「CompObj」の前のファむル名たたは理解できないスペヌスの前。



これらは、最初の章で蚀及したOLE甚に予玄されおいる文字です0〜0x1F。名前にそのような文字を含むファむルを䜜成できないため、スキップされたすが、それらのデヌタは「cannotread.log」ずいうログに曞き蟌たれたす。



もちろん、これは簡単に凊理できたすが、デモでも同様です。



この䟋のコヌドは、フォルダヌ「.. \ RawStorageReader \」 のアヌカむブにありたす。



しかし、なぜこれをすべお曞いたのですか

前の章で曞いたアプリケヌションを䜿甚しお、このファむルを開きたしょう ".. \砎損した\ corrupted_storage.bin"



次のようなものになりたす。









それでは、APIから盎接、第2章の読者を読んでみたしょう。











悲しみ、その埌、どのようなトラブルが発生したかを芋お、このファむルを既にRAWモヌドで開きたす。









ええ、゚ラヌを読んで、スタックを芋おください









ファむルプロパティの読み取り段階で゚ラヌが発生したした。GetBlock関数でReadBufferを実行できたせんでした。

決定したす。



6.デヌタの゚ラヌを修正し、利甚可胜なものすべおを読み蟌もうずしたす。



蚘事の冒頭で、Wordのドキュメントのさたざたな「埩元者」のコホヌト党䜓に぀いおお話したした。今、私たちはそれらのようなものを曞きたす。 :)



これらのナヌティリティはすべお2぀のモヌドで動䜜したす。





もちろん、2番目の段萜に関する情報はありたせんし、必芁ありたせんが、最初の段萜に぀いおは、5番目の章を読んだ埌、ご存じのずおりです。 :)



:





最初のものは凊理されたせん。いいえ、たあ、倚分、FATを構築する最初のセクタヌの倀を蚈算しようずするファンがいるでしょうが、私はそのようなものを芋たこずはありたせん。



2番目は実質的に未凊理です。

FATにセクタヌ番号が含たれおいるこずがわかっおいるため、次の条件によっお砎壊を刀断できたす。次の倀は、ENDOFCHAIN定数-2より小さいか、FAT配列のサむズより倧きくなりたす。

倱敗したブロックの倀をENDOFCHAIN定数に倉曎するこずで修正できたすが、原則ずしお、そのような介入の埌でも、ミニファットセクタヌは郚分的に考慮され、ファむルプロパティの配列は最小限にアクセスできたす幞運な堎合でも。



3番目のオプションは凊理䞭です。

倧たかに、セクタヌ読み取り゚ラヌが発生したFATセルのアドレスを調べお、ENDOFCHAINに蚭定したす。もちろん、これはデヌタの䞀郚を切り取りたす99のケヌスが殺されたしたが、実際に利甚できるものを読みたす。



4番目のオプションは凊理されたせん。このデヌタは耇合ファむルに属さないため、単に保存するだけで、制埡はしたせんサむズだけがなくなりたす。



分析を開始したす

良いこずには、3番目の問題にしか察凊できたせん。぀たり、倱敗したFATむンデックスの数を特定し、修正したす。

RAWモヌドでデヌタを操䜜する方法を知っおいれば、それは難しくありたせん。



最初に、デヌタブロックの読み取り手順を倉曎したす。



 function TPoifsFile.GetBlock(Adress: Integer): TPoifsBlock; var BlockOffset: Integer; begin BlockOffset := GetBlockOffset(Adress); if BlockOffset < FStream.Size then begin FStream.Position := BlockOffset; FStream.ReadBuffer(Result, SizeOf(TPoifsBlock)); end else raise Exception.Create('Wrong block offset at addres: ' + IntToStr(Adress)); end;
      
      





突然䜕かが䞀緒に成長しなかった堎合、圌女はオフセットをチェックし、䟋倖を発生させたしょう。



ReadPropsArrayプロシヌゞャで2番目の倉曎を行い、FAT配列の状態をより厳密に制埡したす。



 function TPoifsFile.ReadPropsArray: Boolean; var I, J, Len, LastGood: Integer; PropsBlock: TPoifsPropsBlock; begin Result := True; //     Len := 0; //    ,    Property Set Storage J := FHeader._sectDirStart; LastGood := J; repeat if J = FREESECT then begin FixFatEntry(LastGood, ENDOFCHAIN); Break; end; //     4  Inc(Len, 4); SetLength(FPropsArray, Len); //     try PropsBlock := TPoifsPropsBlock(GetBlock(J)); except FixFatEntry(LastGood, ENDOFCHAIN); Break; end; for I := 0 to 3 do FPropsArray[Len - 4 + I] := PropsBlock[I]; LastGood := J; //      FAT J := FFat[J]; if J < ENDOFCHAIN then begin FixFatEntry(LastGood, ENDOFCHAIN); Break; end; until J = ENDOFCHAIN; end;
      
      





それでは、FixFatEntryプロシヌゞャを蚘述する必芁がありたす。



 procedure TPoifsFile.FixFatEntry(FatIndex, NewValue: Integer); var J, Offset: Integer; begin //    FAT  J := FatIndex mod 128; Offset := FFatOffset[FatIndex] + J * 4; //       FStream.Position := Offset; FStream.WriteBuffer(NewValue, SizeOf(Integer)); end;
      
      





元のファむルのFATチェヌンに倉曎を加えるのは、圌女の助けを借りおです。



では、䜕が起こったのか芋おみたしょう。










 :)

, , , .



, , FixFatEntry FStream.WriteBuffer.



, , , , , , .



:)



: "..\RawStorageReader\PoifsWithRepair.pas" .



, .



7.



— , .



, , — , , , ( , ) .



— , .

, , ?

, , — , , :)



, , :

, , , , .

() .



, , , .



:

8 (, RAW ) 473 , .

, — 150 , .

24 , 12 .

カりント1日150,000回の打ち䞊げ* 24日* 12か月* 8幎= 345、数癟䞇回の打ち䞊げ平均。



実際、私の手元にはちょうど473個の「半ば焌き」ファむルがありたす月に1〜2回発生するこずもありたすが、数か月にわたっお萜ち着くこずもありたす。これらのうちこの点を考慮する必芁がありたす、玄100人がbeatられたFATを䜿甚しおいたしたそしお、私が蚀ったように、beatられたベヌル、それは非垞に悪いです。

そのため、FATレベルで砎られた数癟のファむルのほずんどすべおが耇合ファむルを削陀し、UnEraseなどのナヌティリティで埩元され、尋ねられたした。

そしお、それらずは䜕の関係もありたせん-削陀したせんでした。埩元するこずはできたせん。



したがっお、この100個を返しお、芋おみたしょう耇合ファむルを壊す可胜性は䜕ですか

はい、100䞇分の1-蚘録速床の速いCDはこの数よりも頻繁に倱敗したす:)



信じられない

( , ) ?



?

— : .

«» — :)



, , — , ( , ).



, :





, .



, " " .



: , ( 1024 ). , , NTFS? :)



頑匵っおね:)



— (Rouse_)



, 2015



All Articles