From 2c92255805ed164d1a2bdaa4adfb7eb66c8c177f Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Wed, 19 Mar 2025 13:16:42 +0530 Subject: [PATCH] [server][core] Empty trash --- server/internal/command/trash/cmd.go | 3 +- server/internal/command/trash/empty.go | 41 ++++++++++++++++++++++++ server/internal/command/trash/restore.go | 6 +++- server/internal/command/trash/summary.go | 21 +++++++----- server/internal/core/db/trash.sql.go | 38 ++++++++++++++++++++-- server/internal/core/fs/fs.go | 1 + server/internal/core/fs/trash.go | 18 +++++++++++ server/sql/queries/trash.sql | 12 +++++-- 8 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 server/internal/command/trash/empty.go diff --git a/server/internal/command/trash/cmd.go b/server/internal/command/trash/cmd.go index e506150c..aa95637e 100644 --- a/server/internal/command/trash/cmd.go +++ b/server/internal/command/trash/cmd.go @@ -21,9 +21,10 @@ func SetupCommand() *cobra.Command { flags.StringP("user", "u", "phylum", "User") cmd.AddCommand( + setupEmptyCommand(), setupListCommand(), - setupSummaryCommand(), setupRestoreCommand(), + setupSummaryCommand(), ) return cmd diff --git a/server/internal/command/trash/empty.go b/server/internal/command/trash/empty.go new file mode 100644 index 00000000..d41d3738 --- /dev/null +++ b/server/internal/command/trash/empty.go @@ -0,0 +1,41 @@ +package trash + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/shroff/phylum/server/internal/command/common" + "github.com/spf13/cobra" +) + +func setupEmptyCommand() *cobra.Command { + cmd := cobra.Command{ + Use: "empty", + Short: "Empty Trash", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + f := common.UserFileSystem(cmd) + printTrashSummary(f) + if noConfirm, err := cmd.Flags().GetBool("no-confirm"); err != nil { + fmt.Println("cannot read flag: " + err.Error()) + os.Exit(1) + } else if !noConfirm { + reader := bufio.NewReader(os.Stdin) + fmt.Print("Are you sure you want to continue (y/N)?: ") + text, _ := reader.ReadString('\n') + text = strings.TrimLeft(text, "\r\n\t ") + text = strings.TrimRight(text, "\r\n\t ") + if text != "Y" && text != "y" { + fmt.Println("Aborting") + os.Exit(0) + } + } + fmt.Println("Deleting") + f.TrashEmpty() + }, + } + cmd.Flags().BoolP("no-confirm", "y", false, "Do not ask for confirmation") + return &cmd +} diff --git a/server/internal/command/trash/restore.go b/server/internal/command/trash/restore.go index 05ccf381..a163f279 100644 --- a/server/internal/command/trash/restore.go +++ b/server/internal/command/trash/restore.go @@ -17,13 +17,17 @@ func setupRestoreCommand() *cobra.Command { f := common.UserFileSystem(cmd) for _, uuid := range args { r, err := f.ResourceByPathOrUUID(uuid) + if err != nil { + fmt.Println("cannot restore'" + uuid + "': " + err.Error()) + os.Exit(1) + } if r.Dir() { if recursive, err := cmd.Flags().GetBool("recursive"); err != nil || !recursive { fmt.Println("cannot restore '" + uuid + "': Is a directory. Try again with '-r'") os.Exit(1) } } - p, err := cmd.Flags().GetString("parent") + p, _ := cmd.Flags().GetString("parent") name, _ := cmd.Flags().GetString("name") autoRename, _ := cmd.Flags().GetBool("auto-rename") r, items, size, err := r.Restore(p, name, autoRename) diff --git a/server/internal/command/trash/summary.go b/server/internal/command/trash/summary.go index b173852b..2a4e2e26 100644 --- a/server/internal/command/trash/summary.go +++ b/server/internal/command/trash/summary.go @@ -5,6 +5,7 @@ import ( "os" "github.com/shroff/phylum/server/internal/command/common" + "github.com/shroff/phylum/server/internal/core/fs" "github.com/spf13/cobra" ) @@ -15,15 +16,19 @@ func setupSummaryCommand() *cobra.Command { Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { f := common.UserFileSystem(cmd) - items, size, err := f.TrashSummary() - if err != nil { - fmt.Println("cannot get trash summary: " + err.Error()) - os.Exit(1) - } - - fmt.Printf("Items in trash: %d\n", items) - fmt.Printf("Space occupied: %s\n", common.FormatSize(size)) + printTrashSummary(f) }, } return &cmd } + +func printTrashSummary(f fs.FileSystem) { + items, size, err := f.TrashSummary() + if err != nil { + fmt.Println("cannot get trash summary: " + err.Error()) + os.Exit(1) + } + + fmt.Printf("Items in trash: %d\n", items) + fmt.Printf("Space occupied: %s\n", common.FormatSize(size)) +} diff --git a/server/internal/core/db/trash.sql.go b/server/internal/core/db/trash.sql.go index f2371238..898dff4e 100644 --- a/server/internal/core/db/trash.sql.go +++ b/server/internal/core/db/trash.sql.go @@ -12,6 +12,40 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const emptyTrash = `-- name: EmptyTrash :many +DELETE FROM resources + WHERE deleted IS NOT NULL + AND CASE + WHEN $1::TEXT IS NULL THEN TRUE + ELSE permissions[$1::TEXT]::integer & 32 <> 0 END + RETURNING id, dir +` + +type EmptyTrashRow struct { + ID uuid.UUID + Dir bool +} + +func (q *Queries) EmptyTrash(ctx context.Context, username pgtype.Text) ([]EmptyTrashRow, error) { + rows, err := q.db.Query(ctx, emptyTrash, username) + if err != nil { + return nil, err + } + defer rows.Close() + var items []EmptyTrashRow + for rows.Next() { + var i EmptyTrashRow + if err := rows.Scan(&i.ID, &i.Dir); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const hardDelete = `-- name: HardDelete :many WITH RECURSIVE nodes(id, parent) AS ( SELECT r.id, r.parent @@ -224,7 +258,7 @@ func (q *Queries) TrashList(ctx context.Context, arg TrashListParams) ([]Resourc } const trashSummary = `-- name: TrashSummary :one -SELECT SUM(content_length) AS size, COUNT(*) AS items +SELECT COALESCE(SUM(content_length), 0)::INTEGER AS size, COUNT(*) AS items FROM resources WHERE deleted IS NOT NULL AND CASE @@ -233,7 +267,7 @@ SELECT SUM(content_length) AS size, COUNT(*) AS items ` type TrashSummaryRow struct { - Size int64 + Size int32 Items int64 } diff --git a/server/internal/core/fs/fs.go b/server/internal/core/fs/fs.go index 00932b25..1dfd0d40 100644 --- a/server/internal/core/fs/fs.go +++ b/server/internal/core/fs/fs.go @@ -37,6 +37,7 @@ type FileSystem interface { // trash.go TrashList(cursor string, n int) ([]Resource, string, error) TrashSummary() (int, int, error) + TrashEmpty() (int, error) } func Open(ctx context.Context, username string, root uuid.UUID, fullAccess bool) FileSystem { diff --git a/server/internal/core/fs/trash.go b/server/internal/core/fs/trash.go index d0842415..688d7c89 100644 --- a/server/internal/core/fs/trash.go +++ b/server/internal/core/fs/trash.go @@ -174,3 +174,21 @@ func HardDeleteExpired(ctx context.Context, t time.Time) (int, error) { storage.Get().DeleteAll(ids) return len(rows), err } + +func (f filesystem) TrashEmpty() (int, error) { + var username pgtype.Text + if !f.fullAccess { + username.String = f.username + username.Valid = true + } + d := db.Get() + rows, err := d.EmptyTrash(f.ctx, username) + ids := make([]uuid.UUID, len(rows)) + for _, d := range rows { + if !d.Dir { + ids = append(ids, d.ID) + } + } + storage.Get().DeleteAll(ids) + return len(rows), err +} diff --git a/server/sql/queries/trash.sql b/server/sql/queries/trash.sql index b7cccd36..98fdac3d 100644 --- a/server/sql/queries/trash.sql +++ b/server/sql/queries/trash.sql @@ -76,9 +76,17 @@ SELECT * FROM resources LIMIT @n::INTEGER; -- name: TrashSummary :one -SELECT SUM(content_length) AS size, COUNT(*) AS items +SELECT COALESCE(SUM(content_length), 0)::INTEGER AS size, COUNT(*) AS items FROM resources WHERE deleted IS NOT NULL AND CASE WHEN sqlc.narg('username')::TEXT IS NULL THEN TRUE - ELSE permissions[@username::TEXT]::integer <> 0 END; \ No newline at end of file + ELSE permissions[@username::TEXT]::integer <> 0 END; + +-- name: EmptyTrash :many +DELETE FROM resources + WHERE deleted IS NOT NULL + AND CASE + WHEN sqlc.narg('username')::TEXT IS NULL THEN TRUE + ELSE permissions[@username::TEXT]::integer & 32 <> 0 END + RETURNING id, dir; \ No newline at end of file