DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

2つのフォルダの同期を行う

フォルダをコピーする際に、コピー元のファイルがコピー先に存在しないか、コピー先のファイルより新しい場合に限りコピーし、さらに2つのフォルダのファイル構成が同じになるようにミラーリング(同期)する方法を紹介します。

まずフォルダのコピーについては、「フォルダをコピーする」をご覧ください。

ここではこのコードに手を加えることにします。

まず、File.ExistsメソッドとFile.GetLastWriteTimeメソッドを使い、コピー元のファイルがコピー先に存在しないか、またはコピー先のファイルより新しいか調べ、ファイルをコピーするようにします。

さらにミラーリングをするために、コピー先にあってコピー元にないファイルを探して削除するためのメソッド(ここでは"DeleteNotExistFiles")を作成します。

このようにして作成された新たなCopyDirectoryメソッドは次のようなものです。

VB.NET
コードを隠すコードを選択
'Imports System.IO
'がソースファイルの一番上に書かれているものとする

''' <summary>
''' ディレクトリをコピーする
''' </summary>
''' <param name="sourceDirName">コピーするディレクトリ</param>
''' <param name="destDirName">コピー先のディレクトリ</param>
''' <param name="newerOnly">新しいファイルのみコピーする</param>
''' <param name="sync">sourceDirNameにないファイルを削除する</param>
Public Shared Sub CopyDirectory(ByVal sourceDirName As String, _
                                ByVal destDirName As String, _
                                ByVal newerOnly As Boolean, _
                                ByVal sync As Boolean)
    'コピー先のディレクトリがないときは作る
    If Not Directory.Exists(destDirName) Then
        Directory.CreateDirectory(destDirName)
        '属性もコピー
        File.SetAttributes(destDirName, File.GetAttributes(sourceDirName))
    End If

    'コピー先のディレクトリ名の末尾に"\"をつける
    If destDirName(destDirName.Length - 1) <> Path.DirectorySeparatorChar Then
        destDirName = destDirName + Path.DirectorySeparatorChar
    End If

    'コピー元のディレクトリにあるファイルをコピー
    Dim files As String() = Directory.GetFiles(sourceDirName)
    Dim f As String
    For Each f In files
        Dim destFileName As String = destDirName + Path.GetFileName(f)
        'コピー先にファイルが存在し、
        'コピー元より更新日時が古い時はコピーする
        If Not newerOnly OrElse _
            Not File.Exists(destFileName) OrElse _
            File.GetLastWriteTime(destFileName) < File.GetLastWriteTime(f) Then

            File.Copy(f, destFileName, True)
        End If
    Next

    'コピー先にあってコピー元にないファイルを削除
    If sync Then
        DeleteNotExistFiles(sourceDirName, destDirName)
    End If

    'コピー元のディレクトリにあるディレクトリについて、再帰的に呼び出す
    Dim dirs As String() = Directory.GetDirectories(sourceDirName)
    Dim dir As String
    For Each dir In dirs
        CopyDirectory(dir, destDirName + Path.GetFileName(dir), newerOnly, sync)
    Next
End Sub

''' <summary>
''' destDirNameにありsourceDirNameにないファイルを削除する
''' </summary>
''' <param name="sourceDirName">比較先のフォルダ</param>
''' <param name="destDirName">比較もとのフォルダ</param>
Private Shared Sub DeleteNotExistFiles(ByVal sourceDirName As String, _
                                       ByVal destDirName As String)
    'sourceDirNameの末尾に"\"をつける
    If sourceDirName(sourceDirName.Length - 1) <> Path.DirectorySeparatorChar Then
        sourceDirName = sourceDirName + Path.DirectorySeparatorChar
    End If

    'destDirNameにありsourceDirNameにないファイルを削除する
    Dim files As String() = Directory.GetFiles(destDirName)
    Dim f As String
    For Each f In files
        If Not File.Exists(sourceDirName + Path.GetFileName(f)) Then
            File.Delete(f)
        End If
    Next

    'destDirNameにありsourceDirNameにないフォルダを削除する
    Dim folders As String() = Directory.GetDirectories(destDirName)
    Dim folder As String
    For Each folder In folders
        If Not Directory.Exists(sourceDirName + Path.GetFileName(folder)) Then
            Directory.Delete(folder, True)
        End If
    Next
