fix: resolve ghost conflicts by assigning unique paths to trashed files
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m2s

This commit is contained in:
Elijah 2026-05-23 14:53:33 -07:00
parent c9f84fc257
commit 4c8e6b814c

View file

@ -529,8 +529,15 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
resolvedPath := c.Locals("resolvedPath").(string) resolvedPath := c.Locals("resolvedPath").(string)
relativePath := c.Locals("relativePath").(string) relativePath := c.Locals("relativePath").(string)
// Soft-delete: move to .trash directory // Generate unique suffix for the trash path to prevent collisions
trashPath := filepath.Join(h.Config.TrashDir, relativePath) trashSuffix := fmt.Sprintf("_%d", time.Now().UnixNano())
trashName := filepath.Base(relativePath) + trashSuffix
trashDir := filepath.Dir(relativePath)
if trashDir == "." {
trashDir = ""
}
trashedRelativePath := filepath.Join(trashDir, trashName)
trashPath := filepath.Join(h.Config.TrashDir, trashedRelativePath)
// Check if permanent // Check if permanent
isPermanent := c.Query("permanent") == "true" isPermanent := c.Query("permanent") == "true"
@ -582,18 +589,27 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
// Update DB // Update DB
cleanPath := filepath.ToSlash(relativePath) cleanPath := filepath.ToSlash(relativePath)
cleanTrashPath := filepath.ToSlash(trashedRelativePath)
h.DB.Exec( h.DB.Exec(
`UPDATE files SET is_trashed = 1, trashed_at = CURRENT_TIMESTAMP, original_path = ?, is_dir = ?, size = ? WHERE path = ?`, `UPDATE files SET is_trashed = 1, trashed_at = CURRENT_TIMESTAMP, original_path = ?, is_dir = ?, size = ?, path = ? WHERE path = ?`,
cleanPath, isDir, size, cleanPath, cleanPath, isDir, size, cleanTrashPath, cleanPath,
) )
if isDir == 1 {
h.DB.Exec(
`UPDATE files SET path = REPLACE(path, ?, ?) WHERE path LIKE ?`,
cleanPath+"/", cleanTrashPath+"/", cleanPath+"/%",
)
}
// Also insert if not tracked // Also insert if not tracked
h.DB.Exec( h.DB.Exec(
`INSERT OR IGNORE INTO files (path, name, is_trashed, trashed_at, original_path, is_dir, size) VALUES (?, ?, 1, CURRENT_TIMESTAMP, ?, ?, ?)`, `INSERT OR IGNORE INTO files (path, name, is_trashed, trashed_at, original_path, is_dir, size) VALUES (?, ?, 1, CURRENT_TIMESTAMP, ?, ?, ?)`,
cleanPath, filepath.Base(cleanPath), cleanPath, isDir, size, cleanTrashPath, filepath.Base(cleanTrashPath), cleanPath, isDir, size,
) )
h.DB.AddAuditLog("deleted", cleanPath, c.IP()) h.DB.AddAuditLog("deleted", cleanTrashPath, c.IP())
return c.JSON(fiber.Map{"message": "moved to trash"}) return c.JSON(fiber.Map{"message": "moved to trash"})
} }
@ -657,21 +673,39 @@ func (h *FSHandler) RestoreFromTrash(c *fiber.Ctx) error {
} }
trashPath := filepath.Join(h.Config.TrashDir, filepath.FromSlash(body.Path)) trashPath := filepath.Join(h.Config.TrashDir, filepath.FromSlash(body.Path))
restorePath := filepath.Join(h.Config.StorageDir, filepath.FromSlash(originalPath)) destFull := filepath.Join(h.Config.StorageDir, filepath.FromSlash(originalPath))
// Check if originalPath already exists, assign new name if so
if _, err := os.Stat(destFull); err == nil {
uniqueName := GetUniquePath(filepath.Dir(destFull), filepath.Base(destFull))
originalPath = filepath.ToSlash(filepath.Join(filepath.Dir(originalPath), uniqueName))
destFull = filepath.Join(filepath.Dir(destFull), uniqueName)
}
// Ensure parent directory exists // Ensure parent directory exists
if err := os.MkdirAll(filepath.Dir(restorePath), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(destFull), 0755); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to prepare restore location"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to prepare restore location"})
} }
if err := os.Rename(trashPath, restorePath); err != nil { if err := os.Rename(trashPath, destFull); err != nil {
if err := copyFile(trashPath, destFull); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to restore file"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to restore file"})
} }
os.RemoveAll(trashPath)
}
h.DB.Exec("UPDATE files SET path = ?, name = ?, is_trashed = 0, trashed_at = NULL WHERE path = ?", originalPath, filepath.Base(originalPath), body.Path)
var isDir int
h.DB.QueryRow("SELECT is_dir FROM files WHERE path = ?", originalPath).Scan(&isDir)
if isDir == 1 {
h.DB.Exec( h.DB.Exec(
"UPDATE files SET is_trashed = 0, trashed_at = NULL WHERE path = ?", `UPDATE files SET path = REPLACE(path, ?, ?) WHERE path LIKE ?`,
body.Path, body.Path+"/", originalPath+"/", body.Path+"/%",
) )
}
h.DB.AddAuditLog("restored", originalPath, c.IP())
h.DB.AddAuditLog("restored", fmt.Sprintf("Restored '%s' from trash", originalPath), c.IP()) h.DB.AddAuditLog("restored", fmt.Sprintf("Restored '%s' from trash", originalPath), c.IP())