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 }