Skip to content

peony structure zh_CN

Yue-Lan edited this page Jun 22, 2020 · 1 revision

Peony架构简述

架构对于一个非小型的长期项目的影响是非常深远的,我认为任何人都有必要在实际展开对Peony的Hacking之前对于现有架构有所了解,只有这样才能够保证我们在修复一个问题或者增加一个新功能时,不会引入对项目健壮性和长远发展不利的额外问题。

当然,Peony的架构并非完美,它存在很多问题,这些问题本身可能不算“bug”,但是会体现在其它的issue上,这种内在的联系会使得Peony的维护工作变得更加困难。我在介绍Peony整体架构的同时,也会对当前存在的缺陷进行分析,让大家对问题的处理有一个更全面的出发点。

回到正题,我们开始介绍Peony的架构。

Peony的技术路线

Peony采用Qt前端+GVfs/GIO后端的技术路线,Qt想必大家都不陌生,GVfs/GIO可以理解为一个跨文件系统提供统一API的框架,这使得我们能够专注于更上层的开发,而不用为底层的实现花费太多的精力。

为了满足跟随系统主题的需求,Peony抛弃了qss,使用基于主题框架(qt5-ukui-platformtheme)+基本控件+自定义QStyle的方式进行界面的开发,这使得我能够从繁琐的样式修改任务中解脱出来,从而能够专注于核心功能的开发,这种可重用的开发模式也是我在UKUI3.0应用开发的过程中所推崇的,只有在这样的模式下,我们才能够保证桌面应用风格的统一性和应用开发的效率。

在UKUI3.0阶段,技术路线的选择完全由我们决定,我们需要自己评估如何展开项目的重构,并且实施,我认为这也是UKUI3.0和UKUI2.0项目的主要区别之一。Peony之所以能够在短期内实现快速的发展,是因为选择了当前较为合理的技术路线,充分利用了现有的资源,摆脱或规避了琐碎或者棘手的问题,专注于核心功能和需求的实现,做到了扬长避短。

当然,gvfs和qt并没有帮助我们把所有的问题完美的解决,很多时候我们需要更深一步的探究,才能够解决现有框架没有为我们解决的问题,这也是我们下一个阶段需要推进的工作。

Peony的代码结构

我在项目的文档中,也零散的介绍了Peony的五层代码结构,core、file operation、model、extensions、和ui层,这里我再较为集中的描述一下它们的定位、功能以及存在的问题。

Core

  • Core层结构与特性

Core层的代码在${top-src-dir}/libpeony-qt下,是Peony对底层GIO库的一层封装,它主要的类有FileInfo、FileInfoJob、FileEnumerator、FileWatcher这四个。

FileInfo是文件信息的载体,FileInfoJob是获取文件信息的类,在Peony的设计中,FileInfo是在应用中共享的,我们有一套类似与引用计数的机制管理FileInfo,这样做的好处是我们可以减少重复的资源占用,同时可以较为方便的获取最近一次更新的FileInfo缓存,这些缓存存在FileInfoManager单例中。之后,我们又将gvfs的metainfo机制引入了进来,抽象成了FileMetaInfo,由于metainfo与文件的一一对应关系,我们基于它实现了文件标记和桌面图标位置的记录。

FileEnumerator是目录的枚举器,它提供了便利的枚举接口,并且集成了错误处理机制,是整个Core层中最为复杂的一个类。在Peony的设计原则里,所有包含文件的视图都应该可以被表示为一个uri,这个uri可以是本地的,可以是远程的,也可以是虚拟的。这意味着除了file://,Peony也支持ftp、smb等协议,同时Peony内部实现了search协议(间libpeony-qt/vfs),而且它们的枚举是无差别的,这得益于GIO框架的优势。当然在枚举的过程中,我们可能会由于权限或者其它问题导致失败,我们在正式调用enumreate方法之前,可以通过prepare首先尝试解决一些问题,比如分区的挂载、远程服务器的链接验证(MountOperation和connect sever dialog)等,事实上Peony自身也是这样做的。

