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

扩展 DirectoryInfoEx 以支持 Archive

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2009年8月22日

LGPL3

13分钟阅读

viewsIcon

31241

downloadIcon

350

允许用户创建虚拟目录并通过类似于 DirectoryInfo 的接口使用它们。

引言

DirectoryInfoEx 是一个使用 IShellFolder 列出文件系统内容的组件。它具有类似于 System.IO.DirectoryInfo 的接口,但它可以列出桌面项以及不在文件系统中的项。

它工作得很好,但无法列出存档中的文件。Windows XP+ 提供了 zip 文件夹,这是一个命名空间扩展,允许列出和提取 zip 中的文件,但这不足以满足我的需求(顺便说一下,可以使用 IShellFolderDirectoryInfoEx 中列出 zip,但我故意禁用了它,zip 被视为文件而不是文件夹),因此开发了一个新组件来实现此目的。

本文中包含的组件进一步扩展了之前的组件 (DirectoryInfoEx) 以支持存档,并为其他用户提供了一个接口来添加对不存在文件夹(例如书签)的支持。开发此组件的最初原因是支持压缩存档,但该组件非常灵活,也就是说,您只需一行代码即可切换存档支持。

该组件(以及 DirectoryInfoEx)是用 .NET 2.0 编写的,尽管演示需要 .NET 3.5 框架。

本文主要介绍了我如何使用我的方法创建该组件,因此本文的技术性不如我之前的文章,但我希望读者能发现本文包含的组件很有用。

遇到的问题

第一个问题是,我希望提供一个类似于 DirectoryInfoEx (ex) 的接口,但 ex 版本工作得很好,我希望重用大部分代码而无需进行任何修改,结果就是 DirectoryInfoExA (exa) 版本,以及对 ex 进行的一些小的修改以启用继承。

exa 版本位于单独的程序集中。(ex 的 PIDL,exa 的 COFE。)

另一个问题是 ArchiveDirectoryInfoExADirectoryInfoExA 有很大不同。ArchiveDirectoryInfoExA 无法从 DirectoryInfoExA 继承。为了解决这个问题,为每个类都使用了一个接口(例如,DirectoryInfoExA.GetDirectories() 返回 IDirectoryInfoExA 接口而不是 DirectoryInfoExA 类)。

第三个问题是,由于 exa 中的一些项是虚拟的,需要实际文件/目录存在的特性,如拖放和上下文菜单,除非项被展开,否则无法使用。通过“展开”这些项两次来解决此问题,通过

  1. 生成零字节文件/空目录(以获取 PIDL)。
  2. 启动命令。并在需要时
  3. 展开文件/目录(覆盖那些零字节文件/空目录)。
  4. 调用选定的命令。

目录

FileSystemInfoExA 实现

FileSystemInfoExA 是所有 ExA 项的基类;这类似于 FileSystemInfoEx,但与 ex 版本不同,exa 版本是 IFileSystemInfoExA 接口的抽象实现。getFullName() 方法在子类中实现,例如

public new string FullName { get { return getFullName(); } }
protected abstract string getFullName(); 

除此之外,exa 版本还添加了一个名为 ParseName 的属性。与 ex 版本不同,某些路径是“假的”,无法被 shell 解析;这些路径由 ParseName 表示,而 FullName 只返回其临时路径。

有两个层次结构:接口和类。

接口层次结构

IFileSystemInfoExA、IDirectoryInfoExA 和 IFileInfoExA 接口

public interface IFileSystemInfoExA
{
 PIDL PIDLRel { get; }
 PIDL PIDL { get; }
 string Label { get; }
 string FullName { get; }
 string Name { get; }
 string ParseName { get; }
 string Extension { get; }
 bool Exists { get; }
 IDirectoryInfoExA Parent { get; }
 FileAttributes Attributes { get; }
 DateTime LastWriteTime { get; }
 DateTime LastAccessTime { get; }
 DateTime CreationTime { get; }
 bool IsFolder { get; }
 void Delete();
 void Refresh();
 bool Equals(object obj);
 int GetHashCode();
}
public interface IDirectoryInfoExA : IFileSystemInfoExA
{ 
 IDirectoryInfoExA Root { get; }
 bool IsBrowsable { get; }
 bool IsFileSystem { get; }
 bool HasSubFolder { get; }
 DirectoryInfoEx.DirectoryTypeEnum DirectoryType { get; }

