ducpy 3.0.5__tar.gz → 3.1.0__tar.gz
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.
- {ducpy-3.0.5 → ducpy-3.1.0}/Cargo.lock +42 -42
- {ducpy-3.0.5 → ducpy-3.1.0}/PKG-INFO +1 -1
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducpy/crate/Cargo.toml +1 -1
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/build.rs +56 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/db/bootstrap.rs +46 -1
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/parse.rs +230 -43
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/serialize.rs +30 -6
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/types.rs +34 -21
- {ducpy-3.0.5 → ducpy-3.1.0}/Cargo.toml +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/LICENSE +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/README.md +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducpy/crate/src/lib.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/.gitignore +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/Cargo.toml +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/LICENSE +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/README.md +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/package.json +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/release.config.cjs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/api/document.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/api/meta.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/api/mod.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/api/version_control.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/db/mod.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/db/native.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/db/wasm.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/src/lib.rs +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/packages/ducrs/tests/.gitignore +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/pyproject.toml +0 -0
- {ducpy-3.0.5 → ducpy-3.1.0}/src/ducpy_native/__init__.py +0 -0
|
@@ -288,9 +288,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
|
288
288
|
|
|
289
289
|
[[package]]
|
|
290
290
|
name = "crypto-common"
|
|
291
|
-
version = "0.1.
|
|
291
|
+
version = "0.1.7"
|
|
292
292
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
293
|
-
checksum = "
|
|
293
|
+
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
|
294
294
|
dependencies = [
|
|
295
295
|
"generic-array",
|
|
296
296
|
"typenum",
|
|
@@ -318,9 +318,9 @@ dependencies = [
|
|
|
318
318
|
|
|
319
319
|
[[package]]
|
|
320
320
|
name = "deranged"
|
|
321
|
-
version = "0.5.
|
|
321
|
+
version = "0.5.8"
|
|
322
322
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
323
|
-
checksum = "
|
|
323
|
+
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
|
324
324
|
dependencies = [
|
|
325
325
|
"powerfmt",
|
|
326
326
|
]
|
|
@@ -411,7 +411,7 @@ dependencies = [
|
|
|
411
411
|
|
|
412
412
|
[[package]]
|
|
413
413
|
name = "ducpy-native"
|
|
414
|
-
version = "3.0
|
|
414
|
+
version = "3.1.0"
|
|
415
415
|
dependencies = [
|
|
416
416
|
"duc",
|
|
417
417
|
"pyo3",
|
|
@@ -579,9 +579,9 @@ checksum = "4e42f72dc65ff480af54c73c63d4b425936aac4167a01b7c9e1ed15aacace22c"
|
|
|
579
579
|
|
|
580
580
|
[[package]]
|
|
581
581
|
name = "generic-array"
|
|
582
|
-
version = "0.14.
|
|
582
|
+
version = "0.14.7"
|
|
583
583
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
584
|
-
checksum = "
|
|
584
|
+
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|
585
585
|
dependencies = [
|
|
586
586
|
"typenum",
|
|
587
587
|
"version_check",
|
|
@@ -713,9 +713,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
|
|
713
713
|
|
|
714
714
|
[[package]]
|
|
715
715
|
name = "hipdf"
|
|
716
|
-
version = "1.3.
|
|
716
|
+
version = "1.3.5"
|
|
717
717
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
718
|
-
checksum = "
|
|
718
|
+
checksum = "e3b986659f7ff291144addff668450799e4c2cd69686601cd48dfe23d7afc316"
|
|
719
719
|
dependencies = [
|
|
720
720
|
"jpeg-decoder",
|
|
721
721
|
"lopdf",
|
|
@@ -880,24 +880,24 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
|
880
880
|
|
|
881
881
|
[[package]]
|
|
882
882
|
name = "jiff"
|
|
883
|
-
version = "0.2.
|
|
883
|
+
version = "0.2.23"
|
|
884
884
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
885
|
-
checksum = "
|
|
885
|
+
checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359"
|
|
886
886
|
dependencies = [
|
|
887
887
|
"jiff-static",
|
|
888
888
|
"jiff-tzdb-platform",
|
|
889
889
|
"log",
|
|
890
890
|
"portable-atomic",
|
|
891
891
|
"portable-atomic-util",
|
|
892
|
-
"
|
|
893
|
-
"windows-sys 0.
|
|
892
|
+
"serde_core",
|
|
893
|
+
"windows-sys 0.61.2",
|
|
894
894
|
]
|
|
895
895
|
|
|
896
896
|
[[package]]
|
|
897
897
|
name = "jiff-static"
|
|
898
|
-
version = "0.2.
|
|
898
|
+
version = "0.2.23"
|
|
899
899
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
900
|
-
checksum = "
|
|
900
|
+
checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4"
|
|
901
901
|
dependencies = [
|
|
902
902
|
"proc-macro2",
|
|
903
903
|
"quote",
|
|
@@ -906,9 +906,9 @@ dependencies = [
|
|
|
906
906
|
|
|
907
907
|
[[package]]
|
|
908
908
|
name = "jiff-tzdb"
|
|
909
|
-
version = "0.1.
|
|
909
|
+
version = "0.1.6"
|
|
910
910
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
911
|
-
checksum = "
|
|
911
|
+
checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076"
|
|
912
912
|
|
|
913
913
|
[[package]]
|
|
914
914
|
name = "jiff-tzdb-platform"
|
|
@@ -1166,9 +1166,9 @@ dependencies = [
|
|
|
1166
1166
|
|
|
1167
1167
|
[[package]]
|
|
1168
1168
|
name = "num-conv"
|
|
1169
|
-
version = "0.
|
|
1169
|
+
version = "0.2.0"
|
|
1170
1170
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1171
|
-
checksum = "
|
|
1171
|
+
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
|
1172
1172
|
|
|
1173
1173
|
[[package]]
|
|
1174
1174
|
name = "num-traits"
|
|
@@ -1322,9 +1322,9 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
|
|
1322
1322
|
|
|
1323
1323
|
[[package]]
|
|
1324
1324
|
name = "portable-atomic-util"
|
|
1325
|
-
version = "0.2.
|
|
1325
|
+
version = "0.2.5"
|
|
1326
1326
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1327
|
-
checksum = "
|
|
1327
|
+
checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5"
|
|
1328
1328
|
dependencies = [
|
|
1329
1329
|
"portable-atomic",
|
|
1330
1330
|
]
|
|
@@ -1478,18 +1478,18 @@ dependencies = [
|
|
|
1478
1478
|
|
|
1479
1479
|
[[package]]
|
|
1480
1480
|
name = "rand_core"
|
|
1481
|
-
version = "0.9.
|
|
1481
|
+
version = "0.9.5"
|
|
1482
1482
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1483
|
-
checksum = "
|
|
1483
|
+
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
|
1484
1484
|
dependencies = [
|
|
1485
1485
|
"getrandom",
|
|
1486
1486
|
]
|
|
1487
1487
|
|
|
1488
1488
|
[[package]]
|
|
1489
1489
|
name = "rangemap"
|
|
1490
|
-
version = "1.
|
|
1490
|
+
version = "1.7.1"
|
|
1491
1491
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1492
|
-
checksum = "
|
|
1492
|
+
checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68"
|
|
1493
1493
|
|
|
1494
1494
|
[[package]]
|
|
1495
1495
|
name = "rayon"
|
|
@@ -1978,30 +1978,30 @@ dependencies = [
|
|
|
1978
1978
|
|
|
1979
1979
|
[[package]]
|
|
1980
1980
|
name = "time"
|
|
1981
|
-
version = "0.3.
|
|
1981
|
+
version = "0.3.47"
|
|
1982
1982
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1983
|
-
checksum = "
|
|
1983
|
+
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
|
1984
1984
|
dependencies = [
|
|
1985
1985
|
"deranged",
|
|
1986
1986
|
"itoa",
|
|
1987
1987
|
"num-conv",
|
|
1988
1988
|
"powerfmt",
|
|
1989
|
-
"
|
|
1989
|
+
"serde_core",
|
|
1990
1990
|
"time-core",
|
|
1991
1991
|
"time-macros",
|
|
1992
1992
|
]
|
|
1993
1993
|
|
|
1994
1994
|
[[package]]
|
|
1995
1995
|
name = "time-core"
|
|
1996
|
-
version = "0.1.
|
|
1996
|
+
version = "0.1.8"
|
|
1997
1997
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1998
|
-
checksum = "
|
|
1998
|
+
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
|
1999
1999
|
|
|
2000
2000
|
[[package]]
|
|
2001
2001
|
name = "time-macros"
|
|
2002
|
-
version = "0.2.
|
|
2002
|
+
version = "0.2.27"
|
|
2003
2003
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2004
|
-
checksum = "
|
|
2004
|
+
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
|
2005
2005
|
dependencies = [
|
|
2006
2006
|
"num-conv",
|
|
2007
2007
|
"time-core",
|
|
@@ -2104,9 +2104,9 @@ checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
|
|
2104
2104
|
|
|
2105
2105
|
[[package]]
|
|
2106
2106
|
name = "unicode-normalization"
|
|
2107
|
-
version = "0.1.
|
|
2107
|
+
version = "0.1.25"
|
|
2108
2108
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2109
|
-
checksum = "
|
|
2109
|
+
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
|
2110
2110
|
dependencies = [
|
|
2111
2111
|
"tinyvec",
|
|
2112
2112
|
]
|
|
@@ -2182,9 +2182,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
|
2182
2182
|
|
|
2183
2183
|
[[package]]
|
|
2184
2184
|
name = "wasip2"
|
|
2185
|
-
version = "1.0.
|
|
2185
|
+
version = "1.0.2+wasi-0.2.9"
|
|
2186
2186
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2187
|
-
checksum = "
|
|
2187
|
+
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
|
2188
2188
|
dependencies = [
|
|
2189
2189
|
"wit-bindgen",
|
|
2190
2190
|
]
|
|
@@ -2429,9 +2429,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
|
2429
2429
|
|
|
2430
2430
|
[[package]]
|
|
2431
2431
|
name = "wit-bindgen"
|
|
2432
|
-
version = "0.
|
|
2432
|
+
version = "0.51.0"
|
|
2433
2433
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2434
|
-
checksum = "
|
|
2434
|
+
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
|
2435
2435
|
|
|
2436
2436
|
[[package]]
|
|
2437
2437
|
name = "write-fonts"
|
|
@@ -2477,18 +2477,18 @@ dependencies = [
|
|
|
2477
2477
|
|
|
2478
2478
|
[[package]]
|
|
2479
2479
|
name = "zerocopy"
|
|
2480
|
-
version = "0.8.
|
|
2480
|
+
version = "0.8.41"
|
|
2481
2481
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2482
|
-
checksum = "
|
|
2482
|
+
checksum = "96e13bc581734df6250836c59a5f44f3c57db9f9acb9dc8e3eaabdaf6170254d"
|
|
2483
2483
|
dependencies = [
|
|
2484
2484
|
"zerocopy-derive",
|
|
2485
2485
|
]
|
|
2486
2486
|
|
|
2487
2487
|
[[package]]
|
|
2488
2488
|
name = "zerocopy-derive"
|
|
2489
|
-
version = "0.8.
|
|
2489
|
+
version = "0.8.41"
|
|
2490
2490
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2491
|
-
checksum = "
|
|
2491
|
+
checksum = "3545ea9e86d12ab9bba9fcd99b54c1556fd3199007def5a03c375623d05fac1c"
|
|
2492
2492
|
dependencies = [
|
|
2493
2493
|
"proc-macro2",
|
|
2494
2494
|
"quote",
|
|
@@ -43,7 +43,63 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
43
43
|
}
|
|
44
44
|
println!("cargo:rerun-if-changed={}", src.display());
|
|
45
45
|
}
|
|
46
|
+
// Scan schema/migrations/*.sql, parse (from, to) from filenames like
|
|
47
|
+
// "3000000_to_3000001.sql", and generate migrations_registry.rs with a
|
|
48
|
+
// sorted static array of (from_version, to_version, sql) tuples.
|
|
49
|
+
// Adding a new migration requires only a new SQL file — no Rust changes.
|
|
50
|
+
let migrations_dir = schema_dir.join("migrations");
|
|
51
|
+
let mut migrations: Vec<(i64, i64, String)> = Vec::new();
|
|
46
52
|
|
|
53
|
+
if migrations_dir.is_dir() {
|
|
54
|
+
for entry in fs::read_dir(&migrations_dir)? {
|
|
55
|
+
let entry = entry?;
|
|
56
|
+
let path = entry.path();
|
|
57
|
+
if path.extension().and_then(|e| e.to_str()) != Some("sql") {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
let stem = match path.file_stem().and_then(|s| s.to_str()) {
|
|
61
|
+
Some(s) => s.to_owned(),
|
|
62
|
+
None => continue,
|
|
63
|
+
};
|
|
64
|
+
// Expect exactly "<from>_to_<to>".
|
|
65
|
+
if let Some((from_str, to_str)) = stem.split_once("_to_") {
|
|
66
|
+
if let (Ok(from), Ok(to)) = (from_str.parse::<i64>(), to_str.parse::<i64>()) {
|
|
67
|
+
let sql = fs::read_to_string(&path)?;
|
|
68
|
+
migrations.push((from, to, sql));
|
|
69
|
+
println!("cargo:rerun-if-changed={}", path.display());
|
|
70
|
+
} else {
|
|
71
|
+
eprintln!("cargo:warning=Skipping migration file with non-integer versions: {stem}.sql");
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
eprintln!("cargo:warning=Skipping migration file with unexpected name: {stem}.sql");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
println!("cargo:rerun-if-changed={}", migrations_dir.display());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Sort ascending by from_version so chained migrations apply in order.
|
|
81
|
+
migrations.sort_by_key(|(from, _, _)| *from);
|
|
82
|
+
|
|
83
|
+
// Emit migrations_registry.rs: a static array included by bootstrap.rs.
|
|
84
|
+
let mut reg = String::from(
|
|
85
|
+
"// Auto-generated by build.rs — do not edit by hand.\n\
|
|
86
|
+
// Each entry: (from_version, to_version, sql).\n\
|
|
87
|
+
pub(crate) static MIGRATIONS: &[(i64, i64, &str)] = &[\n",
|
|
88
|
+
);
|
|
89
|
+
for (from, to, sql) in &migrations {
|
|
90
|
+
reg.push_str(&format!(" ({from}i64, {to}i64, \""));
|
|
91
|
+
for c in sql.chars() {
|
|
92
|
+
match c {
|
|
93
|
+
'"' => reg.push_str("\\\""),
|
|
94
|
+
'\\' => reg.push_str("\\\\"),
|
|
95
|
+
'\r' => {} // normalise CRLF → LF
|
|
96
|
+
c => reg.push(c),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
reg.push_str("\"),\n");
|
|
100
|
+
}
|
|
101
|
+
reg.push_str("];\n");
|
|
102
|
+
fs::write(out_dir.join("migrations_registry.rs"), reg)?;
|
|
47
103
|
let sql_path = schema_dir.join("duc.sql");
|
|
48
104
|
let (user_version, semver_version) = match fs::read_to_string(&sql_path) {
|
|
49
105
|
Ok(sql) => {
|
|
@@ -18,6 +18,24 @@ const SEARCH_SCHEMA: &str = include_str!(concat!(env!("OUT_DIR"), "/search.sql")
|
|
|
18
18
|
/// Expected `application_id` written by `duc.sql`.
|
|
19
19
|
const APP_ID: i64 = 1_146_569_567; // "DUC_" in ASCII
|
|
20
20
|
|
|
21
|
+
/// Schema version read from `duc.sql` at compile time by `build.rs`.
|
|
22
|
+
/// Never hardcode this — it is always in sync with the SQL file.
|
|
23
|
+
const CURRENT_VERSION: i64 = {
|
|
24
|
+
// build.rs emits DUC_SCHEMA_USER_VERSION as a decimal integer string.
|
|
25
|
+
let bytes = env!("DUC_SCHEMA_USER_VERSION").as_bytes();
|
|
26
|
+
let mut val: i64 = 0;
|
|
27
|
+
let mut i = 0;
|
|
28
|
+
while i < bytes.len() {
|
|
29
|
+
val = val * 10 + (bytes[i] - b'0') as i64;
|
|
30
|
+
i += 1;
|
|
31
|
+
}
|
|
32
|
+
val
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Migration registry auto-generated by build.rs from `schema/migrations/*.sql`.
|
|
36
|
+
// Each entry is (from_version, to_version, sql). Sorted ascending by from_version.
|
|
37
|
+
include!(concat!(env!("OUT_DIR"), "/migrations_registry.rs"));
|
|
38
|
+
|
|
21
39
|
/// Per-connection PRAGMAs that must be re-applied on every open (they are
|
|
22
40
|
/// either ephemeral or take effect only after the first statement on the
|
|
23
41
|
/// connection).
|
|
@@ -57,8 +75,35 @@ pub(crate) fn bootstrap(conn: &Connection) -> Result<(), DbError> {
|
|
|
57
75
|
"unexpected application_id after bootstrap: {app_id} (expected {APP_ID})"
|
|
58
76
|
)));
|
|
59
77
|
}
|
|
78
|
+
} else if user_version == CURRENT_VERSION {
|
|
79
|
+
// Current schema — just ensure per-connection pragmas are active.
|
|
80
|
+
conn.execute_batch(CONN_PRAGMAS)
|
|
81
|
+
.map_err(|e| DbError::Bootstrap(format!("pragma apply failed: {e}")))?;
|
|
60
82
|
} else {
|
|
61
|
-
//
|
|
83
|
+
// Walk the migration chain until we reach CURRENT_VERSION.
|
|
84
|
+
// build.rs generates MIGRATIONS sorted by from_version, so chaining
|
|
85
|
+
// (e.g. 3000000→3000001→3000002) works without any code changes here.
|
|
86
|
+
let mut current = user_version;
|
|
87
|
+
loop {
|
|
88
|
+
match MIGRATIONS.iter().find(|(from, _, _)| *from == current) {
|
|
89
|
+
Some((from, to, sql)) => {
|
|
90
|
+
conn.execute_batch(sql).map_err(|e| {
|
|
91
|
+
DbError::Bootstrap(format!("migration {from}\u{2192}{to} failed: {e}"))
|
|
92
|
+
})?;
|
|
93
|
+
// Re-read the version the migration SQL set via PRAGMA user_version.
|
|
94
|
+
current =
|
|
95
|
+
conn.pragma_query_value(None, "user_version", |r| r.get(0))?;
|
|
96
|
+
if current == CURRENT_VERSION {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
None => {
|
|
101
|
+
return Err(DbError::Bootstrap(format!(
|
|
102
|
+
"unsupported schema version {user_version}; expected 0 or {CURRENT_VERSION}"
|
|
103
|
+
)));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
62
107
|
conn.execute_batch(CONN_PRAGMAS)
|
|
63
108
|
.map_err(|e| DbError::Bootstrap(format!("pragma apply failed: {e}")))?;
|
|
64
109
|
}
|
|
@@ -121,48 +121,131 @@ pub fn parse_lazy(buf: &[u8]) -> ParseResult<ExportedDataState> {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/// Fetch a single external file from a `.duc` buffer by file ID.
|
|
124
|
-
pub fn get_external_file(buf: &[u8], file_id: &str) -> ParseResult<Option<
|
|
124
|
+
pub fn get_external_file(buf: &[u8], file_id: &str) -> ParseResult<Option<DucExternalFile>> {
|
|
125
125
|
let conn = load_db_bytes(buf)?;
|
|
126
|
-
let
|
|
127
|
-
"SELECT
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
let has_revisions_table: bool = conn.prepare(
|
|
127
|
+
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='external_file_revisions'"
|
|
128
|
+
)?.query_row([], |row| row.get::<_, i32>(0)).unwrap_or(0) > 0;
|
|
129
|
+
|
|
130
|
+
if has_revisions_table {
|
|
131
|
+
// New schema: load file header + revisions.
|
|
132
|
+
let file = conn.prepare(
|
|
133
|
+
"SELECT id, active_revision_id, updated, version FROM external_files WHERE id = ?1"
|
|
134
|
+
)?.query_row(params![file_id], |row| {
|
|
135
|
+
Ok(DucExternalFile {
|
|
136
|
+
id: row.get(0)?,
|
|
137
|
+
active_revision_id: row.get(1)?,
|
|
138
|
+
updated: row.get(2)?,
|
|
139
|
+
revisions: HashMap::new(),
|
|
140
|
+
version: row.get(3)?,
|
|
141
|
+
})
|
|
142
|
+
});
|
|
143
|
+
let mut file = match file {
|
|
144
|
+
Ok(f) => f,
|
|
145
|
+
Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(None),
|
|
146
|
+
Err(e) => return Err(e.into()),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let mut rev_stmt = conn.prepare(
|
|
150
|
+
"SELECT id, size_bytes, checksum, source_name, mime_type, message,
|
|
151
|
+
created, last_retrieved, data
|
|
152
|
+
FROM external_file_revisions WHERE file_id = ?1"
|
|
153
|
+
)?;
|
|
154
|
+
let rev_rows = rev_stmt.query_map(params![file_id], |row| {
|
|
155
|
+
let id: String = row.get(0)?;
|
|
156
|
+
Ok((id.clone(), ExternalFileRevision {
|
|
134
157
|
id,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
158
|
+
size_bytes: row.get(1)?,
|
|
159
|
+
checksum: row.get(2)?,
|
|
160
|
+
source_name: row.get(3)?,
|
|
161
|
+
mime_type: row.get(4)?,
|
|
162
|
+
message: row.get(5)?,
|
|
163
|
+
created: row.get(6)?,
|
|
164
|
+
last_retrieved: row.get(7)?,
|
|
165
|
+
data: row.get(8)?,
|
|
166
|
+
}))
|
|
167
|
+
})?;
|
|
168
|
+
for row in rev_rows {
|
|
169
|
+
let (rev_id, rev) = row?;
|
|
170
|
+
file.revisions.insert(rev_id, rev);
|
|
171
|
+
}
|
|
172
|
+
Ok(Some(file))
|
|
173
|
+
} else {
|
|
174
|
+
// Legacy schema: wrap flat row into a single-revision DucExternalFile.
|
|
175
|
+
let result = conn.prepare(
|
|
176
|
+
"SELECT id, mime_type, data, created, last_retrieved, version FROM external_files WHERE id = ?1"
|
|
177
|
+
)?.query_row(params![file_id], |row| {
|
|
178
|
+
let id: String = row.get(0)?;
|
|
179
|
+
let rev_id = format!("{}_rev1", id);
|
|
180
|
+
Ok(DucExternalFile {
|
|
181
|
+
id: id.clone(),
|
|
182
|
+
active_revision_id: rev_id.clone(),
|
|
183
|
+
updated: row.get(3)?,
|
|
139
184
|
version: row.get(5)?,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
185
|
+
revisions: {
|
|
186
|
+
let mut revs = HashMap::new();
|
|
187
|
+
revs.insert(rev_id.clone(), ExternalFileRevision {
|
|
188
|
+
id: rev_id,
|
|
189
|
+
size_bytes: 0,
|
|
190
|
+
checksum: None,
|
|
191
|
+
source_name: None,
|
|
192
|
+
mime_type: row.get(1)?,
|
|
193
|
+
message: None,
|
|
194
|
+
created: row.get(3)?,
|
|
195
|
+
last_retrieved: row.get(4)?,
|
|
196
|
+
data: row.get(2)?,
|
|
197
|
+
});
|
|
198
|
+
revs
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
});
|
|
202
|
+
match result {
|
|
203
|
+
Ok(file) => Ok(Some(file)),
|
|
204
|
+
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
|
205
|
+
Err(e) => Err(e.into()),
|
|
206
|
+
}
|
|
147
207
|
}
|
|
148
208
|
}
|
|
149
209
|
|
|
150
|
-
/// List metadata for all external files (
|
|
151
|
-
pub fn list_external_files(buf: &[u8]) -> ParseResult<Vec<
|
|
210
|
+
/// List metadata summary for all external files (no data blobs loaded).
|
|
211
|
+
pub fn list_external_files(buf: &[u8]) -> ParseResult<Vec<ExternalFileMeta>> {
|
|
152
212
|
let conn = load_db_bytes(buf)?;
|
|
153
|
-
let
|
|
154
|
-
"SELECT
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
mime_type
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
213
|
+
let has_revisions_table: bool = conn.prepare(
|
|
214
|
+
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='external_file_revisions'"
|
|
215
|
+
)?.query_row([], |row| row.get::<_, i32>(0)).unwrap_or(0) > 0;
|
|
216
|
+
|
|
217
|
+
if has_revisions_table {
|
|
218
|
+
let mut stmt = conn.prepare(
|
|
219
|
+
"SELECT f.id, r.mime_type, r.created, r.last_retrieved, f.version
|
|
220
|
+
FROM external_files f
|
|
221
|
+
JOIN external_file_revisions r ON r.id = f.active_revision_id"
|
|
222
|
+
)?;
|
|
223
|
+
let files: Vec<ExternalFileMeta> = stmt.query_map([], |row| {
|
|
224
|
+
Ok(ExternalFileMeta {
|
|
225
|
+
id: row.get(0)?,
|
|
226
|
+
mime_type: row.get(1)?,
|
|
227
|
+
created: row.get(2)?,
|
|
228
|
+
last_retrieved: row.get(3)?,
|
|
229
|
+
version: row.get(4)?,
|
|
230
|
+
})
|
|
231
|
+
})?.collect::<Result<Vec<_>, _>>()?;
|
|
232
|
+
Ok(files)
|
|
233
|
+
} else {
|
|
234
|
+
// Legacy schema flat table.
|
|
235
|
+
let mut stmt = conn.prepare(
|
|
236
|
+
"SELECT id, mime_type, created, last_retrieved, version FROM external_files"
|
|
237
|
+
)?;
|
|
238
|
+
let files: Vec<ExternalFileMeta> = stmt.query_map([], |row| {
|
|
239
|
+
Ok(ExternalFileMeta {
|
|
240
|
+
id: row.get(0)?,
|
|
241
|
+
mime_type: row.get(1)?,
|
|
242
|
+
created: row.get(2)?,
|
|
243
|
+
last_retrieved: row.get(3)?,
|
|
244
|
+
version: row.get(4)?,
|
|
245
|
+
})
|
|
246
|
+
})?.collect::<Result<Vec<_>, _>>()?;
|
|
247
|
+
Ok(files)
|
|
248
|
+
}
|
|
166
249
|
}
|
|
167
250
|
|
|
168
251
|
// ─── Database import ─────────────────────────────────────────────────────────
|
|
@@ -1685,27 +1768,131 @@ fn read_hatch_pattern_lines(conn: &Connection, owner_type: &str, owner_id: i64)
|
|
|
1685
1768
|
|
|
1686
1769
|
// ─── external_files ──────────────────────────────────────────────────────────
|
|
1687
1770
|
|
|
1688
|
-
fn read_external_files(conn: &Connection) -> ParseResult<Option<HashMap<String,
|
|
1771
|
+
fn read_external_files(conn: &Connection) -> ParseResult<Option<HashMap<String, DucExternalFile>>> {
|
|
1772
|
+
// Check if the new schema (with external_file_revisions) is present.
|
|
1773
|
+
let has_revisions_table: bool = conn.prepare(
|
|
1774
|
+
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='external_file_revisions'"
|
|
1775
|
+
)?.query_row([], |row| row.get::<_, i32>(0)).unwrap_or(0) > 0;
|
|
1776
|
+
|
|
1777
|
+
if has_revisions_table {
|
|
1778
|
+
read_external_files_v2(conn)
|
|
1779
|
+
} else {
|
|
1780
|
+
read_external_files_v1_legacy(conn)
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/// Read from the current schema (external_files + external_file_revisions).
|
|
1785
|
+
fn read_external_files_v2(conn: &Connection) -> ParseResult<Option<HashMap<String, DucExternalFile>>> {
|
|
1786
|
+
// Load all revisions grouped by file_id.
|
|
1787
|
+
let mut rev_stmt = conn.prepare(
|
|
1788
|
+
"SELECT id, file_id, size_bytes, checksum, source_name, mime_type, message,
|
|
1789
|
+
created, last_retrieved, data
|
|
1790
|
+
FROM external_file_revisions"
|
|
1791
|
+
)?;
|
|
1792
|
+
let mut revisions_by_file: HashMap<String, HashMap<String, ExternalFileRevision>> = HashMap::new();
|
|
1793
|
+
let rev_rows = rev_stmt.query_map([], |row| {
|
|
1794
|
+
Ok(ExternalFileRevision {
|
|
1795
|
+
id: row.get(0)?,
|
|
1796
|
+
size_bytes: row.get(2)?,
|
|
1797
|
+
checksum: row.get(3)?,
|
|
1798
|
+
source_name: row.get(4)?,
|
|
1799
|
+
mime_type: row.get(5)?,
|
|
1800
|
+
message: row.get(6)?,
|
|
1801
|
+
created: row.get(7)?,
|
|
1802
|
+
last_retrieved: row.get(8)?,
|
|
1803
|
+
data: row.get(9)?,
|
|
1804
|
+
})
|
|
1805
|
+
})?;
|
|
1806
|
+
|
|
1807
|
+
// We need file_id as well; re-query with it included.
|
|
1808
|
+
let mut rev_stmt2 = conn.prepare(
|
|
1809
|
+
"SELECT id, file_id, size_bytes, checksum, source_name, mime_type, message,
|
|
1810
|
+
created, last_retrieved, data
|
|
1811
|
+
FROM external_file_revisions"
|
|
1812
|
+
)?;
|
|
1813
|
+
// Suppress unused warning from first query
|
|
1814
|
+
drop(rev_rows);
|
|
1815
|
+
|
|
1816
|
+
let rev_rows2 = rev_stmt2.query_map([], |row| {
|
|
1817
|
+
let rev_id: String = row.get(0)?;
|
|
1818
|
+
let file_id: String = row.get(1)?;
|
|
1819
|
+
Ok((file_id, rev_id.clone(), ExternalFileRevision {
|
|
1820
|
+
id: rev_id,
|
|
1821
|
+
size_bytes: row.get(2)?,
|
|
1822
|
+
checksum: row.get(3)?,
|
|
1823
|
+
source_name: row.get(4)?,
|
|
1824
|
+
mime_type: row.get(5)?,
|
|
1825
|
+
message: row.get(6)?,
|
|
1826
|
+
created: row.get(7)?,
|
|
1827
|
+
last_retrieved: row.get(8)?,
|
|
1828
|
+
data: row.get(9)?,
|
|
1829
|
+
}))
|
|
1830
|
+
})?;
|
|
1831
|
+
for row in rev_rows2 {
|
|
1832
|
+
let (file_id, rev_id, rev) = row?;
|
|
1833
|
+
revisions_by_file.entry(file_id).or_default().insert(rev_id, rev);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
// Load the file headers.
|
|
1837
|
+
let mut file_stmt = conn.prepare(
|
|
1838
|
+
"SELECT id, active_revision_id, updated, version FROM external_files"
|
|
1839
|
+
)?;
|
|
1840
|
+
let mut map: HashMap<String, DucExternalFile> = HashMap::new();
|
|
1841
|
+
let file_rows = file_stmt.query_map([], |row| {
|
|
1842
|
+
let id: String = row.get(0)?;
|
|
1843
|
+
Ok((id.clone(), DucExternalFile {
|
|
1844
|
+
id,
|
|
1845
|
+
active_revision_id: row.get(1)?,
|
|
1846
|
+
updated: row.get(2)?,
|
|
1847
|
+
revisions: HashMap::new(),
|
|
1848
|
+
version: row.get(3)?,
|
|
1849
|
+
}))
|
|
1850
|
+
})?;
|
|
1851
|
+
for row in file_rows {
|
|
1852
|
+
let (k, mut v) = row?;
|
|
1853
|
+
v.revisions = revisions_by_file.remove(&k).unwrap_or_default();
|
|
1854
|
+
map.insert(k, v);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
if map.is_empty() { Ok(None) } else { Ok(Some(map)) }
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
/// Fallback for old schema files that only have the flat `external_files` table.
|
|
1861
|
+
/// Wraps each row in a single-revision DucExternalFile.
|
|
1862
|
+
fn read_external_files_v1_legacy(conn: &Connection) -> ParseResult<Option<HashMap<String, DucExternalFile>>> {
|
|
1689
1863
|
let mut stmt = conn.prepare(
|
|
1690
1864
|
"SELECT id, mime_type, data, created, last_retrieved, version FROM external_files"
|
|
1691
1865
|
)?;
|
|
1692
1866
|
let mut map = HashMap::new();
|
|
1693
1867
|
let rows = stmt.query_map([], |row| {
|
|
1694
1868
|
let id: String = row.get(0)?;
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
last_retrieved: row.get(4)?,
|
|
1869
|
+
let rev_id = format!("{}_rev1", id);
|
|
1870
|
+
Ok((id.clone(), DucExternalFile {
|
|
1871
|
+
id: id.clone(),
|
|
1872
|
+
active_revision_id: rev_id.clone(),
|
|
1873
|
+
updated: row.get(3)?,
|
|
1701
1874
|
version: row.get(5)?,
|
|
1875
|
+
revisions: {
|
|
1876
|
+
let mut revs = HashMap::new();
|
|
1877
|
+
revs.insert(rev_id.clone(), ExternalFileRevision {
|
|
1878
|
+
id: rev_id,
|
|
1879
|
+
size_bytes: 0,
|
|
1880
|
+
checksum: None,
|
|
1881
|
+
source_name: None,
|
|
1882
|
+
mime_type: row.get(1)?,
|
|
1883
|
+
message: None,
|
|
1884
|
+
created: row.get(3)?,
|
|
1885
|
+
last_retrieved: row.get(4)?,
|
|
1886
|
+
data: row.get(2)?,
|
|
1887
|
+
});
|
|
1888
|
+
revs
|
|
1889
|
+
},
|
|
1702
1890
|
}))
|
|
1703
1891
|
})?;
|
|
1704
1892
|
for row in rows {
|
|
1705
1893
|
let (k, v) = row?;
|
|
1706
1894
|
map.insert(k, v);
|
|
1707
1895
|
}
|
|
1708
|
-
|
|
1709
1896
|
if map.is_empty() { Ok(None) } else { Ok(Some(map)) }
|
|
1710
1897
|
}
|
|
1711
1898
|
|
|
@@ -1123,15 +1123,39 @@ fn write_hatch_pattern_lines(
|
|
|
1123
1123
|
|
|
1124
1124
|
fn write_external_files(
|
|
1125
1125
|
tx: &Transaction,
|
|
1126
|
-
files: &Option<std::collections::HashMap<String,
|
|
1126
|
+
files: &Option<std::collections::HashMap<String, DucExternalFile>>,
|
|
1127
1127
|
) -> SerializeResult<()> {
|
|
1128
1128
|
let Some(files) = files else { return Ok(()) };
|
|
1129
|
-
let mut
|
|
1130
|
-
"INSERT OR REPLACE INTO external_files (id,
|
|
1131
|
-
VALUES (?1, ?2, ?3, ?4
|
|
1129
|
+
let mut file_stmt = tx.prepare_cached(
|
|
1130
|
+
"INSERT OR REPLACE INTO external_files (id, active_revision_id, updated, version)
|
|
1131
|
+
VALUES (?1, ?2, ?3, ?4)"
|
|
1132
|
+
)?;
|
|
1133
|
+
let mut rev_stmt = tx.prepare_cached(
|
|
1134
|
+
"INSERT OR REPLACE INTO external_file_revisions
|
|
1135
|
+
(id, file_id, size_bytes, checksum, source_name, mime_type, message, created, last_retrieved, data)
|
|
1136
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)"
|
|
1132
1137
|
)?;
|
|
1133
|
-
for (_key,
|
|
1134
|
-
|
|
1138
|
+
for (_key, file) in files {
|
|
1139
|
+
file_stmt.execute(params![
|
|
1140
|
+
file.id,
|
|
1141
|
+
file.active_revision_id,
|
|
1142
|
+
file.updated,
|
|
1143
|
+
file.version,
|
|
1144
|
+
])?;
|
|
1145
|
+
for (_rev_key, rev) in &file.revisions {
|
|
1146
|
+
rev_stmt.execute(params![
|
|
1147
|
+
rev.id,
|
|
1148
|
+
file.id,
|
|
1149
|
+
rev.size_bytes,
|
|
1150
|
+
rev.checksum,
|
|
1151
|
+
rev.source_name,
|
|
1152
|
+
rev.mime_type,
|
|
1153
|
+
rev.message,
|
|
1154
|
+
rev.created,
|
|
1155
|
+
rev.last_retrieved,
|
|
1156
|
+
rev.data,
|
|
1157
|
+
])?;
|
|
1158
|
+
}
|
|
1135
1159
|
}
|
|
1136
1160
|
Ok(())
|
|
1137
1161
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Aligns text vertically within its bounding box.
|
|
3
5
|
*/
|
|
@@ -1461,40 +1463,51 @@ pub struct VersionGraph {
|
|
|
1461
1463
|
|
|
1462
1464
|
// =============== EXTERNAL FILES ===============
|
|
1463
1465
|
|
|
1464
|
-
///
|
|
1466
|
+
/// Snapshot of an external file at a given point in time.
|
|
1465
1467
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
1466
1468
|
#[serde(rename_all = "camelCase")]
|
|
1467
|
-
pub struct
|
|
1469
|
+
pub struct ExternalFileRevision {
|
|
1470
|
+
// Revision number as string, functions as an incremental serial id
|
|
1468
1471
|
pub id: String,
|
|
1472
|
+
pub size_bytes: i64,
|
|
1473
|
+
/// Content hash for integrity checks and optional deduplication.
|
|
1474
|
+
pub checksum: Option<String>,
|
|
1475
|
+
/// Original upload filename shown to the user.
|
|
1476
|
+
pub source_name: Option<String>,
|
|
1469
1477
|
pub mime_type: String,
|
|
1478
|
+
/// Optional note describing what changed in this revision.
|
|
1479
|
+
pub message: Option<String>,
|
|
1480
|
+
/// Epoch ms when this revision was created.
|
|
1470
1481
|
pub created: i64,
|
|
1482
|
+
/// Epoch ms when this revision was last loaded onto the scene.
|
|
1471
1483
|
pub last_retrieved: Option<i64>,
|
|
1472
|
-
pub version: Option<i32>,
|
|
1473
|
-
}
|
|
1474
1484
|
|
|
1485
|
+
/// The actual file content bytes
|
|
1486
|
+
#[serde(with = "serde_bytes")]
|
|
1487
|
+
pub data: Vec<u8>,
|
|
1488
|
+
}
|
|
1475
1489
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
1476
1490
|
#[serde(rename_all = "camelCase")]
|
|
1477
|
-
pub struct
|
|
1478
|
-
pub mime_type: String,
|
|
1491
|
+
pub struct DucExternalFile {
|
|
1479
1492
|
pub id: String,
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
pub
|
|
1483
|
-
|
|
1484
|
-
pub
|
|
1485
|
-
/**
|
|
1486
|
-
* Epoch timestamp in milliseconds when the file was last retrieved from storage to be loaded onto the scene.
|
|
1487
|
-
* Used to determine whether to delete unused files from storage.
|
|
1488
|
-
*/
|
|
1489
|
-
pub last_retrieved: Option<i64>,
|
|
1493
|
+
pub active_revision_id: String,
|
|
1494
|
+
/// Epoch ms when the logical file was last mutated (revision added or active changed).
|
|
1495
|
+
pub updated: i64,
|
|
1496
|
+
// All revisions of this file, keyed by their `id`.
|
|
1497
|
+
pub revisions: HashMap<String, ExternalFileRevision>,
|
|
1490
1498
|
pub version: Option<i32>,
|
|
1491
1499
|
}
|
|
1492
1500
|
|
|
1501
|
+
/// Lightweight summary of an external file used for lazy/metadata-only access
|
|
1502
|
+
/// (active revision's metadata + file-level version, no data blob).
|
|
1493
1503
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
1494
1504
|
#[serde(rename_all = "camelCase")]
|
|
1495
|
-
pub struct
|
|
1496
|
-
pub
|
|
1497
|
-
pub
|
|
1505
|
+
pub struct ExternalFileMeta {
|
|
1506
|
+
pub id: String,
|
|
1507
|
+
pub mime_type: String,
|
|
1508
|
+
pub created: i64,
|
|
1509
|
+
pub last_retrieved: Option<i64>,
|
|
1510
|
+
pub version: Option<i32>,
|
|
1498
1511
|
}
|
|
1499
1512
|
|
|
1500
1513
|
// =============== ROOT TYPE ===============
|
|
@@ -1509,7 +1522,7 @@ pub struct ExportedDataState {
|
|
|
1509
1522
|
pub source: String,
|
|
1510
1523
|
#[serde(rename = "type")]
|
|
1511
1524
|
pub data_type: String,
|
|
1512
|
-
pub dictionary: Option<
|
|
1525
|
+
pub dictionary: Option<HashMap<String, String>>,
|
|
1513
1526
|
#[serde(with = "serde_bytes", default, skip_serializing_if = "Option::is_none")]
|
|
1514
1527
|
pub thumbnail: Option<Vec<u8>>,
|
|
1515
1528
|
pub elements: Vec<ElementWrapper>,
|
|
@@ -1528,5 +1541,5 @@ pub struct ExportedDataState {
|
|
|
1528
1541
|
/** In case it is needed to embed the version control into the file format */
|
|
1529
1542
|
pub version_graph: Option<VersionGraph>,
|
|
1530
1543
|
#[serde(rename = "files")]
|
|
1531
|
-
pub external_files: Option<
|
|
1544
|
+
pub external_files: Option<HashMap<String, DucExternalFile>>,
|
|
1532
1545
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|