文件操作并没有model/view编程那么的抽象,这里我会介绍一下peony-qt文件操作api的几个关键的设计理念,具体如何实现则需要大家循着这些线索到代码中窥探一二了。
永远不要尝试在主线程中进行这种繁重的io操作,所有文件操作有一个继承自QRunnable的接口,并且应该被设计为同步且阻塞的,我使用Qt的线程池启动一个文件操作,使用接口的提供的信号连接主线程中对应控件的槽进行交互。
在peony-qt中,文件操作的接口大概可以分为三类:
- FileOperation的信号接口
- 处理FileOperation异常的槽接口
- 响应FileOperation状态变化的槽接口
其中第二个接口是每一个文件操作必须要实现的接口,它必须使用阻塞式的连接方式,等待槽函数的返回值;第三种接口不一定要实现,但是用户可能会因为一个文件操作还没有完成就进行一些有危险的操作,导致数据被破坏,所以还是建议在复杂操作时实现这类接口。
在这种接口设计的模式下,文件操作的前后端可以做到完全的分离,这意味着我们可以在不影响文件操作的情况下,随意的修改ui,如果觉得我的ui写的丑的话,你自己DIY一个也无妨,这种思路会贯穿整个Peony-Qt的设计流程,也是其它文件管理器开发框架不具备的一个设计特色。
在peony-qt中,所有文件操作都尝试保持自身的原子性,这意味着文件操作不会“半途而废”,要实现这一点,我并非让文件操作不可取消,而是增加了回滚的机制,在peony中其实也有相同的功能,但是它将取消和回滚分离开来了,我认为这种设计并不合理——除非程序崩溃,否则不能破坏一个操作的原子性。
peony-qt的回滚操作主要通过实时记录每一个子操作的状态实现,对应的抽象就是FileNode类。对于复杂的操作,可能还要设计到递归回滚,然而虽然说我极力保持操作的可回滚性,也不能百分之百的确定回滚后源文件或者目标文件的正确性——由于文件操作中错误处理类型的存在,一些子项操作是难以回滚的,比如说对一个已经overwrite处理的操作是难以恢复的,至少现在不支持。
为什么说是期望,因为我还没有完成所有的文件操作的重构,在保证所有文件操作的原子性之后有一个好处,就是可以对已经完成(未取消)的操作进行undo和redo,也就是实现操作栈。在原子操作的前提下构造undo和redo也是相对简单的。这一块的内容等我完成了整个文件操作部分api的重构之后再进行补充。
目前实现了一个简单的undo/redo框架,具体示例可以通过
进行查看。
undo和redo的实现主要是使用了qt提供的QStack作为容器,使用一个用于管理的抽象类FileOperationManager进行执行和记录。
- 对于重名的处理
最简单的一个例子,在同一目录下对一个文件进行多次拷贝和粘贴,第一次之后的粘贴还没有向peony一样能够自行增加后缀,而不是直接报错。
- 文件操作的多操作并发执行
为了保证文件操作的undo&redo能够被顺利执行以及文件操作的安全性,我使用线程池最大线程数控制了FileOperationManager一次最多只能执行一个操作(其它操作排队等待之前的操作结束)。其实对于两个互不相干的操作,我们是能够使其并发执行的,但是要考虑的情况会变得复杂很多。
- 文件操作回滚的部分细节
文件操作的回滚无法保证所有的数据都能够被还原到之前的状态,比如如果我们在进行copy操作时,如果中途覆盖了一些目标文件,那么这些目标文件在回滚之后也不会还原了。