 void Create();
 void MoveTo(string destDirName);
 
 IDirectoryInfoExA CreateDirectory(string dirName);
 IVirtualDirectoryInfoExA CreateVirtualDirectory(string dirName, string type);

 IFileInfoExA[] GetFiles();
 IDirectoryInfoExA[] GetDirectories();
 FileSystemInfoExA[] GetFileSystemInfos();
}
public interface IFileInfoExA : IFileSystemInfoExA
{
 long Length { get; }
 void MoveTo(string destFileName); 
 FileStreamEx OpenRead();
 StreamWriter AppendText();
 StreamReader OpenText();
 FileStreamEx Open(FileMode mode, FileAccess access);
 FileStreamEx Open(FileMode mode);
 FileStreamEx Open();
 FileStreamEx Create();
}

公开原始 FileSystemInfoDirectoryInfoFileInfo 类中的属性和方法。IDirectoryInfoExA 包含 CreateVirtualDirectory() 方法,用于构造特殊目录(例如,此处为 Archive)。

IInternalDirectoryInfoExA 接口

public interface IInternalDirectoryInfoExA : IDirectoryInfoExA
{
 void put(IFileSystemInfoExA file);
 IFileInfoExA CreateFile(string fileName);
}

继承自 IDirectoryInfoExA,它包含一些不设计给最终用户看到的方法。

IVirtualFileSystemInfoExA、IVirtualFileInfoExA、IVirtualDirectoryInfoExA 接口

public interface IVirtualFileSystemInfoExA : IFileSystemInfoExA
{
 string TempPath { get; }
 string RelativePath { get; }
 void expandSelf();
}

IVirtualFileSystemInfoExA 公开 TempPath(展开后的路径)和 RelativePath(相对于其根目录的路径)属性,以及 expandSelf() 方法。

public interface IVirtualDirectoryInfoExA : 
       IDirectoryInfoExA, IVirtualFileSystemInfoExA
{
 bool IsRoot { get; }
}
public interface IVirtualFileInfoExA : 
       IFileInfoExA, IVirtualFileSystemInfoExA
{
}

IVirtualFileInfoExAIVirtualDirectoryInfoExA 目前是空的接口。根虚拟目录应具有返回 root 的 DirectoryType 属性,并实现 IFileBasedFS 接口(如果适用)。

IFileBasedFS 接口

public interface IFileBasedFS
{
 IFileInfoExA RootFile { get; }
}

这由 IVirtualDirectoryInfoExA 的根来实现,它指定了 RootFile 属性。对于 ArchiveInfoExARootFile 指向存档文件。

层次结构

基类 FileSystemInfoExA

为减少复杂性,FileSystemInfoExA 不继承自 FileSystemInfoEx。它实现了 IFileSystemInfoExA 接口,提供了相同的属性/方法。

public PIDL PIDL { get { return getPIDL(); } }
protected abstract PIDL getPIDL();

大多数属性实现为 abstract,它们必须由继承的类实现。

PIDL 目录和文件 (WrapFileSystemInfoEx)

DirectoryInfoExAFileInfoExA 继承自 WrapFileSystemInfoEx,而 WrapFileSystemInfoEx 又继承自 FileSystemInfoExA

public FileSystemInfoEx embeddedItem { get; private set; }
protected override PIDL getPIDL() { return embeddedItem.PIDL; }

WrapFileSystemInfoEx 有一个名为 embeddedItem 的属性。所有属性请求和方法都重定向到 embeddedItem。它包含一个名为 embeddedItem 的属性,它是一个 FileSystemInfoEx,exa 版本中的所有工作都重定向到 ex 版本。

虚拟目录和文件 (VirtualFileSystemInfoExA)

