ソースコードはこちら
https://github.com/mi3w2a1/incremental-backup

バックアップにしてもファイルの復元にしても時間がかかります。これまで作成したアプリケーションは処理がおこなわれているあいだ、他の処理ができませんでした。大きなサイズのファイルをバックアップしているときはフリーズしてしまったように見えてしまいます。

それから処理がどの程度まで進行しているのかもわかりません。せめてバックアップすべきファイルがどれだけあって、現在バックアップが完了しているファイルがどれくらいあるのかをわかるようにしたいものです。

そこでプログレスバーで進行状況がわかるようにしました。

まずバックアップに時間がかかるときにフリーズしてしまったように見えてしまう問題を回避するためには非同期処理を使います。

これでよさそうなのですが、これでは処理中にリストビューの選択項目を変更することができたり、別のバックアップの処理を開始することもできてしまいます。すると処理が正常に行なわれなくなるので、バックアップの処理が完了するまでメニューの選択やコントロール類の操作ができないようにします。

EnableMenu(bool isEnable)メソッドを引数falseで実行すれば処理中に他のメニューを選択することはできなくなります。また処理の途中でアプリを終了してバックアップが途中で止まってしまうことも避けられます。

本当に途中で処理を中断したい場合もあるので、そのための機能も追加します。

Backupメソッドは時間がかかるので、[処理を中止する]がクリックされた時にフラグを立て、時間がかかりそうな処理が繰り返される場所でフラッグをチェックし、フラッグが立っていれば処理を中断しています。

それからBackup(string sourceFolder, string targetFolder)メソッドがちょっと長いので分けます。

途中で処理を中断したくなる理由として、「思っていた以上に時間がかかりそうだ。だから今は中断してあとでやろう」というのがあります。実際、このアプリケーションはどうなのでしょうか?

以下が考えられます。

バックアップ元にフォルダが大量にあり取得に時間がかかるかもしれない
バックアップ元にファイルが大量にあり取得に時間がかかるかもしれない
GetLastLogFilePath(targetFolder)が取得したログファイルに大量のファイルが登録されているかもしれない。
DoesNeedBackup(fileAndTimes, folderPaths, filePaths)の実行に時間がかかるかもしれない
実際にバックアップが必要なフォルダとファイル数が想像以上に多いかもしれない
実際にバックアップが必要なファイルのサイズが想像以上に大きいかもしれない

まず、

バックアップ元にフォルダが大量にあり取得に時間がかかるかもしれない
バックアップ元にファイルが大量にあり取得に時間がかかるかもしれない

上記についてはどんなパソコン(Yahooショッピングで購入した中古のノートパソコン)を使っているかにもよりますが、20,000個程度であれば5秒以内に取得できます。ただフォルダによってはアクセス拒否をされて取得できない場合があります。ここは改善の要ありです。

GetLastLogFilePath(targetFolder)が取得したログファイルに大量のファイルが登録されているかもしれない。

この場合、CreateFileAndTimes(lastLogFilePath)メソッドの実行に時間がかかるかもしれませんが、これは30,000個のファイルと10,000個のフォルダであってもすぐに終わります。

DoesNeedBackup(fileAndTimes, folderPaths, filePaths)の実行に時間がかかるかもしれない

もし30,000個のファイルと10,000個のフォルダがあり、バックアップが必要なファイルがひとつもない場合、30秒くらいかかります。時間がかかる処理のひとつがこれです。

実際にバックアップが必要なフォルダとファイル数が想像以上に多いかもしれない
実際にバックアップが必要なファイルのサイズが想像以上に大きいかもしれない

このふたつはたしかに時間がかかります。そこでプログレスバーで進行状況を表示させます。

最後にログファイルや差分ファイルの作成をしていますが、これは30,000個のファイルと10,000個のフォルダ程度であれば数秒で終わります。ただし進行状況をプログレスバーで表示しようとするともっと時間がかかります。しかし進行状況が可視化されることで耐えがたい遅さには感じません。

非同期処理をするため、そのままではデバッグ時に「コントロールが作成されたスレッド以外のスレッドからコントロール ‘XXX’ がアクセスされました」という例外がでてしまいます。そこで

と書くことで例外の発生を回避します。

