diff --git a/.gitignore b/.gitignore
index aff9a41..c26ba5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,7 @@ target/
!/source/packages/**/*.curio
.DS_Store
/curios/*/backend/
+/curios/harmony-connector/legal/sources/
+/curios/harmony-connector/legal/*-COPYING
+/curios/harmony-connector/legal/build.mjs
+/curios/harmony-connector/legal/rust/
diff --git a/README.md b/README.md
index d99ee9a..6c23e5e 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,10 @@ Trove 官方插件库。发现喜欢的 Curio,装进你的工作台,让常
每个 Curio 专注一件事,用轻巧的独立工具承接日常操作,把更多空间留给手头的工作。
+### Harmony Connector
+
+让文件,自在往来。一根 USB 数据线,连接 Mac 与鸿蒙设备;浏览文件、收发文档,把照片与灵感带到更大的屏幕。
+
## 按你的习惯组合
按需安装,自由搭配。工具各自更新,也能通过服务相互配合,让你的工作方式逐渐长成自己的样子。
diff --git a/curios/harmony-connector/assets/icon.svg b/curios/harmony-connector/assets/icon.svg
new file mode 100644
index 0000000..26a13f3
--- /dev/null
+++ b/curios/harmony-connector/assets/icon.svg
@@ -0,0 +1 @@
+
diff --git a/curios/harmony-connector/build.mjs b/curios/harmony-connector/build.mjs
new file mode 100644
index 0000000..c8594d4
--- /dev/null
+++ b/curios/harmony-connector/build.mjs
@@ -0,0 +1,221 @@
+import {
+ readFile,
+ writeFile,
+ mkdir,
+ cp,
+ copyFile,
+ rm,
+ readdir,
+} from "node:fs/promises";
+import { createHash } from "node:crypto";
+import { spawnSync } from "node:child_process";
+import { fileURLToPath } from "node:url";
+import path from "node:path";
+
+const plugin = path.dirname(fileURLToPath(import.meta.url));
+const root = path.resolve(plugin, "../..");
+const build = path.join(plugin, "dist/native-deps");
+const prefix = path.join(build, "install");
+const sources = [
+ {
+ name: "libusb-1.0.30",
+ archive: "libusb-1.0.30.tar.bz2",
+ sha256: "fea36f34f9156400209595e300840767ab1a385ede1dc7ee893015aea9c6dbaf",
+ options: [],
+ },
+ {
+ name: "libmtp-1.1.23",
+ archive: "libmtp-1.1.23.tar.gz",
+ sha256: "74a2b6e8cb4a0304e95b995496ea3ac644c29371649b892b856e22f12a0bdeed",
+ options: ["--disable-mtpz"],
+ },
+];
+function run(command, args, cwd = plugin, env = {}) {
+ const result = spawnSync(command, args, {
+ cwd,
+ stdio: "inherit",
+ env: { ...process.env, ...env },
+ });
+ if (result.error) throw result.error;
+ if (result.status !== 0)
+ throw new Error(`${command} exited with ${result.status}`);
+}
+if (process.platform !== "darwin")
+ throw new Error("Harmony Connector native builds require macOS.");
+const env = {
+ MACOSX_DEPLOYMENT_TARGET: "13.0",
+ PKG_CONFIG_PATH: path.join(prefix, "lib/pkgconfig"),
+ HARMONY_NATIVE_PREFIX: prefix,
+};
+await mkdir(build, { recursive: true });
+await mkdir(path.join(plugin, "legal/sources"), { recursive: true });
+for (const source of sources) {
+ const archive = path.join(root, "vendor/harmony-connector", source.archive);
+ const bytes = await readFile(archive);
+ if (createHash("sha256").update(bytes).digest("hex") !== source.sha256)
+ throw new Error(`Source checksum mismatch: ${source.archive}`);
+ const directory = path.join(build, source.name);
+ const stamp = path.join(directory, ".harmony-built");
+ const identity = `${process.arch}:macos13:${source.sha256}`;
+ const previous = await readFile(stamp, "utf8").catch(() => "");
+ if (previous !== identity) {
+ await rm(directory, { recursive: true, force: true });
+ run("tar", ["-xf", archive, "-C", build]);
+ run(
+ "./configure",
+ [
+ `--prefix=${prefix}`,
+ "--disable-static",
+ "--enable-shared",
+ "--disable-dependency-tracking",
+ ...source.options,
+ ],
+ directory,
+ env,
+ );
+ run("make", ["-j4"], directory, env);
+ run("make", ["install"], directory, env);
+ await writeFile(stamp, identity);
+ }
+ await copyFile(
+ path.join(directory, "COPYING"),
+ path.join(plugin, "legal", `${source.name}-COPYING`),
+ );
+ await copyFile(archive, path.join(plugin, "legal/sources", source.archive));
+}
+if (process.argv.includes("--dependencies-only")) process.exit(0);
+run(
+ "cargo",
+ ["build", "--release", "--locked", "--manifest-path", "native/Cargo.toml"],
+ plugin,
+ env,
+);
+const backend = path.join(plugin, "backend");
+await mkdir(path.join(backend, "lib"), { recursive: true });
+const executable = path.join(backend, "harmony-connector");
+await copyFile(
+ path.join(plugin, "native/target/release/harmony-connector"),
+ executable,
+);
+const libraries = ["libmtp.9.dylib", "libusb-1.0.0.dylib"];
+for (const library of libraries)
+ await copyFile(
+ path.join(prefix, "lib", library),
+ path.join(backend, "lib", library),
+ );
+for (const file of [
+ executable,
+ ...libraries.map((name) => path.join(backend, "lib", name)),
+]) {
+ const isExecutable = file === executable;
+ const listing = spawnSync("otool", ["-L", file], { encoding: "utf8" });
+ if (listing.status !== 0)
+ throw new Error("Cannot inspect Mach-O dependencies");
+ for (const line of listing.stdout.split("\n").slice(1)) {
+ const dependency = line.trim().split(" (")[0];
+ if (libraries.includes(path.basename(dependency)) && dependency !== file) {
+ run("install_name_tool", [
+ "-change",
+ dependency,
+ `${isExecutable ? "@executable_path/lib" : "@loader_path"}/${path.basename(dependency)}`,
+ file,
+ ]);
+ } else if (
+ dependency &&
+ !dependency.startsWith("/System/") &&
+ !dependency.startsWith("/usr/lib/")
+ ) {
+ throw new Error(`Unbundled native dependency: ${dependency}`);
+ }
+ }
+ if (!isExecutable)
+ run("install_name_tool", [
+ "-id",
+ `@loader_path/${path.basename(file)}`,
+ file,
+ ]);
+ run("codesign", ["--force", "--sign", "-", file]);
+}
+const ui = path.join(plugin, "dist/ui");
+await rm(ui, { recursive: true, force: true });
+await cp(path.join(plugin, "ui"), ui, {
+ recursive: true,
+ filter: (source) => !source.endsWith(".test.js"),
+});
+await cp(
+ path.dirname(fileURLToPath(import.meta.resolve("@trove/plugin-sdk"))),
+ path.join(ui, "sdk"),
+ { recursive: true },
+);
+await copyFile(
+ path.join(plugin, "build.mjs"),
+ path.join(plugin, "legal", "build.mjs"),
+);
+const metadata = spawnSync(
+ "cargo",
+ [
+ "metadata",
+ "--locked",
+ "--format-version",
+ "1",
+ "--filter-platform",
+ `${process.arch === "arm64" ? "aarch64" : "x86_64"}-apple-darwin`,
+ "--manifest-path",
+ "native/Cargo.toml",
+ ],
+ { cwd: plugin, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 },
+);
+if (metadata.status !== 0)
+ throw new Error("Cannot collect Rust dependency notices");
+const graph = JSON.parse(metadata.stdout);
+const manifest = JSON.parse(
+ await readFile(path.join(plugin, "curio.json"), "utf8"),
+);
+if (
+ graph.packages.find((pkg) => pkg.id === graph.resolve.root)?.version !==
+ manifest.version
+)
+ throw new Error(
+ "curio.json and native/Cargo.toml must declare the same version",
+ );
+await rm(path.join(plugin, "legal/rust"), { recursive: true, force: true });
+for (const dependency of graph.packages.filter((pkg) => pkg.source)) {
+ const directory = path.dirname(dependency.manifest_path);
+ const destination = path.join(
+ plugin,
+ "legal/rust",
+ `${dependency.name}-${dependency.version}`,
+ );
+ await mkdir(destination, { recursive: true });
+ await writeFile(
+ path.join(destination, "package.json"),
+ JSON.stringify(
+ {
+ name: dependency.name,
+ version: dependency.version,
+ license: dependency.license,
+ repository: dependency.repository,
+ },
+ null,
+ 2,
+ ) + "\n",
+ );
+ let noticeCount = 0;
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
+ if (
+ entry.isFile() &&
+ /^(license|licence|copying|copyright|notice)([.-]|$)/i.test(entry.name)
+ ) {
+ await copyFile(
+ path.join(directory, entry.name),
+ path.join(destination, entry.name),
+ );
+ noticeCount++;
+ }
+ }
+ if (!noticeCount)
+ throw new Error(`Missing license notice: ${dependency.name}`);
+}
+console.log(
+ `Built Harmony Connector for ${process.arch}; USB libraries and their corresponding source are bundled.`,
+);
diff --git a/curios/harmony-connector/curio.json b/curios/harmony-connector/curio.json
new file mode 100644
index 0000000..cf3d0dd
--- /dev/null
+++ b/curios/harmony-connector/curio.json
@@ -0,0 +1,21 @@
+{
+ "schemaVersion": 1,
+ "id": "dev.trove.harmony-connector",
+ "name": "Harmony Connector",
+ "description": "通过 USB 连接鸿蒙设备,在 Mac 与手机之间浏览、发送和接收文件。",
+ "version": "0.1.0",
+ "engine": { "trove": ">=0.1.0 <1.0.0" },
+ "icon": "assets/icon.svg",
+ "ui": {
+ "entry": "dist/ui/index.html",
+ "window": { "title": "Harmony Connector", "width": 1080, "height": 760, "minWidth": 760, "minHeight": 580, "resizable": true }
+ },
+ "backend": {
+ "entry": "backend/harmony-connector",
+ "lifecycle": "on-demand", "transport": "unix", "startWithUi": true,
+ "startupTimeoutMs": 5000, "shutdownGraceMs": 5000, "idleTimeoutMs": 0
+ },
+ "services": { "requires": [{ "id": "host.dialog", "version": "^1.0.0" }] },
+ "build": { "command": ["node", "build.mjs"] },
+ "package": { "files": ["dist/ui", "backend", "assets", "legal"] }
+}
diff --git a/curios/harmony-connector/legal/THIRD-PARTY.md b/curios/harmony-connector/legal/THIRD-PARTY.md
new file mode 100644
index 0000000..c8c2921
--- /dev/null
+++ b/curios/harmony-connector/legal/THIRD-PARTY.md
@@ -0,0 +1,54 @@
+# Harmony Connector — Third-party software
+
+Harmony Connector dynamically links the following libraries. This distribution
+includes their original license texts and complete, unmodified source archives.
+
+| Library | Version | Source | License text in this package |
+| --- | --- | --- | --- |
+| libmtp | 1.1.23 | https://github.com/libmtp/libmtp | `libmtp-1.1.23-COPYING` |
+| libusb | 1.0.30 | https://github.com/libusb/libusb | `libusb-1.0.30-COPYING` |
+
+Copyright belongs to the authors identified in the corresponding source files.
+libmtp's distributed COPYING is LGPL 2.1; individual source headers retain their
+original version 2 or later notices. libusb is LGPL 2.1 or later. These notices
+do not replace the original terms. The source archives are in `sources/`.
+
+The native executable also uses Rust crates listed in `rust/`, with the license,
+copyright and notice files provided by each crate. Trove's UI SDK is distributed
+from the versioned SDK archive in the source repository.
+
+## Building the distributed libraries
+
+The exact build commands and SHA-256 pins are in the accompanying `build.mjs`.
+The source repository contains the same script at
+`curios/harmony-connector/build.mjs` and archives under `vendor/harmony-connector/`.
+Requires macOS, Xcode Command Line Tools, make and pkg-config. Libraries are
+compiled with deployment target macOS 13.0, shared linkage, no static library,
+and libmtp's MTPZ support disabled. No changes are made to the upstream sources.
+Mach-O install names are adjusted after compilation; libraries are ad-hoc signed.
+
+From the source repository, run:
+
+```sh
+node curios/harmony-connector/build.mjs --dependencies-only
+```
+
+The resulting libraries are under
+`curios/harmony-connector/dist/native-deps/install/lib/`.
+
+## Replacing or modifying the libraries
+
+You may modify and replace these libraries with ABI-compatible versions. No
+restriction is imposed on reverse engineering needed to debug such modifications.
+Copy compatible libraries to the plugin's `backend/lib/`, retaining their names
+`libmtp.9.dylib` and `libusb-1.0.0.dylib`. Use `install_name_tool` to set the library
+IDs and inter-library references to `@loader_path/` as the build
+script does. Ad-hoc sign each changed Mach-O using `codesign --force --sign -`.
+
+Use the source project and Trove's local development/install workflow for modified
+packages. Change the plugin version before packing a modified release: published
+source artifacts are immutable, and changing their checksum in place is not the
+way to install a modified library. The executable has no library validation or
+publisher signature restriction that prevents loading a compatible replacement.
+
+The LGPL libraries are provided without warranty, as detailed in their licenses.
diff --git a/curios/harmony-connector/native/Cargo.lock b/curios/harmony-connector/native/Cargo.lock
new file mode 100644
index 0000000..f260b91
--- /dev/null
+++ b/curios/harmony-connector/native/Cargo.lock
@@ -0,0 +1,242 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "bitflags"
+version = "2.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
+
+[[package]]
+name = "cc"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+]
+
+[[package]]
+name = "harmony-connector"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "cc",
+ "serde",
+ "serde_json",
+ "tempfile",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "syn"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/curios/harmony-connector/native/Cargo.toml b/curios/harmony-connector/native/Cargo.toml
new file mode 100644
index 0000000..babba13
--- /dev/null
+++ b/curios/harmony-connector/native/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "harmony-connector"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+anyhow = "1"
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+tempfile = "3"
+
+[build-dependencies]
+cc = "1"
diff --git a/curios/harmony-connector/native/build.rs b/curios/harmony-connector/native/build.rs
new file mode 100644
index 0000000..61a861a
--- /dev/null
+++ b/curios/harmony-connector/native/build.rs
@@ -0,0 +1,19 @@
+use std::{env, path::PathBuf};
+fn main() {
+ let prefix = PathBuf::from(
+ env::var("HARMONY_NATIVE_PREFIX")
+ .expect("Build native dependencies with node ../build.mjs first"),
+ );
+ cc::Build::new()
+ .file("src/mtp_bridge.c")
+ .include(prefix.join("include"))
+ .warnings(true)
+ .compile("harmony_mtp_bridge");
+ println!(
+ "cargo:rustc-link-search=native={}",
+ prefix.join("lib").display()
+ );
+ println!("cargo:rustc-link-lib=dylib=mtp");
+ println!("cargo:rerun-if-env-changed=HARMONY_NATIVE_PREFIX");
+ println!("cargo:rerun-if-changed=src/mtp_bridge.c");
+}
diff --git a/curios/harmony-connector/native/src/connector.rs b/curios/harmony-connector/native/src/connector.rs
new file mode 100644
index 0000000..7e798c9
--- /dev/null
+++ b/curios/harmony-connector/native/src/connector.rs
@@ -0,0 +1,456 @@
+use crate::{
+ model::*,
+ mtp::{self, DeviceIo, NativeDevice, ROOT},
+};
+use serde::{de::DeserializeOwned, Deserialize};
+use serde_json::{json, Value};
+use std::{
+ fs::File,
+ path::{Component, Path},
+ sync::{
+ atomic::{AtomicBool, AtomicU64, Ordering},
+ Arc, Mutex,
+ },
+ thread,
+};
+
+struct Gate(Arc);
+impl Drop for Gate {
+ fn drop(&mut self) {
+ self.0.store(false, Ordering::Release);
+ }
+}
+struct Session {
+ device: Box,
+}
+struct Job {
+ id: u64,
+ progress: Arc