Files
seanime-docker/seanime-2.9.10/internal/handlers/settings.go
2025-09-20 14:08:38 +01:00

302 lines
8.7 KiB
Go

package handlers
import (
"errors"
"os"
"path/filepath"
"runtime"
"seanime/internal/database/models"
"seanime/internal/torrents/torrent"
"seanime/internal/util"
"time"
"github.com/labstack/echo/v4"
"github.com/samber/lo"
)
// HandleGetSettings
//
// @summary returns the app settings.
// @route /api/v1/settings [GET]
// @returns models.Settings
func (h *Handler) HandleGetSettings(c echo.Context) error {
settings, err := h.App.Database.GetSettings()
if err != nil {
return h.RespondWithError(c, err)
}
if settings.ID == 0 {
return h.RespondWithError(c, errors.New(runtime.GOOS))
}
return h.RespondWithData(c, settings)
}
// HandleGettingStarted
//
// @summary updates the app settings.
// @desc This will update the app settings.
// @desc The client should re-fetch the server status after this.
// @route /api/v1/start [POST]
// @returns handlers.Status
func (h *Handler) HandleGettingStarted(c echo.Context) error {
type body struct {
Library models.LibrarySettings `json:"library"`
MediaPlayer models.MediaPlayerSettings `json:"mediaPlayer"`
Torrent models.TorrentSettings `json:"torrent"`
Anilist models.AnilistSettings `json:"anilist"`
Discord models.DiscordSettings `json:"discord"`
Manga models.MangaSettings `json:"manga"`
Notifications models.NotificationSettings `json:"notifications"`
Nakama models.NakamaSettings `json:"nakama"`
EnableTranscode bool `json:"enableTranscode"`
EnableTorrentStreaming bool `json:"enableTorrentStreaming"`
DebridProvider string `json:"debridProvider"`
DebridApiKey string `json:"debridApiKey"`
}
var b body
if err := c.Bind(&b); err != nil {
return h.RespondWithError(c, err)
}
// Check settings
if b.Library.LibraryPaths == nil {
b.Library.LibraryPaths = []string{}
}
b.Library.LibraryPath = filepath.ToSlash(b.Library.LibraryPath)
settings, err := h.App.Database.UpsertSettings(&models.Settings{
BaseModel: models.BaseModel{
ID: 1,
UpdatedAt: time.Now(),
},
Library: &b.Library,
MediaPlayer: &b.MediaPlayer,
Torrent: &b.Torrent,
Anilist: &b.Anilist,
Discord: &b.Discord,
Manga: &b.Manga,
Notifications: &b.Notifications,
Nakama: &b.Nakama,
AutoDownloader: &models.AutoDownloaderSettings{
Provider: b.Library.TorrentProvider,
Interval: 20,
Enabled: false,
DownloadAutomatically: true,
EnableEnhancedQueries: true,
},
})
if err != nil {
return h.RespondWithError(c, err)
}
if b.EnableTorrentStreaming {
go func() {
defer util.HandlePanicThen(func() {})
prev, found := h.App.Database.GetTorrentstreamSettings()
if found {
prev.Enabled = true
//prev.IncludeInLibrary = true
_, _ = h.App.Database.UpsertTorrentstreamSettings(prev)
}
}()
}
if b.EnableTranscode {
go func() {
defer util.HandlePanicThen(func() {})
prev, found := h.App.Database.GetMediastreamSettings()
if found {
prev.TranscodeEnabled = true
_, _ = h.App.Database.UpsertMediastreamSettings(prev)
}
}()
}
if b.DebridProvider != "" && b.DebridProvider != "none" {
go func() {
defer util.HandlePanicThen(func() {})
prev, found := h.App.Database.GetDebridSettings()
if found {
prev.Enabled = true
prev.Provider = b.DebridProvider
prev.ApiKey = b.DebridApiKey
//prev.IncludeDebridStreamInLibrary = true
_, _ = h.App.Database.UpsertDebridSettings(prev)
}
}()
}
h.App.WSEventManager.SendEvent("settings", settings)
status := h.NewStatus(c)
// Refresh modules that depend on the settings
h.App.InitOrRefreshModules()
return h.RespondWithData(c, status)
}
// HandleSaveSettings
//
// @summary updates the app settings.
// @desc This will update the app settings.
// @desc The client should re-fetch the server status after this.
// @route /api/v1/settings [PATCH]
// @returns handlers.Status
func (h *Handler) HandleSaveSettings(c echo.Context) error {
type body struct {
Library models.LibrarySettings `json:"library"`
MediaPlayer models.MediaPlayerSettings `json:"mediaPlayer"`
Torrent models.TorrentSettings `json:"torrent"`
Anilist models.AnilistSettings `json:"anilist"`
Discord models.DiscordSettings `json:"discord"`
Manga models.MangaSettings `json:"manga"`
Notifications models.NotificationSettings `json:"notifications"`
Nakama models.NakamaSettings `json:"nakama"`
}
var b body
if err := c.Bind(&b); err != nil {
return h.RespondWithError(c, err)
}
if b.Library.LibraryPath != "" {
b.Library.LibraryPath = filepath.ToSlash(filepath.Clean(b.Library.LibraryPath))
}
if b.Library.LibraryPaths == nil || b.Library.LibraryPath == "" {
b.Library.LibraryPaths = []string{}
}
for i, path := range b.Library.LibraryPaths {
b.Library.LibraryPaths[i] = filepath.ToSlash(filepath.Clean(path))
}
b.Library.LibraryPaths = lo.Filter(b.Library.LibraryPaths, func(s string, _ int) bool {
if s == "" || util.IsSameDir(s, b.Library.LibraryPath) {
return false
}
info, err := os.Stat(s)
if err != nil {
return false
}
return info.IsDir()
})
// Check that any library paths are not subdirectories of each other
for i, path1 := range b.Library.LibraryPaths {
if util.IsSubdirectory(b.Library.LibraryPath, path1) || util.IsSubdirectory(path1, b.Library.LibraryPath) {
return h.RespondWithError(c, errors.New("library paths cannot be subdirectories of each other"))
}
for j, path2 := range b.Library.LibraryPaths {
if i != j && util.IsSubdirectory(path1, path2) {
return h.RespondWithError(c, errors.New("library paths cannot be subdirectories of each other"))
}
}
}
autoDownloaderSettings := models.AutoDownloaderSettings{}
prevSettings, err := h.App.Database.GetSettings()
if err == nil && prevSettings.AutoDownloader != nil {
autoDownloaderSettings = *prevSettings.AutoDownloader
}
// Disable auto-downloader if the torrent provider is set to none
if b.Library.TorrentProvider == torrent.ProviderNone && autoDownloaderSettings.Enabled {
h.App.Logger.Debug().Msg("app: Disabling auto-downloader because the torrent provider is set to none")
autoDownloaderSettings.Enabled = false
}
settings, err := h.App.Database.UpsertSettings(&models.Settings{
BaseModel: models.BaseModel{
ID: 1,
UpdatedAt: time.Now(),
},
Library: &b.Library,
MediaPlayer: &b.MediaPlayer,
Torrent: &b.Torrent,
Anilist: &b.Anilist,
Manga: &b.Manga,
Discord: &b.Discord,
Notifications: &b.Notifications,
Nakama: &b.Nakama,
AutoDownloader: &autoDownloaderSettings,
})
if err != nil {
return h.RespondWithError(c, err)
}
h.App.WSEventManager.SendEvent("settings", settings)
status := h.NewStatus(c)
// Refresh modules that depend on the settings
h.App.InitOrRefreshModules()
return h.RespondWithData(c, status)
}
// HandleSaveAutoDownloaderSettings
//
// @summary updates the auto-downloader settings.
// @route /api/v1/settings/auto-downloader [PATCH]
// @returns bool
func (h *Handler) HandleSaveAutoDownloaderSettings(c echo.Context) error {
type body struct {
Interval int `json:"interval"`
Enabled bool `json:"enabled"`
DownloadAutomatically bool `json:"downloadAutomatically"`
EnableEnhancedQueries bool `json:"enableEnhancedQueries"`
EnableSeasonCheck bool `json:"enableSeasonCheck"`
UseDebrid bool `json:"useDebrid"`
}
var b body
if err := c.Bind(&b); err != nil {
return h.RespondWithError(c, err)
}
currSettings, err := h.App.Database.GetSettings()
if err != nil {
return h.RespondWithError(c, err)
}
// Validation
if b.Interval < 15 {
return h.RespondWithError(c, errors.New("interval must be at least 15 minutes"))
}
autoDownloaderSettings := &models.AutoDownloaderSettings{
Provider: currSettings.Library.TorrentProvider,
Interval: b.Interval,
Enabled: b.Enabled,
DownloadAutomatically: b.DownloadAutomatically,
EnableEnhancedQueries: b.EnableEnhancedQueries,
EnableSeasonCheck: b.EnableSeasonCheck,
UseDebrid: b.UseDebrid,
}
currSettings.AutoDownloader = autoDownloaderSettings
currSettings.BaseModel = models.BaseModel{
ID: 1,
UpdatedAt: time.Now(),
}
_, err = h.App.Database.UpsertSettings(currSettings)
if err != nil {
return h.RespondWithError(c, err)
}
// Update Auto Downloader - This runs in a goroutine
h.App.AutoDownloader.SetSettings(autoDownloaderSettings, currSettings.Library.TorrentProvider)
return h.RespondWithData(c, true)
}