diff --git a/backend/handlers/files.go b/backend/handlers/files.go index c38645f..c68720e 100644 --- a/backend/handlers/files.go +++ b/backend/handlers/files.go @@ -529,8 +529,15 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error { resolvedPath := c.Locals("resolvedPath").(string) relativePath := c.Locals("relativePath").(string) - // Soft-delete: move to .trash directory - trashPath := filepath.Join(h.Config.TrashDir, relativePath) + // Generate unique suffix for the trash path to prevent collisions + 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 isPermanent := c.Query("permanent") == "true" @@ -582,18 +589,27 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error { // Update DB cleanPath := filepath.ToSlash(relativePath) + cleanTrashPath := filepath.ToSlash(trashedRelativePath) + h.DB.Exec( - `UPDATE files SET is_trashed = 1, trashed_at = CURRENT_TIMESTAMP, original_path = ?, is_dir = ?, size = ? WHERE path = ?`, - cleanPath, isDir, size, cleanPath, + `UPDATE files SET is_trashed = 1, trashed_at = CURRENT_TIMESTAMP, original_path = ?, is_dir = ?, size = ?, path = ? WHERE path = ?`, + 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 h.DB.Exec( `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"}) } @@ -657,21 +673,39 @@ func (h *FSHandler) RestoreFromTrash(c *fiber.Ctx) error { } 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 - 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"}) } - if err := os.Rename(trashPath, restorePath); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to restore file"}) + 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"}) + } + os.RemoveAll(trashPath) } - h.DB.Exec( - "UPDATE files SET is_trashed = 0, trashed_at = NULL WHERE path = ?", - body.Path, - ) + 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( + `UPDATE files SET path = REPLACE(path, ?, ?) WHERE path LIKE ?`, + 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())