├── .github
└── workflows
│ ├── build.yaml
│ └── check.yaml
├── .gitignore
├── .vscode
└── launch.json
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── assets
├── icons
│ ├── address-book-symbolic.svg
│ ├── chat-symbolic.svg
│ ├── keyboard-symbolic.svg
│ ├── menu-symbolic.svg
│ ├── people-symbolic.svg
│ ├── person2-symbolic.svg
│ ├── qr-code-symbolic.svg
│ └── send-symbolic.svg
├── meson.build
└── resources.gresource.xml
├── docs
├── dark.png
└── light.png
├── libs
├── resource-loader
│ ├── .gitignore
│ ├── Cargo.toml
│ └── src
│ │ ├── configs
│ │ ├── avatar.rs
│ │ ├── client.rs
│ │ ├── config.rs
│ │ ├── local_db.rs
│ │ ├── mod.rs
│ │ └── temporary.rs
│ │ ├── lib.rs
│ │ ├── ops
│ │ ├── avatar.rs
│ │ ├── client.rs
│ │ ├── database.rs
│ │ ├── mod.rs
│ │ └── temporary.rs
│ │ ├── resource_directories.rs
│ │ ├── static_data.rs
│ │ └── utils.rs
└── widgets
│ ├── Cargo.toml
│ └── src
│ ├── lib.rs
│ ├── link_copier
│ ├── component.rs
│ ├── mod.rs
│ ├── payloads.rs
│ └── widgets.rs
│ ├── pwd_login
│ ├── component.rs
│ ├── mod.rs
│ ├── payloads.rs
│ └── widgets.rs
│ └── qrcode_login
│ ├── component.rs
│ ├── mod.rs
│ ├── payloads.rs
│ └── widgets.rs
├── meson.build
└── src
├── actions.rs
├── app
├── login
│ ├── captcha
│ │ └── mod.rs
│ ├── device_lock
│ │ └── mod.rs
│ ├── mod.rs
│ ├── service.rs
│ └── service
│ │ ├── handle_respond.rs
│ │ ├── login_server.rs
│ │ ├── pwd_login.rs
│ │ └── token.rs
├── main
│ ├── chatroom
│ │ ├── message_group.rs
│ │ └── mod.rs
│ ├── mod.rs
│ └── sidebar
│ │ ├── chats
│ │ ├── chat_item.rs
│ │ └── mod.rs
│ │ ├── contact
│ │ ├── friends
│ │ │ ├── friends_group.rs
│ │ │ ├── mod.rs
│ │ │ └── search_item.rs
│ │ ├── groups
│ │ │ ├── group_item.rs
│ │ │ └── mod.rs
│ │ └── mod.rs
│ │ └── mod.rs
└── mod.rs
├── config.rs.in
├── db
├── fs.rs
├── mod.rs
└── sql.rs
├── global
└── mod.rs
├── handler.rs
├── main.rs
├── meson.build
├── styles
└── style.css
└── utils
├── avatar
├── error.rs
├── loader
│ ├── group.rs
│ ├── mod.rs
│ └── user.rs
└── mod.rs
├── message
├── content.rs
├── mod.rs
└── utils.rs
└── mod.rs
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths-ignore:
8 | - '**.md'
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-22.04
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions-rs/toolchain@v1
16 | with:
17 | toolchain: nightly
18 | default: true
19 | - run: sudo apt-get update
20 | - run: sudo apt-get install meson ninja-build libgtk-4-dev libadwaita-1-dev
21 | - run: meson setup builddir
22 | - run: meson compile -C builddir
23 | - run: cargo build --release
24 | - uses: actions/upload-artifact@v3
25 | with:
26 | name: gtk-qq
27 | path: target/release/gtk-qq
28 |
--------------------------------------------------------------------------------
/.github/workflows/check.yaml:
--------------------------------------------------------------------------------
1 | name: check
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 | branches:
8 | - main
9 | pull_request:
10 | paths-ignore:
11 | - '**.md'
12 |
13 | jobs:
14 | check:
15 | runs-on: ubuntu-22.04
16 | env:
17 | RUSTFLAGS: -D warnings
18 | steps:
19 | - uses: actions/checkout@v2
20 | - uses: actions-rs/toolchain@v1
21 | with:
22 | toolchain: nightly
23 | components: clippy, rustfmt
24 | default: true
25 | - run: sudo apt-get update
26 | - run: sudo apt-get install meson ninja-build libgtk-4-dev libadwaita-1-dev
27 | - run: meson setup builddir
28 | - run: meson compile -C builddir
29 | - run: cargo fmt --check
30 | - run: cargo clippy
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | target
3 |
4 | # Generated by Meson
5 | builddir
6 | src/config.rs
7 |
8 | # Generated by RICQ
9 | device.json
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "lldb",
9 | "request": "launch",
10 | "name": "Debug executable 'gtk-qq'",
11 | "cargo": {
12 | "args": [
13 | "build",
14 | "--bin=gtk-qq",
15 | "--package=gtk-qq"
16 | ],
17 | "filter": {
18 | "name": "gtk-qq",
19 | "kind": "bin"
20 | }
21 | },
22 | "args": [],
23 | "cwd": "${workspaceFolder}"
24 | },
25 | {
26 | "type": "lldb",
27 | "request": "launch",
28 | "name": "Debug unit tests in executable 'gtk-qq'",
29 | "cargo": {
30 | "args": [
31 | "test",
32 | "--no-run",
33 | "--bin=gtk-qq",
34 | "--package=gtk-qq"
35 | ],
36 | "filter": {
37 | "name": "gtk-qq",
38 | "kind": "bin"
39 | }
40 | },
41 | "args": [],
42 | "cwd": "${workspaceFolder}"
43 | }
44 | ]
45 | }
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "gtk-qq"
3 | version = "0.2.0"
4 | edition = "2021"
5 |
6 | [workspace]
7 | members = ["./libs/*"]
8 |
9 | [dependencies.relm4]
10 | git = "https://github.com/Relm4/Relm4.git"
11 | # next, 2022/05/30
12 | rev = "144f48319ffd7a889f28853df00e802cfc97dc26"
13 | features = ["macros", "libadwaita"]
14 |
15 | [dependencies.ricq]
16 | git = "https://github.com/lz1998/ricq.git"
17 | # v0.1.17, master, 2022/09/07
18 | rev = "56620d755f35f7b6ade52991be62360b3377547c"
19 |
20 | [dependencies.widgets]
21 | path = "./libs/widgets"
22 |
23 | [dependencies.resource-loader]
24 | path = "./libs/resource-loader"
25 |
26 | [dependencies]
27 | tokio = { version = "1.18.2", features = ["sync"] }
28 | rand = "0.8.5"
29 | async-trait = "0.1.53"
30 | once_cell = "1.11.0"
31 | rusqlite = "0.27.0"
32 | reqwest = "0.11.10"
33 | qrcode-png = "0.4.0"
34 | typed-builder = "0.10"
35 | bincode = "1.3.3"
36 | base64 = "0.13.0"
37 |
38 | [profile.release]
39 | lto = true
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GTK QQ
2 |
3 | [![license-badge]][license-link]
4 | [![dependency-badge]][dependency-link]
5 | [![check-badge]][check-link]
6 | [![build-badge]][build-link]
7 |
8 | [license-badge]: https://img.shields.io/badge/License-AGPL%20v3-blue.svg
9 | [license-link]: https://www.gnu.org/licenses/agpl-3.0
10 | [dependency-badge]: https://deps.rs/repo/github/lomirus/gtk-qq/status.svg
11 | [dependency-link]: https://deps.rs/repo/github/lomirus/gtk-qq
12 | [check-badge]: https://github.com/lomirus/gtk-qq/workflows/check/badge.svg
13 | [check-link]: https://github.com/lomirus/gtk-qq/actions/workflows/check.yaml
14 | [build-badge]: https://github.com/lomirus/gtk-qq/workflows/build/badge.svg
15 | [build-link]: https://github.com/lomirus/gtk-qq/actions/workflows/build.yaml
16 |
17 |
18 | Unofficial Linux [QQ](https://im.qq.com/) client, based on GTK4 and libadwaita, developed with Rust and [Relm4](https://relm4.org/).
19 |
20 | This app uses [ricq](https://github.com/lz1998/ricq) as the rust bindings of QQ APIs.
21 |
22 | ## Current Status
23 |
24 | > **Warning**
25 | >
26 | > This project has been discontinued due to following reasons:
27 | > - Tencent has released the official [Linux QQ](https://im.qq.com/linuxqq/index.shtml) in early 2023. Therefore this project may have copyright issues with it if continues.
28 | > - This project is based on the `gtk-rs`. To be honest, it is a disastrous development experience with it. If I could restart this project, I would not choose this library anymore. I've tried Tauri before when it's only in v0.x, the development experience is fine but the webview it uses on linux is too old to support many new features on modern browers now at that time.
29 | > - It is a very rare skill to develop app with `gtk-rs`. Only few people are able to contribute to this project, and the code quality is far from what I expected.
30 | > - The owner and main maintainer of this repository @lomirus is busy with some other projects and affairs IRL, and he doesn't use QQ on Linux very much now. So for now, he doesn't have enough motivation on this project.
31 | >
32 | > However, you can still create any pull request if you want. And if you want to find any alternative, [Icalingua plus plus](https://github.com/Icalingua-plus-plus/Icalingua-plus-plus) would be a good choice (compared with the official one :p)
33 |
34 | ## Screenshots
35 |
36 | | Light | Dark |
37 | | ------------------------------------------ | ---------------------------------------- |
38 | |  |  |
39 |
40 | > **Note**
41 | >
42 | > The two screenshots have been a little outdated. The UI now has been adjusted and improved compared to them.
43 |
44 | ## Installation
45 |
46 | ### AUR
47 |
48 | For Arch users, you can install via the AUR package [gtk-qq-git](https://aur.archlinux.org/packages/gtk-qq-git):
49 |
50 | ```
51 | paru -S gtk-qq-git
52 | ```
53 |
54 | ## Manual Build
55 |
56 | ### Requirements
57 |
58 | You will need to install [Rust](https://www.rust-lang.org/tools/install) and [Meson](https://mesonbuild.com/Getting-meson.html) to build this project, and the neccessary libraries below:
59 |
60 | #### Ubuntu (>= 22.04)
61 |
62 | ```bash
63 | sudo apt install gcc libssl-dev libsqlite3-dev libgtk-4-dev libadwaita-1-dev
64 | ```
65 |
66 | #### Fedora
67 |
68 | ```bash
69 | sudo dnf install gtk4-devel libadwaita-devel
70 | ```
71 |
72 | #### Arch
73 |
74 | ```bash
75 | sudo pacman -S pkgconf gtk4 libadwaita
76 | ```
77 |
78 | #### Windows & MacOS
79 |
80 | GTK4 projects would be more complex to compile on Windows/MacOS. Nevertheless, considering some special reasons that you know, we shall not offer the Windows/MacOS release or even build scripts.
81 |
82 | > **Warning**
83 | >
84 | > You can try to build it still if you are just for personal use. At the same time, you should also promise that you will not distribute the Windows/MacOS build to the public in order to ensure the maintenance of this project.
85 | >
86 | > The user builds, uses or distributes this project at the user's own risk. This project and its contributors assume no responsibility.
87 |
88 | ### Setup
89 |
90 | You only need to run the commands below once unless you change the related codes.
91 |
92 | ```bash
93 | # In the root directory of project
94 | meson setup builddir
95 | meson compile -C builddir
96 | ```
97 |
98 | ### Build
99 |
100 | Switch to nightly toolchain before building.
101 |
102 | ```bash
103 | # In the root directory of project
104 | rustup override set nightly
105 | cargo build --release
106 | ```
107 |
108 | ## Contributing
109 |
110 | - You can feel free to use English or Chinese to open an issue or pull request.
111 | - The commit message should follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
112 | - If you want make changes to the UI part, read the [GNOME Human Interface Guidelines](https://developer.gnome.org/hig/index.html) before it.
113 |
114 | ## License
115 |
116 | This repository is under the [AGPL-3.0 license ](https://github.com/lomirus/gtk-qq/blob/main/LICENSE).
117 |
--------------------------------------------------------------------------------
/assets/icons/address-book-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/icons/chat-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/icons/keyboard-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/icons/menu-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/icons/people-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/icons/person2-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/icons/qr-code-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/icons/send-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/meson.build:
--------------------------------------------------------------------------------
1 | gnome = import('gnome')
2 |
3 | resources = gnome.compile_resources(
4 | 'resources',
5 | 'resources.gresource.xml',
6 | gresource_bundle: true,
7 | source_dir: meson.current_build_dir(),
8 | )
9 |
--------------------------------------------------------------------------------
/assets/resources.gresource.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | icons/chat-symbolic.svg
5 | icons/address-book-symbolic.svg
6 | icons/send-symbolic.svg
7 | icons/menu-symbolic.svg
8 | icons/people-symbolic.svg
9 | icons/person2-symbolic.svg
10 | icons/keyboard-symbolic.svg
11 | icons/qr-code-symbolic.svg
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lomirus/gtk-qq/ac4ee7627b402c849b1aa9e16186bad97c08e6b2/docs/dark.png
--------------------------------------------------------------------------------
/docs/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lomirus/gtk-qq/ac4ee7627b402c849b1aa9e16186bad97c08e6b2/docs/light.png
--------------------------------------------------------------------------------
/libs/resource-loader/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /Cargo.lock
3 |
--------------------------------------------------------------------------------
/libs/resource-loader/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "resource-loader"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [features]
7 | default = []
8 | logger = ["log"]
9 |
10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11 | [dependencies]
12 | serde = { version = "1", features = ["derive"] }
13 | once_cell = "1.11"
14 | derivative = "2.2"
15 | log = { version = "0.4", optional = true }
16 | tokio = { version = "1", features = ["fs"] }
17 | tempfile = "3.3"
18 | toml = "0.5.9"
19 | tap = "1"
20 | rand = "0.8.5"
21 | directories = "4"
22 |
23 | [dev-dependencies]
24 | serde_json = "1"
25 |
26 | [dependencies.ricq]
27 | git = "https://github.com/lz1998/ricq.git"
28 | # v0.1.17, master, 2022/09/07
29 | rev = "56620d755f35f7b6ade52991be62360b3377547c"
--------------------------------------------------------------------------------
/libs/resource-loader/src/configs/avatar.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | use serde::{Deserialize, Serialize};
4 |
5 | use crate::resource_directories::ResourceDirectories;
6 |
7 | use super::{free_path_ref, static_leak};
8 |
9 | default_string! {
10 | BaseDir => "avatars"
11 | Group => "groups"
12 | User => "users"
13 | }
14 |
15 | #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, derivative::Derivative)]
16 | #[derivative(Default)]
17 | pub struct AvatarConfig {
18 | #[derivative(Default(value = r#"BaseDir::get_default()"#))]
19 | #[serde(default = "BaseDir::get_default")]
20 | #[serde(alias = "base")]
21 | base_dir: String,
22 | #[derivative(Default(value = r#"Group::get_default()"#))]
23 | #[serde(default = "Group::get_default")]
24 | group: String,
25 | #[derivative(Default(value = r#"User::get_default()"#))]
26 | #[serde(default = "User::get_default")]
27 | user: String,
28 | }
29 |
30 | /// # Panic
31 | /// using string literal construct this struct will cause
32 | /// ***STATUS_HEAP_CORRUPTION***,
33 | ///
34 | /// the internal `& 'static Path` comes from `Box::leak`
35 | #[derive(Debug, PartialEq, Eq)]
36 | pub(crate) struct InnerAvatarConfig {
37 | pub group: &'static Path,
38 | pub user: &'static Path,
39 | }
40 | impl AvatarConfig {
41 | pub(crate) fn into_inner(self, root: &ResourceDirectories) -> InnerAvatarConfig {
42 | let avatar = root.get_data_home().join(&self.base_dir);
43 |
44 | let group = avatar.join(&self.group);
45 | let static_group = static_leak(group.into_boxed_path());
46 |
47 | let user = avatar.join(&self.user);
48 | let static_user = static_leak(user.into_boxed_path());
49 |
50 | InnerAvatarConfig {
51 | group: static_group,
52 | user: static_user,
53 | }
54 | }
55 | }
56 |
57 | impl Drop for InnerAvatarConfig {
58 | fn drop(&mut self) {
59 | free_path_ref(self.group);
60 | free_path_ref(self.user);
61 | }
62 | }
63 |
64 | #[cfg(test)]
65 | mod test {
66 |
67 | use std::path::Path;
68 |
69 | use serde_json::json;
70 |
71 | use crate::resource_directories::ResourceDirectories;
72 |
73 | use super::{AvatarConfig, BaseDir, Group, User};
74 |
75 | #[test]
76 | fn test_default_data() {
77 | let avatar = AvatarConfig::default();
78 |
79 | assert_eq!(
80 | avatar,
81 | AvatarConfig {
82 | base_dir: BaseDir::get_default(),
83 | group: Group::get_default(),
84 | user: User::get_default()
85 | }
86 | )
87 | }
88 | #[test]
89 | fn test_not_full() {
90 | let data = json! {
91 | {
92 | "base_dir":"avatar_cache",
93 | "group":"group_avatar"
94 | }
95 | };
96 |
97 | let avatar = serde_json::from_value::(data).unwrap();
98 |
99 | assert_eq!(
100 | avatar,
101 | AvatarConfig {
102 | base_dir: "avatar_cache".into(),
103 | group: "group_avatar".into(),
104 | user: User::get_default()
105 | }
106 | )
107 | }
108 |
109 | #[test]
110 | fn test_inner_drop() {
111 | let avatar = AvatarConfig::default();
112 |
113 | let inner = avatar.into_inner(&ResourceDirectories::new().with_set_path(Some("gtk-qq")));
114 |
115 | assert_eq!(inner.group, Path::new("gtk-qq\\avatars\\groups"));
116 | assert_eq!(inner.user, Path::new("gtk-qq\\avatars\\users"))
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/configs/client.rs:
--------------------------------------------------------------------------------
1 | use derivative::Derivative;
2 | use ricq::version::{get_version, Version};
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Debug, Serialize, Deserialize, Derivative)]
6 | #[derivative(Default)]
7 | pub struct ClientConfig {
8 | #[serde(default = "Default::default")]
9 | #[derivative(Default(value = "Default::default()"))]
10 | protocol: Protocol,
11 |
12 | #[serde(default = "default_seed")]
13 | #[derivative(Default(value = "default_seed()"))]
14 | device_seed: u64,
15 | }
16 |
17 | pub struct ClientInner {
18 | pub(crate) device_seed: u64,
19 | pub(crate) version: Version,
20 | }
21 |
22 | impl From for ClientInner {
23 | fn from(cfg: ClientConfig) -> Self {
24 | ClientInner {
25 | device_seed: cfg.device_seed,
26 | version: get_version(cfg.protocol.into()),
27 | }
28 | }
29 | }
30 |
31 | #[derive(Debug, Clone, Copy, Serialize, Derivative, Deserialize)]
32 | #[derivative(Default)]
33 | pub enum Protocol {
34 | #[serde(alias = "ipad")]
35 | IPad,
36 | #[serde(alias = "android-phone")]
37 | #[serde(alias = "android_phone")]
38 | AndroidPhone,
39 | #[serde(alias = "android-watch")]
40 | #[serde(alias = "android_watch")]
41 | AndroidWatch,
42 | #[derivative(Default)]
43 | #[serde(alias = "macos")]
44 | MacOS,
45 | #[serde(alias = "qi_dian")]
46 | #[serde(alias = "qi-dian")]
47 | QiDian,
48 | }
49 |
50 | impl From for ricq::version::Protocol {
51 | fn from(val: Protocol) -> Self {
52 | use ricq::version::Protocol::*;
53 | match val {
54 | Protocol::IPad => IPad,
55 | Protocol::AndroidPhone => AndroidPhone,
56 | Protocol::AndroidWatch => AndroidWatch,
57 | Protocol::MacOS => MacOS,
58 | Protocol::QiDian => QiDian,
59 | }
60 | }
61 | }
62 |
63 | fn default_seed() -> u64 {
64 | 1145141919810
65 | }
66 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/configs/config.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 |
3 | use derivative::Derivative;
4 | use serde::{Deserialize, Serialize};
5 |
6 | use super::{
7 | avatar::AvatarConfig,
8 | client::{ClientConfig, ClientInner},
9 | local_db::DbConfig,
10 | temporary::{InnerTemporaryConfig, TemporaryConfig},
11 | InnerAvatarConfig, InnerDbConfig,
12 | };
13 |
14 | use crate::resource_directories::ResourceDirectories;
15 |
16 | #[derive(Debug, Serialize, Deserialize, Derivative)]
17 | #[derivative(Default)]
18 | pub struct Config {
19 | #[derivative(Default(value = "None"))]
20 | #[serde(default = "Default::default")]
21 | #[serde(alias = "res", alias = "resource")]
22 | #[serde(skip_serializing_if = "Option::is_none")]
23 | resource_root: Option,
24 | #[serde(default = "Default::default")]
25 | #[serde(alias = "temp", alias = "temporary")]
26 | temporary: TemporaryConfig,
27 | #[serde(default = "Default::default")]
28 | avatar: AvatarConfig,
29 | #[serde(default = "Default::default")]
30 | database: DbConfig,
31 | #[serde(default = "Default::default")]
32 | client: ClientConfig,
33 | }
34 |
35 | pub struct InnerConfig {
36 | pub(crate) temporary: InnerTemporaryConfig,
37 | pub(crate) avatar: InnerAvatarConfig,
38 | pub(crate) database: InnerDbConfig,
39 | pub(crate) client: ClientInner,
40 | }
41 |
42 | impl Config {
43 | pub(crate) fn into_inner(self, root: ResourceDirectories) -> InnerConfig {
44 | let root = root.with_set_path(self.resource_root);
45 | InnerConfig {
46 | avatar: self.avatar.into_inner(&root),
47 | database: self.database.into_inner(&root),
48 | temporary: self.temporary.into_inner(),
49 | client: self.client.into(),
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/configs/local_db.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | use serde::{Deserialize, Serialize};
4 |
5 | use crate::resource_directories::ResourceDirectories;
6 |
7 | use super::{free_path_ref, static_leak};
8 | use derivative::Derivative;
9 | default_string! {
10 | BaseDir => "database"
11 | SqlData => "sql_db.db"
12 | }
13 |
14 | #[derive(Debug, Serialize, Deserialize, Derivative)]
15 | #[derivative(Default)]
16 | pub struct DbConfig {
17 | #[derivative(Default(value = "BaseDir::get_default()"))]
18 | #[serde(default = "BaseDir::get_default")]
19 | #[serde(alias = "base")]
20 | base_dir: String,
21 |
22 | #[derivative(Default(value = "SqlData::get_default()"))]
23 | #[serde(default = "SqlData::get_default")]
24 | #[serde(alias = "app_db")]
25 | sql_data: String,
26 | }
27 |
28 | pub(crate) struct InnerDbConfig {
29 | pub sql_data: &'static Path,
30 | }
31 |
32 | impl DbConfig {
33 | pub(crate) fn into_inner(self, base: &ResourceDirectories) -> InnerDbConfig {
34 | let base = base.get_data_local_home().join(&self.base_dir);
35 |
36 | let sql_data = static_leak(base.join(&self.sql_data).into_boxed_path());
37 |
38 | InnerDbConfig { sql_data }
39 | }
40 | }
41 |
42 | impl Drop for InnerDbConfig {
43 | fn drop(&mut self) {
44 | free_path_ref(self.sql_data)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/configs/mod.rs:
--------------------------------------------------------------------------------
1 | mod client;
2 | mod config;
3 | use std::path::Path;
4 |
5 | mod avatar;
6 | mod local_db;
7 | mod temporary;
8 |
9 | fn free_path_ref(path: &'static Path) {
10 | let box_path = unsafe { Box::from_raw(path as *const _ as *mut Path) };
11 | logger!(trace "dropping Path=> {:?}", &box_path);
12 | drop(box_path)
13 | }
14 |
15 | fn static_leak(boxed: Box) -> &'static T {
16 | Box::leak(boxed) as &'static T
17 | }
18 |
19 | pub(crate) use avatar::InnerAvatarConfig;
20 | pub use config::{Config, InnerConfig};
21 | pub(crate) use local_db::InnerDbConfig;
22 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/configs/temporary.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | use derivative::Derivative;
4 | use serde::{Deserialize, Serialize};
5 | use tempfile::TempDir;
6 |
7 | use super::{free_path_ref, static_leak};
8 |
9 | default_string! {
10 | CaptchaQrCode => "captcha_url.png"
11 | QrLoginQrCode => "qrcode_login.png"
12 | }
13 |
14 | #[derive(Debug, Clone, Serialize, Deserialize, Derivative)]
15 | #[derivative(Default)]
16 | pub struct TemporaryConfig {
17 | #[derivative(Default(value = "CaptchaQrCode::get_default()"))]
18 | #[serde(default = "CaptchaQrCode::get_default")]
19 | #[serde(alias = "captcha", alias = "captcha_url")]
20 | captcha_qrcode: String,
21 |
22 | #[derivative(Default(value = "QrLoginQrCode::get_default()"))]
23 | #[serde(default = "QrLoginQrCode::get_default")]
24 | #[serde(alias = "qr_login")]
25 | qrcode_login: String,
26 | }
27 |
28 | #[derive(Debug)]
29 | pub(crate) struct InnerTemporaryConfig {
30 | pub(crate) temp_dir: TempDir,
31 | pub(crate) captcha_file: &'static Path,
32 | pub(crate) qrcode_login: &'static Path,
33 | }
34 |
35 | impl TemporaryConfig {
36 | pub(crate) fn into_inner(self) -> InnerTemporaryConfig {
37 | let temp_dir = tempfile::tempdir().expect("Cannot Create Temporary Directory");
38 |
39 | let captcha_file = static_leak(temp_dir.path().join(self.captcha_qrcode).into_boxed_path());
40 | let qrcode_login = static_leak(temp_dir.path().join(self.qrcode_login).into_boxed_path());
41 | InnerTemporaryConfig {
42 | temp_dir,
43 | captcha_file,
44 | qrcode_login,
45 | }
46 | }
47 | }
48 |
49 | impl Drop for InnerTemporaryConfig {
50 | fn drop(&mut self) {
51 | free_path_ref(self.captcha_file)
52 | }
53 | }
54 |
55 | #[cfg(test)]
56 | mod test {
57 | use super::TemporaryConfig;
58 |
59 | #[test]
60 | fn test_tmp_file() {
61 | let temp = TemporaryConfig::default();
62 |
63 | let inner = temp.into_inner();
64 |
65 | println!("{:?}", inner)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod ops;
2 | mod resource_directories;
3 | #[macro_use]
4 | mod utils;
5 | mod configs;
6 | mod static_data;
7 |
8 | pub use configs::Config;
9 |
10 | pub use static_data::ResourceConfig;
11 |
12 | pub use ops::{
13 | avatar::{Group as AvatarGroup, User as AvatarUser},
14 | client::{Device, Protocol},
15 | database::SqlDataBase,
16 | temporary::{CaptchaQrCode, QrCodeLoginCode, TempDir},
17 | AsyncCreatePath, AsyncLoadResource, DirAction, GetPath, SyncCreatePath, SyncLoadResource,
18 | };
19 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/ops/avatar.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | use crate::{logger, static_data::load_cfg};
4 |
5 | use super::GetPath;
6 |
7 | pub struct User;
8 |
9 | impl GetPath for User {
10 | fn get_path() -> &'static Path {
11 | let cfg = load_cfg();
12 | logger!(info "loading `User Avatar` path");
13 | cfg.avatar.user
14 | }
15 | }
16 |
17 | pub struct Group;
18 |
19 | impl GetPath for Group {
20 | fn get_path() -> &'static Path {
21 | let cfg = load_cfg();
22 | logger!(info "loading `Group Avatar` path");
23 | cfg.avatar.group
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/ops/client.rs:
--------------------------------------------------------------------------------
1 | use std::convert::Infallible;
2 |
3 | use rand::{prelude::StdRng, SeedableRng};
4 |
5 | use crate::{static_data::load_cfg, SyncLoadResource};
6 |
7 | pub struct Device;
8 |
9 | impl SyncLoadResource for Device {
10 | type Args = ();
11 |
12 | type Error = Infallible;
13 |
14 | fn load_resource(_: Self::Args) -> Result {
15 | let seed = load_cfg().client.device_seed;
16 | Ok(ricq::device::Device::random_with_rng(
17 | &mut StdRng::seed_from_u64(seed),
18 | ))
19 | }
20 | }
21 |
22 | pub struct Protocol;
23 |
24 | impl SyncLoadResource for Protocol {
25 | type Args = ();
26 |
27 | type Error = Infallible;
28 |
29 | fn load_resource(_: Self::Args) -> Result {
30 | Ok(load_cfg().client.version.clone())
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/ops/database.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | use crate::{logger, static_data::load_cfg};
4 |
5 | use super::GetPath;
6 |
7 | pub struct SqlDataBase;
8 |
9 | impl GetPath for SqlDataBase {
10 | fn get_path() -> &'static Path {
11 | let cfg = load_cfg();
12 | logger!(info "loading `Sql DataBase` path");
13 | cfg.database.sql_data
14 | }
15 |
16 | fn path_for_create() -> Option<&'static Path> {
17 | ::get_path().parent()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/libs/resource-loader/src/ops/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod avatar;
2 | pub mod client;
3 | pub mod database;
4 | pub mod temporary;
5 | use std::path::Path;
6 |
7 | pub trait GetPath {
8 | fn get_path() -> &'static Path;
9 |
10 | fn path_for_create() -> Option<&'static Path> {
11 | ::get_path().into()
12 | }
13 | }
14 |
15 | #[derive(Debug, PartialEq, Eq, Clone, Copy)]
16 | pub enum DirAction {
17 | CreateAll,
18 | None,
19 | }
20 |
21 | pub use sync_ops::{SyncCreatePath, SyncLoadResource};
22 |
23 | mod sync_ops {
24 | use std::{any::type_name, fs::create_dir_all, io, path::Path};
25 |
26 | use tap::Tap;
27 |
28 | use crate::{logger, DirAction, GetPath};
29 |
30 | pub trait SyncCreatePath: GetPath {
31 | fn create_and_get_path() -> io::Result<&'static Path> {
32 | if let Some(path) = ::path_for_create()
33 | .tap(|path| logger!(debug "create path {:?} | {}", path, type_name::()))
34 | {
35 | create_dir_all(path)?;
36 | }
37 | Ok(::get_path()
38 | .tap(|path| logger!(info "get path: {:?} | {}", path, type_name::())))
39 | }
40 |
41 | fn do_action_and_get_path(action: DirAction) -> io::Result<&'static Path> {
42 | logger!(debug "Sync Directory action : {:?} | {}", action, type_name::());
43 | match action {
44 | DirAction::CreateAll => ::create_and_get_path(),
45 | DirAction::None => Ok(::get_path()),
46 | }
47 | }
48 | }
49 |
50 | impl SyncCreatePath for T where T: GetPath {}
51 |
52 | pub trait SyncLoadResource {
53 | type Args;
54 | type Error: std::error::Error;
55 | fn load_resource(args: Self::Args) -> Result;
56 | }
57 | }
58 |
59 | pub use async_ops::{AsyncCreatePath, AsyncLoadResource};
60 |
61 | mod async_ops {
62 | use std::{any::type_name, future::Future, io, path::Path, pin::Pin};
63 |
64 | use tokio::fs::create_dir_all;
65 |
66 | use crate::{logger, DirAction, GetPath};
67 |
68 | pub trait AsyncCreatePath: GetPath {
69 | fn create_and_get_path_async(
70 | ) -> Pin> + Send + Sync>> {
71 | let create_path = ::path_for_create();
72 | logger!(debug "create path {:?} | {}", create_path, type_name::());
73 | let path = ::get_path();
74 | logger!(info "get path: {:?} | {}", path, type_name::());
75 | Box::pin(async move {
76 | if let Some(path) = create_path {
77 | create_dir_all(path).await?;
78 | }
79 | Ok(path)
80 | })
81 | }
82 |
83 | fn do_action_and_get_path_async(
84 | action: DirAction,
85 | ) -> Pin> + Send + Sync>> {
86 | logger!(debug "Async Directory action : {:?} | {}", action, type_name::());
87 | Box::pin(async move {
88 | match action {
89 | DirAction::CreateAll => {
90 | ::create_and_get_path_async().await
91 | }
92 | DirAction::None => Ok(::get_path()),
93 | }
94 | })
95 | }
96 | }
97 |
98 | impl AsyncCreatePath for T where T: GetPath {}
99 |
100 | pub trait AsyncLoadResource {
101 | type Fut: Future