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:
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>