From b0cab126ebe0e10c2cf63943dd85a2574dd6b6d6 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Sat, 19 Oct 2024 11:54:22 +0800 Subject: [PATCH 1/4] feat: allow user specify project type when adding project --- docs/topics/pakku-add-prj.md | 3 ++ docs/topics/pakku-add.md | 3 ++ .../pakku/api/actions/Addition.kt | 1 + .../pakku/api/actions/update/Update.kt | 4 +- .../pakku/api/models/cf/CfModpackModel.kt | 5 ++- .../pakku/api/models/mr/MrModpackModel.kt | 6 +-- .../pakku/api/platforms/CurseForge.kt | 14 +++---- .../pakku/api/platforms/GitHub.kt | 16 +++++--- .../pakku/api/platforms/Modrinth.kt | 38 +++++++++++++------ .../pakku/api/platforms/Multiplatform.kt | 25 ++++++++---- .../pakku/api/platforms/Platform.kt | 30 +++++++++++---- .../pakku/api/platforms/Provider.kt | 11 +++++- .../teksturepako/pakku/cli/arg/Prompt.kt | 7 ++-- .../kotlin/teksturepako/pakku/cli/cmd/Add.kt | 15 ++++++-- .../teksturepako/pakku/cli/cmd/AddPrj.kt | 17 +++++++-- 15 files changed, 135 insertions(+), 60 deletions(-) diff --git a/docs/topics/pakku-add-prj.md b/docs/topics/pakku-add-prj.md index 9f27191f..acb8dfd3 100644 --- a/docs/topics/pakku-add-prj.md +++ b/docs/topics/pakku-add-prj.md @@ -27,6 +27,9 @@ Specify the project precisely `--gh`, `--github` : GitHub repository URL or `{owner}/{repo}` +`-t`, `--type=(mod|resource_pack|data_pack|world|shader)` +: Project type of project to add + `-h`, `--help` diff --git a/docs/topics/pakku-add.md b/docs/topics/pakku-add.md index efa0cb58..a82eb75c 100644 --- a/docs/topics/pakku-add.md +++ b/docs/topics/pakku-add.md @@ -36,6 +36,9 @@ Add projects `-D`, `--no-deps` : Ignore resolving dependencies +`-t`, `--type=(mod|resource_pack|data_pack|world|shader)` +: Project type of projects to add + `-h`, `--help` diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/actions/Addition.kt b/src/commonMain/kotlin/teksturepako/pakku/api/actions/Addition.kt index 94bac12d..ca20fbcd 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/actions/Addition.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/actions/Addition.kt @@ -5,6 +5,7 @@ import teksturepako.pakku.api.data.LockFile import teksturepako.pakku.api.platforms.GitHub import teksturepako.pakku.api.platforms.Platform import teksturepako.pakku.api.projects.Project +import teksturepako.pakku.api.projects.ProjectType data class RequestHandlers( val onError: suspend (error: ActionError) -> Unit, diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/actions/update/Update.kt b/src/commonMain/kotlin/teksturepako/pakku/api/actions/update/Update.kt index 4ed77c71..93fc8759 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/actions/update/Update.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/actions/update/Update.kt @@ -29,7 +29,7 @@ suspend fun updateMultipleProjectsWithFiles( .filter { GitHub.serialName in it.slug.keys } .map { oldProject -> val ghSlug = oldProject.slug[GitHub.serialName] ?: return@map oldProject - GitHub.requestProjectWithFiles(emptyList(), emptyList(), ghSlug) + GitHub.requestProjectWithFiles(emptyList(), emptyList(), ghSlug, projectType = oldProject.type) ?.inheritPropertiesFrom(configFile) ?.takeIf { it.hasFiles() } ?: oldProject @@ -40,7 +40,7 @@ suspend fun updateMultipleProjectsWithFiles( val platformProjects = platform.requestMultipleProjectsWithFiles( mcVersions, loaders, - accProjects.mapNotNull { it.id[platform.serialName] }, + accProjects.mapNotNull { project -> project.id[platform.serialName]?.let { it to project.type } }.toMap(), Int.MAX_VALUE ).inheritPropertiesFrom(configFile) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/models/cf/CfModpackModel.kt b/src/commonMain/kotlin/teksturepako/pakku/api/models/cf/CfModpackModel.kt index 2b35536f..355a70ab 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/models/cf/CfModpackModel.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/models/cf/CfModpackModel.kt @@ -45,6 +45,7 @@ data class CfModpackModel( val projects = CurseForge.requestMultipleProjects(this.files.map { it.projectID.toString() }) val projectFiles = CurseForge.requestMultipleProjectFiles(lockFile.getMcVersions(), lockFile.getLoaders(), + projects.mapNotNull { project -> project.slug[CurseForge.serialName]?.let { it to project.type } }.toMap(), this.files.map { it.fileID.toString() }) projects.assignFiles(projectFiles, CurseForge) @@ -55,8 +56,8 @@ data class CfModpackModel( debug { println("Modrinth sub-import") } val slugs = projects.mapNotNull { project -> - project.slug[CurseForge.serialName] - } + project.slug[CurseForge.serialName]?.let { it to project.type } + }.toMap() val mrProjects = Modrinth.requestMultipleProjectsWithFiles( lockFile.getMcVersions(), lockFile.getLoaders(), slugs, 1 diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/models/mr/MrModpackModel.kt b/src/commonMain/kotlin/teksturepako/pakku/api/models/mr/MrModpackModel.kt index 66bf7fda..7f7c9812 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/models/mr/MrModpackModel.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/models/mr/MrModpackModel.kt @@ -55,14 +55,14 @@ data class MrModpackModel( debug { println("CurseForge sub-import") } val slugs = projects.mapNotNull { project -> - project.slug[Modrinth.serialName] + project.slug[Modrinth.serialName]?.let { project to it } } val cfProjects = slugs.map { slug -> async { - CurseForge.requestProjectFromSlug(slug)?.apply { + CurseForge.requestProjectFromSlug(slug.second)?.apply { files += CurseForge.requestFilesForProject( - lockFile.getMcVersions(), lockFile.getLoaders(), this + lockFile.getMcVersions(), lockFile.getLoaders(), this, projectType = slug.first.type ) } } diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/CurseForge.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/CurseForge.kt index 2e49f3e1..29a8929f 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/CurseForge.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/CurseForge.kt @@ -63,11 +63,11 @@ object CurseForge : Platform( // -- PROJECT -- - override suspend fun requestProject(input: String): Project? = when + override suspend fun requestProject(input: String, projectType: ProjectType?): Project? = when { input.matches("[0-9]{5,6}".toRegex()) -> requestProjectFromId(input) else -> requestProjectFromSlug(input) - } + }.also { project -> projectType?.let { project?.type = it } } private fun CfModModel.toProject(): Project? { @@ -174,7 +174,7 @@ object CurseForge : Platform( } override suspend fun requestProjectFiles( - mcVersions: List, loaders: List, projectId: String, fileId: String? + mcVersions: List, loaders: List, projectId: String, fileId: String?, projectType: ProjectType? ): MutableSet { // Handle optional fileId @@ -219,7 +219,7 @@ object CurseForge : Platform( } override suspend fun requestMultipleProjectFiles( - mcVersions: List, loaders: List, ids: List + mcVersions: List, loaders: List, projectInfos: Map, ids: List ): MutableSet { // Handle mcVersions @@ -240,11 +240,11 @@ object CurseForge : Platform( } override suspend fun requestMultipleProjectsWithFiles( - mcVersions: List, loaders: List, ids: List, numberOfFiles: Int + mcVersions: List, loaders: List, projectInfos: Map, numberOfFiles: Int ): MutableSet { val response = json.decodeFromString( - this.requestProjectBody("mods", MultipleProjectsRequest(ids.map(String::toInt))) + this.requestProjectBody("mods", MultipleProjectsRequest(projectInfos.keys.map(String::toInt))) ?: return mutableSetOf() ).data @@ -252,7 +252,7 @@ object CurseForge : Platform( model.latestFilesIndexes.map { it.fileId.toString() } } - val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, fileIds) + val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, projectInfos, fileIds) val projects = response.mapNotNull { it.toProject() } projects.assignFiles(projectFiles, this) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt index b1e0a8e2..a295706f 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt @@ -29,12 +29,11 @@ object GitHub : Http(), Provider ) } - override suspend fun requestProject(input: String): Project? + override suspend fun requestProject(input: String, projectType: ProjectType?): Project? { return json.decodeFromString( - this.requestBody("https://api.github.com/repos/$input") - ?: return null - ).toProject() + this.requestBody("https://api.github.com/repos/$input") ?: return null + ).toProject().also { project -> projectType?.let { project.type = it } } } private fun GhReleaseModel.toProjectFiles(parentId: String): List @@ -63,10 +62,15 @@ object GitHub : Http(), Provider } override suspend fun requestProjectWithFiles( - mcVersions: List, loaders: List, input: String, fileId: String?, numberOfFiles: Int + mcVersions: List, + loaders: List, + input: String, + fileId: String?, + numberOfFiles: Int , + projectType: ProjectType? ): Project? { - val project = requestProject(input) ?: return null + val project = requestProject(input, projectType) ?: return null val projectFiles = if (fileId == null) { diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt index a582d4e3..cbd89d9d 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt @@ -74,12 +74,12 @@ object Modrinth : Platform( // -- PROJECT -- - override suspend fun requestProject(input: String): Project? = when + override suspend fun requestProject(input: String, projectType: ProjectType?): Project? = when { input.matches("[0-9]{6}".toRegex()) -> null input.matches("\b[0-9a-zA-Z]{8}\b".toRegex()) -> requestProjectFromId(input) else -> requestProjectFromSlug(input) - } + }.also { project -> projectType?.let { project?.type = it } } private fun MrProjectModel.toProject(): Project? { @@ -144,7 +144,11 @@ object Modrinth : Platform( } ?: true // If no loaders found, accept model } - internal fun compareByLoaders(loaders: List) = { version: MrVersionModel -> + internal fun compareByLoaders(loaders: List) = if (loaders.size <= 1) + { + { 0 } + } + else { version: MrVersionModel -> loaders.indexOfFirst { it in version.loaders }.let { if (it == -1) loaders.size else it } } @@ -177,18 +181,21 @@ object Modrinth : Platform( }.asReversed() // Reverse to make non source files first } + private const val DATAPACK_LOADER = "datapack" + override suspend fun requestProjectFiles( - mcVersions: List, loaders: List, projectId: String, fileId: String? + mcVersions: List, loaders: List, projectId: String, fileId: String?, projectType: ProjectType? ): MutableSet { + val actualLoaders = projectType?.let { if (it == ProjectType.DATA_PACK) listOf(DATAPACK_LOADER) else null } ?: loaders return if (fileId == null) { // Multiple files json.decodeFromString>( this.requestProjectBody("project/$projectId/version") ?: return mutableSetOf() ) - .filterFileModels(mcVersions, loaders) - .sortedWith(compareBy(compareByLoaders(loaders))) + .filterFileModels(mcVersions, actualLoaders) + .sortedWith(compareBy(compareByLoaders(actualLoaders))) .flatMap { version -> version.toProjectFiles() } .debugIfEmpty { println("${this::class.simpleName}#requestProjectFiles: file is null") @@ -206,8 +213,10 @@ object Modrinth : Platform( } override suspend fun requestMultipleProjectFiles( - mcVersions: List, loaders: List, ids: List + mcVersions: List, loaders: List, projectInfos: Map, ids: List ): MutableSet = coroutineScope { + val loadersWithType = + (if (projectInfos.values.any { it == ProjectType.DATA_PACK }) listOf(DATAPACK_LOADER) else listOf()) + loaders // Chunk requests if there are too many ids; Also do this in parallel return@coroutineScope ids.chunked(1_000).map { list -> async { @@ -220,24 +229,29 @@ object Modrinth : Platform( } .awaitAll() .flatten() - .filterFileModels(mcVersions, loaders) - .sortedWith(compareBy ({ Instant.parse(it.datePublished) }, compareByLoaders(loaders))) + .filterFileModels(mcVersions, loadersWithType) + .sortedWith(compareBy({ Instant.parse(it.datePublished) }, { file -> + compareByLoaders(projectInfos[file.projectId]?.let { + if (it == ProjectType.DATA_PACK) listOf(DATAPACK_LOADER) else null + } ?: loaders)(file) + }) + ) .flatMap { version -> version.toProjectFiles() } .toMutableSet() } override suspend fun requestMultipleProjectsWithFiles( - mcVersions: List, loaders: List, ids: List, numberOfFiles: Int + mcVersions: List, loaders: List, projectInfos: Map, numberOfFiles: Int ): MutableSet { - val url = encode("projects?ids=${ids.map { "\"$it\"" }}".filterNot { it.isWhitespace() }, allow = "?=") + val url = encode("projects?ids=${projectInfos.keys.map { "\"$it\"" }}".filterNot { it.isWhitespace() }, allow = "?=") val response = json.decodeFromString>( this.requestProjectBody(url) ?: return mutableSetOf() ) val fileIds = response.flatMap { it.versions } - val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, fileIds) + val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, projectInfos, fileIds) val projects = response.mapNotNull { it.toProject() } projects.assignFiles(projectFiles, this) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Multiplatform.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Multiplatform.kt index e2481000..7e54dc97 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Multiplatform.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Multiplatform.kt @@ -2,6 +2,7 @@ package teksturepako.pakku.api.platforms import com.github.michaelbull.result.get import teksturepako.pakku.api.projects.Project +import teksturepako.pakku.api.projects.ProjectType object Multiplatform : Provider { @@ -29,12 +30,13 @@ object Multiplatform : Provider * project is found on either platform. * * @param input The project ID or slug. + * @param projectType The type of project. * @return A [project][Project] containing data retrieved from all platforms, or null if no data is found. */ - override suspend fun requestProject(input: String): Project? + override suspend fun requestProject(input: String, projectType: ProjectType?): Project? { - var cf = CurseForge.requestProject(input) - var mr = Modrinth.requestProject(input) + var cf = CurseForge.requestProject(input, projectType) + var mr = Modrinth.requestProject(input, projectType) // Retrieve project from another platform if it's missing. if (cf == null && mr != null) @@ -48,7 +50,9 @@ object Multiplatform : Provider // Combine projects or return just one of them. return cf?.let { c -> + projectType?.let { c.type = it } mr?.let { m -> + projectType?.let { m.type = it } (c + m).get() // Combine projects if project is available from both platforms. } ?: c // Return the CurseForge project if Modrinth project is missing. } ?: mr // Return the Modrinth project if CurseForge project is missing. @@ -65,23 +69,28 @@ object Multiplatform : Provider * @return A [project][Project] with files from all platforms or null if the initial project request is unsuccessful. */ override suspend fun requestProjectWithFiles( - mcVersions: List, loaders: List, input: String, fileId: String?, numberOfFiles: Int + mcVersions: List, + loaders: List, + input: String, + fileId: String?, + numberOfFiles: Int , + projectType: ProjectType? ): Project? { - val project = requestProject(input) ?: return null + val project = requestProject(input, projectType) ?: return null if (fileId == null) { for (platform in platforms) { - project.files.addAll(platform.requestFilesForProject(mcVersions, loaders, project, null, numberOfFiles)) + project.files.addAll(platform.requestFilesForProject(mcVersions, loaders, project, null, numberOfFiles, projectType)) } } else { if (project.isOnPlatform(CurseForge)) { - val cfFile = CurseForge.requestProjectFiles(mcVersions, loaders, project.id[CurseForge.serialName]!!, fileId).firstOrNull() + val cfFile = CurseForge.requestProjectFiles(mcVersions, loaders, project.id[CurseForge.serialName]!!, fileId, projectType).firstOrNull() val hash = cfFile?.hashes?.get("sha1") @@ -100,7 +109,7 @@ object Multiplatform : Provider if (project.isOnPlatform(Modrinth)) { - val mrFile = Modrinth.requestProjectFiles(mcVersions, loaders, project.id[Modrinth.serialName]!!, fileId).firstOrNull() + val mrFile = Modrinth.requestProjectFiles(mcVersions, loaders, project.id[Modrinth.serialName]!!, fileId, projectType).firstOrNull() val bytes = mrFile?.url?.let { Modrinth.requestByteArray(it) } diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Platform.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Platform.kt index 7c00c1ab..5a513c54 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Platform.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Platform.kt @@ -46,11 +46,15 @@ abstract class Platform( * [projectId] & [fileId]. */ abstract suspend fun requestProjectFiles( - mcVersions: List, loaders: List, projectId: String, fileId: String? = null + mcVersions: List, + loaders: List, + projectId: String, + fileId: String? = null, + projectType: ProjectType? = null ): MutableSet abstract suspend fun requestMultipleProjectFiles( - mcVersions: List, loaders: List, ids: List + mcVersions: List, loaders: List, projectInfos: Map, ids: List ): MutableSet @@ -59,11 +63,16 @@ abstract class Platform( * [number of files][numberOfFiles] to take. */ suspend fun requestFilesForProject( - mcVersions: List, loaders: List, project: Project, fileId: String? = null, numberOfFiles: Int = 1 + mcVersions: List, + loaders: List, + project: Project, + fileId: String? = null, + numberOfFiles: Int = 1, + projectType: ProjectType? = null ): MutableSet { return project.id[this.serialName]?.let { projectId -> - this.requestProjectFiles(mcVersions, loaders, projectId, fileId).take(numberOfFiles).toMutableSet() + this.requestProjectFiles(mcVersions, loaders, projectId, fileId, projectType).take(numberOfFiles).toMutableSet() } ?: mutableSetOf() } @@ -72,16 +81,21 @@ abstract class Platform( * with optional [number of files][numberOfFiles] to take. */ override suspend fun requestProjectWithFiles( - mcVersions: List, loaders: List, input: String, fileId: String?, numberOfFiles: Int + mcVersions: List, + loaders: List, + input: String, + fileId: String?, + numberOfFiles: Int, + projectType: ProjectType? ): Project? { - return requestProject(input)?.apply { - files.addAll(requestFilesForProject(mcVersions, loaders, this, fileId, numberOfFiles)) + return requestProject(input, projectType)?.apply { + files.addAll(requestFilesForProject(mcVersions, loaders, this, fileId, numberOfFiles, projectType)) } } abstract suspend fun requestMultipleProjectsWithFiles( - mcVersions: List, loaders: List, ids: List, numberOfFiles: Int + mcVersions: List, loaders: List, projectInfos: Map, numberOfFiles: Int ): MutableSet companion object diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Provider.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Provider.kt index b2d0846d..e7e06150 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Provider.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Provider.kt @@ -1,6 +1,7 @@ package teksturepako.pakku.api.platforms import teksturepako.pakku.api.projects.Project +import teksturepako.pakku.api.projects.ProjectType interface Provider { @@ -27,7 +28,7 @@ interface Provider val siteUrl: String? /** Requests a [project][Project] based on provided [input]. */ - suspend fun requestProject(input: String): Project? + suspend fun requestProject(input: String, projectType: ProjectType?): Project? /** * Requests project with files for specified combinations of Minecraft versions and mod loaders. @@ -36,9 +37,15 @@ interface Provider * @param loaders The list of mod loaders. * @param input The input for the project files request. * @param numberOfFiles The number of files to take. Defaults to 1. + * @param projectType The type of project. * @return A [Project] with requested project files, or null if no data is found. */ suspend fun requestProjectWithFiles( - mcVersions: List, loaders: List, input: String, fileId: String? = null, numberOfFiles: Int = 1 + mcVersions: List, + loaders: List, + input: String, + fileId: String? = null, + numberOfFiles: Int = 1, + projectType: ProjectType? = null ): Project? } \ No newline at end of file diff --git a/src/commonMain/kotlin/teksturepako/pakku/cli/arg/Prompt.kt b/src/commonMain/kotlin/teksturepako/pakku/cli/arg/Prompt.kt index c74baad2..e8d4c1a4 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/cli/arg/Prompt.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/cli/arg/Prompt.kt @@ -12,9 +12,10 @@ import teksturepako.pakku.api.data.LockFile import teksturepako.pakku.api.platforms.GitHub import teksturepako.pakku.api.platforms.Provider import teksturepako.pakku.api.projects.Project +import teksturepako.pakku.api.projects.ProjectType suspend fun promptForProject( - provider: Provider, terminal: Terminal, lockFile: LockFile, fileId: String? = null + provider: Provider, terminal: Terminal, lockFile: LockFile, fileId: String? = null, projectType: ProjectType? = null ): Result, ActionError> { val prompt: String? = StringPrompt("Specify ${provider.name}", terminal).ask() @@ -26,12 +27,12 @@ suspend fun promptForProject( return arg.fold( commonArg = { Ok(provider.requestProjectWithFiles( - lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId ?: fileId + lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId ?: fileId, projectType = projectType ) to it) }, gitHubArg = { Ok(GitHub.requestProjectWithFiles( - listOf(), listOf(), "${it.owner}/${it.repo}", it.tag ?: fileId + listOf(), listOf(), "${it.owner}/${it.repo}", it.tag ?: fileId, projectType = projectType ) to it) } ) diff --git a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Add.kt b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Add.kt index 429d9fcc..cad2cd2f 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Add.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Add.kt @@ -6,6 +6,7 @@ import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.arguments.transformAll import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.mordant.terminal.danger import com.github.michaelbull.result.fold import com.github.michaelbull.result.getOrElse @@ -17,6 +18,7 @@ import teksturepako.pakku.api.data.LockFile import teksturepako.pakku.api.platforms.GitHub import teksturepako.pakku.api.platforms.Platform import teksturepako.pakku.api.projects.Project +import teksturepako.pakku.api.projects.ProjectType import teksturepako.pakku.cli.arg.* import teksturepako.pakku.cli.resolveDependencies import teksturepako.pakku.cli.ui.getFullMsg @@ -38,6 +40,13 @@ class Add : CliktCommand() private val noDepsFlag: Boolean by option("-D", "--no-deps", help = "Ignore resolving dependencies").flag() + private val type: ProjectType? by option( + "-t", + "--type", + help = "Project type of projects to add", + metavar = "project type" + ).enum() + private val flags by findOrSetObject { mutableMapOf() } init @@ -73,7 +82,7 @@ class Add : CliktCommand() { suspend fun handleMissingProject(error: NotFoundOn, arg: ProjectArg) { - val prompt = promptForProject(error.provider, terminal, lockFile, arg.fold({it.fileId}, {it.tag})).onFailure { + val prompt = promptForProject(error.provider, terminal, lockFile, arg.fold({it.fileId}, {it.tag}), type).onFailure { if (it is EmptyArg) return add(projectIn, arg, strict = false) }.getOrElse { return terminal.pError(it) @@ -123,12 +132,12 @@ class Add : CliktCommand() arg.fold( commonArg = { projectProvider.requestProjectWithFiles( - lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId + lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = type ) to it }, gitHubArg = { GitHub.requestProjectWithFiles( - listOf(), listOf(), "${it.owner}/${it.repo}", it.tag + listOf(), listOf(), "${it.owner}/${it.repo}", it.tag, projectType = type ) to it } ) diff --git a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/AddPrj.kt b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/AddPrj.kt index 99f00273..2eea14f2 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/AddPrj.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/AddPrj.kt @@ -6,6 +6,7 @@ import com.github.ajalt.clikt.core.requireObject import com.github.ajalt.clikt.core.terminal import com.github.ajalt.clikt.parameters.options.convert import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.mordant.terminal.danger import com.github.michaelbull.result.get import com.github.michaelbull.result.getOrElse @@ -19,6 +20,7 @@ import teksturepako.pakku.api.platforms.GitHub import teksturepako.pakku.api.platforms.Modrinth import teksturepako.pakku.api.platforms.Platform import teksturepako.pakku.api.projects.Project +import teksturepako.pakku.api.projects.ProjectType import teksturepako.pakku.cli.arg.* import teksturepako.pakku.cli.resolveDependencies import teksturepako.pakku.cli.ui.getFullMsg @@ -63,6 +65,13 @@ class AddPrj : CliktCommand("prj") } } + private val type: ProjectType? by option( + "-t", + "--type", + help = "Project type of project to add", + metavar = "project type" + ).enum() + private val flags by requireObject>() override fun run(): Unit = runBlocking { @@ -91,7 +100,7 @@ class AddPrj : CliktCommand("prj") { suspend fun handleMissingProject(error: NotFoundOn) { - val prompt = promptForProject(error.provider, terminal, lockFile).onFailure { + val prompt = promptForProject(error.provider, terminal, lockFile, projectType = type).onFailure { if (it is EmptyArg) return add(projectIn, strict = false) }.getOrElse { return terminal.pError(it) @@ -138,19 +147,19 @@ class AddPrj : CliktCommand("prj") val cf = cfOpt?.let { CurseForge.requestProjectWithFiles( - lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId + lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = type ) } val mr = mrOpt?.let { Modrinth.requestProjectWithFiles( - lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId + lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = type ) } val gh = ghOpt?.let { GitHub.requestProjectWithFiles( - listOf(), listOf(), "${it.owner}/${it.repo}", it.tag + listOf(), listOf(), "${it.owner}/${it.repo}", it.tag, projectType = type ) } From 6d7dbac4183306c4b7e3e4313188b04f98a4c058 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Sat, 19 Oct 2024 23:04:38 +0800 Subject: [PATCH 2/4] fix: the sort of modrinth projects is reversed --- .../teksturepako/pakku/api/platforms/Modrinth.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt index cbd89d9d..a18bb91f 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt @@ -230,12 +230,16 @@ object Modrinth : Platform( .awaitAll() .flatten() .filterFileModels(mcVersions, loadersWithType) - .sortedWith(compareBy({ Instant.parse(it.datePublished) }, { file -> - compareByLoaders(projectInfos[file.projectId]?.let { - if (it == ProjectType.DATA_PACK) listOf(DATAPACK_LOADER) else null - } ?: loaders)(file) - }) - ) + .sortedWith( + compareByDescending { Instant.parse(it.datePublished) } + .thenBy { file -> + compareByLoaders(projectInfos[file.projectId]?.let { + if (it == ProjectType.DATA_PACK) listOf( + DATAPACK_LOADER + ) + else null + } ?: loaders)(file) + }) .flatMap { version -> version.toProjectFiles() } .toMutableSet() } From 8c882a6f5ea02c4cdc60d030d0ca6eab82984ba9 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Sun, 20 Oct 2024 14:45:40 +0800 Subject: [PATCH 3/4] fix: avoid duplicate files for platform when updating --- .../kotlin/teksturepako/pakku/api/actions/update/Update.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/actions/update/Update.kt b/src/commonMain/kotlin/teksturepako/pakku/api/actions/update/Update.kt index 93fc8759..5e0e76d1 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/actions/update/Update.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/actions/update/Update.kt @@ -81,6 +81,7 @@ private fun combineProjects(accProject: Project, newProject: Project, platformNa val updatedFiles = (newFiles.take(numberOfFiles) + accProject.files).filterNot { projectFile -> projectFile.type == platformName && projectFile.datePublished < accPublished } + .distinctBy { it.type } .toMutableSet() return (accProject + newProject).get()?.copy(files = updatedFiles) From 2090a1b422ba17e309e5923a714f9ca74a752b93 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Tue, 29 Oct 2024 22:20:09 +0800 Subject: [PATCH 4/4] chore: better naming & types --- .../pakku/api/models/mr/MrModpackModel.kt | 8 ++--- .../pakku/api/platforms/CurseForge.kt | 10 +++---- .../pakku/api/platforms/GitHub.kt | 2 +- .../pakku/api/platforms/Modrinth.kt | 30 +++++++++++-------- .../pakku/api/platforms/Platform.kt | 4 +-- .../pakku/api/platforms/Provider.kt | 2 +- .../kotlin/teksturepako/pakku/cli/cmd/Add.kt | 8 ++--- .../teksturepako/pakku/cli/cmd/AddPrj.kt | 10 +++---- 8 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/models/mr/MrModpackModel.kt b/src/commonMain/kotlin/teksturepako/pakku/api/models/mr/MrModpackModel.kt index 7f7c9812..5334382f 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/models/mr/MrModpackModel.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/models/mr/MrModpackModel.kt @@ -54,15 +54,15 @@ data class MrModpackModel( runBlocking { debug { println("CurseForge sub-import") } - val slugs = projects.mapNotNull { project -> + val projectToSlugs = projects.mapNotNull { project -> project.slug[Modrinth.serialName]?.let { project to it } } - val cfProjects = slugs.map { slug -> + val cfProjects = projectToSlugs.map { (project, slug) -> async { - CurseForge.requestProjectFromSlug(slug.second)?.apply { + CurseForge.requestProjectFromSlug(slug)?.apply { files += CurseForge.requestFilesForProject( - lockFile.getMcVersions(), lockFile.getLoaders(), this, projectType = slug.first.type + lockFile.getMcVersions(), lockFile.getLoaders(), this, projectType = project.type ) } } diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/CurseForge.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/CurseForge.kt index 29a8929f..1393070b 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/CurseForge.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/CurseForge.kt @@ -129,7 +129,7 @@ object CurseForge : Platform( } ?: true // If no loaders found, accept model } - internal fun compareByLoaders(loaders: List) = { file: CfModModel.File -> + internal fun compareByLoaders(loaders: List): (CfModModel.File) -> Comparable<*> = { file: CfModModel.File -> val fileLoaders = file.sortableGameVersions.filter { it.gameVersionTypeId == LOADER_VERSION_TYPE_ID } .map { it.gameVersionName.lowercase() } loaders.indexOfFirst { it in fileLoaders }.let { if (it == -1) loaders.size else it } @@ -219,7 +219,7 @@ object CurseForge : Platform( } override suspend fun requestMultipleProjectFiles( - mcVersions: List, loaders: List, projectInfos: Map, ids: List + mcVersions: List, loaders: List, projectIdsToTypes: Map, ids: List ): MutableSet { // Handle mcVersions @@ -240,11 +240,11 @@ object CurseForge : Platform( } override suspend fun requestMultipleProjectsWithFiles( - mcVersions: List, loaders: List, projectInfos: Map, numberOfFiles: Int + mcVersions: List, loaders: List, projectIdsToTypes: Map, numberOfFiles: Int ): MutableSet { val response = json.decodeFromString( - this.requestProjectBody("mods", MultipleProjectsRequest(projectInfos.keys.map(String::toInt))) + this.requestProjectBody("mods", MultipleProjectsRequest(projectIdsToTypes.keys.map(String::toInt))) ?: return mutableSetOf() ).data @@ -252,7 +252,7 @@ object CurseForge : Platform( model.latestFilesIndexes.map { it.fileId.toString() } } - val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, projectInfos, fileIds) + val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, projectIdsToTypes, fileIds) val projects = response.mapNotNull { it.toProject() } projects.assignFiles(projectFiles, this) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt index a295706f..5ae51921 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt @@ -66,7 +66,7 @@ object GitHub : Http(), Provider loaders: List, input: String, fileId: String?, - numberOfFiles: Int , + numberOfFiles: Int, projectType: ProjectType? ): Project? { diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt index a18bb91f..147294f1 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt @@ -144,7 +144,7 @@ object Modrinth : Platform( } ?: true // If no loaders found, accept model } - internal fun compareByLoaders(loaders: List) = if (loaders.size <= 1) + internal fun compareByLoaders(loaders: List): (MrVersionModel) -> Comparable<*> = if (loaders.size <= 1) { { 0 } } @@ -187,7 +187,12 @@ object Modrinth : Platform( mcVersions: List, loaders: List, projectId: String, fileId: String?, projectType: ProjectType? ): MutableSet { - val actualLoaders = projectType?.let { if (it == ProjectType.DATA_PACK) listOf(DATAPACK_LOADER) else null } ?: loaders + val actualLoaders = when (projectType) + { + ProjectType.DATA_PACK -> listOf(DATAPACK_LOADER) + else -> loaders + } + return if (fileId == null) { // Multiple files @@ -213,10 +218,10 @@ object Modrinth : Platform( } override suspend fun requestMultipleProjectFiles( - mcVersions: List, loaders: List, projectInfos: Map, ids: List + mcVersions: List, loaders: List, projectIdsToTypes: Map, ids: List ): MutableSet = coroutineScope { val loadersWithType = - (if (projectInfos.values.any { it == ProjectType.DATA_PACK }) listOf(DATAPACK_LOADER) else listOf()) + loaders + (if (projectIdsToTypes.values.any { it == ProjectType.DATA_PACK }) listOf(DATAPACK_LOADER) else listOf()) + loaders // Chunk requests if there are too many ids; Also do this in parallel return@coroutineScope ids.chunked(1_000).map { list -> async { @@ -233,11 +238,12 @@ object Modrinth : Platform( .sortedWith( compareByDescending { Instant.parse(it.datePublished) } .thenBy { file -> - compareByLoaders(projectInfos[file.projectId]?.let { - if (it == ProjectType.DATA_PACK) listOf( - DATAPACK_LOADER - ) - else null + compareByLoaders(projectIdsToTypes[file.projectId]?.let { + when (it) + { + ProjectType.DATA_PACK -> listOf(DATAPACK_LOADER) + else -> null + } } ?: loaders)(file) }) .flatMap { version -> version.toProjectFiles() } @@ -245,17 +251,17 @@ object Modrinth : Platform( } override suspend fun requestMultipleProjectsWithFiles( - mcVersions: List, loaders: List, projectInfos: Map, numberOfFiles: Int + mcVersions: List, loaders: List, projectIdsToTypes: Map, numberOfFiles: Int ): MutableSet { - val url = encode("projects?ids=${projectInfos.keys.map { "\"$it\"" }}".filterNot { it.isWhitespace() }, allow = "?=") + val url = encode("projects?ids=${projectIdsToTypes.keys.map { "\"$it\"" }}".filterNot { it.isWhitespace() }, allow = "?=") val response = json.decodeFromString>( this.requestProjectBody(url) ?: return mutableSetOf() ) val fileIds = response.flatMap { it.versions } - val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, projectInfos, fileIds) + val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, projectIdsToTypes, fileIds) val projects = response.mapNotNull { it.toProject() } projects.assignFiles(projectFiles, this) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Platform.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Platform.kt index 5a513c54..baa4f8fe 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Platform.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Platform.kt @@ -54,7 +54,7 @@ abstract class Platform( ): MutableSet abstract suspend fun requestMultipleProjectFiles( - mcVersions: List, loaders: List, projectInfos: Map, ids: List + mcVersions: List, loaders: List, projectIdsToTypes: Map, ids: List ): MutableSet @@ -95,7 +95,7 @@ abstract class Platform( } abstract suspend fun requestMultipleProjectsWithFiles( - mcVersions: List, loaders: List, projectInfos: Map, numberOfFiles: Int + mcVersions: List, loaders: List, projectIdsToTypes: Map, numberOfFiles: Int ): MutableSet companion object diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Provider.kt b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Provider.kt index e7e06150..defba31d 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Provider.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/platforms/Provider.kt @@ -28,7 +28,7 @@ interface Provider val siteUrl: String? /** Requests a [project][Project] based on provided [input]. */ - suspend fun requestProject(input: String, projectType: ProjectType?): Project? + suspend fun requestProject(input: String, projectType: ProjectType? = null): Project? /** * Requests project with files for specified combinations of Minecraft versions and mod loaders. diff --git a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Add.kt b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Add.kt index cad2cd2f..630748cf 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Add.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Add.kt @@ -40,7 +40,7 @@ class Add : CliktCommand() private val noDepsFlag: Boolean by option("-D", "--no-deps", help = "Ignore resolving dependencies").flag() - private val type: ProjectType? by option( + private val projectTypeOpt: ProjectType? by option( "-t", "--type", help = "Project type of projects to add", @@ -82,7 +82,7 @@ class Add : CliktCommand() { suspend fun handleMissingProject(error: NotFoundOn, arg: ProjectArg) { - val prompt = promptForProject(error.provider, terminal, lockFile, arg.fold({it.fileId}, {it.tag}), type).onFailure { + val prompt = promptForProject(error.provider, terminal, lockFile, arg.fold({it.fileId}, {it.tag}), projectTypeOpt).onFailure { if (it is EmptyArg) return add(projectIn, arg, strict = false) }.getOrElse { return terminal.pError(it) @@ -132,12 +132,12 @@ class Add : CliktCommand() arg.fold( commonArg = { projectProvider.requestProjectWithFiles( - lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = type + lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = projectTypeOpt ) to it }, gitHubArg = { GitHub.requestProjectWithFiles( - listOf(), listOf(), "${it.owner}/${it.repo}", it.tag, projectType = type + listOf(), listOf(), "${it.owner}/${it.repo}", it.tag, projectType = projectTypeOpt ) to it } ) diff --git a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/AddPrj.kt b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/AddPrj.kt index 2eea14f2..7d0e1ea1 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/AddPrj.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/AddPrj.kt @@ -65,7 +65,7 @@ class AddPrj : CliktCommand("prj") } } - private val type: ProjectType? by option( + private val projectTypeOpt: ProjectType? by option( "-t", "--type", help = "Project type of project to add", @@ -100,7 +100,7 @@ class AddPrj : CliktCommand("prj") { suspend fun handleMissingProject(error: NotFoundOn) { - val prompt = promptForProject(error.provider, terminal, lockFile, projectType = type).onFailure { + val prompt = promptForProject(error.provider, terminal, lockFile, projectType = projectTypeOpt).onFailure { if (it is EmptyArg) return add(projectIn, strict = false) }.getOrElse { return terminal.pError(it) @@ -147,19 +147,19 @@ class AddPrj : CliktCommand("prj") val cf = cfOpt?.let { CurseForge.requestProjectWithFiles( - lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = type + lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = projectTypeOpt ) } val mr = mrOpt?.let { Modrinth.requestProjectWithFiles( - lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = type + lockFile.getMcVersions(), lockFile.getLoaders(), it.input, it.fileId, projectType = projectTypeOpt ) } val gh = ghOpt?.let { GitHub.requestProjectWithFiles( - listOf(), listOf(), "${it.owner}/${it.repo}", it.tag, projectType = type + listOf(), listOf(), "${it.owner}/${it.repo}", it.tag, projectType = projectTypeOpt ) }