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
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m2s
This commit is contained in:
parent
c9f84fc257
commit
4c8e6b814c
1 changed files with 48 additions and 14 deletions
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
||||||
Reference in a new issue