mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2025-12-11 20:15:30 +01:00
Batch load tags for entries and refine path filter
This commit is contained in:
parent
83138f798e
commit
97cc3853b5
@ -684,6 +684,94 @@ impl File {
|
||||
});
|
||||
}
|
||||
|
||||
// Batch load tags (both entry-scoped and content-scoped)
|
||||
use crate::infra::db::entities::{tag, user_metadata, user_metadata_tag};
|
||||
let mut tags_by_entry: HashMap<Uuid, Vec<Tag>> = HashMap::new();
|
||||
|
||||
// Load user_metadata for entries and content
|
||||
let metadata_records = user_metadata::Entity::find()
|
||||
.filter(
|
||||
user_metadata::Column::EntryUuid
|
||||
.is_in(entry_uuids.iter().copied())
|
||||
.or(user_metadata::Column::ContentIdentityUuid.is_in(content_uuids.clone())),
|
||||
)
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
if !metadata_records.is_empty() {
|
||||
let metadata_ids: Vec<i32> = metadata_records.iter().map(|m| m.id).collect();
|
||||
|
||||
// Load user_metadata_tag records
|
||||
let metadata_tags = user_metadata_tag::Entity::find()
|
||||
.filter(user_metadata_tag::Column::UserMetadataId.is_in(metadata_ids))
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
if !metadata_tags.is_empty() {
|
||||
let tag_ids: Vec<i32> = metadata_tags.iter().map(|mt| mt.tag_id).collect();
|
||||
|
||||
// Load tag entities
|
||||
let tag_models = tag::Entity::find()
|
||||
.filter(tag::Column::Id.is_in(tag_ids))
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
// Build tag_id -> Tag mapping using the tags manager converter
|
||||
let tag_map: HashMap<i32, Tag> = tag_models
|
||||
.into_iter()
|
||||
.filter_map(|t| {
|
||||
let db_id = t.id;
|
||||
crate::ops::tags::manager::model_to_domain(t)
|
||||
.ok()
|
||||
.map(|tag| (db_id, tag))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Build metadata_id -> Vec<Tag> mapping
|
||||
let mut tags_by_metadata: HashMap<i32, Vec<Tag>> = HashMap::new();
|
||||
for mt in metadata_tags {
|
||||
if let Some(tag) = tag_map.get(&mt.tag_id) {
|
||||
tags_by_metadata
|
||||
.entry(mt.user_metadata_id)
|
||||
.or_default()
|
||||
.push(tag.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Map tags to entries (handle both entry-scoped and content-scoped)
|
||||
for metadata in &metadata_records {
|
||||
if let Some(tags) = tags_by_metadata.get(&metadata.id) {
|
||||
// Entry-scoped metadata
|
||||
if let Some(entry_uuid) = metadata.entry_uuid {
|
||||
tags_by_entry
|
||||
.entry(entry_uuid)
|
||||
.or_default()
|
||||
.extend(tags.clone());
|
||||
}
|
||||
// Content-scoped metadata: apply to all entries with this content
|
||||
else if let Some(content_uuid) = metadata.content_identity_uuid {
|
||||
// Find all entries with this content_id
|
||||
if let Some(ci) = content_by_id
|
||||
.values()
|
||||
.find(|ci| ci.uuid == Some(content_uuid))
|
||||
{
|
||||
if let Some(alt_entries) = entries_by_content_id.get(&ci.id) {
|
||||
for alt_entry in alt_entries {
|
||||
if let Some(entry_uuid) = alt_entry.uuid {
|
||||
tags_by_entry
|
||||
.entry(entry_uuid)
|
||||
.or_default()
|
||||
.extend(tags.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build File instances
|
||||
let mut files = Vec::new();
|
||||
for entry_model in entries {
|
||||
@ -780,6 +868,11 @@ impl File {
|
||||
}
|
||||
}
|
||||
|
||||
// Add tags from batch lookup
|
||||
if let Some(tags) = tags_by_entry.get(&entry_uuid) {
|
||||
file.tags = tags.clone();
|
||||
}
|
||||
|
||||
files.push(file);
|
||||
}
|
||||
|
||||
|
||||
@ -315,30 +315,38 @@ export function filterBatchResources(
|
||||
!options.includeDescendants
|
||||
) {
|
||||
filtered = filtered.filter((resource: any) => {
|
||||
// Files can use Content-based sd_path in some events
|
||||
// but have Physical paths in alternate_paths
|
||||
const alternatePaths = resource.alternate_paths || [];
|
||||
const physicalPath = alternatePaths.find((p: any) => p.Physical);
|
||||
|
||||
if (!physicalPath?.Physical) {
|
||||
return false; // No physical path
|
||||
}
|
||||
|
||||
const pathStr = physicalPath.Physical.path;
|
||||
// Get the scope path (must be Physical)
|
||||
const scopeStr = (options.pathScope as any).Physical?.path;
|
||||
|
||||
if (!scopeStr) {
|
||||
return false; // No scope path
|
||||
return false; // No Physical scope path
|
||||
}
|
||||
|
||||
// Normalize scope: remove trailing slashes for consistent comparison
|
||||
const normalizedScope = String(scopeStr).replace(/\/+$/, "");
|
||||
|
||||
// Try to find a Physical path - check alternate_paths first, then sd_path
|
||||
const alternatePaths = resource.alternate_paths || [];
|
||||
const physicalFromAlternate = alternatePaths.find((p: any) => p.Physical);
|
||||
const physicalFromSdPath = resource.sd_path?.Physical;
|
||||
|
||||
const physicalPath = physicalFromAlternate?.Physical || physicalFromSdPath;
|
||||
|
||||
if (!physicalPath?.path) {
|
||||
return false; // No physical path found
|
||||
}
|
||||
|
||||
const pathStr = String(physicalPath.path);
|
||||
|
||||
// Extract parent directory from file path
|
||||
const lastSlash = pathStr.lastIndexOf("/");
|
||||
invariant(lastSlash !== -1, "File path must have a parent directory");
|
||||
if (lastSlash === -1) {
|
||||
return false; // File path has no parent directory
|
||||
}
|
||||
|
||||
const parentDir = pathStr.substring(0, lastSlash);
|
||||
|
||||
// Only match if parent equals scope
|
||||
return parentDir === scopeStr;
|
||||
// Only match if parent equals scope (normalized)
|
||||
return parentDir === normalizedScope;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user