@@ -467,6 +467,27 @@ public async Task<List<BsonFileManagerModel>> getFilesByParentId(string dbName,
467467 . ToListAsync ( ) ;
468468 }
469469
470+ /// <summary>
471+ /// Find a folder by its name and parent FilterPath.
472+ /// This is useful for finding folders when FilePath format doesn't match the navigation path.
473+ /// </summary>
474+ public async Task < BsonFileManagerModel ? > getFolderByNameAndParentPath ( string dbName , string folderName , string parentFilterPath , string collectionName = "directory" )
475+ {
476+ if ( collectionName == null )
477+ collectionName = "directory" ;
478+
479+ // For first-level folders, FilterPath is "/"
480+ // For nested folders, FilterPath is the parent path (e.g., "/Parent/")
481+ var filter = Builders < BsonFileManagerModel > . Filter . Where ( x =>
482+ ! x . IsFile &&
483+ x . Name == folderName &&
484+ x . FilterPath == parentFilterPath ) ;
485+
486+ return await ( await getDatabase ( dbName ) . GetCollection < BsonFileManagerModel > ( collectionName )
487+ . FindAsync ( filter ) )
488+ . FirstOrDefaultAsync ( ) ;
489+ }
490+
470491 public async Task < List < int > > getAllIdsFromChannel ( string dbName , string collectionName = "directory" )
471492 {
472493 if ( collectionName == null )
@@ -548,8 +569,9 @@ public async Task<BsonFileManagerModel> copyItem(string dbName, string sourceId,
548569 result . DateModified = DateTime . Now ;
549570 result . FilterId = ( target . FilterId ?? "" ) + target . Id + "/" ;
550571 result . ParentId = target . Id ;
551- // Both empty string and "/" indicate root folder
552- var isRootTarget = string . IsNullOrEmpty ( target . FilterPath ) || target . FilterPath == "/" ;
572+ // Only empty string indicates root folder (the "Files" folder)
573+ // FilterPath "/" means a first-level folder, not the root itself
574+ var isRootTarget = string . IsNullOrEmpty ( target . FilterPath ) ;
553575 result . FilterPath = isRootTarget ? "/" : target . FilterPath + target . Name + "/" ;
554576 result . FilePath = isFile ? targetPath + result . Name : targetPath . TrimEnd ( '/' ) ;
555577 await getDatabase ( dbName ) . GetCollection < BsonFileManagerModel > ( collectionName ) . InsertOneAsync ( result ) ;
@@ -998,6 +1020,165 @@ public async Task<DatabaseStats> GetDatabaseStats(string dbName)
9981020 return stats ;
9991021 }
10001022
1023+ /// <summary>
1024+ /// Analyze FilterPath issues without repairing them.
1025+ /// Returns detailed information about items that need repair.
1026+ /// </summary>
1027+ public async Task < FilterPathAnalysisResult > AnalyzeFilterPaths ( string dbName , string collectionName = "directory" )
1028+ {
1029+ if ( collectionName == null )
1030+ collectionName = "directory" ;
1031+
1032+ var result = new FilterPathAnalysisResult { DatabaseName = dbName } ;
1033+
1034+ try
1035+ {
1036+ var collection = getDatabase ( dbName ) . GetCollection < BsonFileManagerModel > ( collectionName ) ;
1037+ var allItems = await ( await collection . FindAsync ( Builders < BsonFileManagerModel > . Filter . Empty ) ) . ToListAsync ( ) ;
1038+
1039+ result . TotalItems = allItems . Count ;
1040+
1041+ // Build a dictionary for quick lookup by Id
1042+ var itemsById = allItems . ToDictionary ( x => x . Id , x => x ) ;
1043+
1044+ foreach ( var item in allItems )
1045+ {
1046+ // Skip the root folder (no ParentId)
1047+ if ( string . IsNullOrEmpty ( item . ParentId ) )
1048+ continue ;
1049+
1050+ // Calculate the correct FilterPath and FilterId
1051+ var correctFilterPath = CalculateFilterPath ( item . ParentId , itemsById ) ;
1052+ var correctFilterId = CalculateFilterId ( item . ParentId , itemsById ) ;
1053+ var normalizedFilePath = item . FilePath ? . Replace ( "\\ " , "/" ) ;
1054+
1055+ bool hasIssue = false ;
1056+
1057+ if ( item . FilterPath != correctFilterPath )
1058+ {
1059+ result . FilterPathIssues ++ ;
1060+ hasIssue = true ;
1061+ }
1062+
1063+ if ( item . FilterId != correctFilterId )
1064+ {
1065+ result . FilterIdIssues ++ ;
1066+ hasIssue = true ;
1067+ }
1068+
1069+ if ( normalizedFilePath != item . FilePath )
1070+ {
1071+ result . FilePathIssues ++ ;
1072+ hasIssue = true ;
1073+ }
1074+
1075+ if ( hasIssue )
1076+ {
1077+ result . ItemsWithIssues ++ ;
1078+ }
1079+ }
1080+ }
1081+ catch ( Exception ex )
1082+ {
1083+ _logger . LogError ( ex , "Error analyzing FilterPaths for database {DbName}" , dbName ) ;
1084+ result . Error = ex . Message ;
1085+ }
1086+
1087+ return result ;
1088+ }
1089+
1090+ /// <summary>
1091+ /// Repair FilterPath and FilterId for all items in a database.
1092+ /// This fixes data corruption caused by incorrect path calculations during move operations.
1093+ /// </summary>
1094+ public async Task < int > RepairFilterPaths ( string dbName , string collectionName = "directory" )
1095+ {
1096+ if ( collectionName == null )
1097+ collectionName = "directory" ;
1098+
1099+ var collection = getDatabase ( dbName ) . GetCollection < BsonFileManagerModel > ( collectionName ) ;
1100+ var allItems = await ( await collection . FindAsync ( Builders < BsonFileManagerModel > . Filter . Empty ) ) . ToListAsync ( ) ;
1101+
1102+ // Build a dictionary for quick lookup by Id
1103+ var itemsById = allItems . ToDictionary ( x => x . Id , x => x ) ;
1104+
1105+ int repairedCount = 0 ;
1106+
1107+ foreach ( var item in allItems )
1108+ {
1109+ // Skip the root folder (no ParentId)
1110+ if ( string . IsNullOrEmpty ( item . ParentId ) )
1111+ continue ;
1112+
1113+ // Calculate the correct FilterPath and FilterId by walking up the parent chain
1114+ var correctFilterPath = CalculateFilterPath ( item . ParentId , itemsById ) ;
1115+ var correctFilterId = CalculateFilterId ( item . ParentId , itemsById ) ;
1116+
1117+ // Also normalize FilePath (replace backslashes with forward slashes)
1118+ var normalizedFilePath = item . FilePath ? . Replace ( "\\ " , "/" ) ;
1119+
1120+ bool needsUpdate = false ;
1121+ var updateDef = Builders < BsonFileManagerModel > . Update . Combine ( ) ;
1122+
1123+ if ( item . FilterPath != correctFilterPath )
1124+ {
1125+ updateDef = updateDef . Set ( x => x . FilterPath , correctFilterPath ) ;
1126+ needsUpdate = true ;
1127+ }
1128+
1129+ if ( item . FilterId != correctFilterId )
1130+ {
1131+ updateDef = updateDef . Set ( x => x . FilterId , correctFilterId ) ;
1132+ needsUpdate = true ;
1133+ }
1134+
1135+ if ( normalizedFilePath != item . FilePath )
1136+ {
1137+ updateDef = updateDef . Set ( x => x . FilePath , normalizedFilePath ) ;
1138+ needsUpdate = true ;
1139+ }
1140+
1141+ if ( needsUpdate )
1142+ {
1143+ await collection . UpdateOneAsync (
1144+ Builders < BsonFileManagerModel > . Filter . Eq ( x => x . Id , item . Id ) ,
1145+ updateDef ) ;
1146+ repairedCount ++ ;
1147+ }
1148+ }
1149+
1150+ _logger . LogInformation ( "Repaired {Count} items in database {DbName}" , repairedCount , dbName ) ;
1151+ return repairedCount ;
1152+ }
1153+
1154+ private string CalculateFilterPath ( string parentId , Dictionary < string , BsonFileManagerModel > itemsById )
1155+ {
1156+ if ( string . IsNullOrEmpty ( parentId ) || ! itemsById . TryGetValue ( parentId , out var parent ) )
1157+ return "/" ;
1158+
1159+ // If parent is root (no ParentId), return "/"
1160+ if ( string . IsNullOrEmpty ( parent . ParentId ) )
1161+ return "/" ;
1162+
1163+ // Otherwise, build the path recursively
1164+ var parentPath = CalculateFilterPath ( parent . ParentId , itemsById ) ;
1165+ return parentPath + parent . Name + "/" ;
1166+ }
1167+
1168+ private string CalculateFilterId ( string parentId , Dictionary < string , BsonFileManagerModel > itemsById )
1169+ {
1170+ if ( string . IsNullOrEmpty ( parentId ) || ! itemsById . TryGetValue ( parentId , out var parent ) )
1171+ return "" ;
1172+
1173+ // If parent is root (no ParentId), return just the parent's Id
1174+ if ( string . IsNullOrEmpty ( parent . ParentId ) )
1175+ return parent . Id + "/" ;
1176+
1177+ // Otherwise, build the FilterId recursively
1178+ var parentFilterId = CalculateFilterId ( parent . ParentId , itemsById ) ;
1179+ return parentFilterId + parent . Id + "/" ;
1180+ }
1181+
10011182 #endregion
10021183
10031184 }
0 commit comments