ソースコードはこちら
https://github.com/mi3w2a1/incremental-backup
バックアップにしてもファイルの復元にしても時間がかかります。これまで作成したアプリケーションは処理がおこなわれているあいだ、他の処理ができませんでした。大きなサイズのファイルをバックアップしているときはフリーズしてしまったように見えてしまいます。
それから処理がどの程度まで進行しているのかもわかりません。せめてバックアップすべきファイルがどれだけあって、現在バックアップが完了しているファイルがどれくらいあるのかをわかるようにしたいものです。
そこでプログレスバーで進行状況がわかるようにしました。
まずバックアップに時間がかかるときにフリーズしてしまったように見えてしまう問題を回避するためには非同期処理を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { private void StartBackupMenuItem_Click(object sender, EventArgs e) { StartBackup(); } void StartBackup() { var task = Task.Run(() => { string sourceFolder = GetSourceFolderPath(); string targetFolder = GetTargetFolderPath(); if(Backup(sourceFolder, targetFolder)) MessageBox.Show("終了"); }); } } |
これでよさそうなのですが、これでは処理中にリストビューの選択項目を変更することができたり、別のバックアップの処理を開始することもできてしまいます。すると処理が正常に行なわれなくなるので、バックアップの処理が完了するまでメニューの選択やコントロール類の操作ができないようにします。
EnableMenu(bool isEnable)メソッドを引数falseで実行すれば処理中に他のメニューを選択することはできなくなります。また処理の途中でアプリを終了してバックアップが途中で止まってしまうことも避けられます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public partial class FormRestore : Form { private void StartBackupMenuItem_Click(object sender, EventArgs e) { StartBackup(); } void StartBackup() { var task = Task.Run(() => { EnableMenu(false); string sourceFolder = GetSourceFolderPath(); string targetFolder = GetTargetFolderPath(); if(Backup(sourceFolder, targetFolder)) MessageBox.Show("終了"); EnableMenu(true); }); } bool CanEnd = true; void EnableMenu(bool isEnable) { CongigMenuItem.Enabled = isEnable; StartBackupMenuItem.Enabled = isEnable; RestoreFilesMenuItem.Enabled = isEnable; listView1.Enabled = isEnable; CanEnd = isEnable; } protected override void OnClosing(CancelEventArgs e) { if(!CanEnd) { e.Cancel = true; MessageBox.Show("処理中なので終了できません!", "報告", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } base.OnClosing(e); } } |
本当に途中で処理を中断したい場合もあるので、そのための機能も追加します。
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’ がアクセスされました」という例外がでてしまいます。そこで
1 2 3 |
Invoke((Action)(() => { })); |
と書くことで例外の発生を回避します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { void StartBackup() { var task = Task.Run(() => { EnableMenu(false); string sourceFolder = GetSourceFolderPath(); string targetFolder = GetTargetFolderPath(); if(Backup(sourceFolder, targetFolder)) MessageBox.Show("終了"); EnableMenu(true); }); } } |
EnableMenu(bool isEnable)メソッドはバックアップの最中に別のメニューを選択して他のフォルダのバックアップが開始されたり、リストビューコントロールで選択されている項目が変更されて正しくバックアップがされない問題を回避するためのものです。EnableMenu(false)とすることでメニューが使用不可となり、アプリケーションを終了することができなくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { void EnableMenu(bool isEnable) { Invoke((Action)(() => { CongigMenuItem.Enabled = isEnable; StartBackupMenuItem.Enabled = isEnable; RestoreFilesMenuItem.Enabled = isEnable; listView1.Enabled = isEnable; CanEnd = isEnable; })); } } |
非同期処理時にリストビューコントロールで選択されている項目を取得するので、GetSourceFolderPath()メソッドやGetTargetFolderPath()メソッドでもInvoke((Action)(() => { }))とする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public partial class Form1 : Form { string GetSourceFolderPath() { string ret = ""; Invoke((Action)(() => { var indexes = listView1.SelectedIndices; if(indexes.Count == 1) ret = listView1.Items[indexes[0]].SubItems[0].Text; })); return ret; } string GetTargetFolderPath() { string ret = ""; Invoke((Action)(() => { var indexes = listView1.SelectedIndices; if(indexes.Count == 1) ret = listView1.Items[indexes[0]].SubItems[1].Text; })); return ret; } } |
Backup(string sourceFolder, string targetFolder)メソッドですが、バックアップしているときにどこまで処理が終わっているかを示すプログレスバーを表示させます。また[中止]をクリックすればバックアップを途中でやめることもできるようにしました。
FormBackupProgressクラスにはフィールド変数 BackupCancelとIsEndがあります。BackupCancelはバックアップを中止しようとするとtrueになります。IsEnd はバックアップが完了したときにtrueになります。フォームが閉じられようとしているときに、×がクリックされたのか、バックアップが終了したからなのかを区別できるようにしています。
バックアップの進行状態を知らせるダイアログをそのまま表示させようとすると大量にファイルをバックアップしようとするとフリーズしたようになってしまうので、Show()メソッドを変更しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
public partial class FormBackupProgress : Form { public FormBackupProgress() { InitializeComponent(); } new public void Show() { Task.Run(()=> { ShowDialog(); }); } public bool BackupCancel = false; public bool IsEnd = false; private void button1_Click(object sender, EventArgs e) { BackupCancel = true; } protected override void OnClosing(CancelEventArgs e) { if(!IsEnd) { if(MessageBox.Show("バックアップ処理を中止しますか?", "", MessageBoxButtons.YesNo) == DialogResult.Yes) BackupCancel = true; else e.Cancel = true; } base.OnClosing(e); } public ProgressBar GetProgressBarGetSourceFiles() { return progressBarGetSourceFiles; } public ProgressBar GetProgressBarGetBackupFiles() { return progressBarGetBackupFiles; } public ProgressBar GetProgressBarBackupFolders() { return progressBarBackupFolders; } public ProgressBar GetProgressBarBackupFiles() { return progressBarBackupFiles; } public ProgressBar GetProgressBarLogFile() { return progressBarLogFile; } public ProgressBar GetProgressBarDifferenceFile() { return progressBarDifferenceFile; } } |
それからFormBackupProgressをDisposeしようとすると例外が発生するのでFormBackupProgress.Designer.csを修正します。本当はこのファイルを直接編集するのはよくないのですが、今回はしかたありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
partial class FormBackupProgress { protected override void Dispose(bool disposing) { Invoke((System.Action)(() => { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); })); } } |
Backup(string sourceFolder, string targetFolder)メソッドですが、長くなってしまったので書き直しました。
ところどころにある
1 2 |
if(StopBackupIfCancel(formBackupProgress)) return false; |
はダイアログで中止のボタンが押されていないか確認する処理です。中止ボタンが押されていた場合は処理はやめてBackupメソッドもfalseを返して終了させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
public partial class Form1 : Form { bool Backup(string sourceFolder, string targetFolder) { // 引数が適切かチェック if(!CheckFolderPaths(sourceFolder, targetFolder)) return false; // プログレスバーのついたフォームを表示 FormBackupProgress formBackupProgress = new FormBackupProgress(); formBackupProgress.Show(); // バックアップの候補になるファイルとフォルダのパスの取得 formBackupProgress.progressBarGetSourceFiles.Maximum = 2; string[] filePaths = GetSourceFilePaths(sourceFolder, formBackupProgress); if (filePaths == null) { Invoke((Action)(() => { formBackupProgress.IsEnd = true; formBackupProgress.Dispose(); })); return false; } else { Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarGetSourceFiles(); Invoke((Action)(() => { progressBar.Value = 1; })); })); } string[] folderPaths = GetSourceFolderPaths(sourceFolder, formBackupProgress); if (folderPaths == null) { Invoke((Action)(() => { formBackupProgress.IsEnd = true; formBackupProgress.Dispose(); })); return false; } else { Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarGetSourceFiles(); Invoke((Action)(() => { progressBar.Value = 2; })); })); } // 以前にバックアップの処理はされているか。ログがあるはずなので取得して解析する // ログがない場合は初めてのバックアップなのですべてバックアップする List<FileAndTime> fileAndTimes = null; string lastLogFilePath = GetLastLogFilePath(targetFolder); if(lastLogFilePath != "") { fileAndTimes = CreateFileAndTimes(lastLogFilePath); if(StopBackupIfCancel(formBackupProgress)) { Invoke((Action)(() => { formBackupProgress.IsEnd = true; formBackupProgress.Dispose(); })); return false; } // DoesNeedBackupメソッドの処理は // バックアップ元フォルダにたくさんのファイルがあると時間がかかるかもしれない if(!DoesNeedBackup(fileAndTimes, folderPaths, filePaths, formBackupProgress)) { Invoke((Action)(() => { formBackupProgress.IsEnd = true; formBackupProgress.Dispose(); })); MessageBox.Show("バックアップをとる必要があるファイルは見つかりませんでした", "報告", MessageBoxButtons.OK, MessageBoxIcon.Information); return false; } } else { fileAndTimes = new List<FileAndTime>(); } Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarGetBackupFiles(); Invoke((Action)(() => { progressBar.Value = 0; progressBar.Maximum = 1; progressBar.Value = 1; })); })); long nowTicks = DateTime.Now.Ticks; string targetFolder1 = GetFolderPathFromTicks(nowTicks); if(!Directory.Exists(targetFolder1)) Directory.CreateDirectory(targetFolder1); List<string> newFiles = new List<string>(); List<string> newFolders = new List<string>(); List<string> modifiedFiles = new List<string>(); List<string> deletedFiles = new List<string>(); // 必要であればフォルダをバックアップする if(!BackupFolderIfNeed(sourceFolder, targetFolder1, folderPaths, fileAndTimes, newFolders, formBackupProgress)) { Invoke((Action)(() => { formBackupProgress.IsEnd = true; formBackupProgress.Dispose(); })); return false; } // 必要であればファイルをバックアップする if(!BackupFilesIfNeed(sourceFolder, targetFolder1, filePaths, fileAndTimes, newFiles, modifiedFiles, formBackupProgress)) { Invoke((Action)(() => { formBackupProgress.IsEnd = true; formBackupProgress.Dispose(); })); return false; } // deletedFilesには前回のバックアップでは存在したがいまは存在しない(削除された)ファイルのパスが格納される deletedFiles = fileAndTimes.Where(x => !x.IsChecked).Select(x => x.FilePath).ToList(); // ログを保存する if(!SaveLogFile(targetFolder, folderPaths, filePaths, nowTicks, formBackupProgress)) { Invoke((Action)(() => { formBackupProgress.IsEnd = true; formBackupProgress.Dispose(); })); return false; } // 差分をファイルとして保存する SaveDifferenceFile(targetFolder, newFiles, newFolders, modifiedFiles, deletedFiles, nowTicks, formBackupProgress); Invoke((Action)(() => { formBackupProgress.IsEnd = true; formBackupProgress.Dispose(); })); return true; } } |
CheckFolderPaths(string sourceFolder, string targetFolder)メソッドはBackupメソッドに渡されたパスが適切なものかどうかを判断するものです。不適切なパスが渡された場合はエラーを表示して終了します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { bool CheckFolderPaths(string sourceFolder, string targetFolder) { if(sourceFolder == "" || targetFolder == "") { MessageBox.Show("バックアップするフォルダが選択されていません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } if(!Directory.Exists(sourceFolder) || !Directory.Exists(targetFolder)) { MessageBox.Show("設定されているフォルダは存在しません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } return true; } } |
GetSourceFolderPathsメソッドとGetSourceFilePathsメソッドはバックアップの対象になるかもしれないフォルダとファイルのパスを取得するメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { string[] GetSourceFilePaths(string sourceFolder, FormBackupProgress formBackupProgress) { string[] filePaths = Directory.GetFiles(sourceFolder, "*.*", SearchOption.AllDirectories); if(StopBackupIfCancel(formBackupProgress)) return null; else return filePaths; } string[] GetSourceFolderPaths(string sourceFolder, FormBackupProgress formBackupProgress) { string[] folderPaths = Directory.GetDirectories(sourceFolder, "*.*", SearchOption.AllDirectories); if(StopBackupIfCancel(formBackupProgress)) return null; else return folderPaths; } } |
DoesNeedBackup(List<FileAndTime> fileAndTimes, string[] folderPaths, string[] filePaths, FormBackupProgress formBackupProgress)メソッドは本当にバックアップをとらなければならないファイルまたはフォルダがひとつ以上存在するか調べるメソッドです。この処理はファイル数が多くなると時間がかかるので、プログレスバーで進行状況が見えるようにしています。また途中で中止することもできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
public partial class Form1 : Form { bool DoesNeedBackup(List<FileAndTime> fileAndTimes, string[] folderPaths, string[] filePaths, FormBackupProgress formBackupProgress) { if(fileAndTimes.Count != folderPaths.Length + filePaths.Length) return true; bool doesNeed = false; Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarGetBackupFiles(); Invoke((Action)(() => { progressBar.Maximum = filePaths.Length + folderPaths.Length; })); })); foreach(string path in filePaths) { if(StopBackupIfCancel(formBackupProgress)) return false; FileAndTime fileAndTime = fileAndTimes.FirstOrDefault(x => x.FilePath == path); if(fileAndTime == null) return true; else { if (File.GetLastWriteTime(path).Ticks > fileAndTime.Tick) return true; Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarGetBackupFiles(); Invoke((Action)(() => { progressBar.Value++; })); })); } } foreach(string path in folderPaths) { if(StopBackupIfCancel(formBackupProgress)) return false; FileAndTime fileAndTime = fileAndTimes.FirstOrDefault(x => x.FilePath == path); if(fileAndTime == null) return true; else { if(Directory.GetLastWriteTime(path).Ticks > fileAndTime.Tick) return true; Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarGetBackupFiles(); Invoke((Action)(() => { progressBar.Value++; })); })); } } return doesNeed; } } |
BackupFolderIfNeed(string sourceFolder, string targetFolder1, string[] folderPaths, List<FileAndTime> fileAndTimes, List<string> newFolders, FormBackupProgress formBackupProgress)メソッドはバックアップが必要かどうか調べて必要であればバックアップしています。
これもプログレスバーで進行状況がわかるようになっているとともに、中止することも可能です。その場合はfalseを返して中止したことがわかるようにしています。実はフォルダをつくるだけならたいした時間はかかりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public partial class Form1 : Form { bool BackupFolderIfNeed(string sourceFolder, string targetFolder1, string[] folderPaths, List<FileAndTime> fileAndTimes, List<string> newFolders, FormBackupProgress formBackupProgress) { Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarBackupFolders(); Invoke((Action)(() => { progressBar.Maximum = folderPaths.Length; })); })); foreach(string path in folderPaths) { if(StopBackupIfCancel(formBackupProgress)) return false; FileAndTime fileAndTime = fileAndTimes.FirstOrDefault(x => x.FilePath == path); if(fileAndTime == null || Directory.GetLastWriteTime(path).Ticks != fileAndTime.Tick) { string destfolderPath = path.Replace(sourceFolder, targetFolder1); Directory.CreateDirectory(destfolderPath); } if(fileAndTime != null) fileAndTime.IsChecked = true; else newFolders.Add(path); Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarBackupFolders(); Invoke((Action)(() => { progressBar.Value++; })); })); } return true; } } |
BackupFilesIfNeed(string sourceFolder, string targetFolder1, string[] filePaths, List<FileAndTime> fileAndTimes, List<string> newFiles, List<string> modifiedFiles, FormBackupProgress formBackupProgress)メソッドはファイルのバックアップが必要かどうか調べて必要であればバックアップしています。
これもプログレスバーで進行状況がわかるようになっているとともに、中止することも可能です。その場合はfalseを返して中止したことがわかるようにしています。大きなファイルをコピーしているときに中止をしようとしてもループ内のStopBackupIfCancelメソッドが実行されないと処理が止まることはありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public partial class Form1 : Form { bool BackupFilesIfNeed(string sourceFolder, string targetFolder1, string[] filePaths, List<FileAndTime> fileAndTimes, List<string> newFiles, List<string> modifiedFiles, FormBackupProgress formBackupProgress) { Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarBackupFiles(); Invoke((Action)(() => { progressBar.Maximum = filePaths.Length; })); })); foreach(string path in filePaths) { if(StopBackupIfCancel(formBackupProgress)) return false; string destFilePath = path.Replace(sourceFolder, targetFolder1); FileAndTime fileAndTime = fileAndTimes.FirstOrDefault(x => x.FilePath == path); if(fileAndTime == null) { CopyFile(path, destFilePath); newFiles.Add(path); } else if(File.GetLastWriteTime(path).Ticks != fileAndTime.Tick) { CopyFile(path, destFilePath); modifiedFiles.Add(path); } if(fileAndTime != null) fileAndTime.IsChecked = true; Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarBackupFiles(); Invoke((Action)(() => { progressBar.Value++; })); })); } return true; } } |
SaveLogFile(string targetFolder, string[] folderPaths, string[] filePaths, long nowTicks, FormBackupProgress formBackupProgress)メソッドはログファイルを保存するためのメソッドです。一応、中止ボタンのクリックにも対応させていますが、実際の処理はすぐに終わるので、このメソッド内で中止の処理がおこなわれることはないと考えています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
public partial class Form1 : Form { bool SaveLogFile(string targetFolder, string[] folderPaths, string[] filePaths, long nowTicks, FormBackupProgress formBackupProgress) { string logFolderPath = GetLogFolderPath(targetFolder); if(!Directory.Exists(logFolderPath)) Directory.CreateDirectory(logFolderPath); string logFilePath = GetLogFilePathFromTicks(nowTicks); SaveLogFile(logFilePath, folderPaths, filePaths, formBackupProgress); if(StopBackupIfCancel(formBackupProgress)) return false; return true; } void SaveLogFile(string logFilePath, string[] folderPaths, string[] filePaths, FormBackupProgress formBackupProgress) { Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarLogFile(); Invoke((Action)(() => { progressBar.Maximum = folderPaths.Length + filePaths.Length; })); })); StringBuilder sb = new StringBuilder(); foreach(string path in folderPaths) { if(formBackupProgress.BackupCancel) return; DateTime dt = Directory.GetLastWriteTime(path); string time = dt.Ticks.ToString(); // ファイル名やフォルダ名として使えない文字でパスと更新時刻を区切る string str = String.Format("{0}::{1}\n", path, time); sb.Append(str); Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarLogFile(); Invoke((Action)(() => { progressBar.Value++; })); })); } foreach(string path in filePaths) { if(formBackupProgress.BackupCancel) return; DateTime dt = File.GetLastWriteTime(path); string time = dt.Ticks.ToString(); string str = String.Format("{0}::{1}\n", path, time); sb.Append(str); Invoke((Action)(() => { ProgressBar progressBar = formBackupProgress.GetProgressBarLogFile(); Invoke((Action)(() => { progressBar.Value++; })); })); } using(StreamWriter sw = new StreamWriter(logFilePath)) { sw.Write(sb.ToString()); } } } |
最後に差分がわかるように結果をファイルに保存します。この処理をやってくれるのが、SaveDifferenceFile(string targetFolder, List<
すぐ終わると思っていたらけっこう大変な量になってしまいました。バックアップから復元する処理は次回にまわします。
お世話になっております。
最後の第8回目までコピペしながら作りました。
しかしながら、ここの4回目のプログレスバーだけがどうしてもうまくいきません。
”有効ではないスレッド間の操作エラー”が出てしまいます。
どこで出るんだと言われそうですが、素人なりにいじくり回してコメントアウトして回避すると次の場所で出たりして、訳がわからなくなりました。
最初は以下で出たと記憶しております。
自分なりに、簡素化したコードで確認してもエラーは出ないのですが、貴殿のコードをコピペすると出てしまいます。
大変恐縮ですがソースコードを丸ごとZIPで頂けないでしょうか?
よろしくお願いいたします。
”有効ではないスレッド間の操作エラー”の原因ですが、
Windowsフォームのコントロールは、必ずコントロールを生成したスレッドから行わなければならない。
非同期処理をするとWindowsフォームのコントロールに対してコントロールを生成したスレッド以外からコントロールへアクセスすることになる
例外(有効ではないスレッド間の操作エラー)が発生する
対策としてこのページに記述しているとおり以下で囲います。
ところがプログレスバーを操作するときにこの処理をしていなかったので例外が発生してしまったわけです。
>大変恐縮ですがソースコードを丸ごとZIPで頂けないでしょうか?
上記リクエストに対しては処理の進行状況を表示させる C#で増分バックアップができるアプリケーション(4)までのものになります。完成品はしばらくお待ちください。
修正したものをgithubで公開しました。
https://github.com/mi3w2a1/incremental-backup
早速のご返答ありがとうございます。
>処理の進行状況を表示させる C#で増分バックアップができるアプリケーション(4)のバグを修正したものです。
ご返答の中で、既に(4)のバグを修正したものを提示いただいていること見逃しておりました。楽しみに確認してみます。
完成品は気長にお待ちしておりますので、管理人様のご都合でよろしくお願いいたします。
修正したものをgithubで公開しました。
https://github.com/mi3w2a1/incremental-backup
壊れたPCが直ったので、もしやと覗いてみたら、なんと完成版が提示されていました。
本当にありがとうございました。
またまたお世話になります。
有名なフリーソフトのBunBackupやFastCopyでさえ、使ってみると私にとっては、いまひとつ納得いかないところがあり、貴殿のバックアップソフトを活用してみることにしました。
しかし、私の環境では思わぬ不具合が発生しましたので報告させていただくとともに、改善方法を教えていただければ幸いです。
■不具合内容
●バックアップ元がNASの場合プログレスバーが一切進まず止まってしまう。
以下の場合は止まってしまう
・バックアップ元がNAS → バックアップ先NAS
・バックアップ元がNAS → バックアップ先PC内蔵ハードディスク
●以下の場合は問題なく動く
・バックアップ元がPC内蔵ハードディスク → バックアップ先NAS
・バックアップ元がPC内蔵ハードディスク → バックアップ先PC内蔵ハードディスク
・バックアップ元がUSB外付けハードディスク → バックアップ先USB外付けハードディスク
よろしくお願い致します。
NAS(Network Attached Storage)での使用は想定していません。ちょっと時間がかかるかもしれませんが考えてみます。