Skip to content

Commit 5d5e702

Browse files
committed
feat: update changelog for version 0.1.5, fix cache bug, migrate single-user API to UM, and add read/write operations
1 parent 300c858 commit 5d5e702

5 files changed

Lines changed: 134 additions & 72 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ This release includes the following improvements:
2828

2929
- Added support for SSH ProxyJump.
3030
- Improved Execution of commands.
31+
32+
# Version 0.1.5 (2025-09-03)
33+
34+
- Fixed a bug in the cache system that caused incorrect behavior when the cache file was missing or corrupted.
35+
- Migrate single-user API to UM.
36+
- Add `write` and `read` operations.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dv4lua"
3-
version = "0.1.4"
3+
version = "0.1.5"
44
edition = "2024"
55
description = "a lua-based command line tool that provides abstract user (device) interoperability"
66
authors = ["km0e <kmdr.error@gmail.com>"]

src/main.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
#![allow(clippy::await_holding_refcell_ref)]
2-
use std::collections::HashMap;
3-
42
use clap::Parser;
53

64
use dv_wrap::{Context, MultiCache, TermInteractor};
@@ -23,13 +21,11 @@ async fn main() -> Result<(), mlua::Error> {
2321

2422
let mut cache = MultiCache::default();
2523
cache.add_sqlite(dbpath);
26-
let ctx = Context {
27-
dry_run: args.dry_run,
24+
let ctx = Context::new(
25+
args.dry_run,
2826
cache,
29-
interactor: TermInteractor::new().expect("Failed to create interactor"),
30-
users: HashMap::new(),
31-
devices: HashMap::new(),
32-
};
27+
TermInteractor::new().expect("Failed to create interactor"),
28+
);
3329

3430
let ctx = multi::register(ctx)?;
3531

src/multi/op.rs

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
#![allow(clippy::await_holding_refcell_ref)]
22

33
use super::dev::*;
4-
use dv_api::process::ScriptExecutor;
54
use dv_wrap::ops;
65
use futures::{StreamExt, TryStreamExt, stream};
7-
use mlua::{Error as LuaError, FromLua, Function, LuaSerdeExt, Value};
6+
use mlua::{FromLua, Function, LuaSerdeExt, Value};
87

98
use crate::util::conversion_error;
109

@@ -34,24 +33,6 @@ impl FromLua for SyncPath {
3433
}
3534
}
3635

37-
#[derive(serde::Deserialize)]
38-
struct ExecOptions {
39-
reply: bool,
40-
etor: Option<ScriptExecutor>,
41-
}
42-
43-
impl FromLua for ExecOptions {
44-
fn from_lua(value: Value, lua: &mlua::Lua) -> mlua::Result<Self> {
45-
if let Some(b) = value.as_boolean() {
46-
return Ok(ExecOptions {
47-
reply: b,
48-
etor: None,
49-
});
50-
}
51-
lua.from_value(value)
52-
}
53-
}
54-
5536
impl UserData for Op {
5637
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
5738
methods.add_async_method(
@@ -90,14 +71,6 @@ impl UserData for Op {
9071
},
9172
);
9273

93-
methods.add_async_method(
94-
"exec",
95-
|_, this, (uid, commands, opt): (String, String, ExecOptions)| async move {
96-
let ctx = this.ctx.ctx();
97-
external_error(ops::exec(&ctx, &uid, &commands, opt.reply, opt.etor)).await
98-
},
99-
);
100-
10174
methods.add_async_method(
10275
"once",
10376
|_, this, (id, key, f): (String, String, Function)| async move {
@@ -121,19 +94,11 @@ impl UserData for Op {
12194
external_error(ops::refresh(&this.ctx.ctx(), &id, &key)).await
12295
},
12396
);
124-
125-
methods.add_async_method("os", |_, this, uid: String| async move {
126-
let ctx = this.ctx.ctx();
127-
let user = ctx.get_user(&uid).map_err(LuaError::external)?;
128-
Ok::<_, LuaError>(user.os().to_string())
129-
});
13097
}
13198
}
13299

