tagliatelle

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

commit c06445458d80ed78a599d26779344cf4e5f5cf72
parent 18fcc578584fc559d1a6df11f66efe7ee4c29f11
Author: breadcat <breadcat@users.noreply.github.com>
Date:   Tue, 17 Feb 2026 12:58:29 +0000

Add reverse orphans feature

Diffstat:
Minclude-admin-orphans.go | 31+++++++++++++++++++++++--------
Minclude-admin.go | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Minclude-types.go | 5+++++
Mtemplates/admin.html | 31+++++++++++++++++++++++++++----
4 files changed, 115 insertions(+), 38 deletions(-)

diff --git a/include-admin-orphans.go b/include-admin-orphans.go @@ -5,15 +5,20 @@ import ( "os" ) -func getOrphanedFiles(uploadDir string) ([]string, error) { +func getOrphanedFiles(uploadDir string) (OrphanData, error) { diskFiles, err := getFilesOnDisk(uploadDir) if err != nil { - return nil, err + return OrphanData{}, err } - dbFiles, err := getFilesInDB() if err != nil { - return nil, err + return OrphanData{}, err + } + + // Build a set of disk files for reverse lookup + diskFileSet := make(map[string]bool, len(diskFiles)) + for _, f := range diskFiles { + diskFileSet[f] = true } var orphans []string @@ -22,17 +27,27 @@ func getOrphanedFiles(uploadDir string) ([]string, error) { orphans = append(orphans, f) } } - return orphans, nil + + var reverseOrphans []string + for f := range dbFiles { + if !diskFileSet[f] { + reverseOrphans = append(reverseOrphans, f) + } + } + + return OrphanData{ + Orphans: orphans, + ReverseOrphans: reverseOrphans, + }, nil } func orphansHandler(w http.ResponseWriter, r *http.Request) { - orphans, err := getOrphanedFiles(config.UploadDir) + orphanData, err := getOrphanedFiles(config.UploadDir) if err != nil { renderError(w, "Error reading orphaned files", http.StatusInternalServerError) return } - - pageData := buildPageData("Orphaned Files", orphans) + pageData := buildPageData("Orphaned Files", orphanData) renderTemplate(w, "orphans.html", pageData) } diff --git a/include-admin.go b/include-admin.go @@ -63,7 +63,7 @@ func validateConfig(newConfig Config) error { func adminHandler(w http.ResponseWriter, r *http.Request) { // Get orphaned files - orphans, _ := getOrphanedFiles(config.UploadDir) + orphanData, _ := getOrphanedFiles(config.UploadDir) // Get video files for thumbnails missingThumbnails, _ := getMissingThumbnailVideos() @@ -74,7 +74,7 @@ func adminHandler(w http.ResponseWriter, r *http.Request) { switch action { case "save", "": - handleSaveSettings(w, r, orphans, missingThumbnails) + handleSaveSettings(w, r, orphanData, missingThumbnails) return case "backup": @@ -83,13 +83,15 @@ func adminHandler(w http.ResponseWriter, r *http.Request) { Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: errorString(err), Success: successString(err, "Database backup created successfully!"), - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) @@ -101,20 +103,22 @@ func adminHandler(w http.ResponseWriter, r *http.Request) { Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: errorString(err), Success: successString(err, "Database vacuum completed successfully!"), - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) return case "save_aliases": - handleSaveAliases(w, r, orphans, missingThumbnails) + handleSaveAliases(w, r, orphanData, missingThumbnails) return } @@ -123,20 +127,22 @@ func adminHandler(w http.ResponseWriter, r *http.Request) { Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: "", Success: "", - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) } } -func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphans []string, missingThumbnails []VideoFile) { +func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) { aliasesJSON := r.FormValue("aliases_json") var aliases []TagAliasGroup @@ -146,13 +152,15 @@ func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphans []string, Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: "Invalid aliases JSON: " + err.Error(), Success: "", - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) @@ -167,13 +175,15 @@ func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphans []string, Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: "Failed to save configuration: " + err.Error(), Success: "", - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) @@ -184,19 +194,21 @@ func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphans []string, Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: "", Success: "Tag aliases saved successfully!", - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) } -func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphans []string, missingThumbnails []VideoFile) { +func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) { newConfig := Config{ DatabasePath: strings.TrimSpace(r.FormValue("database_path")), UploadDir: strings.TrimSpace(r.FormValue("upload_dir")), @@ -212,13 +224,15 @@ func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphans []string Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: err.Error(), Success: "", - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) @@ -234,13 +248,15 @@ func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphans []string Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: "Failed to save configuration: " + err.Error(), Success: "", - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) @@ -258,13 +274,15 @@ func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphans []string Config Config Error string Success string - Orphans []string + OrphanData OrphanData + ActiveTab string MissingThumbnails []VideoFile }{ Config: config, Error: "", Success: message, - Orphans: orphans, + OrphanData: orphanData, + ActiveTab: r.FormValue("active_tab"), MissingThumbnails: missingThumbnails, }) renderTemplate(w, "admin.html", pageData) @@ -313,9 +331,25 @@ func vacuumDatabase(dbPath string) error { } func getFilesInDB() (map[string]bool, error) { - rows, err := db.Query(`SELECT filename FROM files`) - if err != nil { - return nil, err + rows, err := db.Query("SELECT filename FROM files") + if err != nil { + return nil, err + } + defer rows.Close() + + fileMap := make(map[string]bool) + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + fileMap[name] = true + } + if err := rows.Err(); err != nil { + return nil, err + } + return fileMap, nil +} } defer rows.Close() diff --git a/include-types.go b/include-types.go @@ -98,3 +98,8 @@ type TagPair struct { Category string Value string } + +type OrphanData struct { + Orphans []string // on disk, not in DB + ReverseOrphans []string // in DB, not on disk +} diff --git a/templates/admin.html b/templates/admin.html @@ -36,6 +36,7 @@ <div id="admin-content-settings"> <h2>Settings</h2> <form method="post" style="max-width: 600px;"> + <input type="hidden" name="active_tab" value="settings"> <div style="margin-bottom: 20px;"> <label for="database_path" style="display: block; font-weight: bold; margin-bottom: 5px;">Database Path:</label> <input type="text" id="database_path" name="database_path" value="{{.Data.Config.DatabasePath}}" required @@ -110,6 +111,7 @@ <h2>Database Maintenance</h2> <form method="post" style="margin-bottom: 20px;"> + <input type="hidden" name="active_tab" value="database"> <input type="hidden" name="action" value="backup"> <button type="submit" style="background-color: #28a745; color: white; padding: 10px 20px; border: none; border-radius: 4px; font-size: 16px; cursor: pointer;"> Backup Database @@ -118,6 +120,7 @@ </form> <form method="post"> + <input type="hidden" name="active_tab" value="database"> <input type="hidden" name="action" value="vacuum"> <button type="submit" style="background-color: #6f42c1; color: white; padding: 10px 20px; border: none; border-radius: 4px; font-size: 16px; cursor: pointer;"> Vacuum Database @@ -142,6 +145,7 @@ </button> <form method="post" id="aliases-form" style="margin-top: 20px;"> + <input type="hidden" name="active_tab" value="aliases"> <input type="hidden" name="action" value="save_aliases"> <input type="hidden" name="aliases_json" id="aliases_json"> <button type="submit" style="background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; font-size: 16px; cursor: pointer;"> @@ -154,19 +158,38 @@ <!-- Orphans Tab --> <div id="admin-content-orphans" style="display: none;"> <h2>Orphaned Files</h2> - <p style="color: #666; margin-bottom: 20px;"> + + <!-- Files on disk, not in DB --> + <h3 style="margin-top: 24px;">Disk Orphans</h3> + <p style="color: #666; margin-bottom: 12px;"> These files exist in the upload directory but are not tracked in the database. </p> + {{if .Data.OrphanData.Orphans}} + <ul style="list-style-type: disc; padding-left: 20px;"> + {{range .Data.OrphanData.Orphans}} + <li style="margin-bottom: 5px; font-family: monospace;">{{.}}</li> + {{end}} + </ul> + {{else}} + <div style="padding: 20px; background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; border-radius: 4px;"> + <strong>✓ No disk orphans found!</strong> + </div> + {{end}} - {{if .Data.Orphans}} + <!-- Files in DB, not on disk --> + <h3 style="margin-top: 32px;">Database Orphans</h3> + <p style="color: #666; margin-bottom: 12px;"> + These files are tracked in the database but are missing from the upload directory. + </p> + {{if .Data.OrphanData.ReverseOrphans}} <ul style="list-style-type: disc; padding-left: 20px;"> - {{range .Data.Orphans}} + {{range .Data.OrphanData.ReverseOrphans}} <li style="margin-bottom: 5px; font-family: monospace;">{{.}}</li> {{end}} </ul> {{else}} <div style="padding: 20px; background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; border-radius: 4px;"> - <strong>✓ No orphaned files found!</strong> + <strong>✓ No database orphans found!</strong> </div> {{end}} </div>