65.9K
CodeProject 正在变化。 阅读更多。
Home

文件清单 - 一种混合版本控制系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (8投票s)

2012年1月15日

CPOL

6分钟阅读

viewsIcon

43655

downloadIcon

1481

用于盘点目录中文件的程序。

FileInventory/inv05.png

FileInventory/inv06.png

引言

有时我需要知道一个目录中最近添加或删除的文件。例如,我想知道一个图片目录中是否删除或添加了图片文件。这类似于版本控制系统的功能,但没有复杂的设置、签入、签出和单独存储;它简单易用。Windows 没有提供这样的工具,而且我在任何地方都找不到相关信息。

凭借如今的编程技术知识和谷歌搜索,写一个这样的工具似乎并不难。

在本文中,我将展示我如何

  • 将想法转化为设计
  • 编写一个 Inventory 类来盘点文件
  • 将持久化数据保存到 SQLite 数据库
  • 调用一个 `.bat` 文件,该文件每次都会运行不同的 SQL 脚本
  • 讨论未来增强和设计的多样性

背景

CodeProject 和 MSDN 上已经有很多关于 SQLite 编程、在程序中执行另一个程序以及保存持久化数据等方面的文章。这些文章详细讨论了单个技术;我的文章旨在演示如何将这些技术结合到一个有趣的应用中。

遵循本文的先决条件

我在 Microsoft Visual C# 2010 Express 下开发了代码。如果您使用的是之前的版本,请将 `Form1` 代码复制粘贴到您的 `Form1` 中,删除 `Form1.Desinger.cs`,然后将 `Inventory.cs` 添加到项目中。

如果您在嵌入我的代码时遇到问题,请阅读 msdn.microsoft.com flowlayoutpanel 中的示例代码部分 [^]:“将代码粘贴到 `Form1` 源文件中。如果您的项目包含一个名为 `Form1.Designer.cs` 的文件,请将其从项目中删除。您可能需要单击解决方案资源管理器中的‘显示所有文件’按钮才能看到设计器文件。”

SQLite 文件应与程序 exe 和 bat 文件一起放置在 `Debug` 目录中。

FileInventory/inv03.png

我将跳过如何嵌入 SQLite 的细节。如果您不熟悉 SQLite,请阅读“Chayan”的“在 C# 应用程序中使用 SQLite”。SQLiteCSharp[^]

编程界面设计

FileInventory/inv04.png

应用程序启动时,用户需要输入一个要盘点的目录。因此,我使用一个 `TextBox` 来存储目录文本。应用程序将在 `ListBox` 中显示目录中的所有文件,包括所有子目录。它会查询数据库并显示之前的清单日志在 `ListBox` 中。

如果搜索到日志,用户可以从 `ListBox` 中选择一个条目,以进行比较。结果显示删除、无变化和新增,显示在 `tabPage2` 中。

FileInventory/inv05.png

以下 SQL 脚本将选择已修改、新增和已删除的记录

select 'modified' as optype, a.item, a.fdt as 'filedate'
from temptbl a, CD_TEMP b
where a.item = b.item and a.fdt != b.fdt and b.ckey = '_2012 02/25 14:36:45'
UNION
select 'new' as optype, a.item, a.fdt as 'filedate'
from temptbl a 
where a.item not in (select item from CD_TEMP where ckey = '_2012 02/25 14:36:45')
UNION
select 'deleted' as optype, b.item, b.fdt as 'filedate'
from CD_TEMP b 
where b.ckey = '_2012 02/25 14:36:45' and b.item not in (select item from temptbl);

用户可以按“保存清单”按钮来盘点文件。清单完成后,它将在 `ListBox` 中显示清单目录。我建议您先在一个小目录上进行搜索,因为大目录可能需要一段时间才能完成。

TAB2 用于存储文件比较的统计信息,例如目录中的文件总数、删除的文件数以及新增的文件数。

TAB3 用于存储应用程序配置设置。尚未实现。

持久化数据存储设计

对于这个应用程序,我可以将数据保存在 `.txt` 或逗号分隔的 `.csv` 文件中,但是如果将来需要包含文件日期或文件大小等更复杂的属性,存储数据会很困难,或者进行任何排序和顺序排列会变得过于困难。

我使用 SQLite 进行数据存储,原因如下

  • 可以保存多个属性(字段)
  • 可以查询以获取结果和统计信息
  • SQL 是标准的
  • 从数据库的角度来看,其他功能和优势