EnableMenu(bool isEnable)メソッドはバックアップの最中に別のメニューを選択して他のフォルダのバックアップが開始されたり、リストビューコントロールで選択されている項目が変更されて正しくバックアップがされない問題を回避するためのものです。EnableMenu(false)とすることでメニューが使用不可となり、アプリケーションを終了することができなくなります。

非同期処理時にリストビューコントロールで選択されている項目を取得するので、GetSourceFolderPath()メソッドやGetTargetFolderPath()メソッドでもInvoke((Action)(() => { }))とする必要があります。

Backup(string sourceFolder, string targetFolder)メソッドですが、バックアップしているときにどこまで処理が終わっているかを示すプログレスバーを表示させます。また[中止]をクリックすればバックアップを途中でやめることもできるようにしました。

FormBackupProgressクラスにはフィールド変数 BackupCancelとIsEndがあります。BackupCancelはバックアップを中止しようとするとtrueになります。IsEnd はバックアップが完了したときにtrueになります。フォームが閉じられようとしているときに、×がクリックされたのか、バックアップが終了したからなのかを区別できるようにしています。

バックアップの進行状態を知らせるダイアログをそのまま表示させようとすると大量にファイルをバックアップしようとするとフリーズしたようになってしまうので、Show()メソッドを変更しています。

それからFormBackupProgressをDisposeしようとすると例外が発生するのでFormBackupProgress.Designer.csを修正します。本当はこのファイルを直接編集するのはよくないのですが、今回はしかたありません。

Backup(string sourceFolder, string targetFolder)メソッドですが、長くなってしまったので書き直しました。

ところどころにある

はダイアログで中止のボタンが押されていないか確認する処理です。中止ボタンが押されていた場合は処理はやめてBackupメソッドもfalseを返して終了させます。

CheckFolderPaths(string sourceFolder, string targetFolder)メソッドはBackupメソッドに渡されたパスが適切なものかどうかを判断するものです。不適切なパスが渡された場合はエラーを表示して終了します。

GetSourceFolderPathsメソッドとGetSourceFilePathsメソッドはバックアップの対象になるかもしれないフォルダとファイルのパスを取得するメソッドです。

DoesNeedBackup(List<FileAndTime> fileAndTimes, string[] folderPaths, string[] filePaths, FormBackupProgress formBackupProgress)メソッドは本当にバックアップをとらなければならないファイルまたはフォルダがひとつ以上存在するか調べるメソッドです。この処理はファイル数が多くなると時間がかかるので、プログレスバーで進行状況が見えるようにしています。また途中で中止することもできます。

BackupFolderIfNeed(string sourceFolder, string targetFolder1, string[] folderPaths, List<FileAndTime> fileAndTimes, List<string> newFolders, FormBackupProgress formBackupProgress)メソッドはバックアップが必要かどうか調べて必要であればバックアップしています。

これもプログレスバーで進行状況がわかるようになっているとともに、中止することも可能です。その場合はfalseを返して中止したことがわかるようにしています。実はフォルダをつくるだけならたいした時間はかかりません。

BackupFilesIfNeed(string sourceFolder, string targetFolder1, string[] filePaths, List<FileAndTime> fileAndTimes, List<string> newFiles, List<string> modifiedFiles, FormBackupProgress formBackupProgress)メソッドはファイルのバックアップが必要かどうか調べて必要であればバックアップしています。

これもプログレスバーで進行状況がわかるようになっているとともに、中止することも可能です。その場合はfalseを返して中止したことがわかるようにしています。大きなファイルをコピーしているときに中止をしようとしてもループ内のStopBackupIfCancelメソッドが実行されないと処理が止まることはありません。

SaveLogFile(string targetFolder, string[] folderPaths, string[] filePaths, long nowTicks, FormBackupProgress formBackupProgress)メソッドはログファイルを保存するためのメソッドです。一応、中止ボタンのクリックにも対応させていますが、実際の処理はすぐに終わるので、このメソッド内で中止の処理がおこなわれることはないと考えています。

最後に差分がわかるように結果をファイルに保存します。この処理をやってくれるのが、SaveDifferenceFile(string targetFolder, List<

すぐ終わると思っていたらけっこう大変な量になってしまいました。バックアップから復元する処理は次回にまわします。