Skip to content

Commit

Permalink
Merge branch 'feature/cache' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
froehlichA committed Apr 19, 2022
2 parents 974778a + 3b473a0 commit bcbb2dc
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 46 deletions.
26 changes: 13 additions & 13 deletions src/api/cfapi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,35 @@ const
type
CfApiError* = object of HttpRequestError

proc fetchAddonsByQuery*(query: string, category: Option[CfAddonGameCategory]): Future[JsonNode] {.async.} =
proc fetchAddonsByQuery*(query: string, category: Option[CfAddonGameCategory]): Future[seq[CfAddon]] {.async.} =
## retrieves all addons that match the given `query` search and `category`.
let encodedQuery = encodeUrl(query, usePlus = false)
var url = addonsBaseUrl & "/v1/mods/search?gameId=432&pageSize=50&sortField=6&sortOrder=desc&searchFilter=" & encodedQuery
if category.isSome:
url = url & "&classId=" & $ord(category.get())
return get(url.Url).await.parseJson["data"]
return get(url.Url).await.parseJson["data"].addonsFromForgeSvc

proc fetchAddon*(projectId: int): Future[JsonNode] {.async.} =
proc fetchAddon*(projectId: int): Future[CfAddon] {.async.} =
## get the addon with the given `projectId`.
let url = addonsBaseUrl & "/v1/mods/" & $projectId
try:
return get(url.Url).await.parseJson["data"]
return get(url.Url).await.parseJson["data"].addonFromForgeSvc
except HttpRequestError:
raise newException(CfApiError, "addon with project id '" & $projectId & "' not found.")

proc fetchAddons*(projectIds: seq[int]): Future[JsonNode] {.async.} =
proc fetchAddons*(projectIds: seq[int]): Future[seq[CfAddon]] {.async.} =
## get all addons with their given `projectId`.
let url = addonsBaseUrl & "/v1/mods/"
let body = %* { "modIds": projectIds }
try:
let addons = post(url.Url, $body).await.parseJson["data"]
let addons = post(url.Url, $body).await.parseJson["data"].addonsFromForgeSvc
if projectIds.len != addons.len:
raise newException(CfApiError, "one of the addons of project ids '" & $projectIds & "' was not found.")
return addons
except HttpRequestError:
raise newException(CfApiError, "one of the addons of project ids '" & $projectIds & "' was not found.")

