188 lines
5.7 KiB
Go
188 lines
5.7 KiB
Go
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)
|
||
}
|