133100
#[cfg(test)]
134101
mod tests {
135-
use super::ExecOptions;
136-
use dv_api::process::ScriptExecutor;
137102
use mlua::FromLua;
138103

139104
fn sync_path_des_suc_f(s: &str) -> Result<super::SyncPath, mlua::Error> {
@@ -160,28 +125,4 @@ mod tests {
160125
_ => panic!("Expected Multiple variant"),
161126
}
162127
}
163-
164-
fn exec_options_des_suc_f(s: &str) -> ExecOptions {
165-
let lua = mlua::Lua::new();
166-
let val = lua.load(s).eval::<mlua::Value>().expect("Failed to load");
167-
ExecOptions::from_lua(val, &lua).expect("Failed to deserialize")
168-
}
169-
#[test]
170-
fn exec_options_serde() {
171-
let opt = exec_options_des_suc_f("true");
172-
assert!(opt.reply);
173-
assert!(opt.etor.is_none());
174-
175-
let opt = exec_options_des_suc_f("{reply = false}");
176-
assert!(!opt.reply);
177-
assert!(opt.etor.is_none());
178-
179-
let opt = exec_options_des_suc_f("{reply = true, etor = 'sh'}");
180-
assert!(opt.reply);
181-
assert_eq!(opt.etor, Some(ScriptExecutor::Sh));
182-
183-
let opt = exec_options_des_suc_f("{reply = false, etor = 'bash'}");
184-
assert!(!opt.reply);
185-
assert_eq!(opt.etor, Some(ScriptExecutor::Bash));
186-
}
187128
}

src/multi/user.rs

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,94 @@
11
#![allow(clippy::await_holding_refcell_ref)]
2+
23
use super::dev::*;
4+
use dv_api::process::ScriptExecutor;
35
use dv_wrap::User;
4-
use mlua::{Table, Value};
6+
use dv_wrap::ops;
7+
use mlua::{FromLua, LuaSerdeExt, Table, Value};
8+
use tracing::debug;
9+
10+
pub struct UserWrapper {
11+
ctx: ContextWrapper,
12+
uid: String,
13+
}
14+
15+
impl UserWrapper {
16+
pub fn new(ctx: ContextWrapper, uid: String) -> Self {
17+
Self { ctx, uid }
18+
}
19+
}
20+
21+
#[derive(serde::Deserialize)]
22+
struct ExecOptions {
23+
reply: bool,
24+
etor: Option<ScriptExecutor>,
25+
}
26+
27+
impl FromLua for ExecOptions {
28+
fn from_lua(value: Value, lua: &mlua::Lua) -> mlua::Result<Self> {
29+
if let Some(b) = value.as_boolean() {
30+
return Ok(ExecOptions {
31+
reply: b,
32+
etor: None,
33+
});
34+
}
35+
lua.from_value(value)
36+
}
37+
}
38+
impl UserData for UserWrapper {
39+
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
40+
methods.add_async_method(
41+
"exec",
42+
|_, this, (commands, opt): (String, ExecOptions)| async move {
43+
let ctx = this.ctx.ctx();
44+
external_error(ops::exec(&ctx, &this.uid, &commands, opt.reply, opt.etor)).await
45+
},
46+
);
47+
methods.add_async_method(
48+
"write",
49+
|_, this, (path, content): (String, String)| async move {
50+
let ctx = this.ctx.ctx();
51+
external_error(ops::write(&ctx, &this.uid, &path, &content)).await
52+
},
53+
);
54+
methods.add_async_method("read", |_, this, path: String| async move {
55+
let ctx = this.ctx.ctx();
56+
external_error(ops::read(&ctx, &this.uid, &path)).await
57+
});
58+
methods.add_meta_method(mlua::MetaMethod::Index, |_, this, key: String| {
59+
let ctx = this.ctx.ctx();
60+
let user = ctx.get_user(&this.uid).expect("User must exist");
61+
if let Some(value) = user.vars.get(&key) {
62+
return Ok(Some(value.clone()));
63+
}
64+
Ok(None)
65+
});
66+
}
67+
}
568