proc fetchAddon*(slug: string): Future[JsonNode] {.async.} =
proc fetchAddon*(slug: string): Future[CfAddon] {.async.} =
## get the addon matching the `slug`.
let reqBody = %* {
"query": "{ addons(slug: \"" & slug & "\") { id }}"
Expand All @@ -62,30 +62,30 @@ proc fetchAddon*(slug: string): Future[JsonNode] {.async.} =
let projectId = addons[0]["id"].getInt()
return await fetchAddon(projectId)

proc fetchAddonFiles*(projectId: int): Future[JsonNode] {.async.} =
proc fetchAddonFiles*(projectId: int): Future[seq[CfAddonFile]] {.async.} =
## get all addon files associated with the given `projectId`.
let url = addonsBaseUrl & "/v1/mods/" & $projectId & "/files?pageSize=10000"
try:
return get(url.Url).await.parseJson["data"]
return get(url.Url).await.parseJson["data"].addonFilesFromForgeSvc
except HttpRequestError:
raise newException(CfApiError, "addon with project id '" & $projectId & "' not found.")

proc fetchAddonFiles*(fileIds: seq[int]): Future[JsonNode] {.async.} =
proc fetchAddonFiles*(fileIds: seq[int]): Future[seq[CfAddonFile]] {.async.} =
## get all addon files with their given `fileIds`.
let url = addonsBaseUrl & "/v1/mods/files"
let body = %* { "fileIds": fileIds }
try:
let files = post(url.Url, $body).await.parseJson["data"]
let files = post(url.Url, $body).await.parseJson["data"].addonFilesFromForgeSvc
if fileIds.len != files.len:
raise newException(CfApiError, "one of the addon files of file ids '" & $fileIds & "' was not found.")
return files
except HttpRequestError:
raise newException(CfApiError, "one of the addon files of file ids '" & $fileIds & "' was not found.")

proc fetchAddonFile*(projectId: int, fileId: int): Future[JsonNode] {.async.} =
proc fetchAddonFile*(projectId: int, fileId: int): Future[CfAddonFile] {.async.} =
## get the addon file with the given `fileId` & `projectId`.
let url = addonsBaseUrl & "/v1/mods/" & $projectId & "/files/" & $fileId
try:
return get(url.Url).await.parseJson["data"]
return get(url.Url).await.parseJson["data"].addonFileFromForgeSvc
except HttpRequestError:
raise newException(CfApiError, "addon with project & file id '" & $projectId & ':' & $fileId & "' not found.")
47 changes: 23 additions & 24 deletions src/api/cfcache.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
## and instead data from the local file system is returned.

import std/[json, options, os, times]
import cfcore

const
cacheDir* = getCacheDir("pax") ## the cache folder
Expand All @@ -20,61 +21,59 @@ proc getAddonFileFilename*(fileId: int): string {.inline.} =
## get the filename of an addon file in the cache.
return cacheDir / ("file:" & $fileId)

proc putAddon*(json: JsonNode): void =
proc putAddon*(addon: CfAddon): void =
## put an addon in the cache.
let projectId = json["id"].getInt()
let filename = getAddonFilename(projectId)
let filename = getAddonFilename(addon.projectId)
try:
writeFile(filename, $json)
writeFile(filename, $addon.toJson)
except IOError:
discard

proc putAddons*(json: JsonNode): void =
proc putAddons*(addons: seq[CfAddon]): void =
## put multiple addons in the cache.
for elems in json.getElems():
putAddon(elems)
for addon in addons:
putAddon(addon)

proc putAddonFile*(json: JsonNode): void =
proc putAddonFile*(addonFile: CfAddonFile): void =
## put an addon file in the cache.
let fileId = json["id"].getInt()
let filename = getAddonFileFilename(fileId)
let filename = getAddonFileFilename(addonFile.fileId)
try:
writeFile(filename, $json)
writeFile(filename, $addonFile.toJson)
except IOError:
discard

proc putAddonFiles*(json: JsonNode): void =
proc putAddonFiles*(addonFiles: seq[CfAddonFile]): void =
## put multiple addons in the cache.
for elems in json.getElems():
putAddonFile(elems)
for addonFile in addonFiles:
putAddonFile(addonFile)

proc getAddon*(projectId: int): Option[JsonNode] =
proc getAddon*(projectId: int): Option[CfAddon] =
## retrieve an addon from cache.
let filename = getAddonFilename(projectId)
if not fileExists(filename):
return none[JsonNode]()
return none[CfAddon]()
let info = getFileInfo(filename)
if info.lastWriteTime + addonCacheTime > getTime():
let file = try:
readFile(filename)
except IOError:
return none[JsonNode]()
return some(file.parseJson)
return none[JsonNode]()
return none[CfAddon]()
return some(file.parseJson.addonFromForgeSvc)
return none[CfAddon]()

proc getAddonFile*(fileId: int): Option[JsonNode] =
proc getAddonFile*(fileId: int): Option[CfAddonFile] =
## retrieve an addon file from cache.
let filename = getAddonFileFilename(fileId)
if not fileExists(filename):
return none[JsonNode]()
return none[CfAddonFile]()
let info = getFileInfo(filename)
if info.lastWriteTime + addonFileCacheTime > getTime():
let file = try:
readFile(filename)
except IOError:
return none[JsonNode]()
return some(file.parseJson)
return none[JsonNode]()
return none[CfAddonFile]()
return some(file.parseJson.addonFileFromForgeSvc)
return none[CfAddonFile]()

proc clean*(): int =
## remove old files from the cache.
Expand Down
18 changes: 9 additions & 9 deletions src/api/cfclient.nim
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ proc fetchAddonsByQuery*(query: string, category: Option[CfAddonGameCategory]):
## retrieves all addons that match the given `query` search and `category`.
let data = await cfapi.fetchAddonsByQuery(query, category)
cfcache.putAddons(data)
return data.addonsFromForgeSvc
return data

proc fetchAddonsByQuery*(query: string, category: CfAddonGameCategory): Future[seq[CfAddon]] =
## retrieves all addons that match the given `query` search and `category`.
Expand All @@ -45,10 +45,10 @@ proc fetchAddon(projectId: int, lookupCache: bool): Future[CfAddon] {.async.} =
## get the addon with the given `projectId`.
if lookupCache:
withCachedAddon(addon, projectId):
return addon.addonFromForgeSvc
return addon
let data = await cfapi.fetchAddon(projectId)
cfcache.putAddon(data)
return data.addonFromForgeSvc
return data

proc fetchAddon*(projectId: int): Future[CfAddon] =
## get the addon with the given `projectId`.
Expand All @@ -61,7 +61,7 @@ proc fetchAddonsChunks(projectIds: seq[int]): Future[seq[CfAddon]] {.async.} =
try:
let data = await cfapi.fetchAddons(projectIds)
cfcache.putAddons(data)
return data.addonsFromForgeSvc
return data
except CfApiError:
# fallback to looking up the ids individually
return await all(projectIds.map((x) => fetchAddon(x, lookupCache = false)))
Expand Down Expand Up @@ -104,13 +104,13 @@ proc fetchAddon*(slug: string): Future[CfAddon] {.async.} =
## get the addon matching the `slug`.
let data = await cfapi.fetchAddon(slug)
cfcache.putAddon(data)
return data.addonFromForgeSvc
return data

proc fetchAddonFiles*(projectId: int): Future[seq[CfAddonFile]] {.async.} =
## get all addon files associated with the given `projectId`.
let data = await cfapi.fetchAddonFiles(projectId)
cfcache.putAddonFiles(data)
return data.addonFilesFromForgeSvc
return data

proc fetchAddonFilesChunks(fileIds: seq[int], fallback = true): Future[seq[CfAddonFile]] {.async.} =
## get all addons with their given `projectId`.
Expand All @@ -119,7 +119,7 @@ proc fetchAddonFilesChunks(fileIds: seq[int], fallback = true): Future[seq[CfAdd
try:
let data = await cfapi.fetchAddonFiles(fileIds)
cfcache.putAddonFiles(data)
return data.addonFilesFromForgeSvc
return data
except CfApiError as e:
# fallback to looking up the ids individually
if fallback:
Expand Down Expand Up @@ -161,8 +161,8 @@ proc fetchAddonFiles*(fileIds: seq[int], chunk = true): Future[seq[CfAddonFile]]
proc fetchAddonFile*(projectId: int, fileId: int): Future[CfAddonFile] {.async.} =
## get the addon file with the given `fileId` & `projectId`.
withCachedAddonFile(addonFile, fileId):
return addonFile.addonFileFromForgeSvc
return addonFile

let data = await cfapi.fetchAddonFile(projectId, fileId)
cfcache.putAddonFile(data)
return data.addonFileFromForgeSvc
return data
34 changes: 34 additions & 0 deletions src/api/cfcore.nim
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ converter addonFilesFromForgeSvc*(json: JsonNode): seq[CfAddonFile] =
## creates addon files from json retrieved by an forgesvc endpoint
return json.getElems().map(addonFileFromForgeSvc)

converter toJson*(file: CfAddonFile): JsonNode =
## creates json from an addon file
result = %* {
"id": file.fileId,
"displayName": file.name,
"releaseType": file.releaseType.ord,
"downloadUrl": file.downloadUrl,
"gameVersions": file.gameVersions.map((x) => $x),
"dependencies": file.dependencies.map((x) => %* {
"relationType": RequiredDependencyType,
"modId": x
})
}

converter addonFromForgeSvc*(json: JsonNode): CfAddon =
## creates an addon from json retrieved by an forgesvc endpoint
result = CfAddon()
Expand All @@ -83,6 +97,26 @@ converter addonsFromForgeSvc*(json: JsonNode): seq[CfAddon] =
## creates addons from json retrieved by an forgesvc endpoint
result = json.getElems().map(addonFromForgeSvc)

converter toJson*(addon: CfAddon): JsonNode =
result = %* {
"id": addon.projectId,
"name": addon.name,
"summary": addon.description,
"links": {
"websiteUrl": addon.websiteUrl
},
"authors": addon.authors.map((x) => %* {
"name": x
}),
"downloadCount": addon.downloads,
"gamePopularityRank": addon.popularity,
"latestFiles": addon.latestFiles.map((x) => x.toJson()),
"latestFilesIndexes": addon.gameVersionLatestFiles.map((x) => %* {
"gameVersion": x.version.`$`,
"fileId": x.fileId
})
}

proc isFabricCompatible*(file: CfAddonFile): bool =
## returns true if `file` is compatible with the fabric loader.
if "Fabric".Version in file.gameVersions:
Expand Down
33 changes: 33 additions & 0 deletions tests/api/tcfcore.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import std/[json, sequtils, sugar]
import api/cfcore
import modpack/version

proc initCfAddonFile(fileId: int, name: string, gameVersions: seq[string], releaseType: CfAddonFileReleaseType): CfAddonFile =
result = CfAddonFile()
result.fileId = fileId
result.name = name
result.releaseType = releaseType
result.downloadUrl = "https://download-here.com/" & name
result.gameVersions = gameVersions.map((x) => x.Version)

proc initCfAddon(projectId: int, name: string, gameVersionLatestFiles: seq[tuple[version: Version, fileId: int]]): CfAddon =
result = CfAddon()
result.projectId = projectId
result.name = name
result.description = "description"
result.websiteUrl = "https://website-url.com/" & name
result.authors = @["user1", "user2"]
result.downloads = 102039
result.popularity = 0.5
result.latestFiles = @[]
result.gameVersionLatestFiles = gameVersionLatestFiles

block: # AddonFile from/to JSON
let addonFile = initCfAddonFile(300, "jei-1.0.2.jar", @["1.16.1", "1.16.2", "Forge"], CfAddonFileReleaseType.Beta)

doAssert addonFile.toJson.addonFileFromForgeSvc.toJson == addonFile.toJson

block: # Addon from/to JSON
let addon = initCfAddon(200, "Just Enough Items (JEI)", @[(version: "1.16".Version, fileId: 2)])

doAssert addon.toJson.addonFromForgeSvc.toJson == addon.toJson

0 comments on commit bcbb2dc

Please sign in to comment.