diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 00000000..547d00f9 --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + "os" +) + +var ( + deleteCmd = &cobra.Command{ + Use: "delete [indices]", + Short: "Delete the saved bookmarks.", + Long: "Delete bookmarks. " + + "Accepts space-separated list of indices (e.g. 5 6 23 4 110 45) and hyphenated range (e.g. 100-200). " + + "If no arguments, all records will be deleted", + Run: func(cmd *cobra.Command, args []string) { + // If no arguments, confirm to user + if len(args) == 0 { + confirmDelete := "" + fmt.Print("Remove ALL bookmarks? (y/n): ") + fmt.Scanln(&confirmDelete) + + if confirmDelete != "y" { + fmt.Println("No bookmarks deleted") + return + } + } + + // Delete bookmarks from database + oldIndices, newIndices, err := DB.DeleteBookmarks(args...) + if err != nil { + cError.Println(err) + os.Exit(1) + } + + fmt.Println("Bookmarks has been deleted") + if len(oldIndices) > 0 { + for i, oldIndex := range oldIndices { + newIndex := newIndices[i] + fmt.Printf("Index %d moved to %d\n", oldIndex, newIndex) + } + } + }, + } +) + +func init() { + rootCmd.AddCommand(deleteCmd) +} diff --git a/database/database.go b/database/database.go index dacc54d3..33f89d04 100644 --- a/database/database.go +++ b/database/database.go @@ -8,6 +8,7 @@ import ( type Database interface { SaveBookmark(article readability.Article, tags ...string) (model.Bookmark, error) GetBookmarks(indices ...string) ([]model.Bookmark, error) + DeleteBookmarks(indices ...string) ([]int, []int, error) } func checkError(err error) { diff --git a/database/sqlite.go b/database/sqlite.go index 5178e446..6d328923 100644 --- a/database/sqlite.go +++ b/database/sqlite.go @@ -7,6 +7,7 @@ import ( "github.com/RadhiFadlillah/shiori/model" "github.com/jmoiron/sqlx" "log" + "sort" "strconv" "strings" "time" @@ -252,3 +253,124 @@ func (db *SQLiteDatabase) GetBookmarks(indices ...string) ([]model.Bookmark, err return bookmarks, nil } + +func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (oldIndices, newIndices []int, err error) { + // Convert list of index to int + listIndex := []int{} + errInvalidIndex := fmt.Errorf("Index is not valid") + + for _, strIndex := range indices { + if strings.Contains(strIndex, "-") { + parts := strings.Split(strIndex, "-") + if len(parts) != 2 { + return nil, nil, errInvalidIndex + } + + minIndex, errMin := strconv.Atoi(parts[0]) + maxIndex, errMax := strconv.Atoi(parts[1]) + if errMin != nil || errMax != nil || minIndex < 1 || minIndex > maxIndex { + return nil, nil, errInvalidIndex + } + + for i := minIndex; i <= maxIndex; i++ { + listIndex = append(listIndex, i) + } + } else { + index, err := strconv.Atoi(strIndex) + if err != nil || index < 1 { + return nil, nil, errInvalidIndex + } + + listIndex = append(listIndex, index) + } + } + + // Sort the index + sort.Ints(listIndex) + + // Create args and where clause + args := []interface{}{} + whereClause := " WHERE 1" + + if len(listIndex) > 0 { + whereClause = " WHERE id IN (" + for _, idx := range listIndex { + args = append(args, idx) + whereClause += fmt.Sprintf("%d,", idx) + } + + whereClause = whereClause[:len(whereClause)-1] + whereClause += ")" + } + + // Begin transaction + tx, err := db.Beginx() + if err != nil { + return nil, nil, errInvalidIndex + } + + // Make sure to rollback if panic ever happened + defer func() { + if r := recover(); r != nil { + panicErr, _ := r.(error) + tx.Rollback() + + oldIndices = nil + newIndices = nil + err = panicErr + } + }() + + // Delete bookmarks + res, err := tx.Exec("DELETE FROM bookmark "+whereClause, args...) + checkError(err) + + nAffected, err := res.RowsAffected() + checkError(err) + + whereTagClause := strings.Replace(whereClause, "id", "bookmark_id", 1) + _, err = tx.Exec("DELETE FROM bookmark_tag "+whereTagClause, args...) + checkError(err) + + whereContentClause := strings.Replace(whereClause, "id", "docid", 1) + _, err = tx.Exec("DELETE FROM bookmark_content "+whereContentClause, args...) + checkError(err) + + // Get largest index + oldIndices = []int{} + err = tx.Select(&oldIndices, "SELECT id FROM bookmark ORDER BY id DESC LIMIT ?", nAffected) + if err != nil && err != sql.ErrNoRows { + panic(err) + } + sort.Ints(oldIndices) + + // Update index + newIndices = listIndex[:len(oldIndices)] + stmtUpdateBookmark, err := tx.Preparex(`UPDATE bookmark SET id = ? WHERE id = ?`) + checkError(err) + + stmtUpdateBookmarkTag, err := tx.Preparex(`UPDATE bookmark_tag SET bookmark_id = ? WHERE bookmark_id = ?`) + checkError(err) + + stmtUpdateBookmarkContent, err := tx.Preparex(`UPDATE bookmark_content SET docid = ? WHERE docid = ?`) + checkError(err) + + for i, oldIndex := range oldIndices { + newIndex := newIndices[i] + + _, err = stmtUpdateBookmark.Exec(newIndex, oldIndex) + checkError(err) + + _, err = stmtUpdateBookmarkTag.Exec(newIndex, oldIndex) + checkError(err) + + _, err = stmtUpdateBookmarkContent.Exec(newIndex, oldIndex) + checkError(err) + } + + // Commit transaction + err = tx.Commit() + checkError(err) + + return oldIndices, newIndices, err +}