669
pub struct UserManager {
770
ctx: ContextWrapper,
871
}
72+
973
impl UserManager {
1074
pub fn new(ctx: ContextWrapper) -> Self {
1175
Self { ctx }
1276
}
1377
}
1478

79+
impl std::ops::Deref for UserManager {
80+
type Target = ContextWrapper;
81+
fn deref(&self) -> &Self::Target {
82+
&self.ctx
83+
}
84+
}
85+
86+
impl std::ops::DerefMut for UserManager {
87+
fn deref_mut(&mut self) -> &mut Self::Target {
88+
&mut self.ctx
89+
}
90+
}
91+
1592
impl UserData for UserManager {
1693
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
1794
fn add_user_prepare(obj: Table) -> mlua::Result<dv_api::multi::Config> {
@@ -33,7 +110,7 @@ impl UserData for UserManager {
33110
methods.add_async_method_mut("add_cur", |_, this, obj: Table| async move {
34111
let mut cfg = add_user_prepare(obj)?;
35112
external_error(async {
36-
let mut ctx = this.ctx.ctx_mut();
113+
let mut ctx = this.ctx_mut();
37114
if ctx.contains_user("cur") {
38115
dv_api::whatever!("user cur already exists");
39116
}
@@ -48,7 +125,7 @@ impl UserData for UserManager {
48125
|_, this, (uid, obj): (String, Table)| async move {
49126
let mut cfg = add_user_prepare(obj)?;
50127
external_error(async {
51-
let mut ctx = this.ctx.ctx_mut();
128+
let mut ctx = this.ctx_mut();
52129
if ctx.contains_user(&uid) {
53130
dv_api::whatever!("user {uid} already exists");
54131
}
@@ -58,5 +135,47 @@ impl UserData for UserManager {
58135
.await
59136
},
60137
);
138+
139+
methods.add_meta_method(
140+
mlua::MetaMethod::Index,
141+
|_, this, key: String| -> mlua::Result<Option<UserWrapper>> {
142+
debug!("Accessing user: {}", key);
143+
if !this.ctx().contains_user(&key) {
144+
return Ok(None);
145+
}
146+
Ok(Some(UserWrapper::new(this.ctx.clone(), key)))
147+
},
148+
);
149+
}
150+
}
151+
152+
#[cfg(test)]
153+
mod tests {
154+
use super::ExecOptions;
155+
use dv_api::process::ScriptExecutor;
156+
use mlua::FromLua;
157+
158+
fn exec_options_des_suc_f(s: &str) -> ExecOptions {
159+
let lua = mlua::Lua::new();
160+
let val = lua.load(s).eval::<mlua::Value>().expect("Failed to load");
161+
ExecOptions::from_lua(val, &lua).expect("Failed to deserialize")
162+
}
163+
#[test]
164+
fn exec_options_serde() {
165+
let opt = exec_options_des_suc_f("true");
166+
assert!(opt.reply);
167+
assert!(opt.etor.is_none());
168+
169+
let opt = exec_options_des_suc_f("{reply = false}");
170+
assert!(!opt.reply);
171+
assert!(opt.etor.is_none());
172+
173+
let opt = exec_options_des_suc_f("{reply = true, etor = 'sh'}");
174+
assert!(opt.reply);
175+
assert_eq!(opt.etor, Some(ScriptExecutor::Sh));
176+
177+
let opt = exec_options_des_suc_f("{reply = false, etor = 'bash'}");
178+
assert!(!opt.reply);
179+
assert_eq!(opt.etor, Some(ScriptExecutor::Bash));
61180
}
62181
}

0 commit comments

Comments
 (0)