mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-17 00:30:04 +00:00
First implementation of job system
This commit is contained in:
Generated
+2
-9
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "gameyfin",
|
"name": "gameyfin",
|
||||||
"version": "2.0.0.beta4",
|
"version": "2.0.0.beta6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gameyfin",
|
"name": "gameyfin",
|
||||||
"version": "2.0.0.beta4",
|
"version": "2.0.0.beta6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroui/react": "2.7.9",
|
"@heroui/react": "2.7.9",
|
||||||
"@material-tailwind/react": "^2.1.10",
|
"@material-tailwind/react": "^2.1.10",
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"construct-style-sheets-polyfill": "3.1.0",
|
"construct-style-sheets-polyfill": "3.1.0",
|
||||||
"cron-validator": "^1.3.1",
|
|
||||||
"date-fns": "2.29.3",
|
"date-fns": "2.29.3",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"framer-motion": "^12.5.0",
|
"framer-motion": "^12.5.0",
|
||||||
@@ -10300,12 +10299,6 @@
|
|||||||
"url": "https://opencollective.com/core-js"
|
"url": "https://opencollective.com/core-js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cron-validator": {
|
|
||||||
"version": "1.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz",
|
|
||||||
"integrity": "sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
|||||||
+1
-3
@@ -28,7 +28,6 @@
|
|||||||
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
"@vaadin/vaadin-usage-statistics": "2.1.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"construct-style-sheets-polyfill": "3.1.0",
|
"construct-style-sheets-polyfill": "3.1.0",
|
||||||
"cron-validator": "^1.3.1",
|
|
||||||
"date-fns": "2.29.3",
|
"date-fns": "2.29.3",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
"framer-motion": "^12.5.0",
|
"framer-motion": "^12.5.0",
|
||||||
@@ -123,7 +122,6 @@
|
|||||||
"@vaadin/hilla-lit-form": "$@vaadin/hilla-lit-form",
|
"@vaadin/hilla-lit-form": "$@vaadin/hilla-lit-form",
|
||||||
"@vaadin/hilla-react-form": "$@vaadin/hilla-react-form",
|
"@vaadin/hilla-react-form": "$@vaadin/hilla-react-form",
|
||||||
"@vaadin/hilla-react-signals": "$@vaadin/hilla-react-signals",
|
"@vaadin/hilla-react-signals": "$@vaadin/hilla-react-signals",
|
||||||
"cron-validator": "$cron-validator",
|
|
||||||
"moment": "$moment",
|
"moment": "$moment",
|
||||||
"moment-timezone": "$moment-timezone",
|
"moment-timezone": "$moment-timezone",
|
||||||
"react-confetti-boom": "$react-confetti-boom",
|
"react-confetti-boom": "$react-confetti-boom",
|
||||||
@@ -265,6 +263,6 @@
|
|||||||
"workbox-precaching": "7.3.0"
|
"workbox-precaching": "7.3.0"
|
||||||
},
|
},
|
||||||
"disableUsageStatistics": true,
|
"disableUsageStatistics": true,
|
||||||
"hash": "e964f24aa5152284476d9e99245c9b92f8e6f94274818ee46eec2a3f91e29fc6"
|
"hash": "962eccc3fa0735d5234901be4f9e384096113c45bec22564a53688096d62aef4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import {Outlet, useHref, useNavigate} from 'react-router';
|
import {Outlet, useHref, useNavigate} from 'react-router';
|
||||||
import "./main.css";
|
import "./main.css";
|
||||||
import "Frontend/util/custom-validators";
|
|
||||||
import {HeroUIProvider} from "@heroui/react";
|
import {HeroUIProvider} from "@heroui/react";
|
||||||
import {ThemeProvider as NextThemesProvider} from "next-themes";
|
import {ThemeProvider as NextThemesProvider} from "next-themes";
|
||||||
import {themeNames} from "Frontend/theming/themes";
|
import {themeNames} from "Frontend/theming/themes";
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import ConfigFormField from "Frontend/components/administration/ConfigFormField"
|
|||||||
import withConfigPage from "Frontend/components/administration/withConfigPage";
|
import withConfigPage from "Frontend/components/administration/withConfigPage";
|
||||||
import Section from "Frontend/components/general/Section";
|
import Section from "Frontend/components/general/Section";
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
import "Frontend/util/yup-extensions";
|
||||||
import {addToast, Button, Divider, Tooltip, useDisclosure} from "@heroui/react";
|
import {addToast, Button, Divider, Tooltip, useDisclosure} from "@heroui/react";
|
||||||
import {Plus} from "@phosphor-icons/react";
|
import {Plus} from "@phosphor-icons/react";
|
||||||
import {LibraryEndpoint} from "Frontend/generated/endpoints";
|
import {LibraryEndpoint} from "Frontend/generated/endpoints";
|
||||||
@@ -55,7 +56,7 @@ function LibraryManagementLayout({getConfig, formik}: any) {
|
|||||||
|
|
||||||
<Section title="Metadata"/>
|
<Section title="Metadata"/>
|
||||||
<div className="flex flex-row items-baseline">
|
<div className="flex flex-row items-baseline">
|
||||||
<ConfigFormField configElement={getConfig("library.metadata.update.enabled")} isDisabled/>
|
<ConfigFormField configElement={getConfig("library.metadata.update.enabled")}/>
|
||||||
<ConfigFormField configElement={getConfig("library.metadata.update.schedule")}
|
<ConfigFormField configElement={getConfig("library.metadata.update.schedule")}
|
||||||
isDisabled={!formik.values.library.metadata.update.enabled}/>
|
isDisabled={!formik.values.library.metadata.update.enabled}/>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,8 +96,11 @@ const validationSchema = Yup.object({
|
|||||||
library: Yup.object({
|
library: Yup.object({
|
||||||
metadata: Yup.object({
|
metadata: Yup.object({
|
||||||
update: Yup.object({
|
update: Yup.object({
|
||||||
// @ts-ignore
|
enabled: Yup.boolean(),
|
||||||
schedule: Yup.string().cron()
|
schedule: Yup.string().when("enabled", {
|
||||||
|
is: true,
|
||||||
|
then: (schema) => schema.cron()
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
scan: Yup.object({
|
scan: Yup.object({
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import * as Yup from "yup";
|
|
||||||
import {isValidCron} from "cron-validator";
|
|
||||||
|
|
||||||
// Custom validator for cron expressions
|
|
||||||
Yup.addMethod(Yup.string, 'cron', function (message) {
|
|
||||||
return this.test('cron', message, function (value) {
|
|
||||||
const {path, createError} = this;
|
|
||||||
return isValidCron(value as string) || createError({path, message: message || 'Invalid cron expression'});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import {ConfigEndpoint} from 'Frontend/generated/endpoints';
|
||||||
|
|
||||||
|
Yup.addMethod(Yup.string, 'cron', function (message = 'Invalid cron expression') {
|
||||||
|
return this.test('cron', message, async function (value) {
|
||||||
|
const {path, createError} = this;
|
||||||
|
if (!value) return true;
|
||||||
|
try {
|
||||||
|
const isValid = await ConfigEndpoint.validateCronExpression(value);
|
||||||
|
return isValid || createError({path, message});
|
||||||
|
} catch (e) {
|
||||||
|
return createError({path, message: 'Error validating cron expression'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TypeScript: Extend Yup's type definitions
|
||||||
|
declare module 'yup' {
|
||||||
|
interface StringSchema {
|
||||||
|
cron(message?: string): StringSchema;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import org.gameyfin.app.core.Role
|
|||||||
import org.gameyfin.app.core.annotations.DynamicPublicAccess
|
import org.gameyfin.app.core.annotations.DynamicPublicAccess
|
||||||
import org.gameyfin.app.users.UserService
|
import org.gameyfin.app.users.UserService
|
||||||
import org.gameyfin.app.users.util.isAdmin
|
import org.gameyfin.app.users.util.isAdmin
|
||||||
|
import org.springframework.scheduling.support.CronExpression
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
|
|
||||||
@Endpoint
|
@Endpoint
|
||||||
@@ -36,7 +37,15 @@ class ConfigEndpoint(
|
|||||||
|
|
||||||
fun update(update: ConfigUpdateDto) = configService.update(update)
|
fun update(update: ConfigUpdateDto) = configService.update(update)
|
||||||
|
|
||||||
/** Specific read-only endpoint for all users **/
|
/**
|
||||||
|
* Validates a cron expression because Spring has a custom syntax for cron expressions.
|
||||||
|
* @see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html#parse(java.lang.String)
|
||||||
|
*/
|
||||||
|
fun validateCronExpression(cronExpression: String): Boolean {
|
||||||
|
return CronExpression.isValidExpression(cronExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Specific read-only endpoints for all users **/
|
||||||
|
|
||||||
@DynamicPublicAccess
|
@DynamicPublicAccess
|
||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
@@ -49,5 +58,4 @@ class ConfigEndpoint(
|
|||||||
@DynamicPublicAccess
|
@DynamicPublicAccess
|
||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
fun isPublicAccessEnabled(): Boolean = configService.get(ConfigProperties.Libraries.AllowPublicAccess) == true
|
fun isPublicAccessEnabled(): Boolean = configService.get(ConfigProperties.Libraries.AllowPublicAccess) == true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,15 +90,15 @@ sealed class ConfigProperties<T : Serializable>(
|
|||||||
data object UpdateEnabled : ConfigProperties<Boolean>(
|
data object UpdateEnabled : ConfigProperties<Boolean>(
|
||||||
Boolean::class,
|
Boolean::class,
|
||||||
"library.metadata.update.enabled",
|
"library.metadata.update.enabled",
|
||||||
"Enable periodic refresh of video game metadata (coming soon™)",
|
"Enable periodic refresh of video game metadata",
|
||||||
false
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
data object UpdateSchedule : ConfigProperties<String>(
|
data object UpdateSchedule : ConfigProperties<String>(
|
||||||
String::class,
|
String::class,
|
||||||
"library.metadata.update.schedule",
|
"library.metadata.update.schedule",
|
||||||
"Schedule for periodic metadata refresh in cron format",
|
"Schedule for periodic metadata refresh in Spring cron format",
|
||||||
"0 0 * * 0"
|
"@daily"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package org.gameyfin.app.config.dto
|
||||||
|
|
||||||
|
data class CronExpressionVerificationResultDto(
|
||||||
|
val valid: Boolean,
|
||||||
|
val errorMessage: String? = null
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val valid = CronExpressionVerificationResultDto(true)
|
||||||
|
fun invalid(errorMessage: String) = CronExpressionVerificationResultDto(false, errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import jakarta.persistence.*
|
|||||||
import org.gameyfin.app.core.security.EncryptionConverter
|
import org.gameyfin.app.core.security.EncryptionConverter
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@EntityListeners(ConfigEntryEntityListener::class)
|
||||||
@Table(name = "app_config")
|
@Table(name = "app_config")
|
||||||
class ConfigEntry(
|
class ConfigEntry(
|
||||||
@Id
|
@Id
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.gameyfin.app.config.entities
|
||||||
|
|
||||||
|
import jakarta.persistence.PostPersist
|
||||||
|
import jakarta.persistence.PostRemove
|
||||||
|
import jakarta.persistence.PostUpdate
|
||||||
|
import org.gameyfin.app.config.ConfigProperties
|
||||||
|
import org.gameyfin.app.core.events.LibraryScanScheduleUpdatedEvent
|
||||||
|
import org.gameyfin.app.util.EventPublisherHolder
|
||||||
|
|
||||||
|
class ConfigEntryEntityListener {
|
||||||
|
@PostUpdate
|
||||||
|
@PostPersist
|
||||||
|
@PostRemove
|
||||||
|
fun process(configEntry: ConfigEntry) {
|
||||||
|
when (configEntry.key) {
|
||||||
|
in ConfigProperties.Libraries.Metadata.UpdateEnabled.key,
|
||||||
|
ConfigProperties.Libraries.Metadata.UpdateSchedule.key -> {
|
||||||
|
EventPublisherHolder.publish(LibraryScanScheduleUpdatedEvent(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigProperties.Libraries.Scan.EnableFilesystemWatcher.key -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,4 @@ class PasswordResetRequestEvent(source: Any, val token: Token<TokenType.Password
|
|||||||
|
|
||||||
class AccountDeletedEvent(source: Any, val user: User, val baseUrl: String) : ApplicationEvent(source)
|
class AccountDeletedEvent(source: Any, val user: User, val baseUrl: String) : ApplicationEvent(source)
|
||||||
|
|
||||||
class GameRequestEvent(source: Any) : ApplicationEvent(source)
|
class LibraryScanScheduleUpdatedEvent(source: Any) : ApplicationEvent(source)
|
||||||
|
|
||||||
class GameRequestApprovalEvent(source: Any) : ApplicationEvent(source)
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.gameyfin.app.core.jobs
|
||||||
|
|
||||||
|
interface Job {
|
||||||
|
val name: String
|
||||||
|
fun run(): JobRunResult
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.gameyfin.app.core.jobs
|
||||||
|
|
||||||
|
import jakarta.persistence.*
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class JobRunResult(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
val id: Long? = null,
|
||||||
|
val jobName: String,
|
||||||
|
val startedAt: LocalDateTime,
|
||||||
|
val finishedAt: LocalDateTime?,
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
val status: JobRunStatus,
|
||||||
|
val message: String?
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.gameyfin.app.core.jobs
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface JobRunResultRepository : JpaRepository<JobRunResult, Long>
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.gameyfin.app.core.jobs
|
||||||
|
|
||||||
|
enum class JobRunStatus {
|
||||||
|
IN_PROGRESS,
|
||||||
|
SUCCESS,
|
||||||
|
FAILED,
|
||||||
|
CANCELLED
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package org.gameyfin.app.core.jobs
|
||||||
|
|
||||||
|
import com.vaadin.hilla.exception.EndpointException
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import jakarta.annotation.PostConstruct
|
||||||
|
import org.gameyfin.app.config.ConfigProperties
|
||||||
|
import org.gameyfin.app.config.ConfigService
|
||||||
|
import org.gameyfin.app.core.events.LibraryScanScheduleUpdatedEvent
|
||||||
|
import org.gameyfin.app.libraries.LibraryScanService
|
||||||
|
import org.springframework.context.event.EventListener
|
||||||
|
import org.springframework.scheduling.TaskScheduler
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
|
||||||
|
import org.springframework.scheduling.support.CronExpression
|
||||||
|
import org.springframework.scheduling.support.CronTrigger
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.util.concurrent.ScheduledFuture
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class JobService(
|
||||||
|
private val config: ConfigService,
|
||||||
|
libraryScanService: LibraryScanService,
|
||||||
|
private val jobRunResultRepository: JobRunResultRepository
|
||||||
|
) {
|
||||||
|
private val scheduler: TaskScheduler = ThreadPoolTaskScheduler().apply { initialize() }
|
||||||
|
private var libraryScanFuture: ScheduledFuture<*>? = null
|
||||||
|
|
||||||
|
private val libraryScanJob: Job = LibraryScanJob(libraryScanService)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = KotlinLogging.logger { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
fun init() {
|
||||||
|
scheduleLibraryScanJob()
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener(LibraryScanScheduleUpdatedEvent::class)
|
||||||
|
fun onLibraryScanScheduleUpdated() {
|
||||||
|
scheduleLibraryScanJob()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleLibraryScanJob() {
|
||||||
|
libraryScanFuture?.cancel(false)
|
||||||
|
|
||||||
|
if (config.get(ConfigProperties.Libraries.Metadata.UpdateEnabled) != true) {
|
||||||
|
log.debug { "Disabled scheduled library scans" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val cronExpressionString = config.get(ConfigProperties.Libraries.Metadata.UpdateSchedule) ?: return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val cronTrigger = CronTrigger(cronExpressionString)
|
||||||
|
libraryScanFuture = (scheduler as ThreadPoolTaskScheduler).schedule({
|
||||||
|
runAndPersistJob(libraryScanJob)
|
||||||
|
}, cronTrigger)
|
||||||
|
log.debug {
|
||||||
|
"Library scan job scheduled, next run will be @ " +
|
||||||
|
"${CronExpression.parse(cronExpressionString).next(LocalDateTime.now())}"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error { "Failed to schedule library scan job: ${e.message}" }
|
||||||
|
log.debug(e) { }
|
||||||
|
throw EndpointException("Failed to schedule library scan job: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runAndPersistJob(job: Job) {
|
||||||
|
val result = job.run()
|
||||||
|
jobRunResultRepository.save(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package org.gameyfin.app.core.jobs
|
||||||
|
|
||||||
|
import org.gameyfin.app.libraries.LibraryScanService
|
||||||
|
import org.gameyfin.app.libraries.enums.ScanType
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
class LibraryScanJob(private val libraryScanService: LibraryScanService) : Job {
|
||||||
|
override val name: String = "LibraryScan"
|
||||||
|
override fun run(): JobRunResult {
|
||||||
|
val startedAt = LocalDateTime.now()
|
||||||
|
var finishedAt: LocalDateTime?
|
||||||
|
var status: JobRunStatus
|
||||||
|
var message: String?
|
||||||
|
try {
|
||||||
|
libraryScanService.triggerScan(ScanType.SCHEDULED, null)
|
||||||
|
message = "Library scan completed successfully"
|
||||||
|
status = JobRunStatus.SUCCESS
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
message = ex.message
|
||||||
|
status = JobRunStatus.FAILED
|
||||||
|
} finally {
|
||||||
|
finishedAt = LocalDateTime.now()
|
||||||
|
}
|
||||||
|
return JobRunResult(
|
||||||
|
jobName = name,
|
||||||
|
startedAt = startedAt,
|
||||||
|
finishedAt = finishedAt,
|
||||||
|
status = status,
|
||||||
|
message = message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -71,7 +71,8 @@ class LibraryScanService(
|
|||||||
try {
|
try {
|
||||||
when (scanType) {
|
when (scanType) {
|
||||||
ScanType.QUICK -> quickScan(library)
|
ScanType.QUICK -> quickScan(library)
|
||||||
ScanType.FULL -> fullScan(library)
|
ScanType.FULL -> fullScan(library, false)
|
||||||
|
ScanType.SCHEDULED -> fullScan(library, true)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
scansInProgress.remove(libraryId)
|
scansInProgress.remove(libraryId)
|
||||||
@@ -104,7 +105,7 @@ class LibraryScanService(
|
|||||||
*/
|
*/
|
||||||
fun fullScan(libraryDtos: Collection<LibraryDto>?) {
|
fun fullScan(libraryDtos: Collection<LibraryDto>?) {
|
||||||
val libraries = libraryDtos?.map { libraryCoreService.toEntity(it) } ?: libraryRepository.findAll()
|
val libraries = libraryDtos?.map { libraryCoreService.toEntity(it) } ?: libraryRepository.findAll()
|
||||||
libraries.forEach { executor.submit { fullScan(it) } }
|
libraries.forEach { executor.submit { fullScan(it, false) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun quickScan(library: Library) {
|
private fun quickScan(library: Library) {
|
||||||
@@ -191,10 +192,10 @@ class LibraryScanService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fullScan(library: Library) {
|
private fun fullScan(library: Library, triggeredBySchedule: Boolean) {
|
||||||
val progress = LibraryScanProgress(
|
val progress = LibraryScanProgress(
|
||||||
libraryId = library.id!!,
|
libraryId = library.id!!,
|
||||||
type = ScanType.FULL,
|
type = if (triggeredBySchedule) ScanType.SCHEDULED else ScanType.FULL,
|
||||||
currentStep = LibraryScanStep(
|
currentStep = LibraryScanStep(
|
||||||
description = "Scanning filesystem"
|
description = "Scanning filesystem"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.gameyfin.app.libraries.enums
|
package org.gameyfin.app.libraries.enums
|
||||||
|
|
||||||
enum class ScanType {
|
enum class ScanType {
|
||||||
QUICK, FULL
|
QUICK,
|
||||||
|
FULL,
|
||||||
|
SCHEDULED,
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.gameyfin.app.util
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext
|
||||||
|
import org.springframework.context.ApplicationContextAware
|
||||||
|
import org.springframework.context.ApplicationEvent
|
||||||
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
object EventPublisherHolder : ApplicationContextAware {
|
||||||
|
private var publisher: ApplicationEventPublisher? = null
|
||||||
|
|
||||||
|
override fun setApplicationContext(context: ApplicationContext) {
|
||||||
|
publisher = context
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publish(event: ApplicationEvent) {
|
||||||
|
publisher?.publishEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user