feat: add export/import modes, unify service layer, fix binary detection
Export/import:
- Add --export and --import modes for both local and client paths
- Use strfmt crate for --export-filename-format templates ({id}, {tags}, {ts}, {compression})
- Import preserves original timestamps via server ?ts= param
- --import-data-file for file-based import; stdin fallback streams with PIPESIZE buffers
Service unification:
- Merge SyncDataService unique methods into ItemService (delete_item now returns Result<Item>)
- Delete AsyncDataService, AsyncItemService, DataService trait (dead code / async-blocking anti-pattern)
- All server handlers use spawn_blocking + ItemService directly
- Extract shared types (ExportMeta, ImportMeta) and helpers (resolve_item_id(s), check_binary_tty)
Binary detection fix:
- Replace broken metadata.get("map") + is_binary(&[]) with actual content sampling
- Both as_meta and allow_binary paths read PIPESIZE sample before deciding
- Never load entire item into memory for binary check
Other fixes:
- Fix lock consistency: all handlers use blocking_lock() in spawn_blocking (no mixed lock().await)
- Use ISO 8601 format for {ts} in export filenames
- Fix resolve_item_ids returning only 1 item for tag lookups
- Fix client get.rs triple-buffering and export.rs whole-file buffering
- Add KeepClient::get_item_content_stream() for streaming reads
- Pass all clippy --features server lints (Path vs PathBuf, &mut conn, etc.)
This commit is contained in:
@@ -357,6 +357,20 @@ impl KeepClient {
|
||||
}
|
||||
|
||||
pub fn get_item_content_raw(&self, id: i64) -> Result<(Vec<u8>, String), CoreError> {
|
||||
let (mut reader, compression) = self.get_item_content_stream(id)?;
|
||||
let mut bytes = Vec::new();
|
||||
reader
|
||||
.read_to_end(&mut bytes)
|
||||
.map_err(|e| CoreError::Other(anyhow::anyhow!("{}", e)))?;
|
||||
Ok((bytes, compression))
|
||||
}
|
||||
|
||||
/// Get a streaming reader for item content without decompression.
|
||||
///
|
||||
/// Returns a reader over the HTTP response body and the compression type
|
||||
/// from the X-Keep-Compression header. The caller can stream through
|
||||
/// decompression readers without buffering the entire file in memory.
|
||||
pub fn get_item_content_stream(&self, id: i64) -> Result<(Box<dyn Read>, String), CoreError> {
|
||||
let url = format!(
|
||||
"{}?decompress=false",
|
||||
self.url(&format!("/api/item/{id}/content"))
|
||||
@@ -376,12 +390,8 @@ impl KeepClient {
|
||||
.unwrap_or("none")
|
||||
.to_string();
|
||||
|
||||
let mut body = response.into_body();
|
||||
let bytes = body
|
||||
.read_to_vec()
|
||||
.map_err(|e| CoreError::Other(anyhow::anyhow!("{}", e)))?;
|
||||
|
||||
Ok((bytes, compression))
|
||||
let reader = response.into_body().into_reader();
|
||||
Ok((Box::new(reader), compression))
|
||||
}
|
||||
|
||||
pub fn diff_items(&self, id_a: i64, id_b: i64) -> Result<Vec<String>, CoreError> {
|
||||
|
||||
Reference in New Issue
Block a user