public new FileSystemInfoEx embeddedItem { get { initSelf(); return base.embeddedItem; } }
protected override PIDL getPIDL() { expandSelf(); return embeddedItem.PIDL; }

VirtualFileSystemInfoExA 是所有虚拟文件夹和文件的基类;它也继承自 WrapFileSystemInfoEx,但其 embeddedItem 在需要时才初始化。embeddedItem 与文件系统中的 TEMP 项关联,因此大多数原始属性,如 FullNamePIDL,都与 Temp 目录/文件相关联。新属性 ParseName 用于表示虚拟路径(可解析路径)。

initSelf()expandSelf() 方法是惰性加载方法,它们将加载推迟到需要时,因为大多数情况下,您不必使用它们。例如

ParseName C:\temp\cantonese_input.7z\cantonese_input2\ddd.zip
FullName %temp%\Cofe\%HashCode%\ddd.zip (文件夹)
TempPath %temp%\Cofe\%HashCode%\ddd.zip (文件夹)

HashCode 是基于上一个根存档 ParseName 计算的,在这种情况下,"C:\temp\cantonese_input.7z".GetHashCode();。另请注意,FullName 中的 ddd.zip 是一个真实目录而不是存档。

FullNameTempPath 之间的区别在于,当您调用 FullName 时,如果该项不存在,它会被创建(通过调用 expandSelf())。但如果您调用 TempPath,它会在不检查该项是否已创建的情况下返回路径。

internal abstract IDirectoryLister directoryLister { get; }

VirtualDirectoryInfoExA 继承自 VirtualFileSystemInfoExA;它添加了一些与目录相关的​​方法。它还包含一个名为 directoryLister 的属性,用于执行大部分虚拟文件操作(更多信息如下)。

存档、存档目录和存档文件 (ArchiveInfoExA)

存档支持基于我的 CAKE3,这是一个包装器(中间件),用于几个 .NET 和 W32 库。您可以在此处找到更多信息。

ArchiveInfoExAArchiveDirectoryInfoExAArchiveFileInfoExA 继承自 VirtualDirectoryInfoExA / VirtualFileSystemInfoExA。这三个类和 ArchiveDirectoryLister 用于提供存档支持。

internal IFileInfoExA embeddedArchive;
internal override IDirectoryLister directoryLister { 
         get { return ArchiveDirectoryLister.Instance; } }

ArchiveInfoExA 是所有存档的根,因此它有一个名为 embeddedArchive 的属性。正如您所见,它是一个 IFileInfoExA 类型。这意味着 embeddedArchive 也可以是 ArchiveFileInfoExA,因此支持存档中的存档。

ParseName C:\temp\cantonese_input.7z\cantonese_input2\ddd.zip
FullName %temp%\Cofe\%HashCode%\ddd.zip (文件夹)
TempPath %temp%\Cofe\%HashCode%\ddd.zip (文件夹)
embeddedArchive.FullName %temp%\Cofe\%HashCode2%\cantonese_input.7z\cantonese_input2\ddd.zip (文件)

HashCode2 是基于当前根存档 ParseName 计算的,在这种情况下,"C:\temp\cantonese_input.7z\cantonese_input2\ddd.zip".GetHashCode();

书签、书签目录和书签条目 (BookmarkInfoExA)

虚拟文件系统的另一个实现是书签。

Bookmark(根)和 BookmarkDirectory 继承自 VirtualDirectoryInfoExA,它使用 directoryLister 列出其内容,该列表器具有与 ArchiveDirectoryLister 类似的​​方法。BookmarkDirectoryLister 的内部结构基于 XSD(XML 架构定义)。我创建了 XSD 文件,然后运行 xsd.exe 来生成我需要的类。然后可以使用 XmlSerializer 加载和保存它。这项技术是从Mike Elliott 的文章中学到的;还可以在MSDN 文章中找到更多信息。

书签条目(RedirectDirectoryInfo)继承自 WrapFileSystemInfoExA,它与 WrapFileSystemInfoEx 相同,只是 embeddedItemIFileSystemInfoExA,因为书签可能链接到虚拟目录(FileSystemInfoExA)。这两个类共享相似的代码;我无法重构它们,因此存在一些重复的代码。

