Scan nested Java aggregate roots
This commit is contained in:
parent
6e728724bb
commit
ca21654b44
@ -733,47 +733,49 @@ enum JavaContentScanner {
|
|||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
var discoveredItems: [MinecraftContentItem] = []
|
var discoveredItems: [MinecraftContentItem] = []
|
||||||
|
|
||||||
let savesRootURL = existingDirectory(
|
for scanRootURL in contentScanRoots(for: searchRootURL, fileManager: fileManager) {
|
||||||
named: "saves",
|
let savesRootURL = existingDirectory(
|
||||||
in: searchRootURL,
|
named: "saves",
|
||||||
fileManager: fileManager
|
in: scanRootURL,
|
||||||
) ?? 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",
|
|
||||||
fileManager: fileManager
|
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) {
|
if let resourcePacksURL = existingDirectory(named: "resourcepacks", in: scanRootURL, fileManager: fileManager) {
|
||||||
discoveredItems.append(contentsOf: try discoverJavaPackages(
|
let resourcePackItems = try discoverResourcePacks(in: resourcePacksURL, fileManager: fileManager)
|
||||||
in: shaderPacksURL,
|
discoveredItems.append(contentsOf: resourcePackItems)
|
||||||
contentKind: .shaderPack,
|
}
|
||||||
platformType: .shaderPack,
|
|
||||||
packageExtension: "zip",
|
|
||||||
fileManager: fileManager
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let modsURL = existingDirectory(named: "mods", in: searchRootURL, fileManager: fileManager) {
|
if let dataPacksURL = existingDirectory(named: "datapacks", in: scanRootURL, fileManager: fileManager) {
|
||||||
discoveredItems.append(contentsOf: try discoverJavaPackages(
|
discoveredItems.append(contentsOf: try discoverJavaPackages(
|
||||||
in: modsURL,
|
in: dataPacksURL,
|
||||||
contentKind: .mod,
|
contentKind: .dataPack,
|
||||||
platformType: .mod,
|
platformType: .dataPack,
|
||||||
packageExtension: "jar",
|
packageExtension: "zip",
|
||||||
fileManager: fileManager
|
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)
|
discoveredItems.sort(by: WorldScanner.sortItems)
|
||||||
@ -806,21 +808,32 @@ enum JavaContentScanner {
|
|||||||
|
|
||||||
nonisolated static func collectionSnapshots(in sourceRootURL: URL) -> [CollectionSnapshot] {
|
nonisolated static func collectionSnapshots(in sourceRootURL: URL) -> [CollectionSnapshot] {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let candidateRoots = [
|
var snapshots: [CollectionSnapshot] = []
|
||||||
existingDirectory(named: "saves", in: sourceRootURL, fileManager: fileManager),
|
for scanRootURL in contentScanRoots(for: sourceRootURL, fileManager: fileManager) {
|
||||||
existingDirectory(named: "resourcepacks", in: sourceRootURL, fileManager: fileManager),
|
let candidateRoots = [
|
||||||
existingDirectory(named: "datapacks", in: sourceRootURL, fileManager: fileManager),
|
existingDirectory(named: "saves", in: scanRootURL, fileManager: fileManager),
|
||||||
existingDirectory(named: "shaderpacks", in: sourceRootURL, fileManager: fileManager),
|
existingDirectory(named: "resourcepacks", in: scanRootURL, fileManager: fileManager),
|
||||||
existingDirectory(named: "mods", in: sourceRootURL, 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
|
for collectionURL in candidateRoots {
|
||||||
guard let collectionURL else {
|
guard let collectionURL else {
|
||||||
return nil
|
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] {
|
nonisolated private static func discoverWorlds(in savesRootURL: URL, fileManager: FileManager) throws -> [MinecraftContentItem] {
|
||||||
@ -1080,6 +1093,27 @@ enum JavaContentScanner {
|
|||||||
return folders
|
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] {
|
nonisolated private static func uniqueStandardizedURLs(_ urls: [URL]) -> [URL] {
|
||||||
var seen = Set<String>()
|
var seen = Set<String>()
|
||||||
var result: [URL] = []
|
var result: [URL] = []
|
||||||
@ -1099,6 +1133,7 @@ enum JavaContentScanner {
|
|||||||
|
|
||||||
nonisolated private static func collectionSnapshot(
|
nonisolated private static func collectionSnapshot(
|
||||||
for collectionURL: URL,
|
for collectionURL: URL,
|
||||||
|
sourceRootURL: URL,
|
||||||
fileManager: FileManager
|
fileManager: FileManager
|
||||||
) -> CollectionSnapshot? {
|
) -> CollectionSnapshot? {
|
||||||
guard fileManager.fileExists(atPath: collectionURL.path) else {
|
guard fileManager.fileExists(atPath: collectionURL.path) else {
|
||||||
@ -1135,12 +1170,15 @@ enum JavaContentScanner {
|
|||||||
].joined(separator: "@")
|
].joined(separator: "@")
|
||||||
}.joined(separator: "|")
|
}.joined(separator: "|")
|
||||||
|
|
||||||
|
let folderName = relativePath(from: sourceRootURL.standardizedFileURL, to: collectionURL.standardizedFileURL)
|
||||||
|
?? collectionURL.lastPathComponent
|
||||||
|
|
||||||
return CollectionSnapshot(
|
return CollectionSnapshot(
|
||||||
folderName: collectionURL.lastPathComponent,
|
folderName: folderName,
|
||||||
modifiedDate: modifiedDate,
|
modifiedDate: modifiedDate,
|
||||||
childDirectoryCount: childSnapshots.count,
|
childDirectoryCount: childSnapshots.count,
|
||||||
fingerprint: [
|
fingerprint: [
|
||||||
collectionURL.lastPathComponent,
|
folderName,
|
||||||
String(childSnapshots.count),
|
String(childSnapshots.count),
|
||||||
modifiedDate?.timeIntervalSince1970.formatted() ?? "nil",
|
modifiedDate?.timeIntervalSince1970.formatted() ?? "nil",
|
||||||
childFingerprint
|
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 {
|
nonisolated private static func displayName(for item: MinecraftContentItem) -> String {
|
||||||
guard item.contentKind == .world else {
|
guard item.contentKind == .world else {
|
||||||
return item.folderName
|
return item.folderName
|
||||||
|
|||||||
@ -385,6 +385,34 @@ struct World_Manager_for_MinecraftTests {
|
|||||||
#expect(candidates.first?.detectedKinds.contains(.mod) == true)
|
#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 {
|
@Test func sourceLibraryAddSourceResolvesJavaWrapperFolder() async throws {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let rootURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
let rootURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user