@@ -73,6 +73,8 @@ def __init__(self):
7373
7474 self .source_files : List [str ] = [] # Holds both files and folders
7575 self .file_to_folder_map : Dict [str , Optional [str ]] = {} # 记录文件来自哪个文件夹
76+ self .excluded_subfolders : set = set () # 记录被删除的子文件夹路径
77+ self .folder_tree_cache : Dict [str , dict ] = {} # 缓存文件夹的完整树结构 {top_folder: tree_structure}
7678
7779 self .app_config = AppConfig ()
7880 self .logger .info ("主页面应用业务逻辑初始化完成" )
@@ -750,11 +752,47 @@ def remove_file(self, file_path: str):
750752 # 如果是文件,清理 file_to_folder_map
751753 if norm_file_path in self .file_to_folder_map :
752754 del self .file_to_folder_map [norm_file_path ]
755+
756+ # 如果是文件夹,清理排除列表中该文件夹下的所有子文件夹
757+ if os .path .isdir (norm_file_path ):
758+ excluded_to_remove = set ()
759+ for excluded_folder in self .excluded_subfolders :
760+ try :
761+ # 检查 excluded_folder 是否在被删除的文件夹内
762+ common = os .path .commonpath ([norm_file_path , excluded_folder ])
763+ if common == norm_file_path :
764+ excluded_to_remove .add (excluded_folder )
765+ except ValueError :
766+ continue
767+ self .excluded_subfolders -= excluded_to_remove
768+
753769 self .file_removed .emit (file_path )
754770 return
755771
756- # 情况2:文件夹路径(通过单独添加文件自动分组的 )
772+ # 情况2:文件夹路径(可能是顶层文件夹或子文件夹 )
757773 if os .path .isdir (norm_file_path ):
774+ # 检查是否是某个顶层文件夹的子文件夹
775+ parent_folder = None
776+ for folder in self .source_files :
777+ if os .path .isdir (folder ):
778+ try :
779+ # 检查 norm_file_path 是否是 folder 的子文件夹
780+ common = os .path .commonpath ([folder , norm_file_path ])
781+ if common == os .path .normpath (folder ) and norm_file_path != os .path .normpath (folder ):
782+ parent_folder = folder
783+ break
784+ except ValueError :
785+ continue
786+
787+ if parent_folder :
788+ # 这是子文件夹,添加到排除列表
789+ self .excluded_subfolders .add (norm_file_path )
790+ # 发射删除信号让 FileListView 处理
791+ # FileListView 会自动更新树形结构和文件数量
792+ self .file_removed .emit (file_path )
793+ return
794+
795+ # 不是子文件夹,可能是通过单独添加文件自动分组的文件夹
758796 # 删除该文件夹下的所有文件
759797 files_to_remove = []
760798 for source_file in self .source_files :
@@ -833,11 +871,103 @@ def clear_file_list(self):
833871 # TODO: Add confirmation dialog
834872 self .source_files .clear ()
835873 self .file_to_folder_map .clear () # 清空文件夹映射
874+ self .excluded_subfolders .clear () # 清空排除列表
836875 self .files_cleared .emit ()
837876 self .logger .info ("File list cleared by user." )
838877 # endregion
839878
840879 # region 核心任务逻辑
880+ def get_folder_tree_structure (self ) -> dict :
881+ """
882+ 获取完整的文件夹树结构
883+ 返回: {
884+ 'files': [所有文件列表],
885+ 'tree': {
886+ 'folder_path': {
887+ 'files': [该文件夹直接包含的文件],
888+ 'subfolders': [子文件夹路径列表]
889+ }
890+ }
891+ }
892+ """
893+ tree = {}
894+ all_files = []
895+
896+ # 处理每个顶层文件夹
897+ for source_path in self .source_files :
898+ if os .path .isdir (source_path ):
899+ norm_folder = os .path .normpath (source_path )
900+ # 递归构建该文件夹的树结构
901+ folder_files = self ._build_folder_tree (norm_folder , tree )
902+ all_files .extend (folder_files )
903+ elif os .path .isfile (source_path ):
904+ # 单独添加的文件
905+ all_files .append (source_path )
906+
907+ return {
908+ 'files' : all_files ,
909+ 'tree' : tree
910+ }
911+
912+ def _build_folder_tree (self , folder_path : str , tree : dict ) -> List [str ]:
913+ """
914+ 递归构建文件夹树结构
915+ 返回该文件夹及其子文件夹中的所有文件列表
916+ """
917+ # 检查是否被排除
918+ if folder_path in self .excluded_subfolders :
919+ return []
920+
921+ norm_folder = os .path .normpath (folder_path )
922+
923+ # 初始化该文件夹的树节点
924+ if norm_folder not in tree :
925+ tree [norm_folder ] = {
926+ 'files' : [],
927+ 'subfolders' : []
928+ }
929+
930+ all_files = []
931+ image_extensions = {'.png' , '.jpg' , '.jpeg' , '.bmp' , '.webp' }
932+
933+ try :
934+ items = os .listdir (folder_path )
935+ subdirs = []
936+ files = []
937+
938+ for item in items :
939+ if item == 'manga_translator_work' :
940+ continue
941+
942+ item_path = os .path .join (folder_path , item )
943+ norm_item_path = os .path .normpath (item_path )
944+
945+ if os .path .isdir (item_path ):
946+ # 检查是否被排除
947+ if norm_item_path not in self .excluded_subfolders :
948+ subdirs .append (norm_item_path )
949+ tree [norm_folder ]['subfolders' ].append (norm_item_path )
950+ elif os .path .splitext (item )[1 ].lower () in image_extensions :
951+ files .append (norm_item_path )
952+
953+ # 排序
954+ subdirs .sort (key = self .file_service ._natural_sort_key )
955+ files .sort (key = self .file_service ._natural_sort_key )
956+
957+ # 添加该文件夹直接包含的文件
958+ tree [norm_folder ]['files' ] = files
959+ all_files .extend (files )
960+
961+ # 递归处理子文件夹
962+ for subdir in subdirs :
963+ subdir_files = self ._build_folder_tree (subdir , tree )
964+ all_files .extend (subdir_files )
965+
966+ except Exception as e :
967+ self .logger .error (f"Error building tree for folder { folder_path } : { e } " )
968+
969+ return all_files
970+
841971 def _resolve_input_files (self ) -> List [str ]:
842972 """
843973 Expands folders in self.source_files into a list of image files.
@@ -860,13 +990,50 @@ def _resolve_input_files(self) -> List[str]:
860990 if self .file_service .validate_image_file (path ):
861991 individual_files .append (path )
862992
993+ # 清理排除列表:移除不再属于任何 source_files 文件夹的排除项
994+ if self .excluded_subfolders :
995+ excluded_to_remove = set ()
996+ for excluded_folder in self .excluded_subfolders :
997+ # 检查这个排除的文件夹是否还在某个 source_files 的文件夹内
998+ is_valid = False
999+ for folder in folders :
1000+ try :
1001+ common = os .path .commonpath ([folder , excluded_folder ])
1002+ if common == os .path .normpath (folder ):
1003+ is_valid = True
1004+ break
1005+ except ValueError :
1006+ continue
1007+ if not is_valid :
1008+ excluded_to_remove .add (excluded_folder )
1009+ self .excluded_subfolders -= excluded_to_remove
1010+
8631011 # 对文件夹进行自然排序
8641012 folders .sort (key = self .file_service ._natural_sort_key )
8651013
8661014 # 按文件夹分组处理
8671015 for folder in folders :
8681016 # 获取文件夹中的所有图片(递归查找所有子文件夹,已经使用自然排序)
8691017 folder_files = self .file_service .get_image_files_from_folder (folder , recursive = True )
1018+
1019+ # 过滤掉被排除的子文件夹中的文件
1020+ if self .excluded_subfolders :
1021+ filtered_files = []
1022+ for file_path in folder_files :
1023+ # 检查文件是否在被排除的子文件夹中
1024+ is_excluded = False
1025+ for excluded_folder in self .excluded_subfolders :
1026+ try :
1027+ common = os .path .commonpath ([excluded_folder , file_path ])
1028+ if common == excluded_folder :
1029+ is_excluded = True
1030+ break
1031+ except ValueError :
1032+ continue
1033+ if not is_excluded :
1034+ filtered_files .append (file_path )
1035+ folder_files = filtered_files
1036+
8701037 resolved_files .extend (folder_files )
8711038 # 记录这些文件来自这个文件夹
8721039 for file_path in folder_files :
0 commit comments