wood chen 5e2052a711 refactor(api): migrate from Fiber to Gin framework
Migrate the web framework from Fiber to Gin for better performance and community support.
2024-10-28 16:56:29 +08:00

302 lines
9.4 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package config
import (
"encoding/json"
"flag"
"os"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
"time"
"github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
)
const (
TimeDateFormat = "2006-01-02 15:04:05"
// GinLogFormat = "%s - [%s] \"%s %s %s\" %d %s \"%s\" %s"
WebpMax = 16383
AvifMax = 65536
HttpRegexp = `^https?://`
SampleConfig = `
{
"HOST": "127.0.0.1",
"PORT": "3333",
"QUALITY": "80",
"IMG_PATH": "./pics",
"EXHAUST_PATH": "./exhaust",
"IMG_MAP": {},
"ALLOWED_TYPES": ["jpg","png","jpeg","gif","bmp","svg","heic","nef"],
"CONVERT_TYPES": ["webp"],
"STRIP_METADATA": true,
"ENABLE_EXTRA_PARAMS": false,
"EXTRA_PARAMS_CROP_INTERESTING": "InterestingAttention",
"READ_BUFFER_SIZE": 4096,
"CONCURRENCY": 262144,
"DISABLE_KEEPALIVE": false,
"CACHE_TTL": 259200,
"MAX_CACHE_SIZE": 0
}`
)
var (
ConfigPath string
Jobs int
DumpSystemd bool
DumpConfig bool
ShowVersion bool
ProxyMode bool
Prefetch bool
Config = NewWebPConfig()
Version = "0.12.0"
WriteLock = cache.New(5*time.Minute, 10*time.Minute)
ConvertLock = cache.New(5*time.Minute, 10*time.Minute)
LocalHostAlias = "local"
RemoteCache *cache.Cache
)
type MetaFile struct {
Id string `json:"id"` // hash of below path, also json file name id.webp
Path string `json:"path"` // local: path with width and height, proxy: full url
Checksum string `json:"checksum"` // hash of original file or hash(etag). Use this to identify changes
}
type WebpConfig struct {
Host string `json:"HOST"`
Port string `json:"PORT"`
ImgPath string `json:"IMG_PATH"`
Quality int `json:"QUALITY,string"`
AllowedTypes []string `json:"ALLOWED_TYPES"`
ConvertTypes []string `json:"CONVERT_TYPES"`
ImageMap map[string]string `json:"IMG_MAP"`
ExhaustPath string `json:"EXHAUST_PATH"`
MetadataPath string `json:"METADATA_PATH"`
RemoteRawPath string `json:"REMOTE_RAW_PATH"`
EnableWebP bool `json:"ENABLE_WEBP"`
EnableAVIF bool `json:"ENABLE_AVIF"`
EnableJXL bool `json:"ENABLE_JXL"`
EnableExtraParams bool `json:"ENABLE_EXTRA_PARAMS"`
ExtraParamsCropInteresting string `json:"EXTRA_PARAMS_CROP_INTERESTING"`
StripMetadata bool `json:"STRIP_METADATA"`
ReadBufferSize int `json:"READ_BUFFER_SIZE"`
Concurrency int `json:"CONCURRENCY"`
DisableKeepalive bool `json:"DISABLE_KEEPALIVE"`
CacheTTL int `json:"CACHE_TTL"` // In minutes
MaxCacheSize int `json:"MAX_CACHE_SIZE"` // In MB, for max cached exhausted/metadata files(plus remote-raw if applicable), 0 means no limit
}
func NewWebPConfig() *WebpConfig {
return &WebpConfig{
Host: "0.0.0.0",
Port: "3333",
ImgPath: "./pics",
Quality: 80,
AllowedTypes: []string{"jpg", "png", "jpeg", "bmp", "gif", "svg", "nef", "heic", "webp"},
ConvertTypes: []string{"webp"},
ImageMap: map[string]string{},
ExhaustPath: "./exhaust",
MetadataPath: "./metadata",
RemoteRawPath: "./remote-raw",
EnableWebP: false,
EnableAVIF: false,
EnableJXL: false,
EnableExtraParams: false,
ExtraParamsCropInteresting: "InterestingAttention",
StripMetadata: true,
ReadBufferSize: 4096,
Concurrency: 262144,
DisableKeepalive: false,
CacheTTL: 259200,
MaxCacheSize: 0,
}
}
func init() {
flag.StringVar(&ConfigPath, "config", "config.json", "/path/to/config.json. (Default: ./config.json)")
flag.BoolVar(&Prefetch, "prefetch", false, "Prefetch and convert image to WebP format.")
flag.IntVar(&Jobs, "jobs", runtime.NumCPU(), "Prefetch thread, default is all.")
flag.BoolVar(&DumpConfig, "dump-config", false, "Print sample config.json.")
flag.BoolVar(&ShowVersion, "V", false, "Show version information.")
}
func LoadConfig() {
jsonObject, err := os.Open(ConfigPath)
if err != nil {
log.Fatal(err)
}
decoder := json.NewDecoder(jsonObject)
_ = decoder.Decode(&Config)
_ = jsonObject.Close()
Config.ImageMap = parseImgMap(Config.ImageMap)
if slices.Contains(Config.ConvertTypes, "webp") {
Config.EnableWebP = true
}
if slices.Contains(Config.ConvertTypes, "avif") {
Config.EnableAVIF = true
}
if slices.Contains(Config.ConvertTypes, "jxl") {
Config.EnableJXL = true
}
// Read from ENV for override
if os.Getenv("WEBP_HOST") != "" {
Config.Host = os.Getenv("WEBP_HOST")
}
if os.Getenv("WEBP_PORT") != "" {
Config.Port = os.Getenv("WEBP_PORT")
}
if os.Getenv("WEBP_IMG_PATH") != "" {
Config.ImgPath = os.Getenv("WEBP_IMG_PATH")
}
if os.Getenv("WEBP_EXHAUST_PATH") != "" {
Config.ExhaustPath = os.Getenv("WEBP_EXHAUST_PATH")
}
if os.Getenv("WEBP_QUALITY") != "" {
quality, err := strconv.Atoi(os.Getenv("WEBP_QUALITY"))
if err != nil {
log.Warnf("WEBP_QUALITY 不是有效的整数,使用 config.json 中的值 %d", Config.Quality)
} else {
Config.Quality = quality
}
}
if os.Getenv("WEBP_ALLOWED_TYPES") != "" {
Config.AllowedTypes = strings.Split(os.Getenv("WEBP_ALLOWED_TYPES"), ",")
}
// Override enabled convert types
if os.Getenv("WEBP_CONVERT_TYPES") != "" {
Config.ConvertTypes = strings.Split(os.Getenv("WEBP_CONVERT_TYPES"), ",")
Config.EnableWebP = false
Config.EnableAVIF = false
Config.EnableJXL = false
if slices.Contains(Config.ConvertTypes, "webp") {
Config.EnableWebP = true
}
if slices.Contains(Config.ConvertTypes, "avif") {
Config.EnableAVIF = true
}
if slices.Contains(Config.ConvertTypes, "jxl") {
Config.EnableJXL = true
}
}
if os.Getenv("WEBP_ENABLE_EXTRA_PARAMS") != "" {
enableExtraParams := os.Getenv("WEBP_ENABLE_EXTRA_PARAMS")
if enableExtraParams == "true" {
Config.EnableExtraParams = true
} else if enableExtraParams == "false" {
Config.EnableExtraParams = false
} else {
log.Warnf("WEBP_ENABLE_EXTRA_PARAMS 不是有效的布尔值,使用 config.json 中的值 %t", Config.EnableExtraParams)
}
}
if os.Getenv("WEBP_EXTRA_PARAMS_CROP_INTERESTING") != "" {
availableInteresting := []string{"InterestingNone", "InterestingEntropy", "InterestingCentre", "InterestingAttention", "InterestringLow", "InterestingHigh", "InterestingAll"}
if slices.Contains(availableInteresting, os.Getenv("WEBP_EXTRA_PARAMS_CROP_INTERESTING")) {
Config.ExtraParamsCropInteresting = os.Getenv("WEBP_EXTRA_PARAMS_CROP_INTERESTING")
} else {
log.Warnf("WEBP_EXTRA_PARAMS_CROP_INTERESTING 不是有效的兴趣,使用 config.json 中的值 %s", Config.ExtraParamsCropInteresting)
}
}
if os.Getenv("WEBP_STRIP_METADATA") != "" {
stripMetadata := os.Getenv("WEBP_STRIP_METADATA")
if stripMetadata == "true" {
Config.StripMetadata = true
} else if stripMetadata == "false" {
Config.StripMetadata = false
} else {
log.Warnf("WEBP_STRIP_METADATA 不是有效的布尔值,使用 config.json 中的值 %t", Config.StripMetadata)
}
}
if os.Getenv("WEBP_IMG_MAP") != "" {
// TODO
}
if os.Getenv("WEBP_READ_BUFFER_SIZE") != "" {
readBufferSize, err := strconv.Atoi(os.Getenv("WEBP_READ_BUFFER_SIZE"))
if err != nil {
log.Warnf("WEBP_READ_BUFFER_SIZE 不是有效的整数,使用 config.json 中的值 %d", Config.ReadBufferSize)
} else {
Config.ReadBufferSize = readBufferSize
}
}
if os.Getenv("WEBP_CONCURRENCY") != "" {
concurrency, err := strconv.Atoi(os.Getenv("WEBP_CONCURRENCY"))
if err != nil {
log.Warnf("WEBP_CONCURRENCY 不是有效的整数,使用 config.json 中的值 %d", Config.Concurrency)
} else {
Config.Concurrency = concurrency
}
}
if os.Getenv("WEBP_DISABLE_KEEPALIVE") != "" {
disableKeepalive := os.Getenv("WEBP_DISABLE_KEEPALIVE")
if disableKeepalive == "true" {
Config.DisableKeepalive = true
} else if disableKeepalive == "false" {
Config.DisableKeepalive = false
} else {
log.Warnf("WEBP_DISABLE_KEEPALIVE is not a valid boolean, using value in config.json %t", Config.DisableKeepalive)
}
}
if os.Getenv("WEBP_CACHE_TTL") != "" {
cacheTTL, err := strconv.Atoi(os.Getenv("WEBP_CACHE_TTL"))
if err != nil {
log.Warnf("WEBP_CACHE_TTL is not a valid integer, using value in config.json %d", Config.CacheTTL)
} else {
Config.CacheTTL = cacheTTL
}
}
if Config.CacheTTL == 0 {
RemoteCache = cache.New(cache.NoExpiration, 10*time.Minute)
} else {
RemoteCache = cache.New(time.Duration(Config.CacheTTL)*time.Minute, 10*time.Minute)
}
if os.Getenv("WEBP_MAX_CACHE_SIZE") != "" {
maxCacheSize, err := strconv.Atoi(os.Getenv("WEBP_MAX_CACHE_SIZE"))
if err != nil {
log.Warnf("WEBP_MAX_CACHE_SIZE is not a valid integer, using value in config.json %d", Config.MaxCacheSize)
} else {
Config.MaxCacheSize = maxCacheSize
}
}
log.Debugln("Config init complete")
log.Debugln("Config", Config)
}
func parseImgMap(imgMap map[string]string) map[string]string {
var parsedImgMap = map[string]string{}
httpRegexpMatcher := regexp.MustCompile(HttpRegexp)
for uriMap, uriMapTarget := range imgMap {
if httpRegexpMatcher.Match([]byte(uriMapTarget)) || strings.HasPrefix(uriMapTarget, "./") || strings.HasPrefix(uriMapTarget, "/") {
// Valid: remote URL or local path
parsedImgMap[uriMap] = uriMapTarget
} else {
// Invalid
log.Warnf("IMG_MAP 值'%s'不是有效的远程 URL 或本地路径 -已跳过", uriMapTarget)
}
}
return parsedImgMap
}
type ExtraParams struct {
Width int // in px
Height int // in px
MaxWidth int // in px
MaxHeight int // in px
}