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