ducpy 3.1.1__tar.gz → 3.2.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.1.1 → ducpy-3.2.0}/Cargo.lock +1 -1
- {ducpy-3.1.1 → ducpy-3.2.0}/PKG-INFO +2 -2
- {ducpy-3.1.1 → ducpy-3.2.0}/README.md +1 -1
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducpy/crate/Cargo.toml +1 -1
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/README.md +1 -1
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/api/version_control.rs +4 -93
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/lib.rs +4 -3
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/parse.rs +214 -98
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/serialize.rs +17 -10
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/types.rs +20 -24
- {ducpy-3.1.1 → ducpy-3.2.0}/Cargo.toml +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/LICENSE +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducpy/crate/src/lib.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/.gitignore +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/Cargo.toml +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/LICENSE +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/build.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/package.json +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/release.config.cjs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/api/document.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/api/meta.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/api/mod.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/db/bootstrap.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/db/mod.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/db/native.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/db/wasm.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/src/serde_utils.rs +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/packages/ducrs/tests/.gitignore +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/pyproject.toml +0 -0
- {ducpy-3.1.1 → ducpy-3.2.0}/src/ducpy_native/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ducpy
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Classifier: License :: OSI Approved :: MIT License
|
|
5
5
|
Requires-Dist: nanoid>=2.0.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -19,7 +19,7 @@ Project-URL: Source, https://github.com/ducflair/duc/tree/main/packages/ducpy
|
|
|
19
19
|
|
|
20
20
|
<p align="center">
|
|
21
21
|
<br/>
|
|
22
|
-
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://
|
|
22
|
+
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://cdn.jsdelivr.net/gh/ducflair/assets@main/src/duc/duc-extended.png" /></a>
|
|
23
23
|
<p align="center">2D CAD File Format</p>
|
|
24
24
|
<p align="center" style="align: center;">
|
|
25
25
|
<a href="https://pypi.org/project/ducpy/"><img src="https://shields.io/badge/Pip-blue?logo=Pypi&logoColor=white&style=round-square" alt="Pip" /></a>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<br/>
|
|
5
|
-
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://
|
|
5
|
+
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://cdn.jsdelivr.net/gh/ducflair/assets@main/src/duc/duc-extended.png" /></a>
|
|
6
6
|
<p align="center">2D CAD File Format</p>
|
|
7
7
|
<p align="center" style="align: center;">
|
|
8
8
|
<a href="https://pypi.org/project/ducpy/"><img src="https://shields.io/badge/Pip-blue?logo=Pypi&logoColor=white&style=round-square" alt="Pip" /></a>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<br/>
|
|
5
|
-
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://
|
|
5
|
+
<a href="https://duc.ducflair.com" target="_blank"><img width="256px" src="https://cdn.jsdelivr.net/gh/ducflair/assets@main/src/duc/duc-extended.png" /></a>
|
|
6
6
|
<p align="center">2D CAD File Format</p>
|
|
7
7
|
<p align="center" style="align: center;">
|
|
8
8
|
<a href="https://crates.io/crates/duc/"><img src="https://shields.io/badge/Crates-FFC933?logo=Rust&logoColor=646464&style=round-square" alt="Crates" /></a>
|
|
@@ -313,7 +313,7 @@ impl<'a> VersionControl<'a> {
|
|
|
313
313
|
self.conn.with(|c| {
|
|
314
314
|
c.query_row(
|
|
315
315
|
"SELECT current_version, current_schema_version, chain_count,
|
|
316
|
-
|
|
316
|
+
total_size
|
|
317
317
|
FROM version_graph WHERE id = 1",
|
|
318
318
|
[],
|
|
319
319
|
|row| {
|
|
@@ -321,8 +321,7 @@ impl<'a> VersionControl<'a> {
|
|
|
321
321
|
current_version: row.get(0)?,
|
|
322
322
|
current_schema_version: row.get(1)?,
|
|
323
323
|
chain_count: row.get(2)?,
|
|
324
|
-
|
|
325
|
-
total_size: row.get::<_, Option<i64>>(4)?.unwrap_or(0),
|
|
324
|
+
total_size: row.get::<_, Option<i64>>(3)?.unwrap_or(0),
|
|
326
325
|
})
|
|
327
326
|
},
|
|
328
327
|
)
|
|
@@ -465,93 +464,6 @@ impl<'a> VersionControl<'a> {
|
|
|
465
464
|
})
|
|
466
465
|
}
|
|
467
466
|
|
|
468
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
469
|
-
// WRITE — Pruning
|
|
470
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
471
|
-
|
|
472
|
-
/// Prune old deltas that precede the given `keep_after_version`.
|
|
473
|
-
/// Checkpoints are never pruned (they are self-contained recovery points).
|
|
474
|
-
/// Returns the number of deltas removed.
|
|
475
|
-
pub fn prune_deltas_before(&self, keep_after_version: i64) -> DbResult<usize> {
|
|
476
|
-
self.conn.with(|c| {
|
|
477
|
-
let removed = c
|
|
478
|
-
.execute(
|
|
479
|
-
"DELETE FROM deltas WHERE version_number < ?1",
|
|
480
|
-
[keep_after_version],
|
|
481
|
-
)
|
|
482
|
-
.map_err(DbError::from)?;
|
|
483
|
-
|
|
484
|
-
// Update last_pruned timestamp
|
|
485
|
-
let now_ms = std::time::SystemTime::now()
|
|
486
|
-
.duration_since(std::time::UNIX_EPOCH)
|
|
487
|
-
.unwrap_or_default()
|
|
488
|
-
.as_millis() as i64;
|
|
489
|
-
|
|
490
|
-
c.execute(
|
|
491
|
-
"UPDATE version_graph SET last_pruned = ?1 WHERE id = 1",
|
|
492
|
-
[now_ms],
|
|
493
|
-
)
|
|
494
|
-
.map_err(DbError::from)?;
|
|
495
|
-
|
|
496
|
-
// Recalculate total_size
|
|
497
|
-
self.recalculate_total_size(c)?;
|
|
498
|
-
|
|
499
|
-
Ok(removed)
|
|
500
|
-
})
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/// Remove ALL versions (checkpoints + deltas) that are strictly older than
|
|
504
|
-
/// `keep_after_version`, except the most recent checkpoint before that
|
|
505
|
-
/// boundary (needed as a recovery base).
|
|
506
|
-
pub fn prune_before(&self, keep_after_version: i64) -> DbResult<usize> {
|
|
507
|
-
self.conn.with(|c| {
|
|
508
|
-
// Find the latest checkpoint at or before the boundary — keep it
|
|
509
|
-
let safe_cp_version: Option<i64> = c
|
|
510
|
-
.query_row(
|
|
511
|
-
"SELECT MAX(version_number) FROM checkpoints
|
|
512
|
-
WHERE version_number <= ?1",
|
|
513
|
-
[keep_after_version],
|
|
514
|
-
|row| row.get(0),
|
|
515
|
-
)
|
|
516
|
-
.optional()
|
|
517
|
-
.map_err(DbError::from)?
|
|
518
|
-
.flatten();
|
|
519
|
-
|
|
520
|
-
let safe_version = safe_cp_version.unwrap_or(0);
|
|
521
|
-
|
|
522
|
-
// Delete deltas before the safe checkpoint
|
|
523
|
-
let del_deltas = c
|
|
524
|
-
.execute(
|
|
525
|
-
"DELETE FROM deltas WHERE version_number < ?1",
|
|
526
|
-
[safe_version],
|
|
527
|
-
)
|
|
528
|
-
.map_err(DbError::from)?;
|
|
529
|
-
|
|
530
|
-
// Delete checkpoints before the safe checkpoint (keep the safe one)
|
|
531
|
-
let del_cps = c
|
|
532
|
-
.execute(
|
|
533
|
-
"DELETE FROM checkpoints WHERE version_number < ?1",
|
|
534
|
-
[safe_version],
|
|
535
|
-
)
|
|
536
|
-
.map_err(DbError::from)?;
|
|
537
|
-
|
|
538
|
-
let now_ms = std::time::SystemTime::now()
|
|
539
|
-
.duration_since(std::time::UNIX_EPOCH)
|
|
540
|
-
.unwrap_or_default()
|
|
541
|
-
.as_millis() as i64;
|
|
542
|
-
|
|
543
|
-
c.execute(
|
|
544
|
-
"UPDATE version_graph SET last_pruned = ?1 WHERE id = 1",
|
|
545
|
-
[now_ms],
|
|
546
|
-
)
|
|
547
|
-
.map_err(DbError::from)?;
|
|
548
|
-
|
|
549
|
-
self.recalculate_total_size(c)?;
|
|
550
|
-
|
|
551
|
-
Ok(del_deltas + del_cps)
|
|
552
|
-
})
|
|
553
|
-
}
|
|
554
|
-
|
|
555
467
|
// ────────────────────────────────────────────────────────────────────────
|
|
556
468
|
// WRITE — Revert
|
|
557
469
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -808,7 +720,7 @@ pub(crate) fn read_version_graph_inner(
|
|
|
808
720
|
let mut vg_stmt = conn
|
|
809
721
|
.prepare(
|
|
810
722
|
"SELECT current_version, current_schema_version, user_checkpoint_version_id,
|
|
811
|
-
latest_version_id, chain_count,
|
|
723
|
+
latest_version_id, chain_count, total_size
|
|
812
724
|
FROM version_graph WHERE id = 1",
|
|
813
725
|
)
|
|
814
726
|
.map_err(DbError::from)?;
|
|
@@ -819,8 +731,7 @@ pub(crate) fn read_version_graph_inner(
|
|
|
819
731
|
current_version: row.get(0)?,
|
|
820
732
|
current_schema_version: row.get(1)?,
|
|
821
733
|
chain_count: row.get(4)?,
|
|
822
|
-
|
|
823
|
-
total_size: row.get::<_, Option<i64>>(6)?.unwrap_or(0),
|
|
734
|
+
total_size: row.get::<_, Option<i64>>(5)?.unwrap_or(0),
|
|
824
735
|
},
|
|
825
736
|
row.get::<_, Option<String>>(2)?.unwrap_or_default(),
|
|
826
737
|
row.get::<_, Option<String>>(3)?.unwrap_or_default(),
|
|
@@ -114,6 +114,7 @@ mod tests {
|
|
|
114
114
|
.unwrap_or_else(|e| panic!("parse_lazy {} failed: {e}", path.display()));
|
|
115
115
|
|
|
116
116
|
assert!(lazy.external_files.is_none(), "{}: lazy parse must omit external_files", path.display());
|
|
117
|
+
assert!(lazy.external_files_data.is_none(), "{}: lazy parse must omit external_files_data", path.display());
|
|
117
118
|
assert_eq!(full.elements.len(), lazy.elements.len(), "{}: element count diverged", path.display());
|
|
118
119
|
assert_eq!(full.layers.len(), lazy.layers.len(), "{}: layer count diverged", path.display());
|
|
119
120
|
}
|
|
@@ -194,9 +195,9 @@ mod tests {
|
|
|
194
195
|
let file = parse::get_external_file(&buf, &meta.id)
|
|
195
196
|
.unwrap_or_else(|e| panic!("get_external_file {} id={} failed: {e}", path.display(), meta.id));
|
|
196
197
|
assert!(file.is_some(), "{}: listed file '{}' not found via get", path.display(), meta.id);
|
|
197
|
-
let
|
|
198
|
-
assert_eq!(file.id, meta.id);
|
|
199
|
-
assert!(!file.revisions.is_empty(), "{}: file '{}' has no revisions", path.display(), meta.id);
|
|
198
|
+
let loaded = file.unwrap();
|
|
199
|
+
assert_eq!(loaded.file.id, meta.id);
|
|
200
|
+
assert!(!loaded.file.revisions.is_empty(), "{}: file '{}' has no revisions", path.display(), meta.id);
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
203
|
}
|
|
@@ -57,7 +57,7 @@ pub fn parse(buf: &[u8]) -> ParseResult<ExportedDataState> {
|
|
|
57
57
|
let regions = read_regions(&conn)?;
|
|
58
58
|
let (blocks, block_instances, block_collections) = read_blocks(&conn)?;
|
|
59
59
|
let elements = read_elements(&conn)?;
|
|
60
|
-
let external_files = read_external_files(&conn)?;
|
|
60
|
+
let (external_files, external_files_data) = read_external_files(&conn)?;
|
|
61
61
|
let version_graph = read_version_graph(&conn)?;
|
|
62
62
|
|
|
63
63
|
Ok(ExportedDataState {
|
|
@@ -78,6 +78,7 @@ pub fn parse(buf: &[u8]) -> ParseResult<ExportedDataState> {
|
|
|
78
78
|
duc_global_state,
|
|
79
79
|
version_graph,
|
|
80
80
|
external_files,
|
|
81
|
+
external_files_data,
|
|
81
82
|
})
|
|
82
83
|
}
|
|
83
84
|
|
|
@@ -117,16 +118,22 @@ pub fn parse_lazy(buf: &[u8]) -> ParseResult<ExportedDataState> {
|
|
|
117
118
|
duc_global_state,
|
|
118
119
|
version_graph,
|
|
119
120
|
external_files: None,
|
|
121
|
+
external_files_data: None,
|
|
120
122
|
})
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
/// Fetch a single external file from a `.duc` buffer by file ID.
|
|
124
|
-
|
|
126
|
+
/// Returns metadata + data blobs as an `ExternalFileLoaded`.
|
|
127
|
+
pub fn get_external_file(buf: &[u8], file_id: &str) -> ParseResult<Option<ExternalFileLoaded>> {
|
|
125
128
|
let conn = load_db_bytes(buf)?;
|
|
126
129
|
let has_revisions_table: bool = conn.prepare(
|
|
127
130
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='external_file_revisions'"
|
|
128
131
|
)?.query_row([], |row| row.get::<_, i32>(0)).unwrap_or(0) > 0;
|
|
129
132
|
|
|
133
|
+
let has_data_table: bool = conn.prepare(
|
|
134
|
+
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='external_file_revision_data'"
|
|
135
|
+
)?.query_row([], |row| row.get::<_, i32>(0)).unwrap_or(0) > 0;
|
|
136
|
+
|
|
130
137
|
if has_revisions_table {
|
|
131
138
|
// New schema: load file header + revisions.
|
|
132
139
|
let file = conn.prepare(
|
|
@@ -146,30 +153,77 @@ pub fn get_external_file(buf: &[u8], file_id: &str) -> ParseResult<Option<DucExt
|
|
|
146
153
|
Err(e) => return Err(e.into()),
|
|
147
154
|
};
|
|
148
155
|
|
|
149
|
-
let mut
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
156
|
+
let mut data_map: HashMap<String, serde_bytes::ByteBuf> = HashMap::new();
|
|
157
|
+
|
|
158
|
+
if has_data_table {
|
|
159
|
+
// Split schema: metadata in external_file_revisions, data in external_file_revision_data
|
|
160
|
+
let mut meta_stmt = conn.prepare(
|
|
161
|
+
"SELECT id, size_bytes, checksum, source_name, mime_type, message,
|
|
162
|
+
created, last_retrieved
|
|
163
|
+
FROM external_file_revisions WHERE file_id = ?1"
|
|
164
|
+
)?;
|
|
165
|
+
let meta_rows = meta_stmt.query_map(params![file_id], |row| {
|
|
166
|
+
let id: String = row.get(0)?;
|
|
167
|
+
Ok((id.clone(), ExternalFileRevisionMeta {
|
|
168
|
+
id,
|
|
169
|
+
size_bytes: row.get(1)?,
|
|
170
|
+
checksum: row.get(2)?,
|
|
171
|
+
source_name: row.get(3)?,
|
|
172
|
+
mime_type: row.get(4)?,
|
|
173
|
+
message: row.get(5)?,
|
|
174
|
+
created: row.get(6)?,
|
|
175
|
+
last_retrieved: row.get(7)?,
|
|
176
|
+
}))
|
|
177
|
+
})?;
|
|
178
|
+
for row in meta_rows {
|
|
179
|
+
let (rev_id, meta) = row?;
|
|
180
|
+
file.revisions.insert(rev_id, meta);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let mut data_stmt = conn.prepare(
|
|
184
|
+
"SELECT d.revision_id, d.data
|
|
185
|
+
FROM external_file_revision_data d
|
|
186
|
+
JOIN external_file_revisions r ON r.id = d.revision_id
|
|
187
|
+
WHERE r.file_id = ?1"
|
|
188
|
+
)?;
|
|
189
|
+
let data_rows = data_stmt.query_map(params![file_id], |row| {
|
|
190
|
+
let rev_id: String = row.get(0)?;
|
|
191
|
+
let blob: Vec<u8> = row.get(1)?;
|
|
192
|
+
Ok((rev_id, blob))
|
|
193
|
+
})?;
|
|
194
|
+
for row in data_rows {
|
|
195
|
+
let (rev_id, blob) = row?;
|
|
196
|
+
data_map.insert(rev_id, serde_bytes::ByteBuf::from(blob));
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
// Pre-split schema: data still in external_file_revisions
|
|
200
|
+
let mut rev_stmt = conn.prepare(
|
|
201
|
+
"SELECT id, size_bytes, checksum, source_name, mime_type, message,
|
|
202
|
+
created, last_retrieved, data
|
|
203
|
+
FROM external_file_revisions WHERE file_id = ?1"
|
|
204
|
+
)?;
|
|
205
|
+
let rev_rows = rev_stmt.query_map(params![file_id], |row| {
|
|
206
|
+
let id: String = row.get(0)?;
|
|
207
|
+
let data: Vec<u8> = row.get(8)?;
|
|
208
|
+
Ok((id.clone(), ExternalFileRevisionMeta {
|
|
209
|
+
id,
|
|
210
|
+
size_bytes: row.get(1)?,
|
|
211
|
+
checksum: row.get(2)?,
|
|
212
|
+
source_name: row.get(3)?,
|
|
213
|
+
mime_type: row.get(4)?,
|
|
214
|
+
message: row.get(5)?,
|
|
215
|
+
created: row.get(6)?,
|
|
216
|
+
last_retrieved: row.get(7)?,
|
|
217
|
+
}, data))
|
|
218
|
+
})?;
|
|
219
|
+
for row in rev_rows {
|
|
220
|
+
let (rev_id, meta, blob) = row?;
|
|
221
|
+
file.revisions.insert(rev_id.clone(), meta);
|
|
222
|
+
data_map.insert(rev_id, serde_bytes::ByteBuf::from(blob));
|
|
223
|
+
}
|
|
171
224
|
}
|
|
172
|
-
|
|
225
|
+
|
|
226
|
+
Ok(Some(ExternalFileLoaded { file, data: data_map }))
|
|
173
227
|
} else {
|
|
174
228
|
// Legacy schema: wrap flat row into a single-revision DucExternalFile.
|
|
175
229
|
let result = conn.prepare(
|
|
@@ -177,30 +231,34 @@ pub fn get_external_file(buf: &[u8], file_id: &str) -> ParseResult<Option<DucExt
|
|
|
177
231
|
)?.query_row(params![file_id], |row| {
|
|
178
232
|
let id: String = row.get(0)?;
|
|
179
233
|
let rev_id = format!("{}_rev1", id);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
234
|
+
let blob: Vec<u8> = row.get(2)?;
|
|
235
|
+
let meta = ExternalFileRevisionMeta {
|
|
236
|
+
id: rev_id.clone(),
|
|
237
|
+
size_bytes: blob.len() as i64,
|
|
238
|
+
checksum: None,
|
|
239
|
+
source_name: None,
|
|
240
|
+
mime_type: row.get(1)?,
|
|
241
|
+
message: None,
|
|
242
|
+
created: row.get(3)?,
|
|
243
|
+
last_retrieved: row.get(4)?,
|
|
244
|
+
};
|
|
245
|
+
let mut revisions = HashMap::new();
|
|
246
|
+
revisions.insert(rev_id.clone(), meta);
|
|
247
|
+
let mut data_map = HashMap::new();
|
|
248
|
+
data_map.insert(rev_id.clone(), serde_bytes::ByteBuf::from(blob));
|
|
249
|
+
Ok(ExternalFileLoaded {
|
|
250
|
+
file: DucExternalFile {
|
|
251
|
+
id: id.clone(),
|
|
252
|
+
active_revision_id: rev_id,
|
|
253
|
+
updated: row.get(3)?,
|
|
254
|
+
version: row.get(5)?,
|
|
255
|
+
revisions,
|
|
199
256
|
},
|
|
257
|
+
data: data_map,
|
|
200
258
|
})
|
|
201
259
|
});
|
|
202
260
|
match result {
|
|
203
|
-
Ok(
|
|
261
|
+
Ok(loaded) => Ok(Some(loaded)),
|
|
204
262
|
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
|
205
263
|
Err(e) => Err(e.into()),
|
|
206
264
|
}
|
|
@@ -579,14 +637,6 @@ fn int_to_image_status(v: i32) -> IMAGE_STATUS {
|
|
|
579
637
|
_ => IMAGE_STATUS::PENDING,
|
|
580
638
|
}
|
|
581
639
|
}
|
|
582
|
-
fn int_to_pruning_level(v: i32) -> PRUNING_LEVEL {
|
|
583
|
-
match v {
|
|
584
|
-
10 => PRUNING_LEVEL::CONSERVATIVE,
|
|
585
|
-
20 => PRUNING_LEVEL::BALANCED,
|
|
586
|
-
30 => PRUNING_LEVEL::AGGRESSIVE,
|
|
587
|
-
_ => PRUNING_LEVEL::BALANCED,
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
640
|
fn int_to_boolean_operation(v: i32) -> BOOLEAN_OPERATION {
|
|
591
641
|
match v {
|
|
592
642
|
10 => BOOLEAN_OPERATION::UNION,
|
|
@@ -636,7 +686,7 @@ fn read_document(conn: &Connection) -> ParseResult<(Option<String>, String, Stri
|
|
|
636
686
|
|
|
637
687
|
fn read_global_state(conn: &Connection) -> ParseResult<Option<DucGlobalState>> {
|
|
638
688
|
let mut stmt = conn.prepare(
|
|
639
|
-
"SELECT name, view_background_color, main_scope, scope_exponent_threshold
|
|
689
|
+
"SELECT name, view_background_color, main_scope, scope_exponent_threshold
|
|
640
690
|
FROM duc_global_state WHERE id = 1"
|
|
641
691
|
)?;
|
|
642
692
|
let result = stmt.query_row([], |row| {
|
|
@@ -645,7 +695,6 @@ fn read_global_state(conn: &Connection) -> ParseResult<Option<DucGlobalState>> {
|
|
|
645
695
|
view_background_color: row.get(1)?,
|
|
646
696
|
main_scope: row.get(2)?,
|
|
647
697
|
scope_exponent_threshold: row.get(3)?,
|
|
648
|
-
pruning_level: int_to_pruning_level(row.get(4)?),
|
|
649
698
|
})
|
|
650
699
|
});
|
|
651
700
|
|
|
@@ -1768,31 +1817,46 @@ fn read_hatch_pattern_lines(conn: &Connection, owner_type: &str, owner_id: i64)
|
|
|
1768
1817
|
|
|
1769
1818
|
// ─── external_files ──────────────────────────────────────────────────────────
|
|
1770
1819
|
|
|
1771
|
-
|
|
1820
|
+
type ExternalFilesResult = (
|
|
1821
|
+
Option<HashMap<String, DucExternalFile>>,
|
|
1822
|
+
Option<HashMap<String, serde_bytes::ByteBuf>>,
|
|
1823
|
+
);
|
|
1824
|
+
|
|
1825
|
+
fn read_external_files(conn: &Connection) -> ParseResult<ExternalFilesResult> {
|
|
1772
1826
|
// Check if the new schema (with external_file_revisions) is present.
|
|
1773
1827
|
let has_revisions_table: bool = conn.prepare(
|
|
1774
1828
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='external_file_revisions'"
|
|
1775
1829
|
)?.query_row([], |row| row.get::<_, i32>(0)).unwrap_or(0) > 0;
|
|
1776
1830
|
|
|
1777
1831
|
if has_revisions_table {
|
|
1778
|
-
|
|
1832
|
+
let has_data_table: bool = conn.prepare(
|
|
1833
|
+
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='external_file_revision_data'"
|
|
1834
|
+
)?.query_row([], |row| row.get::<_, i32>(0)).unwrap_or(0) > 0;
|
|
1835
|
+
|
|
1836
|
+
if has_data_table {
|
|
1837
|
+
read_external_files_v3(conn)
|
|
1838
|
+
} else {
|
|
1839
|
+
read_external_files_v2(conn)
|
|
1840
|
+
}
|
|
1779
1841
|
} else {
|
|
1780
1842
|
read_external_files_v1_legacy(conn)
|
|
1781
1843
|
}
|
|
1782
1844
|
}
|
|
1783
1845
|
|
|
1784
|
-
/// Read from the
|
|
1785
|
-
fn
|
|
1786
|
-
// Load
|
|
1846
|
+
/// Read from the split schema (external_file_revisions metadata + external_file_revision_data).
|
|
1847
|
+
fn read_external_files_v3(conn: &Connection) -> ParseResult<ExternalFilesResult> {
|
|
1848
|
+
// Load revision metadata
|
|
1787
1849
|
let mut rev_stmt = conn.prepare(
|
|
1788
1850
|
"SELECT id, file_id, size_bytes, checksum, source_name, mime_type, message,
|
|
1789
|
-
created, last_retrieved
|
|
1851
|
+
created, last_retrieved
|
|
1790
1852
|
FROM external_file_revisions"
|
|
1791
1853
|
)?;
|
|
1792
|
-
let mut revisions_by_file: HashMap<String, HashMap<String,
|
|
1854
|
+
let mut revisions_by_file: HashMap<String, HashMap<String, ExternalFileRevisionMeta>> = HashMap::new();
|
|
1793
1855
|
let rev_rows = rev_stmt.query_map([], |row| {
|
|
1794
|
-
|
|
1795
|
-
|
|
1856
|
+
let rev_id: String = row.get(0)?;
|
|
1857
|
+
let file_id: String = row.get(1)?;
|
|
1858
|
+
Ok((file_id, rev_id.clone(), ExternalFileRevisionMeta {
|
|
1859
|
+
id: rev_id,
|
|
1796
1860
|
size_bytes: row.get(2)?,
|
|
1797
1861
|
checksum: row.get(3)?,
|
|
1798
1862
|
source_name: row.get(4)?,
|
|
@@ -1800,23 +1864,69 @@ fn read_external_files_v2(conn: &Connection) -> ParseResult<Option<HashMap<Strin
|
|
|
1800
1864
|
message: row.get(6)?,
|
|
1801
1865
|
created: row.get(7)?,
|
|
1802
1866
|
last_retrieved: row.get(8)?,
|
|
1803
|
-
|
|
1804
|
-
|
|
1867
|
+
}))
|
|
1868
|
+
})?;
|
|
1869
|
+
for row in rev_rows {
|
|
1870
|
+
let (file_id, rev_id, meta) = row?;
|
|
1871
|
+
revisions_by_file.entry(file_id).or_default().insert(rev_id, meta);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// Load data blobs
|
|
1875
|
+
let mut data_stmt = conn.prepare(
|
|
1876
|
+
"SELECT revision_id, data FROM external_file_revision_data"
|
|
1877
|
+
)?;
|
|
1878
|
+
let mut data_map: HashMap<String, serde_bytes::ByteBuf> = HashMap::new();
|
|
1879
|
+
let data_rows = data_stmt.query_map([], |row| {
|
|
1880
|
+
let rev_id: String = row.get(0)?;
|
|
1881
|
+
let blob: Vec<u8> = row.get(1)?;
|
|
1882
|
+
Ok((rev_id, blob))
|
|
1805
1883
|
})?;
|
|
1884
|
+
for row in data_rows {
|
|
1885
|
+
let (rev_id, blob) = row?;
|
|
1886
|
+
data_map.insert(rev_id, serde_bytes::ByteBuf::from(blob));
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// Load file headers
|
|
1890
|
+
let mut file_stmt = conn.prepare(
|
|
1891
|
+
"SELECT id, active_revision_id, updated, version FROM external_files"
|
|
1892
|
+
)?;
|
|
1893
|
+
let mut map: HashMap<String, DucExternalFile> = HashMap::new();
|
|
1894
|
+
let file_rows = file_stmt.query_map([], |row| {
|
|
1895
|
+
let id: String = row.get(0)?;
|
|
1896
|
+
Ok((id.clone(), DucExternalFile {
|
|
1897
|
+
id,
|
|
1898
|
+
active_revision_id: row.get(1)?,
|
|
1899
|
+
updated: row.get(2)?,
|
|
1900
|
+
revisions: HashMap::new(),
|
|
1901
|
+
version: row.get(3)?,
|
|
1902
|
+
}))
|
|
1903
|
+
})?;
|
|
1904
|
+
for row in file_rows {
|
|
1905
|
+
let (k, mut v) = row?;
|
|
1906
|
+
v.revisions = revisions_by_file.remove(&k).unwrap_or_default();
|
|
1907
|
+
map.insert(k, v);
|
|
1908
|
+
}
|
|
1806
1909
|
|
|
1807
|
-
|
|
1808
|
-
let
|
|
1910
|
+
let files = if map.is_empty() { None } else { Some(map) };
|
|
1911
|
+
let data = if data_map.is_empty() { None } else { Some(data_map) };
|
|
1912
|
+
Ok((files, data))
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
/// Read from the pre-split schema (external_files + external_file_revisions with data column).
|
|
1916
|
+
fn read_external_files_v2(conn: &Connection) -> ParseResult<ExternalFilesResult> {
|
|
1917
|
+
let mut rev_stmt = conn.prepare(
|
|
1809
1918
|
"SELECT id, file_id, size_bytes, checksum, source_name, mime_type, message,
|
|
1810
1919
|
created, last_retrieved, data
|
|
1811
1920
|
FROM external_file_revisions"
|
|
1812
1921
|
)?;
|
|
1813
|
-
|
|
1814
|
-
|
|
1922
|
+
let mut revisions_by_file: HashMap<String, HashMap<String, ExternalFileRevisionMeta>> = HashMap::new();
|
|
1923
|
+
let mut data_map: HashMap<String, serde_bytes::ByteBuf> = HashMap::new();
|
|
1815
1924
|
|
|
1816
|
-
let
|
|
1925
|
+
let rev_rows = rev_stmt.query_map([], |row| {
|
|
1817
1926
|
let rev_id: String = row.get(0)?;
|
|
1818
1927
|
let file_id: String = row.get(1)?;
|
|
1819
|
-
|
|
1928
|
+
let blob: Vec<u8> = row.get(9)?;
|
|
1929
|
+
Ok((file_id, rev_id.clone(), ExternalFileRevisionMeta {
|
|
1820
1930
|
id: rev_id,
|
|
1821
1931
|
size_bytes: row.get(2)?,
|
|
1822
1932
|
checksum: row.get(3)?,
|
|
@@ -1825,12 +1935,12 @@ fn read_external_files_v2(conn: &Connection) -> ParseResult<Option<HashMap<Strin
|
|
|
1825
1935
|
message: row.get(6)?,
|
|
1826
1936
|
created: row.get(7)?,
|
|
1827
1937
|
last_retrieved: row.get(8)?,
|
|
1828
|
-
|
|
1829
|
-
}))
|
|
1938
|
+
}, blob))
|
|
1830
1939
|
})?;
|
|
1831
|
-
for row in
|
|
1832
|
-
let (file_id, rev_id,
|
|
1833
|
-
revisions_by_file.entry(file_id).or_default().insert(rev_id,
|
|
1940
|
+
for row in rev_rows {
|
|
1941
|
+
let (file_id, rev_id, meta, blob) = row?;
|
|
1942
|
+
revisions_by_file.entry(file_id).or_default().insert(rev_id.clone(), meta);
|
|
1943
|
+
data_map.insert(rev_id, serde_bytes::ByteBuf::from(blob));
|
|
1834
1944
|
}
|
|
1835
1945
|
|
|
1836
1946
|
// Load the file headers.
|
|
@@ -1854,46 +1964,53 @@ fn read_external_files_v2(conn: &Connection) -> ParseResult<Option<HashMap<Strin
|
|
|
1854
1964
|
map.insert(k, v);
|
|
1855
1965
|
}
|
|
1856
1966
|
|
|
1857
|
-
if map.is_empty() {
|
|
1967
|
+
let files = if map.is_empty() { None } else { Some(map) };
|
|
1968
|
+
let data = if data_map.is_empty() { None } else { Some(data_map) };
|
|
1969
|
+
Ok((files, data))
|
|
1858
1970
|
}
|
|
1859
1971
|
|
|
1860
1972
|
/// Fallback for old schema files that only have the flat `external_files` table.
|
|
1861
1973
|
/// Wraps each row in a single-revision DucExternalFile.
|
|
1862
|
-
fn read_external_files_v1_legacy(conn: &Connection) -> ParseResult<
|
|
1974
|
+
fn read_external_files_v1_legacy(conn: &Connection) -> ParseResult<ExternalFilesResult> {
|
|
1863
1975
|
let mut stmt = conn.prepare(
|
|
1864
1976
|
"SELECT id, mime_type, data, created, last_retrieved, version FROM external_files"
|
|
1865
1977
|
)?;
|
|
1866
1978
|
let mut map = HashMap::new();
|
|
1979
|
+
let mut data_map: HashMap<String, serde_bytes::ByteBuf> = HashMap::new();
|
|
1867
1980
|
let rows = stmt.query_map([], |row| {
|
|
1868
1981
|
let id: String = row.get(0)?;
|
|
1869
1982
|
let rev_id = format!("{}_rev1", id);
|
|
1870
|
-
|
|
1871
|
-
|
|
1983
|
+
let blob: Vec<u8> = row.get(2)?;
|
|
1984
|
+
let meta = ExternalFileRevisionMeta {
|
|
1985
|
+
id: rev_id.clone(),
|
|
1986
|
+
size_bytes: blob.len() as i64,
|
|
1987
|
+
checksum: None,
|
|
1988
|
+
source_name: None,
|
|
1989
|
+
mime_type: row.get(1)?,
|
|
1990
|
+
message: None,
|
|
1991
|
+
created: row.get(3)?,
|
|
1992
|
+
last_retrieved: row.get(4)?,
|
|
1993
|
+
};
|
|
1994
|
+
Ok((id.clone(), rev_id.clone(), DucExternalFile {
|
|
1995
|
+
id,
|
|
1872
1996
|
active_revision_id: rev_id.clone(),
|
|
1873
1997
|
updated: row.get(3)?,
|
|
1874
1998
|
version: row.get(5)?,
|
|
1875
1999
|
revisions: {
|
|
1876
2000
|
let mut revs = HashMap::new();
|
|
1877
|
-
revs.insert(rev_id.clone(),
|
|
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
|
-
});
|
|
2001
|
+
revs.insert(rev_id.clone(), meta);
|
|
1888
2002
|
revs
|
|
1889
2003
|
},
|
|
1890
|
-
}))
|
|
2004
|
+
}, blob))
|
|
1891
2005
|
})?;
|
|
1892
2006
|
for row in rows {
|
|
1893
|
-
let (k, v) = row?;
|
|
2007
|
+
let (k, rev_id, v, blob) = row?;
|
|
2008
|
+
data_map.insert(rev_id, serde_bytes::ByteBuf::from(blob));
|
|
1894
2009
|
map.insert(k, v);
|
|
1895
2010
|
}
|
|
1896
|
-
if map.is_empty() {
|
|
2011
|
+
let files = if map.is_empty() { None } else { Some(map) };
|
|
2012
|
+
let data = if data_map.is_empty() { None } else { Some(data_map) };
|
|
2013
|
+
Ok((files, data))
|
|
1897
2014
|
}
|
|
1898
2015
|
|
|
1899
2016
|
// ─── version_graph ───────────────────────────────────────────────────────────
|
|
@@ -1910,7 +2027,7 @@ fn read_version_graph(conn: &Connection) -> ParseResult<Option<VersionGraph>> {
|
|
|
1910
2027
|
|
|
1911
2028
|
let mut vg_stmt = conn.prepare(
|
|
1912
2029
|
"SELECT current_version, current_schema_version, user_checkpoint_version_id,
|
|
1913
|
-
latest_version_id, chain_count,
|
|
2030
|
+
latest_version_id, chain_count, total_size
|
|
1914
2031
|
FROM version_graph WHERE id = 1"
|
|
1915
2032
|
)?;
|
|
1916
2033
|
let (metadata, user_cp_id, latest_id) = match vg_stmt.query_row([], |row| {
|
|
@@ -1919,8 +2036,7 @@ fn read_version_graph(conn: &Connection) -> ParseResult<Option<VersionGraph>> {
|
|
|
1919
2036
|
current_version: row.get(0)?,
|
|
1920
2037
|
current_schema_version: row.get(1)?,
|
|
1921
2038
|
chain_count: row.get(4)?,
|
|
1922
|
-
|
|
1923
|
-
total_size: row.get::<_, Option<i64>>(6)?.unwrap_or(0),
|
|
2039
|
+
total_size: row.get::<_, Option<i64>>(5)?.unwrap_or(0),
|
|
1924
2040
|
},
|
|
1925
2041
|
row.get::<_, Option<String>>(2)?.unwrap_or_default(),
|
|
1926
2042
|
row.get::<_, Option<String>>(3)?.unwrap_or_default(),
|
|
@@ -68,7 +68,7 @@ pub fn serialize(state: &ExportedDataState) -> SerializeResult<Vec<u8>> {
|
|
|
68
68
|
write_stack_and_containers(&tx, state)?;
|
|
69
69
|
write_blocks(&tx, state)?;
|
|
70
70
|
write_elements(&tx, state)?;
|
|
71
|
-
write_external_files(&tx, &state.external_files)?;
|
|
71
|
+
write_external_files(&tx, &state.external_files, &state.external_files_data)?;
|
|
72
72
|
write_version_graph(&tx, &state.version_graph)?;
|
|
73
73
|
tx.commit()?;
|
|
74
74
|
|
|
@@ -149,14 +149,13 @@ fn write_global_state(tx: &Transaction, gs: &Option<DucGlobalState>) -> Serializ
|
|
|
149
149
|
let Some(gs) = gs else { return Ok(()) };
|
|
150
150
|
tx.execute(
|
|
151
151
|
"INSERT OR REPLACE INTO duc_global_state
|
|
152
|
-
(id, name, view_background_color, main_scope, scope_exponent_threshold
|
|
153
|
-
VALUES (1, ?1, ?2, ?3, ?4
|
|
152
|
+
(id, name, view_background_color, main_scope, scope_exponent_threshold)
|
|
153
|
+
VALUES (1, ?1, ?2, ?3, ?4)",
|
|
154
154
|
params![
|
|
155
155
|
gs.name,
|
|
156
156
|
gs.view_background_color,
|
|
157
157
|
gs.main_scope,
|
|
158
158
|
gs.scope_exponent_threshold,
|
|
159
|
-
gs.pruning_level as i32,
|
|
160
159
|
],
|
|
161
160
|
)?;
|
|
162
161
|
Ok(())
|
|
@@ -1124,6 +1123,7 @@ fn write_hatch_pattern_lines(
|
|
|
1124
1123
|
fn write_external_files(
|
|
1125
1124
|
tx: &Transaction,
|
|
1126
1125
|
files: &Option<std::collections::HashMap<String, DucExternalFile>>,
|
|
1126
|
+
files_data: &Option<std::collections::HashMap<String, serde_bytes::ByteBuf>>,
|
|
1127
1127
|
) -> SerializeResult<()> {
|
|
1128
1128
|
let Some(files) = files else { return Ok(()) };
|
|
1129
1129
|
let mut file_stmt = tx.prepare_cached(
|
|
@@ -1132,8 +1132,12 @@ fn write_external_files(
|
|
|
1132
1132
|
)?;
|
|
1133
1133
|
let mut rev_stmt = tx.prepare_cached(
|
|
1134
1134
|
"INSERT OR REPLACE INTO external_file_revisions
|
|
1135
|
-
(id, file_id, size_bytes, checksum, source_name, mime_type, message, created, last_retrieved
|
|
1136
|
-
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9
|
|
1135
|
+
(id, file_id, size_bytes, checksum, source_name, mime_type, message, created, last_retrieved)
|
|
1136
|
+
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)"
|
|
1137
|
+
)?;
|
|
1138
|
+
let mut data_stmt = tx.prepare_cached(
|
|
1139
|
+
"INSERT OR REPLACE INTO external_file_revision_data (revision_id, data)
|
|
1140
|
+
VALUES (?1, ?2)"
|
|
1137
1141
|
)?;
|
|
1138
1142
|
for (_key, file) in files {
|
|
1139
1143
|
file_stmt.execute(params![
|
|
@@ -1153,10 +1157,15 @@ fn write_external_files(
|
|
|
1153
1157
|
rev.message,
|
|
1154
1158
|
rev.created,
|
|
1155
1159
|
rev.last_retrieved,
|
|
1156
|
-
rev.data,
|
|
1157
1160
|
])?;
|
|
1158
1161
|
}
|
|
1159
1162
|
}
|
|
1163
|
+
// Write data blobs from the separate filesData map
|
|
1164
|
+
if let Some(data_map) = files_data {
|
|
1165
|
+
for (rev_id, blob) in data_map {
|
|
1166
|
+
data_stmt.execute(params![rev_id, blob.as_ref()])?;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1160
1169
|
Ok(())
|
|
1161
1170
|
}
|
|
1162
1171
|
|
|
@@ -1172,8 +1181,7 @@ fn write_version_graph(tx: &Transaction, vg: &Option<VersionGraph>) -> Serialize
|
|
|
1172
1181
|
user_checkpoint_version_id = ?3,
|
|
1173
1182
|
latest_version_id = ?4,
|
|
1174
1183
|
chain_count = ?5,
|
|
1175
|
-
|
|
1176
|
-
total_size = ?7
|
|
1184
|
+
total_size = ?6
|
|
1177
1185
|
WHERE id = 1",
|
|
1178
1186
|
params![
|
|
1179
1187
|
vg.metadata.current_version,
|
|
@@ -1181,7 +1189,6 @@ fn write_version_graph(tx: &Transaction, vg: &Option<VersionGraph>) -> Serialize
|
|
|
1181
1189
|
vg.user_checkpoint_version_id,
|
|
1182
1190
|
vg.latest_version_id,
|
|
1183
1191
|
vg.metadata.chain_count,
|
|
1184
|
-
vg.metadata.last_pruned,
|
|
1185
1192
|
vg.metadata.total_size,
|
|
1186
1193
|
],
|
|
1187
1194
|
)?;
|
|
@@ -249,18 +249,6 @@ pub enum IMAGE_STATUS {
|
|
|
249
249
|
ERROR = 12,
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
#[allow(non_camel_case_types)]
|
|
253
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)]
|
|
254
|
-
#[repr(i32)]
|
|
255
|
-
pub enum PRUNING_LEVEL {
|
|
256
|
-
/** Conservative pruning, retains more history. */
|
|
257
|
-
CONSERVATIVE = 10,
|
|
258
|
-
/** Balanced pruning, optimizes between history and size. */
|
|
259
|
-
BALANCED = 20,
|
|
260
|
-
/** Aggressive pruning, retains less history for smaller size. */
|
|
261
|
-
AGGRESSIVE = 30,
|
|
262
|
-
}
|
|
263
|
-
|
|
264
252
|
/**
|
|
265
253
|
* Defines the types of boolean operations that can be performed.
|
|
266
254
|
*/
|
|
@@ -1326,8 +1314,6 @@ pub struct DucGlobalState {
|
|
|
1326
1314
|
* This value defines a +/- tolerance range around the exponent of the current scope.
|
|
1327
1315
|
*/
|
|
1328
1316
|
pub scope_exponent_threshold: i8,
|
|
1329
|
-
/** The level of pruning to the versions from the version graph. */
|
|
1330
|
-
pub pruning_level: PRUNING_LEVEL,
|
|
1331
1317
|
}
|
|
1332
1318
|
|
|
1333
1319
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
@@ -1464,7 +1450,6 @@ pub struct VersionGraphMetadata {
|
|
|
1464
1450
|
pub current_schema_version: i32,
|
|
1465
1451
|
#[serde(deserialize_with = "crate::serde_utils::trunc_i32")]
|
|
1466
1452
|
pub chain_count: i32,
|
|
1467
|
-
pub last_pruned: i64,
|
|
1468
1453
|
pub total_size: i64,
|
|
1469
1454
|
}
|
|
1470
1455
|
|
|
@@ -1485,11 +1470,11 @@ pub struct VersionGraph {
|
|
|
1485
1470
|
|
|
1486
1471
|
// =============== EXTERNAL FILES ===============
|
|
1487
1472
|
|
|
1488
|
-
///
|
|
1473
|
+
/// Revision metadata without the heavy data blob.
|
|
1474
|
+
/// Used inside `DucExternalFile.revisions` so the file record stays lightweight.
|
|
1489
1475
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
1490
1476
|
#[serde(rename_all = "camelCase")]
|
|
1491
|
-
pub struct
|
|
1492
|
-
// Revision number as string, functions as an incremental serial id
|
|
1477
|
+
pub struct ExternalFileRevisionMeta {
|
|
1493
1478
|
pub id: String,
|
|
1494
1479
|
pub size_bytes: i64,
|
|
1495
1480
|
/// Content hash for integrity checks and optional deduplication.
|
|
@@ -1503,11 +1488,8 @@ pub struct ExternalFileRevision {
|
|
|
1503
1488
|
pub created: i64,
|
|
1504
1489
|
/// Epoch ms when this revision was last loaded onto the scene.
|
|
1505
1490
|
pub last_retrieved: Option<i64>,
|
|
1506
|
-
|
|
1507
|
-
/// The actual file content bytes
|
|
1508
|
-
#[serde(with = "serde_bytes")]
|
|
1509
|
-
pub data: Vec<u8>,
|
|
1510
1491
|
}
|
|
1492
|
+
|
|
1511
1493
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
1512
1494
|
#[serde(rename_all = "camelCase")]
|
|
1513
1495
|
pub struct DucExternalFile {
|
|
@@ -1515,11 +1497,21 @@ pub struct DucExternalFile {
|
|
|
1515
1497
|
pub active_revision_id: String,
|
|
1516
1498
|
/// Epoch ms when the logical file was last mutated (revision added or active changed).
|
|
1517
1499
|
pub updated: i64,
|
|
1518
|
-
|
|
1519
|
-
pub revisions: HashMap<String,
|
|
1500
|
+
/// All revisions of this file (metadata only, no data blobs).
|
|
1501
|
+
pub revisions: HashMap<String, ExternalFileRevisionMeta>,
|
|
1520
1502
|
pub version: Option<i32>,
|
|
1521
1503
|
}
|
|
1522
1504
|
|
|
1505
|
+
/// Result of on-demand file loading — includes both metadata and data blobs.
|
|
1506
|
+
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
1507
|
+
#[serde(rename_all = "camelCase")]
|
|
1508
|
+
pub struct ExternalFileLoaded {
|
|
1509
|
+
#[serde(flatten)]
|
|
1510
|
+
pub file: DucExternalFile,
|
|
1511
|
+
/// Revision data blobs keyed by revision id.
|
|
1512
|
+
pub data: HashMap<String, serde_bytes::ByteBuf>,
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1523
1515
|
/// Lightweight summary of an external file used for lazy/metadata-only access
|
|
1524
1516
|
/// (active revision's metadata + file-level version, no data blob).
|
|
1525
1517
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
@@ -1564,4 +1556,8 @@ pub struct ExportedDataState {
|
|
|
1564
1556
|
pub version_graph: Option<VersionGraph>,
|
|
1565
1557
|
#[serde(rename = "files")]
|
|
1566
1558
|
pub external_files: Option<HashMap<String, DucExternalFile>>,
|
|
1559
|
+
/// Binary data blobs for external file revisions, keyed by revision id.
|
|
1560
|
+
/// Separated from `external_files` so metadata can travel without heavy blobs.
|
|
1561
|
+
#[serde(rename = "filesData", default, skip_serializing_if = "Option::is_none")]
|
|
1562
|
+
pub external_files_data: Option<HashMap<String, serde_bytes::ByteBuf>>,
|
|
1567
1563
|
}
|
|
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
|