feat(app): add internal components batch 1
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user