diff --git a/internal/cmd/delete.go b/internal/cmd/delete.go index 38acee15..cd445506 100644 --- a/internal/cmd/delete.go +++ b/internal/cmd/delete.go @@ -1,6 +1,11 @@ package cmd import ( + "fmt" + "os" + fp "path/filepath" + "strings" + "github.com/spf13/cobra" ) @@ -10,12 +15,70 @@ func deleteCmd() *cobra.Command { Short: "Delete the saved bookmarks", Long: "Delete bookmarks. " + "When a record is deleted, the last record is moved to the removed index. " + - "Accepts space-separated list of indices (e.g. 5 6 23 4 110 45), hyphenated range (e.g. 100-200) or both (e.g. 1-3 7 9). " + + "Accepts space-separated list of indices (e.g. 5 6 23 4 110 45), " + + "hyphenated range (e.g. 100-200) or both (e.g. 1-3 7 9). " + "If no arguments, ALL records will be deleted.", Aliases: []string{"rm"}, + Run: deleteHandler, } cmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and delete ALL bookmarks") return cmd } + +func deleteHandler(cmd *cobra.Command, args []string) { + // Parse flags + skipConfirm, _ := cmd.Flags().GetBool("yes") + + // If no arguments (i.e all bookmarks going to be deleted), confirm to user + if len(args) == 0 && !skipConfirm { + confirmDelete := "" + fmt.Print("Remove ALL bookmarks? (y/N): ") + fmt.Scanln(&confirmDelete) + + if confirmDelete != "y" { + fmt.Println("No bookmarks deleted") + return + } + } + + // Convert args to ids + ids, err := parseStrIndices(args) + if err != nil { + cError.Printf("Failed to parse args: %v\n", err) + return + } + + // Delete bookmarks from database + err = DB.DeleteBookmarks(ids...) + if err != nil { + cError.Printf("Failed to delete bookmarks: %v\n", err) + return + } + + // Delete thumbnail image from local disk + if len(ids) == 0 { + thumbDir := fp.Join(DataDir, "thumb") + os.RemoveAll(thumbDir) + } else { + for _, id := range ids { + imgPath := fp.Join(DataDir, "thumb", fmt.Sprintf("%d.*", id)) + matchedFiles, _ := fp.Glob(imgPath) + + for _, f := range matchedFiles { + os.Remove(f) + } + } + } + + // Show finish message + switch len(args) { + case 0: + fmt.Println("All bookmarks have been deleted") + case 1, 2, 3, 4, 5: + fmt.Printf("Bookmark(s) %s have been deleted\n", strings.Join(args, ", ")) + default: + fmt.Println("Bookmark(s) have been deleted") + } +} diff --git a/internal/database/database.go b/internal/database/database.go index 49151c2d..8770daf7 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -23,6 +23,9 @@ type DB interface { // GetBookmarks fetch list of bookmarks based on submitted options. GetBookmarks(opts GetBookmarksOptions) ([]model.Bookmark, error) + // DeleteBookmarks removes all record with matching ids from database. + DeleteBookmarks(ids ...int) error + // CreateNewID creates new id for specified table. CreateNewID(table string) (int, error) } diff --git a/internal/database/sqlite.go b/internal/database/sqlite.go index c740b2b5..08baf64c 100644 --- a/internal/database/sqlite.go +++ b/internal/database/sqlite.go @@ -267,6 +267,57 @@ func (db *SQLiteDatabase) GetBookmarks(opts GetBookmarksOptions) ([]model.Bookma return bookmarks, nil } +// DeleteBookmarks removes all record with matching ids from database. +func (db *SQLiteDatabase) DeleteBookmarks(ids ...int) (err error) { + // Begin transaction + tx, err := db.Beginx() + if err != nil { + return err + } + + // Make sure to rollback if panic ever happened + defer func() { + if r := recover(); r != nil { + panicErr, _ := r.(error) + tx.Rollback() + + err = panicErr + } + }() + + // Prepare queries + delBookmark := `DELETE FROM bookmark` + delBookmarkTag := `DELETE FROM bookmark_tag` + delBookmarkContent := `DELETE FROM bookmark_content` + + // Delete bookmark(s) + if len(ids) == 0 { + tx.MustExec(delBookmarkContent) + tx.MustExec(delBookmarkTag) + tx.MustExec(delBookmark) + } else { + delBookmark += ` WHERE id = ?` + delBookmarkTag += ` WHERE bookmark_id = ?` + delBookmarkContent += ` WHERE docid = ?` + + stmtDelBookmark, _ := tx.Preparex(delBookmark) + stmtDelBookmarkTag, _ := tx.Preparex(delBookmarkTag) + stmtDelBookmarkContent, _ := tx.Preparex(delBookmarkContent) + + for _, id := range ids { + stmtDelBookmarkContent.MustExec(id) + stmtDelBookmarkTag.MustExec(id) + stmtDelBookmark.MustExec(id) + } + } + + // Commit transaction + err = tx.Commit() + checkError(err) + + return err +} + // CreateNewID creates new ID for specified table func (db *SQLiteDatabase) CreateNewID(table string) (int, error) { var tableID int