FileWatcher用于监听文件或目录变化,然后发出信号,除此之外,由于目前gvfs底层支持的原因,一些虚拟文件系统不能够进行监听操作,FileWatcher还承担了实现备选监听方式的任务(对于不能监听的目录,监听Peony的文件操作,然后触发轮询)。

Core层本身实际上是一个翻译层,它主要的工作是将GIO的接口封装,共给上层调用,关于这四个类的实际应用,可以参考FileItem和FileItemModel的代码。

  • Core层的缺陷

Core层目前存在内存泄漏的隐患,其主要原因是因为FileInfo的引用机制存在设计缺陷,在某些情况下,它可能导致无用的FileInfo资源无法被释放。

另外,FileEnumerator类还有一些异步方法没有实现完全异步,这可能会导致ui线程卡顿。

  • 在Core层进行Hacking

由于Core是整个架构的最底层,Core层的代码改动将对整个Peony有很大的影响,除非有十足的把握,否则不建议对已有的Core层代码进行Hacking。

可以说,单独的在Core层进行Hacking并没有太多意义,要Hacking这一层的机会也并不多,我们对Core的改动往往需要结合其它层的改动,而且这种改动应该是增量的,只有当现有的Core层无法满足逻辑需求时,我们才应该考虑为core层增加一个新类,来作为底层的实现。

File Operation

文件操作层的代码在${top-src-dir}/libpeony-qt/file-operation下,Peony对文件的增删查改操作都由FileOperation实现。

FileOperation和Core层都是对GIO的封装,如果把Core比作读写操作中的读,那么FileOperation就是写,它们在某一些角度可以看做是一层,只是FileOperation对于Peony的意义很大,而且有很强的统一性,足以构成一个单独的框架,所以从代码结构上来看,我还是把它分成了单独的一层。

  • FileOperation的设计思路

一、统一操作接口

为了便于操作的管理(FileOperationManager)和进度的监控(FileOperationProgressWizard),我们需要提供一个接口类(FileOperation),所有的文件操作都应该派生自这个类

二、操作的原子性

操作取消后必须能够回滚到未操作的状态

三、错误处理机制

当操作出错时,需要阻塞操作等待错误处理的反馈(FileOperationErrorHandler接口、FileOperationErrorDialog实现)

  • FileOperation框架的缺陷和实际问题

a.由于设计的缺陷,目前对于并行操作无法进行很好的管理

b.文件操作的回滚机制不太合理,同时对于回收操作的回滚有先天的缺陷

c.文件操作错误处理的机制不太合理,没有对操作提供自定义处理的选项

d.没有考虑和ui层交互的机制,导致交互不便

  • 在FileOperation层进行Hacking

我们可以看到github上报了很多关于文件操作的问题,这些很多都可以归结到之前我设计的不合理上,所以对文件操作层的Hacking,目前的首要任务是考虑如何通过重构解决设计层面的问题,然后在考虑新的Features。

Model

Qt有一个非常重要的框架——Model/View,可以说这个框架贯穿了整个Peony,model是将底层的数据和操作反映到ui层的关键接口,在这一层有太多跨层级的代码,而且model的概念较为抽象,对于没有model/view开发经验的开发人员来说,这一块的代码并不好懂。然而,想要对Peony整个项目有比较全面的认知,model层是必须要了解的一层。

model层的代码在${top-src-dir}/libpeony-qt/model下,注意我们的model层主要指SideBarModel和FileItemModel,以及其相关的Item类。

  • 如何理解model层

由于model层基本上基于Qt的Model/View,我们需要的就是在了解core和file operation层的代码后,掌握model/view编程,这样就能够比较轻松的理解model层了

对于未接触过这方面技术的开发人员,可以参考我之前写过的关于model/view编程的博客,大家可以通过它了解model/view编程的概念和方法,然后再研究model层的代码。

  • model层的缺陷

model的排序效率存在问题,对于大量数据增减、改变的处理还有待优化

  • 在Model层上Hacking

在model层进行hacking也不是一件容易的工作,我并不建议在这一层进行hacking,包括我自己,除非它真的有致命的问题。

实现一个新需求或者修复一个bug时,我们要尽量绕开model层,尽量避免在model层进行代码的改动。

Extensions

