include-admin.go (6994B)
1 package main 2 3 import ( 4 "database/sql" 5 "fmt" 6 "io" 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, _ := getOrphanedFiles(config.UploadDir) 54 55 // Get video files for thumbnails 56 missingThumbnails, _ := getMissingThumbnailVideos() 57 58 switch r.Method { 59 case http.MethodPost: 60 switch r.FormValue("action") { 61 case "save", "": 62 handleSaveSettings(w, r, orphanData, missingThumbnails) 63 64 case "backup": 65 err := backupDatabase(config.DatabasePath) 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(config.DatabasePath) 73 data := currentAdminState(r, orphanData, missingThumbnails) 74 data.Error = errorString(err) 75 data.Success = successString(err, "Database vacuum completed successfully!") 76 renderAdminPage(w, r, data) 77 78 case "save_aliases": 79 handleSaveAliases(w, r, orphanData, missingThumbnails) 80 81 case "save_sed_rules": 82 handleSaveSedRules(w, r, orphanData, missingThumbnails) 83 84 case "compute_properties": 85 handleComputeProperties(w, r, orphanData, missingThumbnails) 86 } 87 88 default: 89 renderAdminPage(w, r, currentAdminState(r, orphanData, missingThumbnails)) 90 } 91 } 92 93 func parseAliasesFromForm(r *http.Request) []TagAliasGroup { 94 var groups []TagAliasGroup 95 for i := 0; ; i++ { 96 category := strings.TrimSpace(r.FormValue(fmt.Sprintf("aliases[%d][category]", i))) 97 if category == "" { 98 break 99 } 100 var aliases []string 101 for j := 0; ; j++ { 102 v := strings.TrimSpace(r.FormValue(fmt.Sprintf("aliases[%d][aliases][%d]", i, j))) 103 if v == "" { 104 break 105 } 106 aliases = append(aliases, v) 107 } 108 if len(aliases) >= 2 { 109 groups = append(groups, TagAliasGroup{Category: category, Aliases: aliases}) 110 } 111 } 112 return groups 113 } 114 115 func parseSedRulesFromForm(r *http.Request) ([]SedRule, error) { 116 var rules []SedRule 117 for i := 0; ; i++ { 118 name := strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][name]", i))) 119 if name == "" { 120 break 121 } 122 command := strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][command]", i))) 123 if command == "" { 124 return nil, fmt.Errorf("rule %s is missing a command", strconv.Itoa(i+1)) 125 } 126 rules = append(rules, SedRule{ 127 Name: name, 128 Description: strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][description]", i))), 129 Command: command, 130 }) 131 } 132 return rules, nil 133 } 134 135 func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) { 136 config.TagAliases = parseAliasesFromForm(r) 137 138 if err := SaveConfig(db, config); err != nil { 139 data := currentAdminState(r, orphanData, missingThumbnails) 140 data.Error = "Failed to save configuration: " + err.Error() 141 renderAdminPage(w, r, data) 142 return 143 } 144 145 data := currentAdminState(r, orphanData, missingThumbnails) 146 data.Success = "Tag aliases saved successfully!" 147 renderAdminPage(w, r, data) 148 } 149 150 func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) { 151 newConfig := config // preserve runtime fields 152 newConfig.GallerySize = strings.TrimSpace(r.FormValue("gallery_size")) 153 newConfig.ItemsPerPage = strings.TrimSpace(r.FormValue("items_per_page")) 154 155 if err := validateConfig(newConfig); err != nil { 156 data := currentAdminState(r, orphanData, missingThumbnails) 157 data.Error = err.Error() 158 renderAdminPage(w, r, data) 159 return 160 } 161 162 config = newConfig 163 164 if err := SaveConfig(db, config); err != nil { 165 data := currentAdminState(r, orphanData, missingThumbnails) 166 data.Error = "Failed to save configuration: " + err.Error() 167 renderAdminPage(w, r, data) 168 return 169 } 170 171 data := currentAdminState(r, orphanData, missingThumbnails) 172 data.Success = "Settings saved successfully!" 173 renderAdminPage(w, r, data) 174 } 175 176 func handleSaveSedRules(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) { 177 rules, err := parseSedRulesFromForm(r) 178 if err != nil { 179 data := currentAdminState(r, orphanData, missingThumbnails) 180 data.Error = err.Error() 181 renderAdminPage(w, r, data) 182 return 183 } 184 185 config.SedRules = rules 186 187 if err := SaveConfig(db, config); err != nil { 188 data := currentAdminState(r, orphanData, missingThumbnails) 189 data.Error = "Failed to save configuration: " + err.Error() 190 renderAdminPage(w, r, data) 191 return 192 } 193 194 data := currentAdminState(r, orphanData, missingThumbnails) 195 data.Success = "Sed rules saved successfully!" 196 renderAdminPage(w, r, data) 197 } 198 199 func backupDatabase(dbPath string) error { 200 if dbPath == "" { 201 return fmt.Errorf("database path not configured") 202 } 203 204 timestamp := time.Now().Format("20060102_150405") 205 backupPath := fmt.Sprintf("%s_backup_%s.db", strings.TrimSuffix(dbPath, filepath.Ext(dbPath)), timestamp) 206 207 input, err := os.Open(dbPath) 208 if err != nil { 209 return fmt.Errorf("failed to open database: %w", err) 210 } 211 defer input.Close() 212 213 output, err := os.Create(backupPath) 214 if err != nil { 215 return fmt.Errorf("failed to create backup file: %w", err) 216 } 217 defer output.Close() 218 219 if _, err := io.Copy(output, input); err != nil { 220 return fmt.Errorf("failed to copy database: %w", err) 221 } 222 223 return nil 224 } 225 226 func vacuumDatabase(dbPath string) error { 227 db, err := sql.Open("sqlite3", dbPath) 228 if err != nil { 229 return fmt.Errorf("failed to open database: %w", err) 230 } 231 defer db.Close() 232 233 if _, err = db.Exec("VACUUM;"); err != nil { 234 return fmt.Errorf("VACUUM failed: %w", err) 235 } 236 237 return nil 238 } 239 240 func getFilesInDB() (map[string]bool, error) { 241 rows, err := db.Query("SELECT filename FROM files") 242 if err != nil { 243 return nil, err 244 } 245 defer rows.Close() 246 247 fileMap := make(map[string]bool) 248 for rows.Next() { 249 var name string 250 if err := rows.Scan(&name); err != nil { 251 return nil, err 252 } 253 fileMap[name] = true 254 } 255 if err := rows.Err(); err != nil { 256 return nil, err 257 } 258 return fileMap, nil 259 }