支持的接口/类

开发了许多接口来支持 FileSystemInfoExA 及其子类

IDirectoryLister 接口

每个虚拟目录都有一个名为 directoryLister 的属性,它是一个实现 IDirectoryLister 的类;它包含列出、添加、删除和提取项的函数,类似于 shell 中的 ShellFolder

DirectoryLister 的作用类似于 shell 中的 ShellFolderStorage

DirectoryLister 的最早版本(在 DirectoryInfoEx 之前设计)中,directoryLister 和实体(FileSystemInfoEx)位于同一个类中。后来为了减少复杂性和其他一些问题,包括

  • 线程问题(从不同线程调用 = 崩溃)。
  • 控制问题(可能存在多个实体实例执行冲突的操作)。
  • 性能问题(list() 可能被多次调用,并且可能存在列出内容的多个副本)。

由于这些问题,使用单独的静态类是更好的解决方案。

IDirectoryLister 接口的一些函数

列表和操作命令
bool Exists(IVirtualFileSystemInfoExA item);
void Rename(IVirtualFileSystemInfoExA item, string newName);
IEnumerable<FileSystemInfoExA> List(IVirtualDirectoryInfoExA parent, bool refresh);
bool Expand(IVirtualFileSystemInfoExA item);
IVirtualFileSystemInfoExA CreateFile(IVirtualFileSystemInfoExA parent, string name);
IVirtualDirectoryInfoExA CreateDirectory(IVirtualDirectoryInfoExA parent, string dir);
void Put(IVirtualDirectoryInfoExA dir, IFileSystemInfoExA item, bool raiseAlert);
bool HasFolder(IVirtualDirectoryInfoExA parent);

命令名称不言自明。

虚拟目录的入口点
IVirtualDirectoryInfoExA CreateVirtualDirectory(IDirectoryInfoExA parent, 
                         string dir, string type);

普通目录和虚拟目录可以使用任何 directoryLister 实现的 IDirectoryLister.CreateVirtualDirectory() 方法创建虚拟目录,“类型”由虚拟目录的类型指定(例如,zip、lha)。

FileSystemInfoExA ConvertItem(FileSystemInfoExA item);

PIDL 目录的 GetFileSystemInfos() 方法(以及 GetDirectories()GetFiles())仅返回实际存在的文件,因此对于返回的每个项,它会为每个 directoryLister 调用 ConvertItem;如果 directoryLister 发现它受支持(例如,存档),它可以转换它(new ArchiveExA(...))并返回一个转换后的项。

如果您需要引用该项,请构造一个新项而不是直接使用该项;该项可能被 GC 回收,当您尝试访问它们时会触发 AccessViolationException;大多数实现的类支持 ICloneable,因此您可以调用 Clone() 方法进行构造。

FileSystemInfoExA[] AppendItems(DirectoryInfoExA parent); 

另一种方法是在调用 GetFileSystemInfos() 时向列表中添加额外项,例如,将书签目录添加到桌面。

文件路径解析

与 PIDL 文件和目录不同,您不能使用 DesktopShellFolder.ParseDisplayName() 来解析虚拟文件路径,因此默认方法是从桌面解析并逐级查找(FileSystemInfoExA.FromStringIterate()),这很慢。

另一种方法是直接解析并构造目录(FileSystemInfoExA.FromStringParse())。对于 PIDL 文件和目录,使用 ParseDisplayName()。对于虚拟文件和目录,由于不同的虚拟目录可能有不同的解析方式,IDirectoryLister 为每个单独的目录列表器提供了一个方法来返回表示的项。

FileSystemInfoExA ParseDisplayName(string displayName);

此方法解析显示名称并返回其表示的 FileSystemInfoExA。对于存档目录列表器,它将尝试形成从最后一个存档节点到第一个节点,并检查节点是否可创建;如果可以,它将从那里迭代。

