扩展 DirectoryInfoEx 以支持 Archive





0/5 (0投票)
允许用户创建虚拟目录并通过类似于 DirectoryInfo 的接口使用它们。
引言
DirectoryInfoEx
是一个使用 IShellFolder
列出文件系统内容的组件。它具有类似于 System.IO.DirectoryInfo
的接口,但它可以列出桌面项以及不在文件系统中的项。
它工作得很好,但无法列出存档中的文件。Windows XP+ 提供了 zip 文件夹,这是一个命名空间扩展,允许列出和提取 zip 中的文件,但这不足以满足我的需求(顺便说一下,可以使用 IShellFolder
在 DirectoryInfoEx
中列出 zip,但我故意禁用了它,zip 被视为文件而不是文件夹),因此开发了一个新组件来实现此目的。
本文中包含的组件进一步扩展了之前的组件 (DirectoryInfoEx
) 以支持存档,并为其他用户提供了一个接口来添加对不存在文件夹(例如书签)的支持。开发此组件的最初原因是支持压缩存档,但该组件非常灵活,也就是说,您只需一行代码即可切换存档支持。
该组件(以及 DirectoryInfoEx
)是用 .NET 2.0 编写的,尽管演示需要 .NET 3.5 框架。
本文主要介绍了我如何使用我的方法创建该组件,因此本文的技术性不如我之前的文章,但我希望读者能发现本文包含的组件很有用。
遇到的问题
第一个问题是,我希望提供一个类似于 DirectoryInfoEx
(ex) 的接口,但 ex 版本工作得很好,我希望重用大部分代码而无需进行任何修改,结果就是 DirectoryInfoExA
(exa) 版本,以及对 ex 进行的一些小的修改以启用继承。
exa 版本位于单独的程序集中。(ex 的 PIDL,exa 的 COFE。)
另一个问题是 ArchiveDirectoryInfoExA
与 DirectoryInfoExA
有很大不同。ArchiveDirectoryInfoExA
无法从 DirectoryInfoExA
继承。为了解决这个问题,为每个类都使用了一个接口(例如,DirectoryInfoExA.GetDirectories()
返回 IDirectoryInfoExA
接口而不是 DirectoryInfoExA
类)。
第三个问题是,由于 exa 中的一些项是虚拟的,需要实际文件/目录存在的特性,如拖放和上下文菜单,除非项被展开,否则无法使用。通过“展开”这些项两次来解决此问题,通过
- 生成零字节文件/空目录(以获取 PIDL)。
- 启动命令。并在需要时
- 展开文件/目录(覆盖那些零字节文件/空目录)。
- 调用选定的命令。
目录
- FileSystemInfoExA 实现
- 接口层次结构
- IFileSystemInfoExA、IDirectoryInfoExA 和 IFileInfoExA 接口
- IInternalDirectoryInfoExA 接口
- IVirtualFileSystemInfoExA、IVirtualFileInfoExA、IVirtualDirectoryInfoExA 接口
- IFileBasedFS 接口
- 类层次结构FileSystemInfoExA 类
- 支持的接口/类
- 如何使用该组件
- 演示程序
- 不完整项
- 问题
- 未来版本 (DirectoryInfoExB)
- 参考文献
- 版本历史
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();
}
公开原始 FileSystemInfo
、DirectoryInfo
和 FileInfo
类中的属性和方法。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
{
}
IVirtualFileInfoExA
和 IVirtualDirectoryInfoExA
目前是空的接口。根虚拟目录应具有返回 root 的 DirectoryType
属性,并实现 IFileBasedFS
接口(如果适用)。
IFileBasedFS 接口
public interface IFileBasedFS
{
IFileInfoExA RootFile { get; }
}
这由 IVirtualDirectoryInfoExA
的根来实现,它指定了 RootFile
属性。对于 ArchiveInfoExA
,RootFile
指向存档文件。
类 层次结构
基类 FileSystemInfoExA
为减少复杂性,FileSystemInfoExA
不继承自 FileSystemInfoEx
。它实现了 IFileSystemInfoExA
接口,提供了相同的属性/方法。
public PIDL PIDL { get { return getPIDL(); } }
protected abstract PIDL getPIDL();
大多数属性实现为 abstract
,它们必须由继承的类实现。
PIDL 目录和文件 (WrapFileSystemInfoEx)
DirectoryInfoExA
和 FileInfoExA
继承自 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 项关联,因此大多数原始属性,如 FullName
和 PIDL
,都与 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 是一个真实目录而不是存档。
FullName
和 TempPath
之间的区别在于,当您调用 FullName
时,如果该项不存在,它会被创建(通过调用 expandSelf()
)。但如果您调用 TempPath
,它会在不检查该项是否已创建的情况下返回路径。
internal abstract IDirectoryLister directoryLister { get; }
VirtualDirectoryInfoExA
继承自 VirtualFileSystemInfoExA
;它添加了一些与目录相关的方法。它还包含一个名为 directoryLister
的属性,用于执行大部分虚拟文件操作(更多信息如下)。
存档、存档目录和存档文件 (ArchiveInfoExA)
存档支持基于我的 CAKE3,这是一个包装器(中间件),用于几个 .NET 和 W32 库。您可以在此处找到更多信息。
ArchiveInfoExA
、ArchiveDirectoryInfoExA
和 ArchiveFileInfoExA
继承自 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
相同,只是 embeddedItem
是 IFileSystemInfoExA
,因为书签可能链接到虚拟目录(FileSystemInfoExA
)。这两个类共享相似的代码;我无法重构它们,因此存在一些重复的代码。
支持的接口/类
开发了许多接口来支持 FileSystemInfoExA
及其子类
IDirectoryLister 接口
每个虚拟目录都有一个名为 directoryLister
的属性,它是一个实现 IDirectoryLister
的类;它包含列出、添加、删除和提取项的函数,类似于 shell 中的 ShellFolder
。
DirectoryLister
的作用类似于 shell 中的 ShellFolder
和 Storage
。
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
构建的大多数属性,包括 Parent
、FullName
以及 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
列表(例如,BookmarkDirectoryLister
和 ArchiveDirectoryLister
)。所有操作都通过该库进行调用。
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 静态类
FileExA
和 DirectoryExA
类似于 FileEx
和 DirectoryEx
,只是它们增加了对虚拟文件夹的支持。
DataObjectExA 类
DataObjectExA
是一个包含一个或多个 IFileSystemInfoExA
的 DataObject
;它与 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 组件,带有两个附加属性:EnableDrag
和 EnableDrop
;您可以在 ListView
和 TreeView
上使用这些属性来启用拖放支持。
尽管我没有测试它们,但 DragDropHelperExA
应该支持 M-V-VM 模式;您必须在 ViewModels 中实现 ISupportDrag
/ISupportDrop
接口。
不完整项
这个组件比上一个复杂得多,因此许多功能尚未完成;它们将在未来的版本中添加。
FileExA
/DirectoryExA
中的一些方法缺失。- 上下文菜单,某些命令应由组件处理(例如,删除)。
- 由实体(或
directoryLister
)指定的自定义上下文菜单。 - WinForms 上的拖放。
- 缺少
FileSystemWatcherExA
/DriveInfoExA
。
问题
DragDropHelperExA.EnableDrop
在拖放后会折叠整个 TreeView。- 在
FileList
中拖放只会更新(刷新)FileList
,反之亦然。 WrapFileInfoEx
和WrapFileInfoExA
有重复的代码。- 存储在
WrapFileSystemInfoEx
类中的非托管FileSystemInfoEx.PIDL
,被ArchiveInfoExA.Parent
使用,在程序集(COFE 和 COFE.Archives)之间共享;这可能导致AccessViolationException
,并且可能需要更改为按需生成,如果需要的话。
未来版本 (DirectoryInfoExB)
我希望扩展 A 版本以提供数据库支持,这将大大提高列表性能,添加标签/用户排序支持,并启用搜索。
还需要一个用于异步复制(或压缩)文件的新接口/类,因为对于存档,一次压缩/解压缩一个文件太慢了。
参考
- XML 到 C# 中的强类型 (作者:Mike Elliott)
- WPF 中的拖放 (作者:Jaime Rodriguez)
- DirectoryInfoEx
- CAKE3
版本历史
- 2010 年 1 月 1 日 - 初始版本:0.1。