Add configurable allowed file types

This commit is contained in:
2026-04-24 16:36:48 +03:00
parent 50246ada85
commit 6953c151fe
7 changed files with 451 additions and 37 deletions

View File

@@ -12,6 +12,7 @@ import (
type OverwriteMode string
type FileSelectMode string
type AllowedFilesMode string
const (
DefaultDestFolder = "media"
@@ -21,6 +22,13 @@ const (
SelectNew FileSelectMode = "new"
SelectAll FileSelectMode = "all"
AllowedFilesByMediaType AllowedFilesMode = "media_types"
AllowedFilesByExtensions AllowedFilesMode = "extensions"
MediaTypeAudio = "audio"
MediaTypeVideo = "video"
MediaTypePhoto = "photo"
)
type SourceFolder struct {
@@ -36,6 +44,9 @@ type Config struct {
Sources []SourceFolder `json:"sources"`
OverwriteMode OverwriteMode `json:"overwrite_mode"`
FileSelectMode FileSelectMode `json:"file_select_mode"`
AllowedFilesMode AllowedFilesMode `json:"allowed_files_mode"`
EnabledMediaTypes []string `json:"enabled_media_types,omitempty"`
AllowedExtensions []string `json:"allowed_extensions,omitempty"`
AutoCopy bool `json:"auto_copy"`
FileReplicaCounts map[string]int `json:"file_replica_counts,omitempty"`
DiskReplicaFiles map[string][]string `json:"disk_replica_files,omitempty"`
@@ -43,11 +54,14 @@ type Config struct {
func defaults() Config {
return Config{
ReserveFreeGB: 2.0,
DestFolder: DefaultDestFolder,
OverwriteMode: OverwriteSkip,
FileSelectMode: SelectNew,
AutoCopy: false,
ReserveFreeGB: 2.0,
DestFolder: DefaultDestFolder,
OverwriteMode: OverwriteSkip,
FileSelectMode: SelectNew,
AllowedFilesMode: AllowedFilesByMediaType,
EnabledMediaTypes: DefaultEnabledMediaTypes(),
AllowedExtensions: DefaultAllowedExtensions(),
AutoCopy: false,
}
}
@@ -69,6 +83,17 @@ func Load(path string) (*Config, error) {
} else {
cfg.DestFolder = defaults().DestFolder
}
if cfg.AllowedFilesMode != AllowedFilesByMediaType && cfg.AllowedFilesMode != AllowedFilesByExtensions {
cfg.AllowedFilesMode = defaults().AllowedFilesMode
}
cfg.EnabledMediaTypes = NormalizeMediaTypes(cfg.EnabledMediaTypes)
if len(cfg.EnabledMediaTypes) == 0 {
cfg.EnabledMediaTypes = defaults().EnabledMediaTypes
}
cfg.AllowedExtensions = NormalizeExtensions(cfg.AllowedExtensions)
if len(cfg.AllowedExtensions) == 0 {
cfg.AllowedExtensions = defaults().AllowedExtensions
}
cfg.MediaPath = NormalizeMediaPath(cfg.MediaPath)
cfg.Sources = NormalizeSources(cfg.Sources, cfg.MediaPath)
return &cfg, nil
@@ -126,9 +151,145 @@ func (c *Config) Validate() error {
default:
return errors.New("file_select_mode must be 'new' or 'all'")
}
switch c.AllowedFilesMode {
case "", AllowedFilesByMediaType:
c.AllowedFilesMode = AllowedFilesByMediaType
c.EnabledMediaTypes = NormalizeMediaTypes(c.EnabledMediaTypes)
if len(c.EnabledMediaTypes) == 0 {
return errors.New("enabled_media_types must contain at least one of: audio, video, photo")
}
case AllowedFilesByExtensions:
c.AllowedExtensions = NormalizeExtensions(c.AllowedExtensions)
if len(c.AllowedExtensions) == 0 {
return errors.New("allowed_extensions must contain at least one file extension")
}
default:
return errors.New("allowed_files_mode must be 'media_types' or 'extensions'")
}
return nil
}
func DefaultEnabledMediaTypes() []string {
return []string{MediaTypeAudio, MediaTypeVideo}
}
func DefaultAllowedExtensions() []string {
return extensionsForMediaTypes(DefaultEnabledMediaTypes())
}
func BuiltInMediaTypeExtensions() map[string][]string {
return map[string][]string{
MediaTypeAudio: {
".aac", ".aif", ".aiff", ".alac", ".ape", ".flac", ".m4a", ".mp2", ".mp3", ".ogg", ".opus", ".wav", ".wma",
},
MediaTypeVideo: {
".3gp", ".avi", ".m2ts", ".m4v", ".mkv", ".mov", ".mp4", ".mpeg", ".mpg", ".mts", ".ts", ".webm", ".wmv",
},
MediaTypePhoto: {
".bmp", ".gif", ".heic", ".heif", ".jpeg", ".jpg", ".png", ".tif", ".tiff", ".webp",
},
}
}
func NormalizeMediaTypes(items []string) []string {
order := []string{MediaTypeAudio, MediaTypeVideo, MediaTypePhoto}
allowed := make(map[string]struct{}, len(order))
for _, item := range order {
allowed[item] = struct{}{}
}
seen := make(map[string]struct{}, len(items))
result := make([]string, 0, len(order))
for _, item := range items {
value := strings.ToLower(strings.TrimSpace(item))
if _, ok := allowed[value]; !ok {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
}
for _, item := range order {
if _, ok := seen[item]; ok {
result = append(result, item)
}
}
return result
}
func NormalizeExtensions(items []string) []string {
seen := make(map[string]struct{}, len(items))
result := make([]string, 0, len(items))
for _, item := range items {
value := normalizeExtension(item)
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
result = append(result, value)
}
return result
}
func normalizeExtension(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
value = strings.TrimPrefix(value, "*")
if value == "" {
return ""
}
if !strings.HasPrefix(value, ".") {
value = "." + value
}
if len(value) < 2 {
return ""
}
for _, ch := range value[1:] {
switch {
case ch >= 'a' && ch <= 'z':
case ch >= '0' && ch <= '9':
default:
return ""
}
}
return value
}
func (c Config) EffectiveAllowedExtensions() []string {
switch c.AllowedFilesMode {
case AllowedFilesByExtensions:
if items := NormalizeExtensions(c.AllowedExtensions); len(items) > 0 {
return items
}
default:
types := NormalizeMediaTypes(c.EnabledMediaTypes)
if len(types) == 0 {
types = DefaultEnabledMediaTypes()
}
return extensionsForMediaTypes(types)
}
return DefaultAllowedExtensions()
}
func extensionsForMediaTypes(items []string) []string {
sets := BuiltInMediaTypeExtensions()
result := make([]string, 0)
seen := make(map[string]struct{})
for _, mediaType := range NormalizeMediaTypes(items) {
for _, ext := range sets[mediaType] {
if _, ok := seen[ext]; ok {
continue
}
seen[ext] = struct{}{}
result = append(result, ext)
}
}
return result
}
func NormalizeMediaPath(value string) string {
value = strings.TrimSpace(value)
if value == "" {