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 }