ソースコードはこちら
https://github.com/mi3w2a1/incremental-backup
復元処理をしたいとき、復元したいファイルはひとつだけとか、バックアップ対象フォルダのなかに存在するひとつのフォルダだけということもあります。復元日時を指定してすべてのフォルダとファイルを復元するのは時間がかかるので、ピンポイントで処理をしたいときがあります。
そこでバックアップしたファイルのうち一部だけを復元できる機能を追加することにします。
以下のようなフォームを作成します。
上のリストボックスには指定されているフォルダの直下のファイルとフォルダが表示され、下のリストボックスには現在のフォルダの直下にあるフォルダが表示されます。下のリストボックスでアイテムを選択して[フォルダを開く]をクリックするとそのフォルダを開くことができます。[上へ移動]をクリックするとひとつ上のフォルダに戻ることができます。
上のリストボックスのアイテムを選択すれば選択されたファイルまたはフォルダをピンポイントで復元する処理がはじまります。
ますFormPartRestoreを生成するときはフィールド変数 fileListにログファイルの内容をセットします。この情報をみてFormPartRestoreは上下のリストボックスにファイルたフォルダを表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class FormPartRestore : Form { public FormPartRestore() { InitializeComponent(); TextBoxFolderPath.ReadOnly = true; } public string fileList = ""; public string SelectedSourcePath = ""; List<string> filePaths = new List<string>(); string curFolder = ""; string topFolder = ""; } |
まずフォームがロードされたらフィールド変数 fileListを利用してバックアップ可能なファイルとフォルダのリストを作成します。1行1行分解して最初の行を調べればバックアップ元のフォルダのパスがわかります。これに「\」を追加した文字列を取り除き、残された文字列のなかに「\」が存在しないのであればフォルダ内直下にあるフォルダかファイルのパスであると考えられます。これをりすリストボックスに表示させます。
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 |
public partial class FormPartRestore : Form { private void FormPartRestore_Load(object sender, EventArgs e) { string[] vs = fileList.Split(new string[] { "\r\n", "\n", }, StringSplitOptions.RemoveEmptyEntries); foreach(string str in vs) { string[] vs1 = str.Split(new string[] { "::", }, StringSplitOptions.RemoveEmptyEntries); if(vs1.Length == 2) filePaths.Add(vs1[0]); } int last = filePaths[0].LastIndexOf("\\"); curFolder = filePaths[0].Substring(0, last); topFolder = curFolder; TextBoxFolderPath.Text = curFolder; // 直下のファイルとフォルダを列挙 foreach(string str in filePaths) { string str1 = str.Substring(last+1); int index2 = str1.IndexOf("\\"); if(index2 == -1) { listBox1.Items.Add(str1); // フォルダと思われるパスを列挙 index2 = str1.IndexOf("."); if(index2 == -1) listBox2.Items.Add(str1); } } } } |
下側のリストのアイテムが選択されている状態で[フォルダを開く]がクリックされたら、現在のフォルダパスに「\」と選択されたアイテムの文字列を追加することで移動先のフォルダのパスを取得できます。これを一番上にあるテキストボックスに表示して現在の位置がわかるようにするとともに、filePathsリストのなかから現在のパス+¥をもつ文字列があるか調べます。これが現在のフォルダの直下または配下に存在するファイルとフォルダのパスです。
リストボックスに表示させたいのは現在のフォルダの配下に存在するすべてのファイルとフォルダではなく、直下にあるファイルとフォルダです。これも現在のフォルダパスに¥を追加したものを取り除き、残された文字列のなかに¥が存在するかどうかで、直下に存在するファイルとフォルダなのか区別できます。
直下に存在するファイルとフォルダのパスが取得できたら、これを上下のリストボックスに表示させます。
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 FormPartRestore : Form { private void ButtonOpenFolder_Click(object sender, EventArgs e) { int index = listBox2.SelectedIndex; if(index == -1) { MessageBox.Show("オープンするフォルダが選択されていません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } curFolder = curFolder + "\\" + (string)listBox2.Items[index]; TextBoxFolderPath.Text = curFolder; var list = filePaths.Where(x => x.IndexOf(curFolder) != -1).ToList(); listBox1.Items.Clear(); listBox2.Items.Clear(); foreach(string str in list) { if(str.Length < curFolder.Length + 1) continue; if(str.IndexOf(curFolder + "\\") == -1) continue; string str2 = str.Substring(curFolder.Length + 1); int index2 = str2.IndexOf("\\"); if(index2 == -1) { listBox1.Items.Add(str2); // フォルダと思われるパスを列挙 index = str2.IndexOf("."); if(index == -1) { listBox2.Items.Add(str2); } } } } } |
ひとつ上のフォルダに移動する場合も同じような処理です。FileInfoクラスをつかえば指定したパスの親フォルダのパスを取得できます。これをつかって移動先の直下にあるファイルとフォルダをリストボックスに表示させています。
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 |
public partial class FormPartRestore : Form { private void ButtonFolderUp_Click(object sender, EventArgs e) { if(curFolder == topFolder) { MessageBox.Show("このフォルダより上へは移動できません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } System.IO.FileInfo info = new System.IO.FileInfo(curFolder); curFolder = info.DirectoryName; TextBoxFolderPath.Text = curFolder; var list = filePaths.Where(x => x.IndexOf(curFolder) != -1).ToList(); listBox1.Items.Clear(); listBox2.Items.Clear(); // 直下のファイルとフォルダのみ列挙 foreach(string str in list) { if(str.Length < curFolder.Length + 1) continue; if(str.IndexOf(curFolder + "\\") == -1) continue; string str2 = str.Substring(curFolder.Length + 1); int index = str2.IndexOf("\\"); if(index == -1) { listBox1.Items.Add(str2); // フォルダと思われるパスを列挙 int index2 = str2.IndexOf("."); if(index2 == -1) { listBox2.Items.Add(str2); } } } } } |
[選択]をクリックしたら確認のためのメッセージボックスを表示し、[OK]がクリックされたら処理を開始します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class FormPartRestore : Form { private void ButtonSelect_Click(object sender, EventArgs e) { int index = listBox1.SelectedIndex; if(index == -1) return; string selectedPath = curFolder + "\\" + (string)listBox1.Items[index]; FormRestore form = (FormRestore)Owner; DateTime dt = new DateTime(form.RestoreTime); string str = dt.ToString("yyyy年 MM月 dd日 HH時 mm分 ss秒"); string mes = String.Format("フォルダ {0} に\n{1} の{2}のデータを復元します。\nよろしいですか?", form.SelectedPath, str, selectedPath); if(MessageBox.Show(mes, "確認", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK) { SelectedSourcePath = selectedPath; Close(); } } } |
上記のフォームはメインフォームのメニュー [バックアップ先から復元] を選択したときに表示される以下のフォームでボタンをクリックしたときに表示されます。そこでFormRestoreクラスも変更します。
チェックボックス[一部のファイル・フォルダだけ復元]がチェックされているとき、[復元の対象を指定する]ボタンが有効になります。このときに同ボタンをクリックすると上記のフォームが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class FormRestore : Form { private void FormRestore_Load(object sender, EventArgs e) { if(TicksList == null) return; foreach(long ticks in TicksList) { DateTime dt = new DateTime(ticks); string str = dt.ToString("yyyy年 MM月 dd日 HH時 mm分 ss秒"); listBox1.Items.Add(str); } // 追加 ChangeButtonSelectFileFolderEnabled(); } } |
ChangeButtonSelectFileFolderEnabled()メソッドは、チェックボックス[一部のファイル・フォルダだけ復元]がチェックされているかどうかで[復元の対象を指定する]ボタンの状態を変えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class FormRestore : Form { void ChangeButtonSelectFileFolderEnabled() { if(CheckBoxIsPartFiles.Checked) ButtonSelectFileFolder.Enabled = true; else ButtonSelectFileFolder.Enabled = false; } private void CheckBoxIsPartFiles_CheckedChanged(object sender, EventArgs e) { ChangeButtonSelectFileFolderEnabled(); } } |
[復元の対象を指定する]ボタンをクリックするとファイルやフォルダをピンポイントで指定して復元するためのフォームが表示されます。そして適切な値が設定されている状態でフォームが消滅するとみずからも消滅して、バックアップの処理が開始されます。
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 |
public partial class FormRestore : Form { public string SelectedSourcePartPath = ""; private void ButtonSelectFileFolder_Click(object sender, EventArgs e) { if (SelectedPath == "" || !Directory.Exists(SelectedPath)) { MessageBox.Show("先に復元先フォルダを指定してください!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } int i = listBox1.SelectedIndex; if (i != -1) { Form1 form1 = (Form1)Owner; string selectLogFilePath = form1.GetLogFilePathFromTicks(TicksList[i]); StreamReader sr = new StreamReader(selectLogFilePath); string str = sr.ReadToEnd(); sr.Close(); RestoreTime = TicksList[listBox1.SelectedIndex]; FormPartRestore form = new FormPartRestore(); form.fileList = str; form.ShowDialog(this); SelectedSourcePartPath = form.SelectedSourcePath; if (SelectedSourcePartPath != "") { this.Close(); } } else { MessageBox.Show("復元する時刻を選択してください!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } |
[バックアップ先から復元] を選択したときに表示されるフォームでファイルやフォルダをピンポイントで指定して復元する処理が選択された場合はそのための処理をおこなえるようにしなければなりません。
FormRestoreクラスのフィールド変数 SelectedSourcePartPathの文字列が空かどうかでその判断をしています。すべて復元するのではなく一部のファイルやフォルダだけを復元する場合はRestoreFilesメソッドではなく、RestorePartFilesメソッドで処理がおこなわれます。
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 |
public partial class Form1 : Form { private void RestoreFilesMenuItem_Click(object sender, EventArgs e) { string targetFolder = GetTargetFolderPath(); if(targetFolder == "") { MessageBox.Show("項目が選択されていません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if(!Directory.Exists(GetLogFolderPath(targetFolder))) { MessageBox.Show("バックアップは存在しません", "報告", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } List<long> ticksList = GetRestoreTicksList(targetFolder); FormRestore form = new FormRestore(); form.TicksList = ticksList; form.ShowDialog(this); if(form.RestoreTime > 0) { string folderPath = form.SelectedPath; long time = form.RestoreTime; List<long> vs = form.TicksList; List<string> backupFolderPaths = GetBackupFolderPaths(time, vs); string logPath = GetLogFilePathFromTicks(time); if(form.SelectedSourcePartPath == "") { Task.Run(() => { EnableMenu(false); RestoreFiles(folderPath, logPath, backupFolderPaths, GetSourceFolderPath()); EnableMenu(true); }); } else { Task.Run(() => { EnableMenu(false); RestorePartFiles(folderPath, logPath, backupFolderPaths, GetSourceFolderPath(), form.SelectedSourcePartPath); EnableMenu(true); }); } } form.Dispose(); } } |
最後にこれが指定されたファイル・フォルダを一部だけ復元するメソッドです。
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 |
public partial class Form1 : Form { /// <summary> /// ファイル・フォルダを一部だけ復元する /// </summary> /// <param name="folderPath">どこへ復元するか</param> /// <param name="logFilePath">復元処理のために必要なログファイルの場所</param> /// <param name="backupFolderPaths">バックアップに必要なデータが存在するフォルダのパス(複数)</param> /// <param name="sourceFolderPath">バックアップ元のフォルダのパス</param> /// <param name="sourcePartFolderPath">本当に復元したいバックアップ元のフォルダのパス</param> void RestorePartFiles(string folderPath, string logFilePath, List<string> backupFolderPaths, string sourceFolderPath, string sourcePartFolderPath) { FormRestoreProgress formRestoreProgress = new FormRestoreProgress(); formRestoreProgress.Show(); StreamReader sr = new StreamReader(logFilePath); string str = sr.ReadToEnd(); string[] vs1 = str.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); vs1 = vs1.Where(x => x.IndexOf(sourcePartFolderPath) != -1).ToArray(); Invoke((Action)(() => { Invoke((Action)(() => { formRestoreProgress.GetProgressBar().Value = 0; formRestoreProgress.GetProgressBar().Maximum = vs1.Length; })); })); foreach (string path in vs1) { if (formRestoreProgress.RestoreCancel) { System.Threading.Thread.Sleep(1000); Invoke((Action)(() => { formRestoreProgress.RestoreCancel = true; formRestoreProgress.Dispose(); })); MessageBox.Show("復元処理は中断されました", "", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } Invoke((Action)(() => { Invoke((Action)(() => { formRestoreProgress.GetProgressBar().Value++; })); })); string[] vs2 = path.Split(new string[] { "::" }, StringSplitOptions.RemoveEmptyEntries); if (vs2.Length != 2) { MessageBox.Show("ログファイルが破損しています!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); System.Threading.Thread.Sleep(1000); Invoke((Action)(() => { formRestoreProgress.RestoreCancel = true; formRestoreProgress.Dispose(); })); return; } string filePath = vs2[0].Replace(sourceFolderPath, folderPath); bool isFind = false; foreach (string backupFolderPath in backupFolderPaths) { string backupFilePath1 = vs2[0].Replace(sourceFolderPath, backupFolderPath); if (File.Exists(backupFilePath1)) { File.Copy(backupFilePath1, filePath); isFind = true; break; } if (Directory.Exists(backupFilePath1)) { string newFolderPath = vs2[0].Replace(sourceFolderPath, folderPath); Directory.CreateDirectory(newFolderPath); isFind = true; break; } } if (!isFind) MessageBox.Show("エラー ファイルがみつからない", vs2[0], MessageBoxButtons.OK, MessageBoxIcon.Error); } System.Threading.Thread.Sleep(1000); Invoke((Action)(() => { formRestoreProgress.RestoreCancel = true; formRestoreProgress.Dispose(); })); MessageBox.Show("復元完了", "報告", MessageBoxButtons.OK, MessageBoxIcon.Information); } } |