mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
feat: ensure unique names for asset nodes during creation and batch upload
This commit is contained in:
parent
6b0e6b2785
commit
a750d87ae4
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from collections.abc import Generator
|
||||
from enum import StrEnum
|
||||
@ -59,12 +60,12 @@ class BatchUploadNode(BaseModel):
|
||||
|
||||
def to_app_asset_nodes(self, parent_id: str | None = None) -> list[AppAssetNode]:
|
||||
"""
|
||||
Generate IDs and convert to AppAssetNode list.
|
||||
Mutates self to set id field.
|
||||
Generate IDs when missing and convert to AppAssetNode list.
|
||||
Mutates self to set id field when it is not set.
|
||||
"""
|
||||
from uuid import uuid4
|
||||
|
||||
self.id = str(uuid4())
|
||||
self.id = self.id or str(uuid4())
|
||||
nodes: list[AppAssetNode] = []
|
||||
|
||||
if self.node_type == AssetNodeType.FOLDER:
|
||||
@ -114,6 +115,37 @@ class AppAssetFileTree(BaseModel):
|
||||
|
||||
nodes: list[AppAssetNode] = Field(default_factory=list, description="Flat list of all nodes in the tree")
|
||||
|
||||
def ensure_unique_name(
|
||||
self,
|
||||
parent_id: str | None,
|
||||
name: str,
|
||||
*,
|
||||
is_file: bool,
|
||||
extra_taken: set[str] | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Return a sibling-unique name by appending numeric suffixes when needed.
|
||||
|
||||
The suffix format is " <n>" (e.g. "report 1", "report 2"). For files,
|
||||
the suffix is inserted before the extension.
|
||||
"""
|
||||
taken = extra_taken or set()
|
||||
if not self.has_child_named(parent_id, name) and name not in taken:
|
||||
return name
|
||||
suffix_index = 1
|
||||
while True:
|
||||
candidate = self._apply_name_suffix(name, suffix_index, is_file=is_file)
|
||||
if not self.has_child_named(parent_id, candidate) and candidate not in taken:
|
||||
return candidate
|
||||
suffix_index += 1
|
||||
|
||||
@staticmethod
|
||||
def _apply_name_suffix(name: str, suffix_index: int, *, is_file: bool) -> str:
|
||||
if not is_file:
|
||||
return f"{name} {suffix_index}"
|
||||
stem, extension = os.path.splitext(name)
|
||||
return f"{stem} {suffix_index}{extension}"
|
||||
|
||||
def get(self, node_id: str) -> AppAssetNode | None:
|
||||
return next((n for n in self.nodes if n.id == node_id), None)
|
||||
|
||||
|
||||
@ -187,7 +187,12 @@ class AppAssetService:
|
||||
assets = AppAssetService.get_or_create_assets(session, app_model, account_id)
|
||||
tree = assets.asset_tree
|
||||
|
||||
node = AppAssetNode.create_folder(str(uuid4()), name, parent_id)
|
||||
unique_name = tree.ensure_unique_name(
|
||||
parent_id,
|
||||
name,
|
||||
is_file=False,
|
||||
)
|
||||
node = AppAssetNode.create_folder(str(uuid4()), unique_name, parent_id)
|
||||
|
||||
try:
|
||||
tree.add(node)
|
||||
@ -408,6 +413,9 @@ class AppAssetService:
|
||||
The file metadata is saved immediately. If the user doesn't upload,
|
||||
the download will fail when the file is accessed.
|
||||
|
||||
If a sibling with the same name exists, a numeric suffix is appended
|
||||
to make the name unique (e.g. "report 1.txt").
|
||||
|
||||
Returns:
|
||||
tuple of (node, upload_url)
|
||||
"""
|
||||
@ -416,8 +424,13 @@ class AppAssetService:
|
||||
assets = AppAssetService.get_or_create_assets(session, app_model, account_id)
|
||||
tree = assets.asset_tree
|
||||
|
||||
unique_name = tree.ensure_unique_name(
|
||||
parent_id,
|
||||
name,
|
||||
is_file=True,
|
||||
)
|
||||
node_id = str(uuid4())
|
||||
node = AppAssetNode.create_file(node_id, name, parent_id, size)
|
||||
node = AppAssetNode.create_file(node_id, unique_name, parent_id, size)
|
||||
|
||||
try:
|
||||
tree.add(node)
|
||||
@ -452,15 +465,41 @@ class AppAssetService:
|
||||
if not input_children:
|
||||
return []
|
||||
|
||||
new_nodes: list[AppAssetNode] = []
|
||||
for child in input_children:
|
||||
new_nodes.extend(child.to_app_asset_nodes(None))
|
||||
|
||||
with AppAssetService._lock(app_model.id):
|
||||
with Session(db.engine, expire_on_commit=False) as session:
|
||||
assets = AppAssetService.get_or_create_assets(session, app_model, account_id)
|
||||
tree = assets.asset_tree
|
||||
|
||||
def assign_ids_and_unique_names(
|
||||
nodes: list[BatchUploadNode],
|
||||
parent_id: str | None,
|
||||
taken_by_parent: dict[str | None, set[str]],
|
||||
) -> None:
|
||||
for node in nodes:
|
||||
if node.id is None:
|
||||
node.id = str(uuid4())
|
||||
if parent_id not in taken_by_parent:
|
||||
taken_by_parent[parent_id] = {
|
||||
child.name for child in tree.get_children(parent_id)
|
||||
}
|
||||
taken = taken_by_parent[parent_id]
|
||||
unique_name = tree.ensure_unique_name(
|
||||
parent_id,
|
||||
node.name,
|
||||
is_file=node.node_type == AssetNodeType.FILE,
|
||||
extra_taken=taken,
|
||||
)
|
||||
node.name = unique_name
|
||||
taken.add(unique_name)
|
||||
if node.node_type == AssetNodeType.FOLDER:
|
||||
assign_ids_and_unique_names(node.children, node.id, taken_by_parent)
|
||||
|
||||
assign_ids_and_unique_names(input_children, None, {})
|
||||
|
||||
new_nodes: list[AppAssetNode] = []
|
||||
for child in input_children:
|
||||
new_nodes.extend(child.to_app_asset_nodes(None))
|
||||
|
||||
try:
|
||||
for node in new_nodes:
|
||||
tree.add(node)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user