ソースコードはこちら
https://github.com/mi3w2a1/incremental-backup
増分バックアップは最初にすべてのデータをバックアップし、その後は変更・追加のあった箇所だけバックアップする方法です。
差分バックアップとの違いは、差分バックアップが初回フルバックアップから変更・追加のあった箇所を毎回バックアップすることに対して、増分バックアップは前回おこなわれたバックアップ(初回ではなく「前回」というのがポイント)から変更・追加のあった箇所のみバックアップすることです。
そのため増分バックアップのほうが実際にバックアップされるデータを小さくすることができます。反面、復元するときは複数のデータブロックをつなぎ合わせる必要があるため作業が複雑になるというデメリットがあります。
実際にバックアップする処理は次回に回して、今回はどの部分をどこにバックアップするのかを設定するまでをつくります。
BackupFolderInfoクラスはバックアップ元フォルダのパスとバックアップ先フォルダのパスを記憶するためのクラスです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class BackupFolderInfo { public BackupFolderInfo() { } public BackupFolderInfo(string sourceFolderPath, string targetFolderPath) { SourceFolderPath = sourceFolderPath; TargetFolderPath = targetFolderPath; } public string SourceFolderPath = ""; public string TargetFolderPath = ""; } |
Configクラスは設定を保存するためのクラスです。
1 2 3 4 |
public class Config { public List<BackupFolderInfo> BackupFolderInfos = null; } |
アプリケーションが開始されたらリストビューコントロールの初期化と設定の読み込みをおこないます。
リストビューコントロールにはバックアップ元とバックアップ先の一覧が表示されます。またこのリストビューコントロールはひとつのアイテムしか選択できません。
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 { List<BackupFolderInfo> BackupFolderInfos = new List<BackupFolderInfo>(); public Form1() { InitializeComponent(); InitListView(); LoadConfig(); } void InitListView() { listView1.View = View.Details; listView1.Columns.Add("バックアップ元"); listView1.Columns.Add("バックアップ先"); listView1.Columns[0].Width = 300; listView1.Columns[1].Width = 300; listView1.FullRowSelect = true; listView1.MultiSelect = false; listView1.HideSelection = false; } } |
設定のファイルはApplication Dataフォルダ内に保存します。ファイルが存在しないということは初めて起動されたときです。その場合はなにもおこないません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public partial class Form1 : Form { void LoadConfig() { string path = Application.UserAppDataPath + "\\config.xml"; if(!File.Exists(path)) return; XmlSerializer xml = new XmlSerializer(typeof(Config)); StreamReader sr = new StreamReader(path); Config config = (Config)xml.Deserialize(sr); sr.Close(); BackupFolderInfos = config.BackupFolderInfos; foreach(BackupFolderInfo info in BackupFolderInfos) { string[] item = { info.SourceFolderPath, info.TargetFolderPath }; listView1.Items.Add(new ListViewItem(item)); } } } |
バックアップ先またはバックアップ先が変更されたときは、SaveConfig()メソッドを呼び出してその設定を保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { void SaveConfig() { string path = Application.UserAppDataPath + "\\config.xml"; Config config = new Config(); config.BackupFolderInfos = BackupFolderInfos; XmlSerializer xml = new XmlSerializer(typeof(Config)); StreamWriter sw = new StreamWriter(path); xml.Serialize(sw, config); sw.Close(); } } |
メニューの[設定] ⇒ [フォルダを登録]が選択されたら設定のためのダイアログを表示させます。そして有効なパスが入力された状態でダイアログが閉じられたら、設定を保存するとともに登録されたフォルダをリストビューに表示させます。
バックアップ元フォルダとバックアップ先のフォルダのパスが同じだったり、バックアップ元フォルダのなかにバックアップ先フォルダを指定した場合、存在しないパスを指定した場合はエラーを示すメッセージボックスを表示します。
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 |
public partial class Form1 : Form { private void RegistFolderMenuItem_Click(object sender, EventArgs e) { FormFolder form = new FormFolder(); if(form.ShowDialog() == DialogResult.OK) { if(Directory.Exists(form.SourceFolderPath) && Directory.Exists(form.TargetFolderPath)) { string[] item = { form.SourceFolderPath, form.TargetFolderPath }; string[] filePaths = Directory.GetDirectories(form.SourceFolderPath, "*.*", SearchOption.AllDirectories); if(form.SourceFolderPath != form.TargetFolderPath && !filePaths.Any(x => x == form.TargetFolderPath)) { listView1.Items.Add(new ListViewItem(item)); BackupFolderInfos.Add(new BackupFolderInfo(form.SourceFolderPath, form.TargetFolderPath)); SaveConfig(); } else { MessageBox.Show("バックアップ元フォルダのなかにバックアップ先のフォルダを指定することはできません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else MessageBox.Show("存在するフォルダでないと登録できません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } form.Dispose(); } } |
バックアップフォルダを設定するためのFormFolderクラスは以下のようになっています。
バックアップ設定のためのダイアログ
テキストボックスは自作したFilePathTextBoxクラスを使用しています。
FilePathTextBoxクラスはここで解説しています。
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 |
public partial class FormFolder : Form { public FormFolder() { InitializeComponent(); buttonOK.DialogResult = DialogResult.OK; buttonCancel.DialogResult = DialogResult.Cancel; } public string SourceFolderPath = ""; public string TargetFolderPath = ""; protected override void OnLoad(EventArgs e) { SourceFolderPathTextBox.Text = SourceFolderPath; TargetFolderPathTextBox.Text = TargetFolderPath; base.OnLoad(e); } private void buttonOK_Click(object sender, EventArgs e) { SourceFolderPath = SourceFolderPathTextBox.Text; TargetFolderPath = TargetFolderPathTextBox.Text; } } |
メニューの[設定] ⇒ [登録内容を変更]が選択されたら設定のためのダイアログを表示させます。そのときに現在の設定が表示されるようにしています。有効なパスが入力された状態でダイアログが閉じられたら、設定を保存するとともに登録されたフォルダをリストビューに表示させます。不正なパスが入力された状態で閉じられた場合はエラーを示すメッセージボックスを表示させます。
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 { private void ChangeFolderMenuItem_Click(object sender, EventArgs e) { var selectedItems = listView1.SelectedItems; if(selectedItems.Count == 0) { MessageBox.Show("編集する項目を選択してください!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } ListViewItem selectedItem = selectedItems[0]; FormFolder form = new FormFolder(); form.SourceFolderPath = selectedItem.SubItems[0].Text; form.TargetFolderPath = selectedItem.SubItems[1].Text; if(form.ShowDialog() == DialogResult.OK) { if(Directory.Exists(form.SourceFolderPath) && Directory.Exists(form.TargetFolderPath)) { string[] filePaths = Directory.GetDirectories(form.SourceFolderPath, "*.*", SearchOption.AllDirectories); if(form.SourceFolderPath != form.TargetFolderPath && !filePaths.Any(x => x == form.TargetFolderPath)) { int index = listView1.Items.IndexOf(selectedItem); listView1.Items[index].SubItems[0].Text = form.SourceFolderPath; listView1.Items[index].SubItems[1].Text = form.TargetFolderPath; BackupFolderInfos[index].SourceFolderPath = form.SourceFolderPath; BackupFolderInfos[index].TargetFolderPath = form.TargetFolderPath; SaveConfig(); } else { MessageBox.Show("バックアップ元フォルダのなかにバックアップ先のフォルダを指定することはできません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else MessageBox.Show("存在するフォルダでないと登録できません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } form.Dispose(); } } |
メニューの[設定] ⇒ [登録内容を削除]が選択されたら登録を解除するバックアップフォルダの情報を表示して確認を求めるメッセージボックスを表示させます。[OK]がクリックされたら登録内容を削除します。
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 |
public partial class Form1 : Form { private void UnregistFolderMenuItem_Click(object sender, EventArgs e) { var selectedItems = listView1.SelectedItems; if(selectedItems.Count == 0) { MessageBox.Show("削除する項目を選択してください!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } ListViewItem selectedItem = selectedItems[0]; int index = listView1.Items.IndexOf(selectedItem); string source = BackupFolderInfos[index].SourceFolderPath; string target = BackupFolderInfos[index].TargetFolderPath; string mes = String.Format("以下の登録を削除します。\n\nバックアップ元:\n{0}\n\nバックアップ先\n{1}", source, target); if(MessageBox.Show(mes, "確認", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK) { BackupFolderInfos.RemoveAt(index); listView1.Items.RemoveAt(index); SaveConfig(); } } } |
素晴らしいサイトありがとうございます。
感謝しかありません。
趣味で久々にプログラムを作ろうと思いネットをみて、運よく”鳩でも”に出会いました。
色々なサイトがありますが、実用的なアプリの作り方が書かれているサイトにはそう出会えません。
普段PCデータのバックアップを取るときに、時間が掛かりすぎたりフリーズしたりと苦労していました。私ならこんなバックアップソフトを作るのになあという思いがあり、そもそも一般的にバックアップソフトはどんな手順で処理をしているのだろうと思っていたところ、ここにたどり着きました。
1行のコードを理解するのに3日掛かったところもありますが、何とか全体の流れが分かってきました。
素晴らしいサイト、どうもありがとうございました。
(補足)
私は、サイトのコードをコピペして何とか作れるレベルです。
たまたまVS2022をダウンロードしたのでC#を始めることになりました。
てっきりC++の後継言語?と思っていたら、違うのですね。
私が望むバックアップソフトは、処理時間が掛かりすぎる場合は、バックアップを一旦停止してPCも再起動して、改めて一旦停止の続きから再開できる仕様です。今回の勉強で疲れ果てたので、また、エネルギーが湧いたらトライしてみようと思います。