End Sub
C#
コードを隠すコードを選択
//using System.IO;
//がソースファイルの一番上に書かれているものとする

/// <summary>
/// ディレクトリをコピーする
/// </summary>
/// <param name="sourceDirName">コピーするディレクトリ</param>
/// <param name="destDirName">コピー先のディレクトリ</param>
/// <param name="newerOnly">新しいファイルのみコピーする</param>
/// <param name="sync">sourceDirNameにないファイルを削除する</param>
public static void CopyDirectory(
    string sourceDirName,
    string destDirName,
    bool newerOnly,
    bool sync)
{
    //コピー先のディレクトリがないときは作る
    if (!Directory.Exists(destDirName))
    {
        Directory.CreateDirectory(destDirName);
        //属性もコピー
        File.SetAttributes(destDirName, File.GetAttributes(sourceDirName));
    }

    //コピー先のディレクトリ名の末尾に"\"をつける
    if (destDirName[destDirName.Length - 1] != Path.DirectorySeparatorChar)
    {
        destDirName = destDirName + Path.DirectorySeparatorChar;
    }

    //コピー元のディレクトリにあるファイルをコピー
    string[] files = Directory.GetFiles(sourceDirName);
    foreach (string f in files)
    {
        string destFileName = destDirName + Path.GetFileName(f);
        //コピー先にファイルが存在し、
        //コピー元より更新日時が古い時はコピーする
        if (!newerOnly ||
            !File.Exists(destFileName) ||
            File.GetLastWriteTime(destFileName) < File.GetLastWriteTime(f))
        {
            File.Copy(f, destFileName, true);
        }
    }

    //コピー先にあってコピー元にないファイルを削除
    if (sync)
    {
        DeleteNotExistFiles(sourceDirName, destDirName);
    }

    //コピー元のディレクトリにあるディレクトリについて、再帰的に呼び出す
    string[] dirs = Directory.GetDirectories(sourceDirName);
    foreach (string dir in dirs)
    {
        CopyDirectory(dir, destDirName + Path.GetFileName(dir), newerOnly, sync);
    }
}

/// <summary>
/// destDirNameにありsourceDirNameにないファイルを削除する
/// </summary>
/// <param name="sourceDirName">比較先のフォルダ</param>
/// <param name="destDirName">比較もとのフォルダ</param>
private static void DeleteNotExistFiles(
    string sourceDirName,
    string destDirName)
{
    //sourceDirNameの末尾に"\"をつける
    if (sourceDirName[sourceDirName.Length - 1] != Path.DirectorySeparatorChar)
    {
        sourceDirName = sourceDirName + Path.DirectorySeparatorChar;
    }

    //destDirNameにありsourceDirNameにないファイルを削除する
    string[] files = Directory.GetFiles(destDirName);
    foreach (string f in files)
    {
        if (!File.Exists(sourceDirName + Path.GetFileName(f)))
        {
            File.Delete(f);
        }
    }

    //destDirNameにありsourceDirNameにないフォルダを削除する
    string[] folders = Directory.GetDirectories(destDirName);
    foreach (string folder in folders)
    {
        if (!Directory.Exists(sourceDirName + Path.GetFileName(folder)))
        {
            Directory.Delete(folder, true);
        }
    }
}

次に使用法を示します。フォルダ"C:\test1"内の更新されたファイルをフォルダ"C:\test2"にコピーし、ミラーリングを行うには、次のようにします。

VB.NET
コードを隠すコードを選択
CopyDirectory("C:\test1", "C:\test2", True, True)
C#
コードを隠すコードを選択
CopyDirectory("C:\\test1", "C:\\test2", true, true);
  • 履歴:
  • 2010/4/8 VB.NETのコードで Or を OrElse に修正。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。