`Inventory.db` 数据库包含一个索引表以及许多表;每个表都唯一地存储一个目录的清单数据。`path_table_map` 是索引表,它将清单目录名称映射到表名称,因为目录名称包含 `\` 和 `:`,需要将其转换为其他字符或去除。

目录的清单将存储在单独的表中。它使用 DateTime 格式生成一个键或目录名称,就像 Microsoft 示例数据库 Invoice 表中的发票编号一样。

我存储的格式是 `YYYYMMDDHHmmss`,年-月-日-时-分-秒,以及带完整路径的文件名。

程序会在第一次启动且 `inventory.db` 不存在时自动创建并执行以下 SQL 脚本。脚本将保存在 `sqlstmt_history.txt` 中,以便于调试。

--******** create inventory.db ********* 
create table path_table_map(
    no INTEGER PRIMARY KEY,  
    dirpath varchar(200),
    tblname varchar(200));

当用户开始盘点一个目录时,程序会创建以下 SQL 脚本来创建用于保存清单数据的表,并将一条记录插入索引表中。

-- use two tables, temptbl and CD_TEMP (c:\temp); replace ':' with 'D', '\' with '_' 
-- 1st table is used to store the current selected directory info 
create table temptbl(no INTEGER PRIMARY KEY, ckey  varchar(20), fdt  varchar(16), item varchar(400));
delete from temptbl;
insert into temptbl(ckey, fdt, item) values ('_2012 02/25 14:36:21', '2012 02/25 14:11', 
                    'C:\TEMP\6032\2011-09-24 Danny Take\421379126g_6324792.png');
insert into temptbl(ckey, fdt, item) values ('_2012 02/25 14:36:21', '2011 09/27 05:38', 
                    'C:\TEMP\6032\2011-09-24 Danny Take\BACKYARD_6367905.JPG');
insert into temptbl(ckey, fdt, item) values ('_2012 02/25 14:36:21', '2011 09/27 05:43', 
                    'C:\TEMP\6032\2011-09-24 Danny Take\BEDROOM 21_6367928.JPG');
insert into temptbl(ckey, fdt, item) values ('_2012 02/25 14:36:21', '2011 09/27 05:41', 
                    'C:\TEMP\6032\2011-09-24 Danny Take\DINING ROOM_6367935.JPG');

-- 2nd table is used to store the inventory catalog info 
create table CD_TEMP(no INTEGER PRIMARY KEY, ckey  varchar(20), fdt  varchar(16), item varchar(400));
insert into path_table_map (dirpath, tblname) values ('C:\TEMP', 'CD_TEMP');

当点击“清单”按钮时,以下 SQL 脚本将被插入到目录清单表中。

insert into CD_TEMP (ckey, fdt, item) select '_2012 02/25 14:36:45', fdt, item from temptbl; 

使用 Inventory 类

我在 `inventory.cs` 中设计了 `Inventory` 类,用于与 SQLite 接口、生成文件名、存储和检索记录。

class Inventory</p>
{
 // following are Public List objects available for caller to access
    ckeys  //inventory catalog
    list1     //current files list
    llist2    //selected inventory files list to compare
    listNew  //new files list
    listDel    //deleted files list
    listReport  //report files list with '+' or '-' prefix for add, delete
    sqls   //store sql statements

// following are Public functions
   Inventory(string sDir)  // inventory object instantiated
   compareDirectories(string catalogdate) // compare button clicked
   saveRec(string sDir) // inventory button clicked
}

递归遍历一个目录,包括所有子目录,以获取文件名列表。

private void listDirFiles(string sDir)
{
    try
    {   //list all the files in directory
        foreach (string f in Directory.GetFiles(sDir))
        {
            Console.WriteLine(f);
            list1.Add(f);
        }
        // do this for every sub-dir
        foreach (string d in Directory.GetDirectories(sDir))
        {
            listDirFiles(d);
        }
    }

    catch (System.Exception excpt)
    {
        Console.WriteLine(excpt.Message);
    }
}

通过生成一个进程来运行批处理程序,方便调试

通常,很难发现 SQL 语句中的错误。尤其是在 SQL 命令中遵循 SQL 语法时,您可能会遗漏用于括起字符串的引号。

将每个 SQL 语句写入文本文件,然后通过批处理命令执行会更方便。这也可以节省执行时间,因为它减少了不必要的打开-关闭数据库操作,并且便于调试。因此,我在 `Inventory` 类中添加了一个 `runBatchJob` 模块。

从 DOS 命令提示符执行以下命令

sqlite3 inventory.db < sqlstmt.sql

使用以下代码

runBatchJob(“batchjob.bat”, “inventory.db  sqlstmt.sql”);

并且 `batchjob.bat` 包含以下内容

sqlite3 %1 < %2

`Inventory` 类中的 `runBatchJob` 模块

private void runBatchJob(string prog, string args)
{ 
   string setupProg = prog;  // program to run
    if (File.Exists(prog))
    {
        System.Diagnostics.Process proc = new System.Diagnostics.Process();
        proc.StartInfo.FileName = prog;
        proc.StartInfo.Arguments = args; //"inventory.db setup.sql";
        proc.StartInfo.RedirectStandardError = false;
        proc.StartInfo.RedirectStandardOutput = false;
        proc.StartInfo.UseShellExecute = false;  // run in background (invisible)
        proc.StartInfo.CreateNoWindow = true;
        proc.Start();
        proc.WaitForExit();

结论

本文档介绍了我是如何将一个想法转化为编程实践、设计用户界面、使用持久化存储、使用 SQL 查询以及调用批处理程序的。由于我试图快速编码,这是一个基本的设计,可以在很多方面进行改进。

附录

如果您下载了代码,最好删除并重新创建 `batchjob.bat` 文件(在删除之前将 `bat` 文件内容保存到 `txt` 文件中)。否则,每次程序调用运行批处理作业时,您都会收到一个烦人的安全确认提示。Windows 有一个安全系统,可以检测批处理文件是否不是来自您自己的系统,并警告用户风险。

历史

  • 2012 年 1 月 14 日 - 第一个版本。
  • 2012 年 2 月 25 日 - 第二个版本 - 添加了文件日期属性,并修改了比较逻辑以显示新增、已修改和已删除的记录,以及搜索整个逻辑驱动器的功能。
© . All rights reserved.