文件夹同步工具揭秘
软件世界
编者按:所谓的文件夹同步,也叫做实时备份,就是保持目标文件夹中的文件随时和源文件夹中的文件完全一致。要实现同步,程序就必须随时知道源文件夹中的文件是否发生了变化。那么,该功能是如何实现的呢?下文给出了实现的方法(API函数)和原理,同时带了一个C++ Builder中的小例子。
对于文件夹同步功能,不同的软件采取不同的实现方法。一些软件采用定时备份的策略来避开这个难题,不管源文件夹文件是否已经变化,只有到了指定的时间间隔,才检查源文件夹和目标文件夹是否一致;而比较专业的软件则能够在源文件夹发生任何变化时马上截获这种变化并采取措施使目标文件夹中的文件更新。显然,后一种方法更加可靠。
那么,当源文件夹中的文件发生变化时,程序如何才能马上知道呢?用定时器当然是不可取的。下面,笔者就将在C++ Builder中的实现方法介绍如下。
编程思路
很多人都知道如何实现在一个进程结束前一直等待的方法。因为Windows 是一个多任务系统,所以如果我们在程序中创建一个进程并执行它,那么不论进程是否执行完毕,我们的程序都会马上返回。典型的例子就是我们在程序中通过WinExec()函数执行一个DOS程序,即使这个DOS程序还在运行,我们的程序也会继续执行下面的语句。还可以找到的例子就是批处理文件执行的效果。如果想要让程序等到外部进程结束才继续运行,就要用WaitForSingleObject()函数循环监视进程的执行情况,直到发现进程已经结束再退出循环,继续下面的语句。而监视文件夹是否发生变化的原理也是采用了这种方法。系统会为我们创建一个进程,只有当文件夹发生变化时这个进程才能退出,否则它将一直运行。而我们的程序则循环监测这个进程是否退出,只有进程退出,我们的程序才继续运行,并调用同步文件夹的自定义函数TongBu(),然后再继续循环监视这个进程……
不同的是,这个进程不是由我们创建并执行的,而是系统创建并执行的。要创建这个进程,就必须调用API函数FindFirstChangeNotification()以及FindNextChangeNotification()。事实上,当我们调用第一个API 函数,系统就会创建一个进程并返回这个进程的句柄,这个进程在不停地运行,直到指定的文件夹发生变化它才退出。而我们就可以通过循环调用WaitForSingleObject()函数监视这个进程是否退出(进程退出后这个函数返回0),从而知道文件夹是否已经发生变化。
当文件夹发生变化,这个系统创建的进程就会退出,这时调用TongBu()函数检查源文件夹已经变化的文件并更新目标文件夹。然后,我们当然必须继续监视文件夹的变化,而系统刚刚创建的进程已经退出,我们必须再次让系统创建这个进程。你有两个选择:一个就是再次调用FindFirstChangeNotification()函数,重新创建进程对指定的文件夹进行监视。另一个就是调用函数FindNextChangeNotification()直接创建进程对上次那个文件夹进行监视。如果你要使用第一种方法的话,必须首先关闭上一次创建进程进行的监视,调用函数FindCloseChangeNotification()即可。所以从简单性考虑,我还是推荐你使用第二种方法。不过,无论哪种方法,在不需要监视时都必须调用函数FindCloseChangeNotification()关闭监视。
这样你可以在一个循环中不停地创建进程、监视进程、变化后同步、再次创建进程、监视进程、变化后同步……但是一定要设置条件可以退出循环,比如一个逻辑变量指示,否则你的软件就会“死路一条”!而且,循环中一定要将系统及当前窗体的消息传递出去,否则系统除了运行你的这个软件,什么也干不了啦,可以使用Application->ProcessMessages()将消息传出去。
示例程序
下面是一个小小的文件夹同步的示例程序。这个程序在文件夹同步函数TongBu()中,简单地将源文件夹中所有的文件拷贝到目标文件夹中覆盖目标文件,并不是很好的解决方案,实际上应该检测源文件夹与目标文件夹不同的文件,只拷贝这些文件即可,但这是一个示例程序,你还能要求它怎么样呢?
注:以下程序在C++ Builder中调试通过。
本程序在主窗体Form1中包含两组GroupBox,每组包含一个DriveComboBox和一个DirectoryListBox,分别用来指定源文件夹和目标文件夹,另外还有两个FileListBox用来显示源文件夹和目标文件夹中的文件列表。上面这些组件通过相应的属性设置联系在一起,使改变驱动器时文件夹自动变化,改变文件夹时文件列表自动变化,根本不用编写一行代码,这就是C++ Builder让我们着迷的地方。当然,具体的设置方法我就不再赘述了。
至于两个按钮,下面的那个BitBtn2是用来退出的。而上面的BitBtn1则是用来开始和停止文件夹变化监控的。如果正在监控,那么单击一下就停止监控,否则单击一下就开始监控。我们用一个逻辑变量StartFlag标识目前的监控状态。
各个API函数在源代码中说明其用法。下面的源代码为了节省篇幅,省略了自动生成的部分。
bool StartFlag=false;//是否正在监视
HANDLE ProcHandle=NULL;//进程句柄
TongBu()//文件夹同步函数
{
Form1->FileListBox1->Update();//更新列表
Form1->FileListBox1->Update();//更新列表
Form1->StatusBar1->SimpleText="源文件已经改变,正在更新目标文件夹...";
Form1->Update();
//删除目标文件夹多余的文件
for(int i=0;i
if(!FileExists(Form1->DirectoryListBox1->Directory+"\\"+Form1->FileListBox2->Items->Strings[i]))
DeleteFile(Form1->DirectoryListBox2->Directory+"\\"+Form1->FileListBox2->Items->Strings[i]);
//源文件夹所有文件拷贝到目标文件夹
for(int i=0;i
{int FH=FileOpen(Form1->DirectoryListBox1->Directory+"\\"+Form1->FileListBox1->Items->Strings[i],fmOpenRead);
int FL=FileSeek(FH,0,2);
char *buf=new char [FL+1];
FileSeek(FH,0,0);
FileRead(FH,buf,FL);
FileClose(FH);
FH=FileCreate(Form1->DirectoryListBox2->Directory+"\\"+Form1->FileListBox1->Items->Strings[i]);
FileWrite(FH,buf,FL);
FileClose(FH);
delete[]buf;
}
Form1->StatusBar1->SimpleText="正在监视源文件夹...";
Form1->Update();
}
void __fastcall TForm1::BitBtn1Click(TObject *Sender)
{
if(StartFlag)//停止同步
{
BitBtn1->Caption="开始";
Form1->StatusBar1->SimpleText="已经停止同步.";
StartFlag=false;
FindCloseChangeNotification(ProcHandle);//关闭监视
GroupBox1->Enabled=true;
GroupBox2->Enabled=true;
}
else//开始同步
{
if(DirectoryListBox1->Directory==DirectoryListBox2->Directory)
{
MessageBox(Handle,"源文件夹和目标文件夹不能相同!","错误",MB_OK);
return;
}
BitBtn1->Caption="停止";
StartFlag=true;
Form1->StatusBar1->SimpleText="正在监视源文件夹...";
//同步时不能修改源文件夹及目标文件夹
GroupBox1->Enabled=false;
GroupBox2->Enabled=false;
ProcHandle=FindFirstChangeNotification(DirectoryListBox1->Directory.c_str(),true,FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME|FILE_NOTIFY_CHANGE_SIZE);
//创建监视,第一个参数是要监视的文件夹;第二个参数为true它同时监视其下一级文件夹,否则不监视;第三个参数是要监视的哪些变化?
DWORD re=1;
while(re)//等待源文件夹改变,如果re=0则进程结束
{if(!StartFlag)return;//手动停止
re=WaitForSingleObject(ProcHandle,100);
Application->ProcessMessages();
}
TongBu();
again:
re=1;
FindNextChangeNotification(ProcHandle);//继续监视
while(re)//等待源文件夹改变
{if(!StartFlag)return:
re=WaitForSingleObject(ProcHandle,100);
Application->ProcessMessages();
}
TongBu();
goto again;
}
}
void__fastcall TForm1::BitBtn2Click(TObject *Sender)
{
Close();//关闭
}