@synopackageland/templates 0.9.5 → 0.9.6
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 +27 -4
- package/package.json +2 -2
- package/templates/_shared/AGENTS.md +95 -18
- package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
- package/templates/native-go/backend/cmd/app/main.go +8 -1
- package/templates/native-go/backend/cmd/background-worker/main.go +5 -1
- package/templates/native-go/backend/go.mod +1 -1
- package/templates/native-go/backend/go.sum +2 -2
- package/templates/native-go/backend/internal/app/background.go +19 -8
- package/templates/native-go/backend/internal/app/background_owner_test.go +2 -2
- package/templates/native-go/backend/internal/app/background_test.go +24 -0
- package/templates/native-go/backend/internal/app/background_work.go +94 -0
- package/templates/native-go/backend/internal/app/background_work_test.go +95 -0
- package/templates/native-go/backend/internal/app/uninstall.go +19 -0
- package/templates/native-go/backend/internal/app/uninstall_test.go +33 -0
- package/templates/native-go/dsm-package.yaml +18 -0
- package/templates/native-go-delegate/backend/cmd/background-worker/main.go +1 -1
- package/templates/native-go-jobs/backend/internal/app/background_routes_test.go +4 -0
- package/templates/native-go-jobs/frontend/test/background-page.test.ts +21 -0
- package/templates/native-go-vue/frontend/package-lock.json +4 -4
- package/templates/native-go-vue/frontend/package.json +1 -1
- package/templates/native-rust/README.md +381 -0
- package/templates/native-rust/backend/Cargo.lock +125 -0
- package/templates/native-rust/backend/Cargo.toml +24 -0
- package/templates/native-rust/backend/THIRD_PARTY_NOTICES.txt +207 -0
- package/templates/native-rust/backend/permissions.json +5 -0
- package/templates/native-rust/backend/rust-toolchain.toml +4 -0
- package/templates/native-rust/backend/src/app/background/mod.rs +424 -0
- package/templates/native-rust/backend/src/app/background/owner.rs +77 -0
- package/templates/native-rust/backend/src/app/background/owner_tests.rs +174 -0
- package/templates/native-rust/backend/src/app/background/schedule.rs +126 -0
- package/templates/native-rust/backend/src/app/background/schedule_tests.rs +108 -0
- package/templates/native-rust/backend/src/app/background/tests.rs +505 -0
- package/templates/native-rust/backend/src/app/background/work.rs +78 -0
- package/templates/native-rust/backend/src/app/background/work_tests.rs +123 -0
- package/templates/native-rust/backend/src/app/background_routes.rs +22 -0
- package/templates/native-rust/backend/src/app/delegate_config.rs +188 -0
- package/templates/native-rust/backend/src/app/delegate_routes.rs +22 -0
- package/templates/native-rust/backend/src/app/identity.rs +56 -0
- package/templates/native-rust/backend/src/app/migrations.rs +79 -0
- package/templates/native-rust/backend/src/app/mod.rs +30 -0
- package/templates/native-rust/backend/src/app/portal.rs +317 -0
- package/templates/native-rust/backend/src/app/root_routes.rs +23 -0
- package/templates/native-rust/backend/src/app/routes.rs +315 -0
- package/templates/native-rust/backend/src/app/session_files.rs +19 -0
- package/templates/native-rust/backend/src/app/shared_folder.rs +383 -0
- package/templates/native-rust/backend/src/app/test_support.rs +200 -0
- package/templates/native-rust/backend/src/app/uninstall.rs +38 -0
- package/templates/native-rust/backend/src/bin/api.rs +72 -0
- package/templates/native-rust/backend/src/bin/background-worker.rs +37 -0
- package/templates/native-rust/backend/src/build_env.rs +155 -0
- package/templates/native-rust/backend/src/lib.rs +6 -0
- package/templates/native-rust/backend/src/permissions.rs +54 -0
- package/templates/native-rust/backend/src/process.rs +91 -0
- package/templates/native-rust/dsm-package.yaml +89 -0
- package/templates/native-rust/gitignore +2 -0
- package/templates/native-rust/i18n/glossary.json +663 -0
- package/templates/native-rust/i18n/messages/de.json +29 -0
- package/templates/native-rust/i18n/messages/en.json +29 -0
- package/templates/native-rust/i18n/messages/es.json +29 -0
- package/templates/native-rust/i18n/messages/fr.json +29 -0
- package/templates/native-rust/i18n/messages/it.json +29 -0
- package/templates/native-rust/i18n/messages/ja.json +29 -0
- package/templates/native-rust/i18n/messages/ko.json +29 -0
- package/templates/native-rust/i18n/messages/zh-CN.json +29 -0
- package/templates/native-rust/i18n/messages/zh-TW.json +29 -0
- package/templates/native-rust/ui/app.css +45 -0
- package/templates/native-rust/ui/app.js +111 -0
- package/templates/native-rust-delegate/backend/src/app/delegate_routes.rs +92 -0
- package/templates/native-rust-delegate/backend/src/bin/background-worker.rs +52 -0
- package/templates/native-rust-jobs/backend/src/app/background_routes.rs +109 -0
- package/templates/native-rust-root-ops/backend/src/app/root_routes.rs +475 -0
- package/templates/native-rust-vue/README.md +55 -0
- package/templates/native-rust-vue/gitignore +3 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Native Rust package build dependencies (crates linked into the backend binaries)
|
|
2
|
+
|
|
3
|
+
syno-pkg-land-sdk v0.9.7 (MIT)
|
|
4
|
+
|
|
5
|
+
MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 synopkgland
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
itoa v1.0.18 (MIT OR Apache-2.0; used under MIT)
|
|
28
|
+
|
|
29
|
+
Permission is hereby granted, free of charge, to any
|
|
30
|
+
person obtaining a copy of this software and associated
|
|
31
|
+
documentation files (the "Software"), to deal in the
|
|
32
|
+
Software without restriction, including without
|
|
33
|
+
limitation the rights to use, copy, modify, merge,
|
|
34
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
35
|
+
the Software, and to permit persons to whom the Software
|
|
36
|
+
is furnished to do so, subject to the following
|
|
37
|
+
conditions:
|
|
38
|
+
|
|
39
|
+
The above copyright notice and this permission notice
|
|
40
|
+
shall be included in all copies or substantial portions
|
|
41
|
+
of the Software.
|
|
42
|
+
|
|
43
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
44
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
45
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
46
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
47
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
48
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
49
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
50
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
51
|
+
DEALINGS IN THE SOFTWARE.
|
|
52
|
+
|
|
53
|
+
libc v0.2.190 (MIT OR Apache-2.0; used under MIT)
|
|
54
|
+
|
|
55
|
+
Copyright (c) The Rust Project Developers
|
|
56
|
+
|
|
57
|
+
Permission is hereby granted, free of charge, to any
|
|
58
|
+
person obtaining a copy of this software and associated
|
|
59
|
+
documentation files (the "Software"), to deal in the
|
|
60
|
+
Software without restriction, including without
|
|
61
|
+
limitation the rights to use, copy, modify, merge,
|
|
62
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
63
|
+
the Software, and to permit persons to whom the Software
|
|
64
|
+
is furnished to do so, subject to the following
|
|
65
|
+
conditions:
|
|
66
|
+
|
|
67
|
+
The above copyright notice and this permission notice
|
|
68
|
+
shall be included in all copies or substantial portions
|
|
69
|
+
of the Software.
|
|
70
|
+
|
|
71
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
72
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
73
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
74
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
75
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
76
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
77
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
78
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
79
|
+
DEALINGS IN THE SOFTWARE.
|
|
80
|
+
|
|
81
|
+
memchr v2.8.3 (Unlicense OR MIT; used under MIT)
|
|
82
|
+
|
|
83
|
+
The MIT License (MIT)
|
|
84
|
+
|
|
85
|
+
Copyright (c) 2015 Andrew Gallant
|
|
86
|
+
|
|
87
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
88
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
89
|
+
in the Software without restriction, including without limitation the rights
|
|
90
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
91
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
92
|
+
furnished to do so, subject to the following conditions:
|
|
93
|
+
|
|
94
|
+
The above copyright notice and this permission notice shall be included in
|
|
95
|
+
all copies or substantial portions of the Software.
|
|
96
|
+
|
|
97
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
98
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
99
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
100
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
101
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
102
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
103
|
+
THE SOFTWARE.
|
|
104
|
+
|
|
105
|
+
serde v1.0.229 (MIT OR Apache-2.0; used under MIT)
|
|
106
|
+
|
|
107
|
+
Permission is hereby granted, free of charge, to any
|
|
108
|
+
person obtaining a copy of this software and associated
|
|
109
|
+
documentation files (the "Software"), to deal in the
|
|
110
|
+
Software without restriction, including without
|
|
111
|
+
limitation the rights to use, copy, modify, merge,
|
|
112
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
113
|
+
the Software, and to permit persons to whom the Software
|
|
114
|
+
is furnished to do so, subject to the following
|
|
115
|
+
conditions:
|
|
116
|
+
|
|
117
|
+
The above copyright notice and this permission notice
|
|
118
|
+
shall be included in all copies or substantial portions
|
|
119
|
+
of the Software.
|
|
120
|
+
|
|
121
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
122
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
123
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
124
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
125
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
126
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
127
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
128
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
129
|
+
DEALINGS IN THE SOFTWARE.
|
|
130
|
+
|
|
131
|
+
serde_core v1.0.229 (MIT OR Apache-2.0; used under MIT)
|
|
132
|
+
|
|
133
|
+
Permission is hereby granted, free of charge, to any
|
|
134
|
+
person obtaining a copy of this software and associated
|
|
135
|
+
documentation files (the "Software"), to deal in the
|
|
136
|
+
Software without restriction, including without
|
|
137
|
+
limitation the rights to use, copy, modify, merge,
|
|
138
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
139
|
+
the Software, and to permit persons to whom the Software
|
|
140
|
+
is furnished to do so, subject to the following
|
|
141
|
+
conditions:
|
|
142
|
+
|
|
143
|
+
The above copyright notice and this permission notice
|
|
144
|
+
shall be included in all copies or substantial portions
|
|
145
|
+
of the Software.
|
|
146
|
+
|
|
147
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
148
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
149
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
151
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
152
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
153
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
154
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
155
|
+
DEALINGS IN THE SOFTWARE.
|
|
156
|
+
|
|
157
|
+
serde_json v1.0.154 (MIT OR Apache-2.0; used under MIT)
|
|
158
|
+
|
|
159
|
+
Permission is hereby granted, free of charge, to any
|
|
160
|
+
person obtaining a copy of this software and associated
|
|
161
|
+
documentation files (the "Software"), to deal in the
|
|
162
|
+
Software without restriction, including without
|
|
163
|
+
limitation the rights to use, copy, modify, merge,
|
|
164
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
165
|
+
the Software, and to permit persons to whom the Software
|
|
166
|
+
is furnished to do so, subject to the following
|
|
167
|
+
conditions:
|
|
168
|
+
|
|
169
|
+
The above copyright notice and this permission notice
|
|
170
|
+
shall be included in all copies or substantial portions
|
|
171
|
+
of the Software.
|
|
172
|
+
|
|
173
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
174
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
175
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
176
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
177
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
178
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
179
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
180
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
181
|
+
DEALINGS IN THE SOFTWARE.
|
|
182
|
+
|
|
183
|
+
zmij v1.0.23 (MIT)
|
|
184
|
+
|
|
185
|
+
Permission is hereby granted, free of charge, to any
|
|
186
|
+
person obtaining a copy of this software and associated
|
|
187
|
+
documentation files (the "Software"), to deal in the
|
|
188
|
+
Software without restriction, including without
|
|
189
|
+
limitation the rights to use, copy, modify, merge,
|
|
190
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
191
|
+
the Software, and to permit persons to whom the Software
|
|
192
|
+
is furnished to do so, subject to the following
|
|
193
|
+
conditions:
|
|
194
|
+
|
|
195
|
+
The above copyright notice and this permission notice
|
|
196
|
+
shall be included in all copies or substantial portions
|
|
197
|
+
of the Software.
|
|
198
|
+
|
|
199
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
200
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
201
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
202
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
203
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
204
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
205
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
206
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
207
|
+
DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
//! The opt-in background jobs API: owner-scoped routes over the SDK's
|
|
2
|
+
//! package-private spool. The overlay `background_routes.rs` decides whether
|
|
3
|
+
//! a build serves them.
|
|
4
|
+
|
|
5
|
+
mod owner;
|
|
6
|
+
#[cfg(test)]
|
|
7
|
+
mod owner_tests;
|
|
8
|
+
mod schedule;
|
|
9
|
+
#[cfg(test)]
|
|
10
|
+
mod schedule_tests;
|
|
11
|
+
#[cfg(test)]
|
|
12
|
+
mod tests;
|
|
13
|
+
mod work;
|
|
14
|
+
#[cfg(test)]
|
|
15
|
+
mod work_tests;
|
|
16
|
+
|
|
17
|
+
pub use work::hash_work;
|
|
18
|
+
|
|
19
|
+
use std::sync::Arc;
|
|
20
|
+
use std::time::Duration;
|
|
21
|
+
|
|
22
|
+
use serde::Deserialize;
|
|
23
|
+
use serde_json::{Value, json};
|
|
24
|
+
use syno_pkg_land_sdk::api::{self, Caller, Route};
|
|
25
|
+
use syno_pkg_land_sdk::background::{
|
|
26
|
+
Access, JOB_TIMEOUT, Job, MAX_INPUT_BYTES, MAX_OWNER_BYTES, MAX_OWNER_PENDING, MAX_QUEUED,
|
|
27
|
+
MAX_SCHEDULES, MAX_TOTAL_BYTES, Store, StoreError, SubmitRequest, WorkerInfo,
|
|
28
|
+
};
|
|
29
|
+
use syno_pkg_land_sdk::ctx::{Context, CtxError};
|
|
30
|
+
use syno_pkg_land_sdk::delegate;
|
|
31
|
+
use syno_pkg_land_sdk::filestation::{Page, Service};
|
|
32
|
+
use syno_pkg_land_sdk::http::Request;
|
|
33
|
+
use syno_pkg_land_sdk::time::Timestamp;
|
|
34
|
+
use syno_pkg_land_sdk::{BoxError, jsonx};
|
|
35
|
+
|
|
36
|
+
use super::UidLookup;
|
|
37
|
+
use super::routes::decode_params;
|
|
38
|
+
|
|
39
|
+
/// How recent the worker heartbeat must be to count as running.
|
|
40
|
+
const WORKER_FRESH: Duration = Duration::from_secs(15);
|
|
41
|
+
|
|
42
|
+
/// Resolves the authenticated DSM username on every request. Unknown UIDs
|
|
43
|
+
/// fail closed for owners and submission.
|
|
44
|
+
pub fn system_uid_lookup() -> UidLookup {
|
|
45
|
+
Arc::new(delegate::lookup_uid)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// What the jobs API needs of File Station: browsing and importing one file
|
|
49
|
+
/// through the caller's own DSM session.
|
|
50
|
+
pub trait BackgroundFiles: Send + Sync {
|
|
51
|
+
fn browse(
|
|
52
|
+
&self,
|
|
53
|
+
ctx: &Context,
|
|
54
|
+
caller: &Caller,
|
|
55
|
+
req: &Request,
|
|
56
|
+
folder: &str,
|
|
57
|
+
offset: i64,
|
|
58
|
+
) -> Result<Page, BoxError>;
|
|
59
|
+
|
|
60
|
+
fn read_file(
|
|
61
|
+
&self,
|
|
62
|
+
ctx: &Context,
|
|
63
|
+
caller: &Caller,
|
|
64
|
+
req: &Request,
|
|
65
|
+
folder: &str,
|
|
66
|
+
rel: &str,
|
|
67
|
+
) -> Result<Vec<u8>, BoxError>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl BackgroundFiles for Service {
|
|
71
|
+
fn browse(
|
|
72
|
+
&self,
|
|
73
|
+
ctx: &Context,
|
|
74
|
+
caller: &Caller,
|
|
75
|
+
req: &Request,
|
|
76
|
+
folder: &str,
|
|
77
|
+
offset: i64,
|
|
78
|
+
) -> Result<Page, BoxError> {
|
|
79
|
+
Service::browse(self, ctx, caller, req, folder, offset)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn read_file(
|
|
83
|
+
&self,
|
|
84
|
+
ctx: &Context,
|
|
85
|
+
caller: &Caller,
|
|
86
|
+
req: &Request,
|
|
87
|
+
folder: &str,
|
|
88
|
+
rel: &str,
|
|
89
|
+
) -> Result<Vec<u8>, BoxError> {
|
|
90
|
+
Service::read_file(self, ctx, caller, req, folder, rel)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// A handler of one background route; the access it works under was derived
|
|
95
|
+
/// from the authenticated caller, never from params.
|
|
96
|
+
type Handler =
|
|
97
|
+
fn(&BackgroundApi, &Context, &Caller, &Request, &Access, &[u8]) -> Result<Value, BoxError>;
|
|
98
|
+
|
|
99
|
+
pub struct BackgroundApi {
|
|
100
|
+
store: Arc<Store>,
|
|
101
|
+
files: Arc<dyn BackgroundFiles>,
|
|
102
|
+
uid_lookup: UidLookup,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
impl BackgroundApi {
|
|
106
|
+
pub fn new(
|
|
107
|
+
store: Arc<Store>,
|
|
108
|
+
files: Arc<dyn BackgroundFiles>,
|
|
109
|
+
uid_lookup: UidLookup,
|
|
110
|
+
) -> Arc<Self> {
|
|
111
|
+
Arc::new(BackgroundApi {
|
|
112
|
+
store,
|
|
113
|
+
files,
|
|
114
|
+
uid_lookup,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// The production assembly: the request-bounded spool under the data
|
|
119
|
+
/// directory and the caller's own File Station session. `synopkgland init
|
|
120
|
+
/// --with jobs` wires it in through `background_routes.rs`.
|
|
121
|
+
pub fn production(data_dir: &std::path::Path, uid_lookup: UidLookup) -> Arc<Self> {
|
|
122
|
+
Self::new(
|
|
123
|
+
Arc::new(Store::new_request(data_dir.join("background"))),
|
|
124
|
+
Arc::new(Service::local(data_dir)),
|
|
125
|
+
uid_lookup,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Every request authenticates against DSM afresh. Owner routes are
|
|
130
|
+
/// available to signed-in users; schedule reapproval and owner cleanup
|
|
131
|
+
/// are admin-only.
|
|
132
|
+
pub fn routes(self: &Arc<Self>) -> Vec<Route> {
|
|
133
|
+
vec![
|
|
134
|
+
self.owner("bg.status", Self::status),
|
|
135
|
+
self.owner("bg.schedules.list", Self::schedules),
|
|
136
|
+
self.owner("bg.schedules.create", Self::create_schedule),
|
|
137
|
+
self.owner("bg.schedules.disable", Self::disable_schedule),
|
|
138
|
+
self.owner("bg.schedules.delete", Self::delete_schedule),
|
|
139
|
+
self.admin("bg.schedules.enable", Self::enable_schedule),
|
|
140
|
+
self.admin("bg.jobs.purge", Self::purge),
|
|
141
|
+
self.owner("bg.browse", Self::browse),
|
|
142
|
+
self.owner("bg.jobs.submit", Self::submit),
|
|
143
|
+
self.owner("bg.jobs.list", Self::list),
|
|
144
|
+
self.owner("bg.jobs.get", Self::get),
|
|
145
|
+
self.owner("bg.jobs.cancel", Self::cancel),
|
|
146
|
+
self.owner("bg.jobs.pause", Self::pause),
|
|
147
|
+
self.owner("bg.jobs.resume", Self::resume),
|
|
148
|
+
self.owner("bg.jobs.result", Self::result),
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fn status(
|
|
153
|
+
&self,
|
|
154
|
+
ctx: &Context,
|
|
155
|
+
_: &Caller,
|
|
156
|
+
_: &Request,
|
|
157
|
+
access: &Access,
|
|
158
|
+
params: &[u8],
|
|
159
|
+
) -> Result<Value, BoxError> {
|
|
160
|
+
decode_params::<jsonx::Empty>(params)?;
|
|
161
|
+
let st = self
|
|
162
|
+
.store
|
|
163
|
+
.status_for(ctx, access)
|
|
164
|
+
.map_err(background_error)?;
|
|
165
|
+
let worker = st.worker.as_ref().map(|w| {
|
|
166
|
+
json!({
|
|
167
|
+
"pid": w.pid, "uid": w.uid, "username": w.username,
|
|
168
|
+
"revision": w.revision, "at": w.at,
|
|
169
|
+
"alive": worker_alive(w, Timestamp::now()),
|
|
170
|
+
})
|
|
171
|
+
});
|
|
172
|
+
Ok(json!({
|
|
173
|
+
"owner": access.owner, "supported": true, "admin": access.admin,
|
|
174
|
+
"algorithm": st.algorithm, "revision": st.revision,
|
|
175
|
+
"queued": st.queued, "running": st.running, "worker": worker,
|
|
176
|
+
"limits": {
|
|
177
|
+
"maxInputBytes": MAX_INPUT_BYTES, "maxQueued": MAX_QUEUED,
|
|
178
|
+
"maxSchedules": MAX_SCHEDULES, "intervalSeconds": [3600, 86400, 604800],
|
|
179
|
+
"maxTotalBytes": MAX_TOTAL_BYTES, "maxOwnerPending": MAX_OWNER_PENDING,
|
|
180
|
+
"maxOwnerBytes": MAX_OWNER_BYTES,
|
|
181
|
+
"jobTimeoutSeconds": JOB_TIMEOUT.as_secs(),
|
|
182
|
+
},
|
|
183
|
+
}))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Lists what the caller's own File Station session can see, so the
|
|
187
|
+
/// panel can pick a file instead of typing a path. It grants nothing:
|
|
188
|
+
/// submit still reads through the same session.
|
|
189
|
+
fn browse(
|
|
190
|
+
&self,
|
|
191
|
+
ctx: &Context,
|
|
192
|
+
caller: &Caller,
|
|
193
|
+
req: &Request,
|
|
194
|
+
_: &Access,
|
|
195
|
+
params: &[u8],
|
|
196
|
+
) -> Result<Value, BoxError> {
|
|
197
|
+
#[derive(Default, Deserialize)]
|
|
198
|
+
#[serde(deny_unknown_fields)]
|
|
199
|
+
struct Params {
|
|
200
|
+
#[serde(default)]
|
|
201
|
+
folder: String,
|
|
202
|
+
#[serde(default)]
|
|
203
|
+
offset: i64,
|
|
204
|
+
}
|
|
205
|
+
let params: Params = decode_params(params)?;
|
|
206
|
+
if params.offset < 0 {
|
|
207
|
+
return Err(api::Error::bad_request("offset must not be negative").into());
|
|
208
|
+
}
|
|
209
|
+
let page = self
|
|
210
|
+
.files
|
|
211
|
+
.browse(ctx, caller, req, ¶ms.folder, params.offset)?;
|
|
212
|
+
Ok(serde_json::to_value(page)?)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
fn submit(
|
|
216
|
+
&self,
|
|
217
|
+
ctx: &Context,
|
|
218
|
+
caller: &Caller,
|
|
219
|
+
req: &Request,
|
|
220
|
+
access: &Access,
|
|
221
|
+
params: &[u8],
|
|
222
|
+
) -> Result<Value, BoxError> {
|
|
223
|
+
#[derive(Default, Deserialize)]
|
|
224
|
+
#[serde(deny_unknown_fields)]
|
|
225
|
+
struct Params {
|
|
226
|
+
#[serde(default)]
|
|
227
|
+
key: String,
|
|
228
|
+
#[serde(default)]
|
|
229
|
+
folder: String,
|
|
230
|
+
#[serde(default)]
|
|
231
|
+
relative: String,
|
|
232
|
+
}
|
|
233
|
+
let params: Params = decode_params(params)?;
|
|
234
|
+
if params.key.is_empty() || params.folder.is_empty() || params.relative.is_empty() {
|
|
235
|
+
return Err(api::Error::bad_request("key, folder and relative are required").into());
|
|
236
|
+
}
|
|
237
|
+
if !access.owner.valid() {
|
|
238
|
+
return Err(identity_unavailable().into());
|
|
239
|
+
}
|
|
240
|
+
let data = self
|
|
241
|
+
.files
|
|
242
|
+
.read_file(ctx, caller, req, ¶ms.folder, ¶ms.relative)?;
|
|
243
|
+
let (job, duplicate) = self
|
|
244
|
+
.store
|
|
245
|
+
.submit(
|
|
246
|
+
ctx,
|
|
247
|
+
SubmitRequest {
|
|
248
|
+
key: params.key,
|
|
249
|
+
name: base_name(¶ms.relative),
|
|
250
|
+
data,
|
|
251
|
+
submitter: access.owner.clone(),
|
|
252
|
+
},
|
|
253
|
+
)
|
|
254
|
+
.map_err(background_error)?;
|
|
255
|
+
Ok(json!({"job": job, "duplicate": duplicate}))
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
fn list(
|
|
259
|
+
&self,
|
|
260
|
+
ctx: &Context,
|
|
261
|
+
_: &Caller,
|
|
262
|
+
_: &Request,
|
|
263
|
+
access: &Access,
|
|
264
|
+
params: &[u8],
|
|
265
|
+
) -> Result<Value, BoxError> {
|
|
266
|
+
decode_params::<jsonx::Empty>(params)?;
|
|
267
|
+
let mut jobs = self.store.list_for(ctx, access).map_err(background_error)?;
|
|
268
|
+
jobs.reverse();
|
|
269
|
+
Ok(json!({"jobs": jobs}))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fn get(
|
|
273
|
+
&self,
|
|
274
|
+
ctx: &Context,
|
|
275
|
+
_: &Caller,
|
|
276
|
+
_: &Request,
|
|
277
|
+
access: &Access,
|
|
278
|
+
params: &[u8],
|
|
279
|
+
) -> Result<Value, BoxError> {
|
|
280
|
+
self.by_id(ctx, access, params, Store::get_for)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fn cancel(
|
|
284
|
+
&self,
|
|
285
|
+
ctx: &Context,
|
|
286
|
+
_: &Caller,
|
|
287
|
+
_: &Request,
|
|
288
|
+
access: &Access,
|
|
289
|
+
params: &[u8],
|
|
290
|
+
) -> Result<Value, BoxError> {
|
|
291
|
+
self.by_id(ctx, access, params, Store::cancel_for)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// Holds a queued job at once; a running job pauses at its next checkpoint.
|
|
295
|
+
fn pause(
|
|
296
|
+
&self,
|
|
297
|
+
ctx: &Context,
|
|
298
|
+
_: &Caller,
|
|
299
|
+
_: &Request,
|
|
300
|
+
access: &Access,
|
|
301
|
+
params: &[u8],
|
|
302
|
+
) -> Result<Value, BoxError> {
|
|
303
|
+
self.by_id(ctx, access, params, Store::pause_for)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fn resume(
|
|
307
|
+
&self,
|
|
308
|
+
ctx: &Context,
|
|
309
|
+
_: &Caller,
|
|
310
|
+
_: &Request,
|
|
311
|
+
access: &Access,
|
|
312
|
+
params: &[u8],
|
|
313
|
+
) -> Result<Value, BoxError> {
|
|
314
|
+
self.by_id(ctx, access, params, Store::resume_for)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
fn by_id(
|
|
318
|
+
&self,
|
|
319
|
+
ctx: &Context,
|
|
320
|
+
access: &Access,
|
|
321
|
+
params: &[u8],
|
|
322
|
+
act: fn(&Store, &Context, &str, &Access) -> Result<Job, BoxError>,
|
|
323
|
+
) -> Result<Value, BoxError> {
|
|
324
|
+
let id = decode_id(params)?;
|
|
325
|
+
let job = act(&self.store, ctx, &id, access).map_err(background_error)?;
|
|
326
|
+
Ok(json!({"job": job}))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fn result(
|
|
330
|
+
&self,
|
|
331
|
+
ctx: &Context,
|
|
332
|
+
_: &Caller,
|
|
333
|
+
_: &Request,
|
|
334
|
+
access: &Access,
|
|
335
|
+
params: &[u8],
|
|
336
|
+
) -> Result<Value, BoxError> {
|
|
337
|
+
let id = decode_id(params)?;
|
|
338
|
+
let job = self
|
|
339
|
+
.store
|
|
340
|
+
.get_for(ctx, &id, access)
|
|
341
|
+
.map_err(background_error)?;
|
|
342
|
+
let result = match (&job.state, &job.result) {
|
|
343
|
+
(syno_pkg_land_sdk::background::State::Succeeded, Some(result)) => result,
|
|
344
|
+
_ => return Err(api::Error::new("not_ready", "the job has no result").into()),
|
|
345
|
+
};
|
|
346
|
+
Ok(json!({"job": job, "result": {
|
|
347
|
+
"jobId": job.id, "worker": job.worker, "workerRevision": job.worker_revision,
|
|
348
|
+
"algorithm": "sha256", "sha256": result.sha256, "bytes": result.bytes,
|
|
349
|
+
"inputDigest": job.input_digest, "name": job.name,
|
|
350
|
+
}}))
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
fn decode_id(params: &[u8]) -> Result<String, api::Error> {
|
|
355
|
+
#[derive(Default, Deserialize)]
|
|
356
|
+
#[serde(deny_unknown_fields)]
|
|
357
|
+
struct IdParams {
|
|
358
|
+
#[serde(default)]
|
|
359
|
+
id: String,
|
|
360
|
+
}
|
|
361
|
+
Ok(decode_params::<IdParams>(params)?.id)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
fn identity_unavailable() -> api::Error {
|
|
365
|
+
api::Error::new("identity_unavailable", "DSM user UID could not be resolved")
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
fn worker_alive(w: &WorkerInfo, now: Timestamp) -> bool {
|
|
369
|
+
now.unix_nanos() - w.at.unix_nanos() <= WORKER_FRESH.as_nanos() as i128
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/// The last element of a slash-separated path, as Go's path.Base.
|
|
373
|
+
fn base_name(path: &str) -> String {
|
|
374
|
+
if path.is_empty() {
|
|
375
|
+
return ".".into();
|
|
376
|
+
}
|
|
377
|
+
let trimmed = path.trim_end_matches('/');
|
|
378
|
+
if trimmed.is_empty() {
|
|
379
|
+
return "/".into();
|
|
380
|
+
}
|
|
381
|
+
trimmed.rsplit('/').next().unwrap_or(trimmed).into()
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/// Maps the spool's errors to the codes the panel knows; anything else is
|
|
385
|
+
/// logged and reported as unavailable.
|
|
386
|
+
fn background_error(err: BoxError) -> BoxError {
|
|
387
|
+
let message = |code: &str, text: &str| -> BoxError { api::Error::new(code, text).into() };
|
|
388
|
+
match api::find_error::<StoreError>(&*err) {
|
|
389
|
+
Some(StoreError::Busy) => message("jobs_busy", "background jobs are busy; retry later"),
|
|
390
|
+
Some(StoreError::ScheduleFull) => {
|
|
391
|
+
message("schedule-full", "the schedule limit has been reached")
|
|
392
|
+
}
|
|
393
|
+
Some(StoreError::InputUnavailable) => {
|
|
394
|
+
message("input-unavailable", "import the private copy again")
|
|
395
|
+
}
|
|
396
|
+
Some(StoreError::OwnerQueueFull) => message(
|
|
397
|
+
"owner-queue-full",
|
|
398
|
+
"your pending job limit has been reached",
|
|
399
|
+
),
|
|
400
|
+
Some(StoreError::OwnerQuota) => message(
|
|
401
|
+
"owner-quota-exceeded",
|
|
402
|
+
"your background storage limit has been reached",
|
|
403
|
+
),
|
|
404
|
+
Some(StoreError::Invalid) => api::Error::bad_request("invalid background request").into(),
|
|
405
|
+
Some(StoreError::TooLarge) => message(
|
|
406
|
+
"limit-exceeded",
|
|
407
|
+
"the file is larger than the background limit",
|
|
408
|
+
),
|
|
409
|
+
Some(StoreError::QueueFull) => message("queue-full", "the background queue is full"),
|
|
410
|
+
Some(StoreError::Quota) => message("quota-exceeded", "the background spool is full"),
|
|
411
|
+
Some(StoreError::KeyConflict | StoreError::NotCancelable | StoreError::NotPausable) => {
|
|
412
|
+
message(
|
|
413
|
+
"conflict",
|
|
414
|
+
"the request conflicts with the job's current state",
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
Some(StoreError::NotFound) => message("not_found", "job not found"),
|
|
418
|
+
_ if api::find_error::<CtxError>(&*err).is_some() => err,
|
|
419
|
+
_ => {
|
|
420
|
+
eprintln!("background spool error: {err}");
|
|
421
|
+
message("unavailable", "the background spool is unavailable")
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|