···
155
-
let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map).await?;
155
+
let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new()).await?;
let uploaded_count = total_files - reused_count;
···
/// Recursively build a Directory from a filesystem path
185
+
/// current_path is the path from the root of the site (e.g., "" for root, "config" for config dir)
agent: &'a Agent<impl jacquard::client::AgentSession + IdentityResolver + 'a>,
existing_blobs: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>,
190
+
current_path: String,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<(Directory<'static>, usize, usize)>> + 'a>>
···
let metadata = entry.metadata().into_diagnostic()?;
217
-
file_tasks.push((name_str, path));
219
+
// Construct full path for this file (for blob map lookup)
220
+
let full_path = if current_path.is_empty() {
223
+
format!("{}/{}", current_path, name_str)
225
+
file_tasks.push((name_str, path, full_path));
} else if metadata.is_dir() {
dir_tasks.push((name_str, path));
···
// Process files concurrently with a limit of 5
let file_results: Vec<(Entry<'static>, bool)> = stream::iter(file_tasks)
225
-
.map(|(name, path)| async move {
226
-
let (file_node, reused) = process_file(agent, &path, &name, existing_blobs).await?;
233
+
.map(|(name, path, full_path)| async move {
234
+
let (file_node, reused) = process_file(agent, &path, &full_path, existing_blobs).await?;
.name(CowStr::from(name))
.node(EntryNode::File(Box::new(file_node)))
···
// Process directories recursively (sequentially to avoid too much nesting)
let mut dir_entries = Vec::new();
for (name, path) in dir_tasks {
254
-
let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs).await?;
262
+
// Construct full path for subdirectory
263
+
let subdir_path = if current_path.is_empty() {
266
+
format!("{}/{}", current_path, name)
268
+
let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path).await?;
dir_entries.push(Entry::new()
.name(CowStr::from(name))
.node(EntryNode::Directory(Box::new(subdir)))
···
/// Process a single file: gzip -> base64 -> upload blob (or reuse existing)
/// Returns (File, reused: bool)
292
+
/// file_path_key is the full path from the site root (e.g., "config/file.json") for blob map lookup
agent: &Agent<impl jacquard::client::AgentSession + IdentityResolver>,
296
+
file_path_key: &str,
existing_blobs: &HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>,
) -> miette::Result<(File<'static>, bool)>
···
// Compute CID for this file (CRITICAL: on base64-encoded gzipped content)
let file_cid = cid::compute_cid(&base64_bytes);
304
-
// Normalize the file path for comparison
305
-
let normalized_path = blob_map::normalize_path(file_name);
// Check if we have an existing blob with the same CID
308
-
let existing_blob = existing_blobs.get(&normalized_path)
309
-
.or_else(|| existing_blobs.get(file_name));
320
+
let existing_blob = existing_blobs.get(file_path_key);
if let Some((existing_blob_ref, existing_cid)) = existing_blob {
if existing_cid == &file_cid {
// CIDs match - reuse existing blob
314
-
println!(" ✓ Reusing blob for {} (CID: {})", file_name, file_cid);
325
+
println!(" ✓ Reusing blob for {} (CID: {})", file_path_key, file_cid);
.r#type(CowStr::from("file"))
···
// File is new or changed - upload it
329
-
println!(" ↑ Uploading {} ({} bytes, CID: {})", file_name, base64_bytes.len(), file_cid);
340
+
println!(" ↑ Uploading {} ({} bytes, CID: {})", file_path_key, base64_bytes.len(), file_cid);
let blob = agent.upload_blob(
MimeType::new_static("application/octet-stream"),