e.g. c:\abc.zip\cde.7z\efg.lha
-> efg.lha exists? no
--> efg.lha 's temp file exists? no
---> cde.7z exists? no
----> cde.7z 's temp file exists? yes
-----> Iterate from cde.7z

如果没有目录列表器返回项,则使用正常的迭代方法,该方法从桌面查找。使用 Parse 构建的大多数属性,包括 ParentFullName 以及 embeddedItem 中的其他属性,在需要之前不会被初始化。请记住,如果项不存在,无论您使用的是 FromStringIterate() 还是 FromStringParse(),都会生成 FileNotFoundException

INotifyFileSystemChanged 接口

虚拟目录中的更改会通过实现 INotifyFileSystemChanged 接口报告给其父目录,如下所示

public enum ModiifiedAction { maRemoved, maAdded, maChanged }
interface INotifyFileSystemChanged
{
 /// <summary>
 /// Update parent if file or archive in archive is updated.
 /// </summary>
 /// <param name="childItem"></param>
 void AlertChildIsModified(VirtualFileSystemInfoExA sender, 
      VirtualFileSystemInfoExA originalSender, ModiifiedAction action);

 /// <summary>
 /// Alert parent that current directory is modified.
 /// </summary>
 void AlertModified(ModiifiedAction action);
}

存档支持继承自虚拟目录,因此当添加/删除/更改项时,例如“c:\abc.zip\cde.7z\newFile

newFile.AlertModified(ModifiedAction.maAdded);
-> newFile.AlertModified(newFile, newFile, ModifiedAction.maAdded);
--> cde.AlertModified(cde, newFile, ModifiedAction.maChanged);
---> abc.AlertModified(abc, newFile, ModifiedAction.maChanged);
-----> because newFile.Root != abc.Root (cde.7z != abc.zip), cde.7z in abc.zip is updated.

此机制支持多级存档,并允许支持被动虚拟文件系统监视。

DirectoryListerLibrary 静态类

DirectoryListerLibrary 维护一个 DirectoryLister 列表(例如,BookmarkDirectoryListerArchiveDirectoryLister)。所有操作都通过该库进行调用。

FileStreamExA 类

private void updateEmbeddedFile()
{
 if (accessMode == FileAccess.ReadWrite || 
 accessMode == FileAccess.Write) //Require update
 if (embeddedFile is VirtualFileSystemInfoExA) // Virtual file
 {
 embeddedFile.Refresh(); //Update exists state.
 if (!embeddedFile.Exists)
 throw new IOException("FileSystemInfoExA save failed...");
 VirtualFileSystemInfoExA file = embeddedFile as VirtualFileSystemInfoExA;
 file.Parent.put(file); //Update back to parent.
 }
}

public override void Flush()
{
 base.Flush();
 closeStream();
 updateEmbeddedFile();
}

FileStreamExA 继承自 FileStreamEx。为了支持虚拟目录系统,打开的临时文件在调用 Flush() 方法时会更新回其父目录。

FileExA 和 DirectoryExA 静态类

FileExADirectoryExA 类似于 FileExDirectoryEx,只是它们增加了对虚拟文件夹的支持。

DataObjectExA 类

DataObjectExA 是一个包含一个或多个 IFileSystemInfoExADataObject;它与 DragDrop.DoDragDrop() (WPF) 或 WinForm.DoDragDrop() 方法一起使用;它重写了 GetData() 方法,该方法可以延迟项的展开过程直到发生拖放。

必须处理发送方的 QueryContinueDrag 事件,在 WPF 和 WinForms 中;它会通知组件何时/是否展开这些项。有关详细信息,请检查 DragDropHelperExA 类(或此处)。

ContextMenuWrapperExA 类

ContextMenuWrapperExA 继承自 ContextMenuWrapper;它执行类似的操作,但针对虚拟项。它实际上显示临时文件/目录的上下文菜单;展开操作会延迟直到选择命令并调用(通过监听 BeforeInvokeCommand 事件)。

它目前无法将项目更新回虚拟文件系统,并且许多命令未被正确处理(例如,删除虚拟项)。

如何使用该组件

该组件的目标之一是提供一个类似于 System.IO.DirectoryInfo 的接口,因此大多数操作基本上相同

