mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
Implement direct download via plugin
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
export async function downloadGame(gameId: number, provider: string) {
|
||||
try {
|
||||
const response = await fetch(`/download/${gameId}?provider=${provider}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const contentDisposition = response.headers.get('Content-Disposition');
|
||||
const filename = contentDisposition
|
||||
? contentDisposition.split('filename=')[1].replace(/"/g, '')
|
||||
: 'downloaded_file';
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', filename);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
} catch (error) {
|
||||
console.error('Error downloading the file', error);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as AvatarEndpoint from './AvatarEndpoint'
|
||||
import * as DownloadEndpoint from './DownloadEndpoint'
|
||||
|
||||
export {AvatarEndpoint}
|
||||
export {AvatarEndpoint, DownloadEndpoint}
|
||||
@@ -7,6 +7,7 @@ import ComboButton, {ComboButtonOption} from "Frontend/components/general/input/
|
||||
import ImageCarousel from "Frontend/components/general/covers/ImageCarousel";
|
||||
import {Chip} from "@heroui/react";
|
||||
import {toTitleCase} from "Frontend/util/utils";
|
||||
import {DownloadEndpoint} from "Frontend/endpoints/endpoints";
|
||||
|
||||
export default function GameView() {
|
||||
const {gameId} = useParams();
|
||||
@@ -18,7 +19,7 @@ export default function GameView() {
|
||||
label: "Direct Download",
|
||||
description: "Download the game in this browser",
|
||||
action: () => {
|
||||
alert("Direct download not yet implemented")
|
||||
DownloadEndpoint.downloadGame(parseInt(gameId!), "de.grimsi.gameyfin.plugins.directdownload.DirectDownloadPlugin$DirectDownloadProvider")
|
||||
}
|
||||
},
|
||||
torrent: {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.grimsi.gameyfin.core.download
|
||||
|
||||
import de.grimsi.gameyfin.core.annotations.DynamicPublicAccess
|
||||
import de.grimsi.gameyfin.games.GameService
|
||||
import de.grimsi.gameyfin.pluginapi.download.FileDownload
|
||||
import de.grimsi.gameyfin.pluginapi.download.LinkDownload
|
||||
import org.springframework.core.io.InputStreamResource
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/download")
|
||||
@DynamicPublicAccess
|
||||
class DownloadEndpoint(
|
||||
private val downloadService: DownloadService,
|
||||
private val gameService: GameService
|
||||
) {
|
||||
fun getProviders(): List<String> {
|
||||
return downloadService.getProviders()
|
||||
}
|
||||
|
||||
@GetMapping("/{gameId}")
|
||||
fun downloadGame(@PathVariable gameId: Long, @RequestParam provider: String): ResponseEntity<Resource> {
|
||||
val game = gameService.getGame(gameId)
|
||||
val downloadElement = downloadService.getDownloadElement(game.path, provider)
|
||||
|
||||
return when (downloadElement) {
|
||||
is FileDownload -> {
|
||||
val resource = InputStreamResource(downloadElement.data)
|
||||
ResponseEntity.ok()
|
||||
.header(
|
||||
"Content-Disposition",
|
||||
"attachment; filename=\"${game.title}.${downloadElement.fileExtension}\""
|
||||
)
|
||||
.body(resource)
|
||||
}
|
||||
|
||||
is LinkDownload -> {
|
||||
TODO("Handle download link")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package de.grimsi.gameyfin.core.download
|
||||
|
||||
import de.grimsi.gameyfin.core.plugins.management.GameyfinPluginManager
|
||||
import de.grimsi.gameyfin.pluginapi.download.Download
|
||||
import de.grimsi.gameyfin.pluginapi.download.DownloadProvider
|
||||
import org.springframework.stereotype.Service
|
||||
import kotlin.io.path.Path
|
||||
|
||||
@Service
|
||||
class DownloadService(
|
||||
private val pluginManager: GameyfinPluginManager,
|
||||
) {
|
||||
private val downloadPlugins: List<DownloadProvider>
|
||||
get() = pluginManager.getExtensions(DownloadProvider::class.java)
|
||||
|
||||
fun getProviders(): List<String> {
|
||||
return downloadPlugins.map { it.javaClass.name }
|
||||
}
|
||||
|
||||
fun getDownloadElement(path: String, provider: String): Download {
|
||||
val provider = downloadPlugins.firstOrNull { it.javaClass.name == provider }
|
||||
?: throw IllegalArgumentException("Download provider $provider not found")
|
||||
|
||||
return provider.getDownloadSources(Path(path))
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -8,6 +8,7 @@ class GameyfinManifestPluginDescriptorFinder() : ManifestPluginDescriptorFinder(
|
||||
companion object {
|
||||
const val PLUGIN_NAME: String = "Plugin-Name"
|
||||
const val PLUGIN_AUTHOR: String = "Plugin-Author"
|
||||
const val PLUGIN_SHORT_DESCRIPTION: String = "Plugin-Short-Description"
|
||||
const val PLUGIN_URL: String = "Plugin-Url"
|
||||
}
|
||||
|
||||
@@ -22,9 +23,10 @@ class GameyfinManifestPluginDescriptorFinder() : ManifestPluginDescriptorFinder(
|
||||
descriptor = pluginDescriptor,
|
||||
name = attributes.getValue(PLUGIN_NAME)
|
||||
?: throw IllegalStateException("Plugin-Name not found in manifest"),
|
||||
shortDescription = attributes.getValue(PLUGIN_SHORT_DESCRIPTION),
|
||||
author = attributes.getValue(PLUGIN_AUTHOR)
|
||||
?: throw IllegalStateException("Plugin-Author not found in manifest"),
|
||||
url = attributes.getValue(PLUGIN_URL) ?: "",
|
||||
url = attributes.getValue(PLUGIN_URL),
|
||||
)
|
||||
}
|
||||
}
|
||||
+11
-4
@@ -4,24 +4,31 @@ import org.pf4j.DefaultPluginDescriptor
|
||||
import org.pf4j.PluginDescriptor
|
||||
|
||||
data class GameyfinPluginDescriptor(
|
||||
var pluginUrl: String,
|
||||
var pluginUrl: String?,
|
||||
var pluginName: String,
|
||||
var pluginShortDescription: String?,
|
||||
var author: String
|
||||
) : DefaultPluginDescriptor() {
|
||||
|
||||
companion object {
|
||||
const val NEWLINE_INDICATOR = "<br>"
|
||||
}
|
||||
|
||||
constructor(
|
||||
descriptor: PluginDescriptor,
|
||||
url: String,
|
||||
url: String?,
|
||||
name: String,
|
||||
shortDescription: String?,
|
||||
author: String
|
||||
) : this(
|
||||
pluginUrl = url,
|
||||
pluginName = name,
|
||||
pluginShortDescription = shortDescription,
|
||||
author = author
|
||||
) {
|
||||
this.pluginId = descriptor.pluginId
|
||||
// The Manifest parser ignores newlines in the description
|
||||
this.pluginDescription = descriptor.pluginDescription.replace("<br>", "\n")
|
||||
// The Manifest spec does not account for line breaks in values
|
||||
this.pluginDescription = descriptor.pluginDescription.replace(NEWLINE_INDICATOR, "\n")
|
||||
this.pluginClass = descriptor.pluginClass
|
||||
this.setPluginVersion(descriptor.version)
|
||||
this.requires = descriptor.requires
|
||||
|
||||
@@ -6,6 +6,7 @@ data class PluginDto(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val shortDescription: String? = null,
|
||||
val version: String,
|
||||
val author: String,
|
||||
val license: String? = null,
|
||||
|
||||
+1
@@ -93,6 +93,7 @@ class PluginManagementService(
|
||||
id = descriptor.pluginId,
|
||||
name = descriptor.pluginName,
|
||||
description = descriptor.pluginDescription,
|
||||
shortDescription = descriptor.pluginShortDescription,
|
||||
version = descriptor.version,
|
||||
author = descriptor.author,
|
||||
license = descriptor.license,
|
||||
|
||||
Reference in New Issue
Block a user