Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ members = [
# 配置包(.twconfig):导出、导入、本机备份与同步共用的格式,以及把一份配置拆成
# 项目与合并单位的模型
"crates/tw-pack",
# 多设备同步:同步位置的存储(文件夹、WebDAV、S3)、keyring、三方合并
"crates/tw-sync",
# 控制通道的握手与加密。桌面端按 tag 依赖它,所以只依赖契约层和 tw-yaml
"crates/tw-link",
# ── bin ──────────────────────────────────────────────
Expand Down Expand Up @@ -80,6 +82,7 @@ tw-bedrock = { path = "crates/tw-bedrock" }
tw-link = { path = "crates/tw-link" }
tw-plugin = { path = "crates/tw-plugin" }
tw-pack = { path = "crates/tw-pack" }
tw-sync = { path = "crates/tw-sync" }

# ── 版本与企业版 workspace 对齐,便于反向依赖时不打架 ────
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "time", "sync", "io-util", "signal"] }
Expand Down Expand Up @@ -146,6 +149,10 @@ aws-credential-types = "1"
aws-smithy-eventstream = "0.60"
aws-smithy-types = "1"
percent-encoding = "2"
# 地址解析(同步位置的 `http://` 只给内网)。reqwest 本来就依赖它
url = "2"
# WebDAV 的 PROPFIND、S3 的 ListObjectsV2 回的是 XML
quick-xml = "0.42"

thiserror = "2"
anyhow = "1"
Expand Down
2 changes: 2 additions & 0 deletions bin/twcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ tw-observe = { workspace = true }
tw-store = { workspace = true }
tw-pricing = { workspace = true }
chrono = { workspace = true }
# `twcore sync` 和控制面说 JSON
serde_json = { workspace = true }
tw-secret = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true }
Expand Down
36 changes: 23 additions & 13 deletions bin/twcore/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ pub fn run(
data: Option<String>,
out: Option<PathBuf>,
) -> Result<()> {
let (status, body) = request(config, method, endpoint, data)?;
match out {
Some(file) => {
std::fs::write(&file, &body).with_context(|| format!("writing {}", file.display()))?;
println!("{status}");
}
None => {
use std::io::Write;
std::io::stdout().write_all(&body)?;
}
}
Ok(())
}

/// 发一个请求,拿回状态码和响应体。`twcore sync` 这类命令也走它
pub fn request(
config: &Path,
method: &str,
endpoint: &str,
data: Option<String>,
) -> Result<(u16, Bytes)> {
let dir = config
.parent()
.map(PathBuf::from)
Expand All @@ -36,7 +57,7 @@ pub fn run(
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
let (status, body) = rt.block_on(async move {
rt.block_on(async move {
match Address::in_dir(&dir) {
#[cfg(unix)]
Address::Socket(p) => {
Expand All @@ -59,18 +80,7 @@ pub fn run(
send(s, &key, method, &endpoint, data).await
}
}
})?;
match out {
Some(file) => {
std::fs::write(&file, &body).with_context(|| format!("writing {}", file.display()))?;
println!("{status}");
}
None => {
use std::io::Write;
std::io::stdout().write_all(&body)?;
}
}
Ok(())
})
}

async fn send<S>(
Expand Down
34 changes: 34 additions & 0 deletions bin/twcore/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod call;
mod lockfile;
mod pack_cli;
mod proc;
mod sync_cmd;
mod upgrade;

use lockfile::{LockFile, LockOutcome};
Expand Down Expand Up @@ -138,6 +139,13 @@ enum Command {
#[command(subcommand)]
what: BackupCmd,
},
/// Show or change multi-device sync. Talks to the running core
//
// 给没有界面的服务器(远程 core)用:和界面走同一套控制面端点
Sync {
#[command(subcommand)]
what: SyncCmd,
},
/// Replace this twcore with another release from GitHub; the configuration is left alone
//
// 只给单独装的 twcore 用(服务器上)。桌面应用包里的那份由应用更新,见 upgrade.rs
Expand All @@ -154,6 +162,22 @@ enum Command {
},
}

#[derive(Subcommand)]
enum SyncCmd {
/// Print the sync state, the location and the devices
Status,
/// Sync once now
Now,
/// Turn sync on; asks for the location and the passphrase
Enable,
/// Turn sync off; this device keeps its configuration
Disable {
/// Also delete every version, device record and the keyring at the sync location
#[arg(long)]
purge: bool,
},
}

#[derive(Subcommand)]
enum RemoteCmd {
/// Print whether the remote control port is open and where to connect
Expand Down Expand Up @@ -260,6 +284,12 @@ fn main() -> Result<()> {
data,
out,
} => call::run(&path, &endpoint, &method, data, out),
Command::Sync { what } => match what {
SyncCmd::Status => sync_cmd::status(&path),
SyncCmd::Now => sync_cmd::now(&path),
SyncCmd::Enable => sync_cmd::enable(&path),
SyncCmd::Disable { purge } => sync_cmd::disable(&path, purge),
},
Command::Upgrade {
check,
restart,
Expand Down Expand Up @@ -964,6 +994,8 @@ fn cmd_serve(path: &Path, port: Option<u16>, safe: bool, parent: Option<u32>) ->
// 这是一种(另一种是上面的默认插件)—— 理由是服务器换发新 refresh
// token 的那一刻旧的就作废了,不写回等于让配置文件从那一秒起就是坏的。
tw_control::rotation::spawn(control.clone());
// 多设备同步:启动时一轮,之后按本机改动、定时、文件夹变化、唤醒。安全模式下不自动同步
let sync = tw_control::sync::start(&control, safe);
// 定期刷新默认价目表(`pricing.auto_update`,默认开)
tw_control::pricing::spawn(control.clone());

Expand Down Expand Up @@ -1032,6 +1064,8 @@ fn cmd_serve(path: &Path, port: Option<u16>, safe: bool, parent: Option<u32>) ->
}
_ = shutdown_requested(&shutdown) => {
tracing::info!("got a shutdown signal");
// 还没上传的本机改动,退出前上传(等不了太久:没传成的下次启动时传)
sync.flush().await;
Ok(())
}
}
Expand Down
Loading
Loading