tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

include-admin.go (7798B)


      1 package main
      2 
      3 import (
      4 	"fmt"
      5 	"io"
      6 	"log"
      7 	"net/http"
      8 	"os"
      9 	"path/filepath"
     10 	"strconv"
     11 	"strings"
     12 	"time"
     13 )
     14 
     15 func renderAdminPage(w http.ResponseWriter, r *http.Request, data AdminPageData) {
     16 	if data.ActiveTab == "" {
     17 		data.ActiveTab = r.FormValue("active_tab")
     18 	}
     19 	pageData := buildPageData("Admin", data)
     20 	renderTemplate(w, "admin.html", pageData)
     21 }
     22 
     23 func currentAdminState(r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) AdminPageData {
     24 	return AdminPageData{
     25 		Config:            config,
     26 		OrphanData:        orphanData,
     27 		ActiveTab:         r.FormValue("active_tab"),
     28 		MissingThumbnails: missingThumbnails,
     29 	}
     30 }
     31 
     32 func validateConfig(newConfig Config) error {
     33 	if newConfig.GallerySize == "" {
     34 		return fmt.Errorf("gallery size cannot be empty")
     35 	}
     36 	if newConfig.ItemsPerPage == "" {
     37 		return fmt.Errorf("items per page cannot be empty")
     38 	}
     39 	return nil
     40 }
     41 
     42 func adminHandler(w http.ResponseWriter, r *http.Request) {
     43 	// Get orphaned files
     44 	orphanData, err := getOrphanedFiles(config.UploadDir)
     45 	if err != nil {
     46 		log.Printf("Warning: adminHandler: failed to get orphaned files: %v", err)
     47 	}
     48 
     49 	// Get video files for thumbnails
     50 	missingThumbnails, err := getMissingThumbnailVideos()
     51 	if err != nil {
     52 		log.Printf("Warning: adminHandler: failed to get missing thumbnails: %v", err)
     53 	}
     54 
     55 	switch r.Method {
     56 	case http.MethodPost:
     57 		switch r.FormValue("action") {
     58 		case "save", "":
     59 			handleSaveSettings(w, r, orphanData, missingThumbnails)
     60 
     61 		case "backup":
     62 			err := backupDatabase(config.DatabasePath)
     63 			if err != nil {
     64 				log.Printf("Error: adminHandler: database backup failed: %v", err)
     65 			}
     66 			data := currentAdminState(r, orphanData, missingThumbnails)
     67 			data.Error = errorString(err)
     68 			data.Success = successString(err, "Database backup created successfully!")
     69 			renderAdminPage(w, r, data)
     70 
     71 		case "vacuum":
     72 			err := vacuumDatabase()
     73 			if err != nil {
     74 				log.Printf("Error: adminHandler: database vacuum failed: %v", err)
     75 			}
     76 			data := currentAdminState(r, orphanData, missingThumbnails)
     77 			data.Error = errorString(err)
     78 			data.Success = successString(err, "Database vacuum completed successfully!")
     79 			renderAdminPage(w, r, data)
     80 
     81 		case "save_aliases":
     82 			handleSaveAliases(w, r, orphanData, missingThumbnails)
     83 
     84 		case "save_sed_rules":
     85 			handleSaveSedRules(w, r, orphanData, missingThumbnails)
     86 
     87 		case "compute_properties":
     88 			handleComputeProperties(w, r, orphanData, missingThumbnails)
     89 		}
     90 
     91 	default:
     92 		renderAdminPage(w, r, currentAdminState(r, orphanData, missingThumbnails))
     93 	}
     94 }
     95 
     96 func parseAliasesFromForm(r *http.Request) []TagAliasGroup {
     97 	var groups []TagAliasGroup
     98 	for i := 0; ; i++ {
     99 		category := strings.TrimSpace(r.FormValue(fmt.Sprintf("aliases[%d][category]", i)))
    100 		if category == "" {
    101 			break
    102 		}
    103 		var aliases []string
    104 		for j := 0; ; j++ {
    105 			v := strings.TrimSpace(r.FormValue(fmt.Sprintf("aliases[%d][aliases][%d]", i, j)))
    106 			if v == "" {
    107 				break
    108 			}
    109 			aliases = append(aliases, v)
    110 		}
    111 		if len(aliases) >= 2 {
    112 			groups = append(groups, TagAliasGroup{Category: category, Aliases: aliases})
    113 		} else {
    114 			log.Printf("Warning: parseAliasesFromForm: alias group for category %q has fewer than 2 aliases (%d) and was discarded", category, len(aliases))
    115 		}
    116 	}
    117 	return groups
    118 }
    119 
    120 func parseSedRulesFromForm(r *http.Request) ([]SedRule, error) {
    121 	var rules []SedRule
    122 	for i := 0; ; i++ {
    123 		name := strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][name]", i)))
    124 		if name == "" {
    125 			break
    126 		}
    127 		command := strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][command]", i)))
    128 		if command == "" {
    129 			return nil, fmt.Errorf("rule %s is missing a command", strconv.Itoa(i+1))
    130 		}
    131 		rules = append(rules, SedRule{
    132 			Name:        name,
    133 			Description: strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][description]", i))),
    134 			Command:     command,
    135 		})
    136 	}
    137 	return rules, nil
    138 }
    139 
    140 func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) {
    141 	config.TagAliases = parseAliasesFromForm(r)
    142 
    143 	if err := SaveConfig(db, config); err != nil {
    144 		log.Printf("Error: handleSaveAliases: failed to save configuration: %v", err)
    145 		data := currentAdminState(r, orphanData, missingThumbnails)
    146 		data.Error = "Failed to save configuration: " + err.Error()
    147 		renderAdminPage(w, r, data)
    148 		return
    149 	}
    150 
    151 	data := currentAdminState(r, orphanData, missingThumbnails)
    152 	data.Success = "Tag aliases saved successfully!"
    153 	renderAdminPage(w, r, data)
    154 }
    155 
    156 func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) {
    157 	newConfig := config // preserve runtime fields
    158 	newConfig.GallerySize = strings.TrimSpace(r.FormValue("gallery_size"))
    159 	newConfig.ItemsPerPage = strings.TrimSpace(r.FormValue("items_per_page"))
    160 
    161 	if err := validateConfig(newConfig); err != nil {
    162 		data := currentAdminState(r, orphanData, missingThumbnails)
    163 		data.Error = err.Error()
    164 		renderAdminPage(w, r, data)
    165 		return
    166 	}
    167 
    168 	config = newConfig
    169 
    170 	if err := SaveConfig(db, config); err != nil {
    171 		log.Printf("Error: handleSaveSettings: failed to save configuration: %v", err)
    172 		data := currentAdminState(r, orphanData, missingThumbnails)
    173 		data.Error = "Failed to save configuration: " + err.Error()
    174 		renderAdminPage(w, r, data)
    175 		return
    176 	}
    177 
    178 	data := currentAdminState(r, orphanData, missingThumbnails)
    179 	data.Success = "Settings saved successfully!"
    180 	renderAdminPage(w, r, data)
    181 }
    182 
    183 func handleSaveSedRules(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) {
    184 	rules, err := parseSedRulesFromForm(r)
    185 	if err != nil {
    186 		data := currentAdminState(r, orphanData, missingThumbnails)
    187 		data.Error = err.Error()
    188 		renderAdminPage(w, r, data)
    189 		return
    190 	}
    191 
    192 	config.SedRules = rules
    193 
    194 	if err := SaveConfig(db, config); err != nil {
    195 		log.Printf("Error: handleSaveSedRules: failed to save configuration: %v", err)
    196 		data := currentAdminState(r, orphanData, missingThumbnails)
    197 		data.Error = "Failed to save configuration: " + err.Error()
    198 		renderAdminPage(w, r, data)
    199 		return
    200 	}
    201 
    202 	data := currentAdminState(r, orphanData, missingThumbnails)
    203 	data.Success = "Sed rules saved successfully!"
    204 	renderAdminPage(w, r, data)
    205 }
    206 
    207 func backupDatabase(dbPath string) error {
    208 	if dbPath == "" {
    209 		return fmt.Errorf("database path not configured")
    210 	}
    211 
    212 	backupDir := filepath.Join(filepath.Dir(dbPath), "backups")
    213 	if err := os.MkdirAll(backupDir, 0755); err != nil {
    214 		return fmt.Errorf("failed to create backups directory: %w", err)
    215 	}
    216 
    217 	dbName := strings.TrimSuffix(filepath.Base(dbPath), filepath.Ext(dbPath))
    218 	timestamp := time.Now().Format("20060102_150405")
    219 	backupPath := filepath.Join(backupDir, fmt.Sprintf("%s_backup_%s.db", dbName, timestamp))
    220 
    221 	input, err := os.Open(dbPath)
    222 	if err != nil {
    223 		return fmt.Errorf("failed to open database: %w", err)
    224 	}
    225 	defer input.Close()
    226 
    227 	output, err := os.Create(backupPath)
    228 	if err != nil {
    229 		return fmt.Errorf("failed to create backup file: %w", err)
    230 	}
    231 	defer output.Close()
    232 
    233 	if _, err := io.Copy(output, input); err != nil {
    234 		return fmt.Errorf("failed to copy database: %w", err)
    235 	}
    236 
    237 	log.Printf("Info: backupDatabase: backup written to %s", backupPath)
    238 	return nil
    239 }
    240 
    241 func vacuumDatabase() error {
    242 	if _, err := db.Exec("VACUUM;"); err != nil {
    243 		return fmt.Errorf("VACUUM failed: %w", err)
    244 	}
    245 
    246 	log.Printf("Info: vacuumDatabase: VACUUM completed successfully")
    247 	return nil
    248 }
    249 
    250 func getFilesInDB() (map[string]bool, error) {
    251 	rows, err := db.Query("SELECT filename FROM files")
    252 	if err != nil {
    253 		return nil, err
    254 	}
    255 	defer rows.Close()
    256 
    257 	fileMap := make(map[string]bool)
    258 	for rows.Next() {
    259 		var name string
    260 		if err := rows.Scan(&name); err != nil {
    261 			return nil, err
    262 		}
    263 		fileMap[name] = true
    264 	}
    265 	if err := rows.Err(); err != nil {
    266 		return nil, err
    267 	}
    268 	return fileMap, nil
    269 }