@mmmbuto/masix 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/install.js +12 -49
- package/package.json +2 -2
- package/packages/plugin-base/discovery/0.2.4/manifest.json +7 -0
- package/packages/plugin-base/discovery/0.3.0/SHA256SUMS +2 -0
- package/packages/plugin-base/discovery/0.3.0/discovery-android-aarch64-termux.pkg +0 -0
- package/packages/plugin-base/discovery/0.3.0/discovery-linux-x86_64.pkg +0 -0
- package/packages/plugin-base/discovery/0.3.0/manifest.json +34 -0
- package/packages/plugin-base/discovery/CHANGELOG.md +7 -0
- package/packages/plugin-base/discovery/README.md +19 -10
- package/packages/plugin-base/discovery/source/Cargo.toml +3 -2
- package/packages/plugin-base/discovery/source/plugin.manifest.json +9 -4
- package/packages/plugin-base/discovery/source/src/doh.rs +103 -0
- package/packages/plugin-base/discovery/source/src/magnet_cache.rs +113 -0
- package/packages/plugin-base/discovery/source/src/main.rs +769 -49
- package/packages/plugin-base/discovery/source/src/torrent.rs +701 -0
- package/packages/plugin-base/discovery/source/src/transport.rs +112 -0
- package/prebuilt/masix +0 -0
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @mmmbuto/masix
|
|
2
2
|
|
|
3
|
-
Official
|
|
3
|
+
Official npm package for the Termux distribution of MasiX (MIT).
|
|
4
4
|
|
|
5
|
-
MasiX is
|
|
5
|
+
MasiX is Termux-first. Linux and macOS are supported through source builds and the Homebrew tap.
|
|
6
6
|
|
|
7
|
-
Current package line: `0.4.
|
|
7
|
+
Current package line: `0.4.3`.
|
|
8
8
|
|
|
9
9
|
## Function Summary
|
|
10
10
|
|
|
@@ -19,6 +19,7 @@ Current package line: `0.4.1`.
|
|
|
19
19
|
- Termux boot automation (`masix termux boot enable|disable|status`)
|
|
20
20
|
- Optional local STT via whisper.cpp (`masix config stt`)
|
|
21
21
|
- Bundled `plugin-base` packages are included and installed during `npm install`
|
|
22
|
+
- If a bundled `.pkg` is missing, MasiX can fall back to source build for the module
|
|
22
23
|
- Startup update checks are available on demand via `masix check-update`
|
|
23
24
|
|
|
24
25
|
## Install (Termux)
|
|
@@ -42,11 +43,11 @@ masix status
|
|
|
42
43
|
## Bundled Packages
|
|
43
44
|
|
|
44
45
|
Postinstall installs these bundled `plugin-base` packages into `~/.masix/plugins`:
|
|
45
|
-
- `discovery` `0.
|
|
46
|
+
- `discovery` `0.3.0`
|
|
46
47
|
- `codex-backend` `0.1.4`
|
|
47
48
|
- `codex-tools` `0.1.3`
|
|
48
49
|
|
|
49
|
-
Package artifacts are
|
|
50
|
+
Package artifacts and source trees are included in the npm tarball under `packages/plugin-base/`.
|
|
50
51
|
|
|
51
52
|
## Useful Commands
|
|
52
53
|
|
|
@@ -67,6 +68,7 @@ masix stats
|
|
|
67
68
|
|
|
68
69
|
- This package targets Android + arm64 in Termux
|
|
69
70
|
- If no prebuilt binary is available, postinstall builds from source
|
|
71
|
+
- The postinstall step uses `masix plugin install-base` so dependency order and source fallback stay aligned with the CLI
|
|
70
72
|
- `masix config stt` can auto-pick and auto-download a Whisper model based on device resources
|
|
71
73
|
|
|
72
74
|
## Full Documentation
|
package/install.js
CHANGED
|
@@ -7,31 +7,6 @@ const BINARY_NAME = 'masix';
|
|
|
7
7
|
const PACKAGE_BIN_PATH = path.join(__dirname, 'prebuilt', BINARY_NAME);
|
|
8
8
|
const PREBUILT_DIR = path.join(__dirname, 'prebuilt');
|
|
9
9
|
const BUNDLED_PACKAGES_DIR = path.join(__dirname, 'packages', 'plugin-base');
|
|
10
|
-
const TERMUX_PLATFORM = 'android-aarch64-termux';
|
|
11
|
-
const BUNDLED_PLUGINS = [
|
|
12
|
-
{
|
|
13
|
-
plugin: 'discovery',
|
|
14
|
-
version: '0.2.4',
|
|
15
|
-
packageType: 'mcp_binary',
|
|
16
|
-
file: path.join(BUNDLED_PACKAGES_DIR, 'discovery', '0.2.4', 'discovery-android-aarch64-termux.pkg'),
|
|
17
|
-
adminOnly: false
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
plugin: 'codex-backend',
|
|
21
|
-
version: '0.1.4',
|
|
22
|
-
packageType: 'library',
|
|
23
|
-
file: path.join(BUNDLED_PACKAGES_DIR, 'codex-backend', '0.1.4', 'codex-backend-android-aarch64-termux.pkg'),
|
|
24
|
-
adminOnly: true
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
plugin: 'codex-tools',
|
|
28
|
-
version: '0.1.3',
|
|
29
|
-
packageType: 'mcp_binary',
|
|
30
|
-
file: path.join(BUNDLED_PACKAGES_DIR, 'codex-tools', '0.1.3', 'codex-tools-android-aarch64-termux.pkg'),
|
|
31
|
-
adminOnly: true
|
|
32
|
-
}
|
|
33
|
-
];
|
|
34
|
-
|
|
35
10
|
const isTermux =
|
|
36
11
|
process.env.TERMUX_VERSION !== undefined ||
|
|
37
12
|
process.env.PREFIX === '/data/data/com.termux/files/usr';
|
|
@@ -100,32 +75,20 @@ function ensureMasixHome() {
|
|
|
100
75
|
}
|
|
101
76
|
|
|
102
77
|
function installBundledPlugins() {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
78
|
+
if (!fs.existsSync(BUNDLED_PACKAGES_DIR)) {
|
|
79
|
+
throw new Error(`Bundled packages root missing: ${BUNDLED_PACKAGES_DIR}`);
|
|
80
|
+
}
|
|
107
81
|
|
|
108
|
-
|
|
82
|
+
execFileSync(
|
|
83
|
+
PACKAGE_BIN_PATH,
|
|
84
|
+
[
|
|
109
85
|
'plugin',
|
|
110
|
-
'install-
|
|
111
|
-
'--
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
plugin.version,
|
|
117
|
-
'--package-type',
|
|
118
|
-
plugin.packageType,
|
|
119
|
-
'--platform',
|
|
120
|
-
TERMUX_PLATFORM
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
if (plugin.adminOnly) {
|
|
124
|
-
args.push('--admin-only');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
execFileSync(PACKAGE_BIN_PATH, args, { stdio: 'inherit' });
|
|
128
|
-
}
|
|
86
|
+
'install-base',
|
|
87
|
+
'--packages-root',
|
|
88
|
+
BUNDLED_PACKAGES_DIR
|
|
89
|
+
],
|
|
90
|
+
{ stdio: 'inherit' }
|
|
91
|
+
);
|
|
129
92
|
}
|
|
130
93
|
|
|
131
94
|
ensureBinary();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmmbuto/masix",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Termux-first MIT automation runtime
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Termux-first MIT automation runtime for Android/Termux; Linux/macOS supported via source builds and Homebrew; bundled plugin-base packages auto-install or auto-build as needed",
|
|
5
5
|
"main": "check-update.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"README.md",
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
"visibility": "plugin-base",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"package_type": "mcp_binary",
|
|
7
|
+
"entrypoint": "masix-plugin-discovery",
|
|
8
|
+
"dependencies": [],
|
|
7
9
|
"platforms": [
|
|
8
10
|
{
|
|
9
11
|
"id": "android-aarch64-termux",
|
|
@@ -21,6 +23,11 @@
|
|
|
21
23
|
"sha256": "b850b42f50e142e65577a418ecace3fefbec32502dba96c71f82593b37e5a004"
|
|
22
24
|
}
|
|
23
25
|
],
|
|
26
|
+
"source": {
|
|
27
|
+
"git_url": "https://github.com/DioNanos/MasiX.git",
|
|
28
|
+
"git_ref": "v0.4.2",
|
|
29
|
+
"subdir": "packages/plugin-base/discovery/source"
|
|
30
|
+
},
|
|
24
31
|
"install": {
|
|
25
32
|
"command": "masix plugin install-file --file <path-to-pkg> --plugin discovery --version 0.2.4 --package-type mcp_binary"
|
|
26
33
|
},
|
|
Binary file
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugin_id": "discovery",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"visibility": "plugin-base",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"package_type": "mcp_binary",
|
|
7
|
+
"entrypoint": "masix-plugin-discovery",
|
|
8
|
+
"dependencies": [],
|
|
9
|
+
"platforms": [
|
|
10
|
+
{
|
|
11
|
+
"id": "android-aarch64-termux",
|
|
12
|
+
"file": "discovery-android-aarch64-termux.pkg",
|
|
13
|
+
"sha256": "d5180da4bf40e1d751d1a785bad7febba517d37df6be5183176b12ee31523a21"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "linux-x86_64",
|
|
17
|
+
"file": "discovery-linux-x86_64.pkg",
|
|
18
|
+
"sha256": "bfcc03af9258882631d86d5c4eafef613f765ede35b22808d26a6377ff6d3351"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"source": {
|
|
22
|
+
"git_url": "https://github.com/DioNanos/MasiX.git",
|
|
23
|
+
"git_ref": "v0.4.3",
|
|
24
|
+
"subdir": "packages/plugin-base/discovery/source"
|
|
25
|
+
},
|
|
26
|
+
"install": {
|
|
27
|
+
"command": "masix plugin install-file --file <path-to-pkg> --plugin discovery --version 0.3.0 --package-type mcp_binary"
|
|
28
|
+
},
|
|
29
|
+
"notes": [
|
|
30
|
+
"Package usable without plugin server.",
|
|
31
|
+
"Tor support is optional and degrades gracefully to clearnet.",
|
|
32
|
+
"macOS currently relies on bundled source-build fallback until an Apple prebuilt artifact is refreshed."
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# discovery Package Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-03-21
|
|
4
|
+
|
|
5
|
+
- Added anti-censorship transport support with Tor auto-detect and graceful clearnet fallback.
|
|
6
|
+
- Expanded SearXNG defaults and added `tor-search` plus `search-status`.
|
|
7
|
+
- Upgraded `torrent-search` with mirror catalog federation, provider filtering, richer metadata, and magnet cache support.
|
|
8
|
+
- Refreshed package artifacts for `linux-x86_64` and `android-aarch64-termux`; macOS currently uses bundled source-build fallback until the Apple prebuilt artifact is refreshed.
|
|
9
|
+
|
|
3
10
|
## 0.2.4 - 2026-03-05
|
|
4
11
|
|
|
5
12
|
- Refreshed package artifacts for android-aarch64-termux, linux-x86_64, macos-aarch64.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# discovery (`plugin-base`)
|
|
2
2
|
|
|
3
|
-
`discovery` provides MCP discovery and
|
|
3
|
+
`discovery` provides MCP discovery, fetch, torrent metadata, and anti-censorship search tools bundled with the MIT repository.
|
|
4
4
|
|
|
5
5
|
## What It Is
|
|
6
6
|
|
|
7
7
|
- Package type: `mcp_binary`
|
|
8
8
|
- Visibility: `plugin-base`
|
|
9
9
|
- Distribution: local `.pkg` install supported (`install-file`)
|
|
10
|
+
- Tor: optional, graceful fallback to clearnet
|
|
11
|
+
- Status tool: `search-status`
|
|
10
12
|
|
|
11
13
|
## Install Example
|
|
12
14
|
|
|
@@ -14,9 +16,9 @@ Termux:
|
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
18
|
masix plugin install-file \
|
|
17
|
-
--file packages/plugin-base/discovery/0.
|
|
19
|
+
--file packages/plugin-base/discovery/0.3.0/discovery-android-aarch64-termux.pkg \
|
|
18
20
|
--plugin discovery \
|
|
19
|
-
--version 0.
|
|
21
|
+
--version 0.3.0 \
|
|
20
22
|
--package-type mcp_binary
|
|
21
23
|
```
|
|
22
24
|
|
|
@@ -24,25 +26,32 @@ Linux:
|
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
28
|
masix plugin install-file \
|
|
27
|
-
--file packages/plugin-base/discovery/0.
|
|
29
|
+
--file packages/plugin-base/discovery/0.3.0/discovery-linux-x86_64.pkg \
|
|
28
30
|
--plugin discovery \
|
|
29
|
-
--version 0.
|
|
31
|
+
--version 0.3.0 \
|
|
30
32
|
--package-type mcp_binary
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
macOS (Apple Silicon):
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
|
-
masix plugin install-
|
|
37
|
-
--file packages/plugin-base/discovery/0.2.4/discovery-macos-aarch64.pkg \
|
|
38
|
-
--plugin discovery \
|
|
39
|
-
--version 0.2.4 \
|
|
40
|
-
--package-type mcp_binary
|
|
38
|
+
masix plugin install-base
|
|
41
39
|
```
|
|
42
40
|
|
|
41
|
+
`0.3.0` currently uses bundled source-build fallback on macOS until the Apple prebuilt artifact is refreshed.
|
|
42
|
+
|
|
43
43
|
After install, run:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
masix plugin enable discovery
|
|
47
47
|
masix restart
|
|
48
48
|
```
|
|
49
|
+
|
|
50
|
+
Available commands in `0.3.0`:
|
|
51
|
+
|
|
52
|
+
- `web-search`
|
|
53
|
+
- `tor-search`
|
|
54
|
+
- `web-fetch`
|
|
55
|
+
- `torrent-search`
|
|
56
|
+
- `torrent-extract`
|
|
57
|
+
- `search-status`
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "masix-plugin-discovery"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
edition.workspace = true
|
|
5
5
|
license.workspace = true
|
|
6
6
|
|
|
7
7
|
[dependencies]
|
|
8
8
|
anyhow.workspace = true
|
|
9
9
|
clap.workspace = true
|
|
10
|
-
|
|
10
|
+
dirs.workspace = true
|
|
11
|
+
reqwest = { workspace = true, features = ["socks"] }
|
|
11
12
|
scraper.workspace = true
|
|
12
13
|
serde.workspace = true
|
|
13
14
|
serde_json.workspace = true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "discovery",
|
|
3
3
|
"name": "MasiX Discovery Tools",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"visibility": "plugin-base",
|
|
6
6
|
"package_type": "mcp_binary",
|
|
7
7
|
"entrypoint": "masix-plugin-discovery",
|
|
@@ -12,19 +12,24 @@
|
|
|
12
12
|
],
|
|
13
13
|
"platforms_tested": [
|
|
14
14
|
"linux-x86_64",
|
|
15
|
-
"android-aarch64-termux"
|
|
16
|
-
"macos-aarch64"
|
|
15
|
+
"android-aarch64-termux"
|
|
17
16
|
],
|
|
18
17
|
"platforms_planned": [],
|
|
19
18
|
"commands": [
|
|
20
19
|
"web-search",
|
|
20
|
+
"tor-search",
|
|
21
21
|
"web-fetch",
|
|
22
22
|
"torrent-search",
|
|
23
23
|
"torrent-extract",
|
|
24
|
+
"search-status",
|
|
25
|
+
"manifest",
|
|
24
26
|
"serve-mcp"
|
|
25
27
|
],
|
|
26
28
|
"tool_access": {
|
|
27
29
|
"default_required_role": "user",
|
|
28
|
-
"per_tool_required_role": {
|
|
30
|
+
"per_tool_required_role": {
|
|
31
|
+
"tor_search": "user",
|
|
32
|
+
"search_status": "user"
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
use anyhow::{anyhow, Result};
|
|
2
|
+
use reqwest::Client;
|
|
3
|
+
use serde::Deserialize;
|
|
4
|
+
use std::net::IpAddr;
|
|
5
|
+
use std::time::Duration;
|
|
6
|
+
|
|
7
|
+
const DOH_TIMEOUT_SECS: u64 = 5;
|
|
8
|
+
const CLOUDFLARE_DOH: &str = "https://cloudflare-dns.com/dns-query";
|
|
9
|
+
const GOOGLE_DOH: &str = "https://dns.google/dns-query";
|
|
10
|
+
const DNS_TYPE_A: &str = "A";
|
|
11
|
+
const DNS_TYPE_AAAA: &str = "AAAA";
|
|
12
|
+
|
|
13
|
+
#[derive(Debug, Clone)]
|
|
14
|
+
pub struct DohResolver {
|
|
15
|
+
client: Client,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[derive(Debug, Deserialize)]
|
|
19
|
+
struct DohResponse {
|
|
20
|
+
#[serde(default, rename = "Answer")]
|
|
21
|
+
answer: Vec<DohAnswer>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[derive(Debug, Deserialize)]
|
|
25
|
+
struct DohAnswer {
|
|
26
|
+
#[serde(default)]
|
|
27
|
+
data: String,
|
|
28
|
+
#[serde(default, rename = "type")]
|
|
29
|
+
record_type: u32,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl DohResolver {
|
|
33
|
+
pub fn new(user_agent: &str) -> Result<Self> {
|
|
34
|
+
let client = Client::builder()
|
|
35
|
+
.user_agent(user_agent)
|
|
36
|
+
.timeout(Duration::from_secs(DOH_TIMEOUT_SECS))
|
|
37
|
+
.build()?;
|
|
38
|
+
Ok(Self { client })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub async fn resolve(&self, hostname: &str) -> Result<Vec<IpAddr>> {
|
|
42
|
+
// Try IPv4 via Cloudflare first (most common case)
|
|
43
|
+
if let Ok(r) = self.resolve_via(CLOUDFLARE_DOH, hostname, DNS_TYPE_A).await {
|
|
44
|
+
if !r.is_empty() {
|
|
45
|
+
return Ok(r);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Try IPv6 via Cloudflare
|
|
49
|
+
if let Ok(r) = self.resolve_via(CLOUDFLARE_DOH, hostname, DNS_TYPE_AAAA).await {
|
|
50
|
+
if !r.is_empty() {
|
|
51
|
+
return Ok(r);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// IPv4 via Google fallback
|
|
55
|
+
if let Ok(r) = self.resolve_via(GOOGLE_DOH, hostname, DNS_TYPE_A).await {
|
|
56
|
+
if !r.is_empty() {
|
|
57
|
+
return Ok(r);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// IPv6 via Google fallback
|
|
61
|
+
self.resolve_via(GOOGLE_DOH, hostname, DNS_TYPE_AAAA).await
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub async fn resolve_with_fallback(&self, hostname: &str) -> String {
|
|
65
|
+
self.resolve(hostname)
|
|
66
|
+
.await
|
|
67
|
+
.ok()
|
|
68
|
+
.and_then(|items| items.into_iter().next())
|
|
69
|
+
.map(|ip| ip.to_string())
|
|
70
|
+
.unwrap_or_else(|| hostname.to_string())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async fn resolve_via(&self, endpoint: &str, hostname: &str, dns_type: &str) -> Result<Vec<IpAddr>> {
|
|
74
|
+
let response = self
|
|
75
|
+
.client
|
|
76
|
+
.get(endpoint)
|
|
77
|
+
.query(&[("name", hostname), ("type", dns_type)])
|
|
78
|
+
.header("Accept", "application/dns-json")
|
|
79
|
+
.send()
|
|
80
|
+
.await?;
|
|
81
|
+
|
|
82
|
+
if !response.status().is_success() {
|
|
83
|
+
return Err(anyhow!(
|
|
84
|
+
"DoH resolver {} returned HTTP {}",
|
|
85
|
+
endpoint,
|
|
86
|
+
response.status()
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let payload: DohResponse = response.json().await?;
|
|
91
|
+
let mut output = Vec::new();
|
|
92
|
+
for answer in payload.answer {
|
|
93
|
+
// Accept A (1) and AAAA (28) records
|
|
94
|
+
if answer.record_type != 1 && answer.record_type != 28 {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if let Ok(ip) = answer.data.parse::<IpAddr>() {
|
|
98
|
+
output.push(ip);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
Ok(output)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
use std::path::{Path, PathBuf};
|
|
5
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
6
|
+
use tokio::sync::Mutex;
|
|
7
|
+
|
|
8
|
+
const CACHE_TTL_SECS: u64 = 72 * 60 * 60;
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
11
|
+
struct CacheEntry {
|
|
12
|
+
magnet: String,
|
|
13
|
+
expires_at: u64,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[derive(Debug)]
|
|
17
|
+
pub struct MagnetCache {
|
|
18
|
+
path: PathBuf,
|
|
19
|
+
entries: Mutex<HashMap<String, CacheEntry>>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl MagnetCache {
|
|
23
|
+
pub async fn new(data_dir: Option<&Path>) -> Result<Self> {
|
|
24
|
+
let root = resolve_cache_root(data_dir);
|
|
25
|
+
std::fs::create_dir_all(&root)?;
|
|
26
|
+
let path = root.join("magnet_cache.json");
|
|
27
|
+
let entries = if path.is_file() {
|
|
28
|
+
let content = std::fs::read_to_string(&path).unwrap_or_default();
|
|
29
|
+
serde_json::from_str(&content).unwrap_or_default()
|
|
30
|
+
} else {
|
|
31
|
+
HashMap::new()
|
|
32
|
+
};
|
|
33
|
+
let cache = Self {
|
|
34
|
+
path,
|
|
35
|
+
entries: Mutex::new(entries),
|
|
36
|
+
};
|
|
37
|
+
cache.cleanup_expired().await?;
|
|
38
|
+
Ok(cache)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub async fn get(&self, url: &str) -> Option<String> {
|
|
42
|
+
let mut entries = self.entries.lock().await;
|
|
43
|
+
let now = now_unix_secs();
|
|
44
|
+
let value = entries.get(url).cloned();
|
|
45
|
+
match value {
|
|
46
|
+
Some(entry) if entry.expires_at > now => Some(entry.magnet),
|
|
47
|
+
Some(_) => {
|
|
48
|
+
entries.remove(url);
|
|
49
|
+
let _ = self.flush_locked(&entries);
|
|
50
|
+
None
|
|
51
|
+
}
|
|
52
|
+
None => None,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub async fn set(&self, url: &str, magnet: &str) -> Result<()> {
|
|
57
|
+
let mut entries = self.entries.lock().await;
|
|
58
|
+
entries.insert(
|
|
59
|
+
url.to_string(),
|
|
60
|
+
CacheEntry {
|
|
61
|
+
magnet: magnet.to_string(),
|
|
62
|
+
expires_at: now_unix_secs().saturating_add(CACHE_TTL_SECS),
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
self.flush_locked(&entries)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pub async fn size(&self) -> usize {
|
|
69
|
+
self.entries.lock().await.len()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub const fn ttl_hours() -> u64 {
|
|
73
|
+
72
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async fn cleanup_expired(&self) -> Result<()> {
|
|
77
|
+
let mut entries = self.entries.lock().await;
|
|
78
|
+
let now = now_unix_secs();
|
|
79
|
+
entries.retain(|_, entry| entry.expires_at > now);
|
|
80
|
+
self.flush_locked(&entries)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn flush_locked(&self, entries: &HashMap<String, CacheEntry>) -> Result<()> {
|
|
84
|
+
let content = serde_json::to_string_pretty(entries)?;
|
|
85
|
+
let tmp = self.path.with_extension("json.tmp");
|
|
86
|
+
std::fs::write(&tmp, &content)?;
|
|
87
|
+
std::fs::rename(&tmp, &self.path)?;
|
|
88
|
+
Ok(())
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn resolve_cache_root(data_dir: Option<&Path>) -> PathBuf {
|
|
93
|
+
if let Ok(value) = std::env::var("MASIX_DATA_DIR") {
|
|
94
|
+
let trimmed = value.trim();
|
|
95
|
+
if !trimmed.is_empty() {
|
|
96
|
+
return PathBuf::from(trimmed).join("discovery");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if let Some(path) = data_dir {
|
|
100
|
+
return path.join("discovery");
|
|
101
|
+
}
|
|
102
|
+
dirs::home_dir()
|
|
103
|
+
.unwrap_or_else(|| PathBuf::from("."))
|
|
104
|
+
.join(".masix")
|
|
105
|
+
.join("discovery")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn now_unix_secs() -> u64 {
|
|
109
|
+
SystemTime::now()
|
|
110
|
+
.duration_since(UNIX_EPOCH)
|
|
111
|
+
.map(|duration| duration.as_secs())
|
|
112
|
+
.unwrap_or(0)
|
|
113
|
+
}
|