Maria 开发记录 II

Maria Logo

Maria 是为 aria2 这款命令行下载软件定制的 Native App,开发 Maria 本身是不用写下载核心代码的。

这个项目最初创建于4月份,至今已经有6个月了,从最初的一个 Today Widget 发展到现在已经快要接近于完整的 macOS 应用了,(但是在我眼里还是不算完整应用,因为下载核心需要依赖 aria2 项目,而缺乏自己的下载核心程序),之前也写过一篇文章简单讲了一下这个程序的编写过程。最近的几次更新主要是对 aria2 的深度集成,到目前也已经有所成果了,所以准备记录一下这其中的过程和坑。

集成 aria2

一开始是根本没打算集成 aria2 的,因为考虑到其他类似的软件比如 Aria2GUI 或者是 Aria2D 都是用的直接打包一个 aria2 二进制包的方式,实际上还是通过 rpc 的方式来控制下载操作。

我个人觉得这种方式集成 aria2 不如不集成,就现在这样依赖外部运行更好,相互之间独立,aria2 、Maria 和 webui-aria2 能够更好的搭配工作。

后来再仔细翻阅了 aria2 的文档,发现了 libaria2 这个东西的存在,才把 Maria 集成 aria2 的计划真正的实施起来。

aria2 是由 C++ 11 编写的,而 Maria 是用 Swift 编写的,如果想要 Maria 直接调用 aria2 的内部接口,还需要一个连接桥梁,也就是 Objective-C,简单来说,就是需要写一个 OC++ 的 wrapper 来向 Swift 引入 aria2 的 C++ 接口。

获取 libaria2.dylib

现在 aria2 静态库文件 libaria2-1.28.0-maria.dylibaria2.h 已经从 git 中移除,你需要下载 aria2 源码自行编译生成:

(需要注意的是:macOS 可能会缺失一些工具包,请根据编译信息自行通过 Homebrew 安装)

# 在编译之前你需要先安装一些依赖包
brew install autoconf
brew install automake
brew install libtool

git clone https://github.com/ShinCurry/aria2
cd aria2
git checkout lib-for-maria 

export PATH="$PATH:/usr/local/opt/gettext/bin"
autoreconf -i
./configure --enable-libaria2
sudo make
sudo make install

如果编译安装成功,你应该能在 /usr/local/lib//usr/local/include/aria2/ 目录下找到相应的文件。然后将相应文件拷贝到 Maria 工程子目录 /Aria2Core 下:

cd path/to/your/projectdir/Aria2Core/

cp /usr/local/lib/libaria2.0.dylib ./Frameworks/libaria2-1.28.0-maria.dylib
install_name_tool -id "@loader_path/Frameworks/libaria2-1.28.0-maria.dylib" ./Frameworks/libaria2-1.28.0-maria.dylib

cp /usr/local/include/aria2/aria2.h ./

拿到静态库相关文件,接下来就需要写 wrapper 了。

我的做法是再新建一个 Objective-C 的 Framework Project 再引入到 Maria 中,这样和之前写好的 Aria2RPC Framework 就更好的统一了。

Objective-C++ for C++ 的 wrapper 不难写,只需要注意其中的一些数据类型的转换,比如:

  • C++ 的结构体(Struct)全部被转换成了 Objective-C++ 的类(Class)
  • C++ 中作为每个下载任务 ID 的 uint64_t A2GID 类型被转换成了 Objective-C++ 的 NSString
  • C++ 中作为 aria2 配置选项的 std::vector<std::pair<std::string, std::string>> 被转换成了 Objective-C++ 的 NSDictionary<NSString *, NSString *>

另外还有一大堆别名定义,为了更方便的进行数据转换,直接在名字前加上 AC(Aria2Core) 的前缀:

// Public
typedef NSDictionary<NSString *, NSString *> ACKeyVals;
typedef NSString ACUri;
typedef NSArray<NSString *> ACUris;
typedef NSString ACGid;
typedef NSArray<NSString *> ACGids;
typedef NSArray<ACUriData *> ACUriDatas;

// Private
typedef std::vector<std::string> Uris;
typedef uint64_t Gid;
typedef std::vector<uint64_t> Gids;
typedef aria2::KeyVals KeyVals;
typedef aria2::OffsetMode OffsetMode;
typedef aria2::BtFileMode BtFileMode;
typedef aria2::UriData UriData;
typedef std::vector<UriData> UriDatas;
typedef aria2::FileData FileData;

值得注意的是,创建的虽然是一个 Objective-C++ Framework Project,不过在 Maria 这个项目里面需要通过桥接文件在 Swift 里调用,所以,OC Framework 对外暴露的方法和类型定义不能有任何 C++ 的东西出现,不然使用 Swift 编译器编译的时候就会出现找不过各种头文件的错误。

另外还有一个待解决的问题就是,dealloc() 函数再程序退出的时候并没有调用。

对 aria2rpc framework 的修改

以前是直接在 framework 里面写成了单例运行模式,现在应该有了 aria2core 的存在,所以为了统一,改回了普通的类定义方式

Maria 调用 aria2 相关

最近的版本,新增了一个 Manager aria.swift 用来统一调用 aria2 相关的动态库,大致结构如下:

class Aria {
    var rpc: Aria2!
    var core: Aria2Core?
    
    static let shared = Aria()
    
    private init() {
        initRPC()
        initCore()
    }
}

在这个 Manager 中使用单例模式,并将所有 aria2 的初始化代码全部移动到这里面。

接下来需要完成的工作

是否完全使用 libaria2 提供的内部接口,去除 rpc 接口还在考虑当中。