注册存档目录列表器

与存档/书签相关的代码位于单独的程序集(COFE.Archive)中,因此在使用 exa 之前必须先注册它们。

此组件中没有插件系统,您必须先注册列表器才能使用它。如果您不注册目录列表器,它将像以前的版本一样,仅列出 shell 文件夹和文件。

BookmarkDirectoryLister.Register();
ArchiveDirectoryLister.Register();

如果您使用的是特定于存档的构造函数(例如,ArchiveInfoExA),则目录列表器会自动注册。

实体构造函数

每个实体类都包含一个接受字符串的构造函数,例如

new DirectoryExA(@"C:\dir");
new FileInfoExA(@"C:\dir\abc.zip");
new ArchiveInfoExA(@"C:\dir\abc,zip");
new ArchiveDirectoryInfoExA(@"C:\dir\abc.zip\cde");
new ArchiveFileInfoExA(@"C:\dir\abc.zip\cde\efg.txt");

路径不一定必须存在,但其父目录必须存在,因为父目录是通过 FromStringParse() 构建的。

除此之外,您还可以使用 FileSystemInfoExA.FromStringIterate()FromStringParse() 方法来构造实体。

(IDirectoryInfoExA)FileSystemInfoExA.FromStringParse(@"C:\Dir");

如果您使用 FromStringXXXX(),则该实体必须存在,否则将抛出 FileNotFoundException

构造一个不存在的实体

IFileInfoExA file = new ArchiveFileInfoExA(path);
using (TextWriter w = new StreamWriter(file.Create())) //FileStreamExA.
{
 w.WriteLine("Test");
}

创建不存在的实体后,您可以使用 Create() 方法来构造它。对于 ArchiveInfoExA,它将根据其扩展名创建基础;目前仅支持 zip/lzh/lha/7z/sqx。如果对其他扩展名调用 Create(),将抛出 NotSupportedException

演示程序

ex 版本中的演示程序,这是一个 WPF 的“类似资源管理器”的应用程序,已稍作修改以适应 A 版本;它仅是演示,并非完全成品控件。为了测试拖放组件,我还将 WinForms 版本包含在同一个应用程序中(尽管我还没有为其编写拖放代码)。

DragDropHelperExA (WPF)

DragDropHelperExA 是一个 WPF 组件,带有两个附加属性:EnableDragEnableDrop;您可以在 ListViewTreeView 上使用这些属性来启用拖放支持。

尽管我没有测试它们,但 DragDropHelperExA 应该支持 M-V-VM 模式;您必须在 ViewModels 中实现 ISupportDrag/ISupportDrop 接口。

不完整项

这个组件比上一个复杂得多,因此许多功能尚未完成;它们将在未来的版本中添加。

  • FileExA/DirectoryExA 中的一些方法缺失。
  • 上下文菜单,某些命令应由组件处理(例如,删除)。
  • 由实体(或 directoryLister)指定的自定义上下文菜单。
  • WinForms 上的拖放。
  • 缺少 FileSystemWatcherExA/DriveInfoExA

问题

  • DragDropHelperExA.EnableDrop 在拖放后会折叠整个 TreeView。
  • FileList 中拖放只会更新(刷新)FileList,反之亦然。
  • WrapFileInfoExWrapFileInfoExA 有重复的代码。
  • 存储在 WrapFileSystemInfoEx 类中的非托管 FileSystemInfoEx.PIDL,被 ArchiveInfoExA.Parent 使用,在程序集(COFE 和 COFE.Archives)之间共享;这可能导致 AccessViolationException,并且可能需要更改为按需生成,如果需要的话。

未来版本 (DirectoryInfoExB)

我希望扩展 A 版本以提供数据库支持,这将大大提高列表性能,添加标签/用户排序支持,并启用搜索。

还需要一个用于异步复制(或压缩)文件的​​新接口/类,因为对于存档,一次压缩/解压缩一个文件太慢了。

版本历史

  • 2010 年 1 月 1 日 - 初始版本:0.1。
© . All rights reserved.