Peony的扩展框架借鉴了老版本的扩展框架,它分为调用和实现两个部分,调用部分散布在UI层中,关于其接口部分的代码可以在${top-src-dir}/plugin-iface中找到,而具体的实现则可能不需要在项目内部,我们可以在ukui项目下的peony-extensions项目中找到。

  • 插件调用的基本流程

上面我们也讲到,扩展框架的调用部分散布在ui层中,这些插件的接口会在特定的上下文中被调用,因此我们可以通过此时传入插件实例的参数实现各种各样的操作扩展,比如菜单插件,它会在菜单初始化时被调用,调用端传入了菜单类型(视图、侧边栏或者桌面),当前目录的uri,以及选中文件的urilist,对应的,实现端需要我们返回一个result,通过这样的一个流程,我们可以在右键菜单中实现打开终端,格式化等可扩展的操作。

  • 插件开发的应用范围

在peony中,菜单、属性窗口的标签页、预览窗格以及文件视图都可以以插件的形式进行开发。peony项目中有一些被注释的插件demo,可以让开发者简单的了解如何编写一个peony的插件。如果你希望在菜单中增加一项,又不希望修改源码,不妨试一试写一个MenuPluginInterface的插件实例。

  • 插件存在的缺陷

目前的插件框架没有一个很好的二进制兼容性,尤其是对于基于libpeony2的文件视图插件来说,我们可能需要在peony更新之后重新编译一遍插件项目,才能保证peony的正常运作。

目前的插件没有实现统一的管理,想要禁用一个插件,就需要从系统中去除它,这对于用户来说可能并不友好

UI

UI层主要分布在${top-src-dir}/src和${top-src-dir}/libpeony-qt/control中,主要的几个大类是MainWindow(FMWindowIface)、DirectoryViewMenu、TabWidget、DirectoryViewContainer、IconView、ListView和SideBar(及Menu)。

FMWindowIface规定了一组对window的操作接口,其中大部分都是对view的操作,比如路径跳转、选择等。

DirectoryViewMenu是一个菜单插件的调用类,它除了本身的action,还会构造插件返回的action lists,目前我们可用的插件有压缩、打开终端、admin插件,不过admin插件目前还存在一些跟主题相关的问题。

TabWidget是一个允许多个视图共存的容器,我们知道window的接口需要和视图有一一对应的关系,TabWidget会帮助我们处理标签页切换和window操作相关的问题。

DirectoryViewWidget是所有视图的统一载体,它允许切换视图,同时记录跳转的历史。

IconView和ListView是视图扩展的内部插件,由于历史原因,它们需要通过IconView2、ListView2(继承自DirectoryViewWidget接口)。

SideBar和SideBarMenu比较独立,本身也可以看作一个model/view的框架,它的model和item提供了分区的卸载操作。

  • UI层的缺陷

除了一些细节上的疏漏,UI层的缺陷大多都是因为其下层的框架存在设计上的缺陷而导致的,比如远程连接时的卡顿,是因为没有完全使用异步方法;文件操作交互的体验不佳,是因为文件操作框架设计的时候没有考虑与视图的交互,只考虑到了model层。我们也许可以通过一些patch修复其中的一部分,但是最根本的解决办法还是对底层进行重构。

  • 在UI层进行Hacking的建议

我们在开发一个新的需求时,应该仔细研究上述的类的接口,考虑如何最大化的重用现有的接口实现这个需求,只有在实在无法满足需求时,才考虑增加接口。

对于某一些需求,我们可能需要几个层级同时考虑,如果发生这种情况,就需要仔细斟酌,必要的时候可以通过邮件进行探讨。

总结

Peony的五层结构界限分明,又紧密相连,在分析问题和需求的时候,我们要能够通过表面的问题定位到问题的发源层或者切入点,在修改问题和增加需求的时候,又要注意是否对现有的层与层之间的联系产生了影响,要做到这一点可能并不容易,可能需要很长的一段时间才能够真正的达到。作为项目的Author,在参与Peony项目的开发者越来越多的这个背景下,我希望能够通过这些文档,帮助大家更快的熟悉整个项目,相信在我们的共同努力下,Peony会成为一个优秀的合作式开源项目。

Clone this wiki locally