tagliatelle

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

include-admin.go (8212B)


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