Files
2026-09-30 10:19:23 +08:00

188 lines
5.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
)
type runtimeSettings struct {
Concurrency int `json:"concurrency"`
RequestConcurrency int `json:"requestConcurrency"`
RequestIntervalMS int `json:"requestIntervalMs"`
ProxyURL *string `json:"proxyURL,omitempty"`
OutputDir *string `json:"outputDir,omitempty"`
GroupBySource *bool `json:"groupBySource,omitempty"`
}
func (settings runtimeSettings) validate() error {
if settings.OutputDir != nil {
if _, err := normalizedOutputDirectory(*settings.OutputDir); err != nil {
return err
}
}
if settings.ProxyURL != nil {
if _, err := configuredProxy(*settings.ProxyURL); err != nil {
return err
}
}
if settings.Concurrency < 1 || settings.Concurrency > 32 {
return errors.New("下载并发范围为 1–32")
}
if settings.RequestConcurrency < 1 || settings.RequestConcurrency > 16 {
return errors.New("剧库请求并发范围为 1–16")
}
if settings.RequestIntervalMS < 100 || settings.RequestIntervalMS > 60000 {
return errors.New("同站请求间隔范围为 100–60000 毫秒")
}
return nil
}
func runtimeSettingsPath(directory string) string {
return filepath.Join(directory, "juku.json")
}
func loadRuntimeSettings(cfg *Config) {
if cfg.settingsLoaded {
return
}
cfg.settingsLoaded = true
document, err := readConfigDocument(runtimeSettingsPath(cfg.runtimeSettingsDirectory()))
if err == nil {
loaded, configErr := document.config()
if configErr == nil {
loaded.dataDir = cfg.dataDirectory()
loaded.settingsLoaded = true
*cfg = loaded
}
return
}
if !errors.Is(err, os.ErrNotExist) {
fmt.Printf("配置读取失败,保留当前设置: %v\n", err)
return
}
body, err := os.ReadFile(filepath.Join(cfg.OutputDir, "settings.json"))
if errors.Is(err, os.ErrNotExist) {
return
}
var settings runtimeSettings
if err == nil {
err = json.Unmarshal(body, &settings)
}
if err == nil {
err = settings.validate()
}
if err != nil {
fmt.Printf(" 界面并发设置读取失败,使用配置文件/默认值: %v\n", err)
return
}
applyRuntimeSettings(cfg, settings)
}
func applyRuntimeSettings(cfg *Config, settings runtimeSettings) {
cfg.Concurrency = settings.Concurrency
cfg.RequestConcurrency = settings.RequestConcurrency
cfg.RequestIntervalMS = settings.RequestIntervalMS
if settings.ProxyURL != nil {
cfg.ProxyURL = *settings.ProxyURL
}
if settings.OutputDir != nil {
cfg.OutputDir, _ = normalizedOutputDirectory(*settings.OutputDir)
}
if settings.GroupBySource != nil {
cfg.GroupBySource = *settings.GroupBySource
}
}
func (a *UIApp) updateRuntimeSettings(writer http.ResponseWriter, request *http.Request) bool {
var settings runtimeSettings
decoder := json.NewDecoder(http.MaxBytesReader(writer, request.Body, 4096))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&settings); err != nil {
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": "无效的并发设置"})
return false
}
if err := settings.validate(); err != nil {
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": err.Error()})
return false
}
a.settingsMu.Lock()
defer a.settingsMu.Unlock()
a.mu.Lock()
cfg := a.cfg
if settings.ProxyURL == nil {
value := a.cfg.ProxyURL
settings.ProxyURL = &value
}
if settings.OutputDir == nil {
value := firstNonEmpty(a.nextOutputDir, cfg.outputDirSetting, cfg.OutputDir)
settings.OutputDir = &value
}
if settings.GroupBySource == nil {
value := cfg.GroupBySource
settings.GroupBySource = &value
}
a.mu.Unlock()
directory, err := normalizedOutputDirectory(*settings.OutputDir)
if err != nil {
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": err.Error()})
return false
}
settings.OutputDir = &directory
if !sameDirectory(directory, cfg.OutputDir) {
if err := checkOutputDirectory(directory); err != nil {
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": a.redactError(err)})
return false
}
}
if err := saveRuntimeSettings(cfg.runtimeSettingsDirectory(), settings); err != nil {
writeJSON(writer, http.StatusInternalServerError, map[string]string{"error": "设置保存失败: " + a.redactError(err)})
return false
}
a.downloader.proxyRouter.configure(*settings.ProxyURL)
a.downloader.client.CloseIdleConnections()
a.mu.Lock()
a.cfg.Concurrency = settings.Concurrency
a.cfg.RequestConcurrency = settings.RequestConcurrency
a.cfg.RequestIntervalMS = settings.RequestIntervalMS
a.cfg.ProxyURL = *settings.ProxyURL
a.cfg.GroupBySource = *settings.GroupBySource
a.downloader.directoryMu.Lock()
a.downloader.downloadGrouping = settings.GroupBySource
a.downloader.directoryMu.Unlock()
a.nextOutputDir = directory
a.downloader.limiter.configure(settings.RequestConcurrency, time.Duration(settings.RequestIntervalMS)*time.Millisecond)
a.ensureWorkersLocked()
a.cond.Broadcast()
a.mu.Unlock()
return true
}
func saveRuntimeSettings(directory string, settings runtimeSettings) error {
if err := settings.validate(); err != nil {
return err
}
path := runtimeSettingsPath(directory)
document, err := readConfigDocument(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
document.Download.Concurrency = settings.Concurrency
document.Network.RequestConcurrency = settings.RequestConcurrency
document.Network.RequestIntervalMS = settings.RequestIntervalMS
if settings.OutputDir != nil {
document.Download.Directory = *settings.OutputDir
}
if settings.GroupBySource != nil {
document.Download.GroupBySource = *settings.GroupBySource
}
if settings.ProxyURL != nil {
document.Network.Proxy = *settings.ProxyURL
}
return writeConfigDocument(path, document)
}