Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow user specify project type when adding project #54

Merged
merged 4 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/topics/pakku-add-prj.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

</snippet>

`-h`, `--help`
Expand Down
3 changes: 3 additions & 0 deletions docs/topics/pakku-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

</snippet>

`-h`, `--help`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ data class MrModpackModel(
runBlocking {
debug { println("CurseForge sub-import") }

val slugs = projects.mapNotNull { project ->
project.slug[Modrinth.serialName]
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)?.apply {
files += CurseForge.requestFilesForProject(
lockFile.getMcVersions(), lockFile.getLoaders(), this
lockFile.getMcVersions(), lockFile.getLoaders(), this, projectType = project.type
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
{
Expand Down Expand Up @@ -129,7 +129,7 @@ object CurseForge : Platform(
} ?: true // If no loaders found, accept model
}

internal fun compareByLoaders(loaders: List<String>) = { file: CfModModel.File ->
internal fun compareByLoaders(loaders: List<String>): (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 }
Expand Down Expand Up @@ -174,7 +174,7 @@ object CurseForge : Platform(
}

override suspend fun requestProjectFiles(
mcVersions: List<String>, loaders: List<String>, projectId: String, fileId: String?
mcVersions: List<String>, loaders: List<String>, projectId: String, fileId: String?, projectType: ProjectType?
): MutableSet<ProjectFile>
{
// Handle optional fileId
Expand Down Expand Up @@ -219,7 +219,7 @@ object CurseForge : Platform(
}

override suspend fun requestMultipleProjectFiles(
mcVersions: List<String>, loaders: List<String>, ids: List<String>
mcVersions: List<String>, loaders: List<String>, projectIdsToTypes: Map<String, ProjectType?>, ids: List<String>
): MutableSet<ProjectFile>
{
// Handle mcVersions
Expand All @@ -240,19 +240,19 @@ object CurseForge : Platform(
}

override suspend fun requestMultipleProjectsWithFiles(
mcVersions: List<String>, loaders: List<String>, ids: List<String>, numberOfFiles: Int
mcVersions: List<String>, loaders: List<String>, projectIdsToTypes: Map<String, ProjectType?>, numberOfFiles: Int
): MutableSet<Project>
{
val response = json.decodeFromString<GetMultipleProjectsResponse>(
this.requestProjectBody("mods", MultipleProjectsRequest(ids.map(String::toInt)))
this.requestProjectBody("mods", MultipleProjectsRequest(projectIdsToTypes.keys.map(String::toInt)))
?: return mutableSetOf()
).data

val fileIds = response.flatMap { model ->
model.latestFilesIndexes.map { it.fileId.toString() }
}

val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, fileIds)
val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, projectIdsToTypes, fileIds)
val projects = response.mapNotNull { it.toProject() }

projects.assignFiles(projectFiles, this)
Expand Down
16 changes: 10 additions & 6 deletions src/commonMain/kotlin/teksturepako/pakku/api/platforms/GitHub.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<GhRepoModel>(
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<ProjectFile>
Expand Down Expand Up @@ -63,10 +62,15 @@ object GitHub : Http(), Provider
}

override suspend fun requestProjectWithFiles(
mcVersions: List<String>, loaders: List<String>, input: String, fileId: String?, numberOfFiles: Int
mcVersions: List<String>,
loaders: List<String>,
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)
{
Expand Down
48 changes: 36 additions & 12 deletions src/commonMain/kotlin/teksturepako/pakku/api/platforms/Modrinth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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?
{
Expand Down Expand Up @@ -144,7 +144,11 @@ object Modrinth : Platform(
} ?: true // If no loaders found, accept model
}

internal fun compareByLoaders(loaders: List<String>) = { version: MrVersionModel ->
internal fun compareByLoaders(loaders: List<String>): (MrVersionModel) -> Comparable<*> = if (loaders.size <= 1)
{
{ 0 }
}
else { version: MrVersionModel ->
loaders.indexOfFirst { it in version.loaders }.let { if (it == -1) loaders.size else it }
}

Expand Down Expand Up @@ -177,18 +181,26 @@ object Modrinth : Platform(
}.asReversed() // Reverse to make non source files first
}

private const val DATAPACK_LOADER = "datapack"

override suspend fun requestProjectFiles(
mcVersions: List<String>, loaders: List<String>, projectId: String, fileId: String?
mcVersions: List<String>, loaders: List<String>, projectId: String, fileId: String?, projectType: ProjectType?
): MutableSet<ProjectFile>
{
val actualLoaders = when (projectType)
{
ProjectType.DATA_PACK -> listOf(DATAPACK_LOADER)
else -> loaders
}

return if (fileId == null)
{
// Multiple files
json.decodeFromString<List<MrVersionModel>>(
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")
Expand All @@ -206,8 +218,10 @@ object Modrinth : Platform(
}

override suspend fun requestMultipleProjectFiles(
mcVersions: List<String>, loaders: List<String>, ids: List<String>
mcVersions: List<String>, loaders: List<String>, projectIdsToTypes: Map<String, ProjectType?>, ids: List<String>
): MutableSet<ProjectFile> = coroutineScope {
val loadersWithType =
(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 {
Expand All @@ -220,24 +234,34 @@ object Modrinth : Platform(
}
.awaitAll()
.flatten()
.filterFileModels(mcVersions, loaders)
.sortedWith(compareBy ({ Instant.parse(it.datePublished) }, compareByLoaders(loaders)))
.filterFileModels(mcVersions, loadersWithType)
.sortedWith(
compareByDescending<MrVersionModel> { Instant.parse(it.datePublished) }
.thenBy { file ->
compareByLoaders(projectIdsToTypes[file.projectId]?.let {
when (it)
{
ProjectType.DATA_PACK -> listOf(DATAPACK_LOADER)
else -> null
}
} ?: loaders)(file)
})
.flatMap { version -> version.toProjectFiles() }
.toMutableSet()
}

override suspend fun requestMultipleProjectsWithFiles(
mcVersions: List<String>, loaders: List<String>, ids: List<String>, numberOfFiles: Int
mcVersions: List<String>, loaders: List<String>, projectIdsToTypes: Map<String, ProjectType?>, numberOfFiles: Int
): MutableSet<Project>
{
val url = encode("projects?ids=${ids.map { "\"$it\"" }}".filterNot { it.isWhitespace() }, allow = "?=")
val url = encode("projects?ids=${projectIdsToTypes.keys.map { "\"$it\"" }}".filterNot { it.isWhitespace() }, allow = "?=")

val response = json.decodeFromString<List<MrProjectModel>>(
this.requestProjectBody(url) ?: return mutableSetOf()
)

val fileIds = response.flatMap { it.versions }
val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, fileIds)
val projectFiles = requestMultipleProjectFiles(mcVersions, loaders, projectIdsToTypes, fileIds)
val projects = response.mapNotNull { it.toProject() }

projects.assignFiles(projectFiles, this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand All @@ -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<String>, loaders: List<String>, input: String, fileId: String?, numberOfFiles: Int
mcVersions: List<String>,
loaders: List<String>,
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")

Expand All @@ -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) }

Expand Down
Loading