Scan nested Java aggregate roots

This commit is contained in:
John Burwell 2026-06-02 16:24:25 -05:00
parent 6e728724bb
commit ca21654b44
2 changed files with 128 additions and 52 deletions

View File

@ -733,47 +733,49 @@ enum JavaContentScanner {
let fileManager = FileManager.default
var discoveredItems: [MinecraftContentItem] = []
let savesRootURL = existingDirectory(
named: "saves",
in: searchRootURL,
fileManager: fileManager
) ?? searchRootURL
let worldItems = try discoverWorlds(in: savesRootURL, fileManager: fileManager)
discoveredItems.append(contentsOf: worldItems)
if let resourcePacksURL = existingDirectory(named: "resourcepacks", in: searchRootURL, fileManager: fileManager) {
let resourcePackItems = try discoverResourcePacks(in: resourcePacksURL, fileManager: fileManager)
discoveredItems.append(contentsOf: resourcePackItems)
}
if let dataPacksURL = existingDirectory(named: "datapacks", in: searchRootURL, fileManager: fileManager) {
discoveredItems.append(contentsOf: try discoverJavaPackages(
in: dataPacksURL,
contentKind: .dataPack,
platformType: .dataPack,
packageExtension: "zip",
for scanRootURL in contentScanRoots(for: searchRootURL, fileManager: fileManager) {
let savesRootURL = existingDirectory(
named: "saves",
in: scanRootURL,
fileManager: fileManager
))
}
) ?? scanRootURL
let worldItems = try discoverWorlds(in: savesRootURL, fileManager: fileManager)
discoveredItems.append(contentsOf: worldItems)
if let shaderPacksURL = existingDirectory(named: "shaderpacks", in: searchRootURL, fileManager: fileManager) {
discoveredItems.append(contentsOf: try discoverJavaPackages(
in: shaderPacksURL,
contentKind: .shaderPack,
platformType: .shaderPack,
packageExtension: "zip",
fileManager: fileManager
))
}
if let resourcePacksURL = existingDirectory(named: "resourcepacks", in: scanRootURL, fileManager: fileManager) {
let resourcePackItems = try discoverResourcePacks(in: resourcePacksURL, fileManager: fileManager)
discoveredItems.append(contentsOf: resourcePackItems)
}
if let modsURL = existingDirectory(named: "mods", in: searchRootURL, fileManager: fileManager) {
discoveredItems.append(contentsOf: try discoverJavaPackages(
in: modsURL,
contentKind: .mod,
platformType: .mod,
packageExtension: "jar",
fileManager: fileManager
))
if let dataPacksURL = existingDirectory(named: "datapacks", in: scanRootURL, fileManager: fileManager) {
discoveredItems.append(contentsOf: try discoverJavaPackages(
in: dataPacksURL,
contentKind: .dataPack,
platformType: .dataPack,
packageExtension: "zip",
fileManager: fileManager
))
}
if let shaderPacksURL = existingDirectory(named: "shaderpacks", in: scanRootURL, fileManager: fileManager) {
discoveredItems.append(contentsOf: try discoverJavaPackages(
in: shaderPacksURL,
contentKind: .shaderPack,
platformType: .shaderPack,
packageExtension: "zip",
fileManager: fileManager
))
}
if let modsURL = existingDirectory(named: "mods", in: scanRootURL, fileManager: fileManager) {
discoveredItems.append(contentsOf: try discoverJavaPackages(
in: modsURL,
contentKind: .mod,
platformType: .mod,
packageExtension: "jar",
fileManager: fileManager
))
}
}
discoveredItems.sort(by: WorldScanner.sortItems)
@ -806,21 +808,32 @@ enum JavaContentScanner {
nonisolated static func collectionSnapshots(in sourceRootURL: URL) -> [CollectionSnapshot] {
let fileManager = FileManager.default
let candidateRoots = [
existingDirectory(named: "saves", in: sourceRootURL, fileManager: fileManager),
existingDirectory(named: "resourcepacks", in: sourceRootURL, fileManager: fileManager),
existingDirectory(named: "datapacks", in: sourceRootURL, fileManager: fileManager),
existingDirectory(named: "shaderpacks", in: sourceRootURL, fileManager: fileManager),
existingDirectory(named: "mods", in: sourceRootURL, fileManager: fileManager)
]
var snapshots: [CollectionSnapshot] = []
for scanRootURL in contentScanRoots(for: sourceRootURL, fileManager: fileManager) {
let candidateRoots = [
existingDirectory(named: "saves", in: scanRootURL, fileManager: fileManager),
existingDirectory(named: "resourcepacks", in: scanRootURL, fileManager: fileManager),
existingDirectory(named: "datapacks", in: scanRootURL, fileManager: fileManager),
existingDirectory(named: "shaderpacks", in: scanRootURL, fileManager: fileManager),
existingDirectory(named: "mods", in: scanRootURL, fileManager: fileManager)
]
return candidateRoots.compactMap { collectionURL in
guard let collectionURL else {
return nil
for collectionURL in candidateRoots {
guard let collectionURL else {
continue
}
if let snapshot = collectionSnapshot(
for: collectionURL,
sourceRootURL: sourceRootURL,
fileManager: fileManager
) {
snapshots.append(snapshot)
}
}
return collectionSnapshot(for: collectionURL, fileManager: fileManager)
}
return snapshots
}
nonisolated private static func discoverWorlds(in savesRootURL: URL, fileManager: FileManager) throws -> [MinecraftContentItem] {
@ -1080,6 +1093,27 @@ enum JavaContentScanner {
return folders
}
nonisolated private static func contentScanRoots(for sourceRootURL: URL, fileManager: FileManager) -> [URL] {
let standardizedRoot = sourceRootURL.standardizedFileURL
if javaProbeScore(for: standardizedRoot, fileManager: fileManager).value > 0 {
return [standardizedRoot]
}
let discoveredRoots = boundedCandidateFolders(
from: standardizedRoot,
maxDepth: 4,
maxFolderCount: 600,
fileManager: fileManager
).filter { candidateURL in
candidateURL != standardizedRoot
&& javaProbeScore(for: candidateURL, fileManager: fileManager).value > 0
}
return uniqueStandardizedURLs(discoveredRoots).sorted {
$0.path.localizedStandardCompare($1.path) == .orderedAscending
}
}
nonisolated private static func uniqueStandardizedURLs(_ urls: [URL]) -> [URL] {
var seen = Set<String>()
var result: [URL] = []
@ -1099,6 +1133,7 @@ enum JavaContentScanner {
nonisolated private static func collectionSnapshot(
for collectionURL: URL,
sourceRootURL: URL,
fileManager: FileManager
) -> CollectionSnapshot? {
guard fileManager.fileExists(atPath: collectionURL.path) else {
@ -1135,12 +1170,15 @@ enum JavaContentScanner {
].joined(separator: "@")
}.joined(separator: "|")
let folderName = relativePath(from: sourceRootURL.standardizedFileURL, to: collectionURL.standardizedFileURL)
?? collectionURL.lastPathComponent
return CollectionSnapshot(
folderName: collectionURL.lastPathComponent,
folderName: folderName,
modifiedDate: modifiedDate,
childDirectoryCount: childSnapshots.count,
fingerprint: [
collectionURL.lastPathComponent,
folderName,
String(childSnapshots.count),
modifiedDate?.timeIntervalSince1970.formatted() ?? "nil",
childFingerprint
@ -1148,6 +1186,16 @@ enum JavaContentScanner {
)
}
nonisolated private static func relativePath(from rootURL: URL, to childURL: URL) -> String? {
let rootPath = rootURL.standardizedFileURL.path
let childPath = childURL.standardizedFileURL.path
guard childPath.hasPrefix(rootPath + "/") else {
return nil
}
return String(childPath.dropFirst(rootPath.count + 1))
}
nonisolated private static func displayName(for item: MinecraftContentItem) -> String {
guard item.contentKind == .world else {
return item.folderName

View File

@ -385,6 +385,34 @@ struct World_Manager_for_MinecraftTests {
#expect(candidates.first?.detectedKinds.contains(.mod) == true)
}
@Test func javaAggregateRootDiscoversNestedInstanceItems() async throws {
let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let firstInstanceURL = workingURL.appendingPathComponent("a/b/c", isDirectory: true)
let secondInstanceURL = workingURL.appendingPathComponent("a/e/f", isDirectory: true)
defer { try? fileManager.removeItem(at: workingURL) }
try fileManager.createDirectory(
at: firstInstanceURL.appendingPathComponent("mods", isDirectory: true),
withIntermediateDirectories: true
)
try Data("jar".utf8).write(to: firstInstanceURL.appendingPathComponent("mods/ExampleMod.jar"))
try fileManager.createDirectory(
at: secondInstanceURL.appendingPathComponent("resourcepacks", isDirectory: true),
withIntermediateDirectories: true
)
try Data("zip".utf8).write(to: secondInstanceURL.appendingPathComponent("resourcepacks/ExamplePack.zip"))
let items = try JavaContentScanner.discoverItems(in: workingURL)
let snapshots = JavaContentScanner.collectionSnapshots(in: workingURL)
#expect(items.contains { $0.contentKind == .mod && $0.folderName == "ExampleMod.jar" })
#expect(items.contains { $0.contentKind == .resourcePack && $0.folderName == "ExamplePack.zip" })
#expect(snapshots.map(\.folderName).contains("a/b/c/mods"))
#expect(snapshots.map(\.folderName).contains("a/e/f/resourcepacks"))
}
@Test func sourceLibraryAddSourceResolvesJavaWrapperFolder() async throws {
let fileManager = FileManager.default
let rootURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)