From 2eb6415fa883b8c7268a72064bf14c38e46babe9 Mon Sep 17 00:00:00 2001 From: Radhi Fadlillah Date: Sat, 21 Sep 2019 16:30:39 +0700 Subject: [PATCH] Add check command --- internal/cmd/check.go | 141 ++++++++++++++++++++++++++++++++++++++++++ internal/cmd/root.go | 1 + 2 files changed, 142 insertions(+) create mode 100644 internal/cmd/check.go diff --git a/internal/cmd/check.go b/internal/cmd/check.go new file mode 100644 index 0000000..660de62 --- /dev/null +++ b/internal/cmd/check.go @@ -0,0 +1,141 @@ +package cmd + +import ( + "fmt" + "net/http" + "sort" + "sync" + "time" + + "github.com/go-shiori/shiori/internal/database" + "github.com/go-shiori/shiori/internal/model" + "github.com/spf13/cobra" +) + +func checkCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "check", + Short: "Find bookmarked sites that no longer exists on the internet", + Long: "Check all bookmarks and find bookmarked sites that no longer exists on the internet. " + + "It might take a long time depending on how many bookmarks that you have and want to check. " + + "If there are no arguments, it will check ALL of your bookmarks.", + Run: checkHandler, + } + + cmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and check ALL bookmarks") + + return cmd +} + +func checkHandler(cmd *cobra.Command, args []string) { + // Parse flags + skipConfirm, _ := cmd.Flags().GetBool("yes") + + // If no arguments (i.e all bookmarks going to be checked), confirm to user + if len(args) == 0 && !skipConfirm { + confirmCheck := "" + fmt.Print("Check ALL bookmarks? (y/N): ") + fmt.Scanln(&confirmCheck) + + if confirmCheck != "y" { + fmt.Println("No bookmarks checked") + return + } + } + + // Convert args to ids + ids, err := parseStrIndices(args) + if err != nil { + cError.Printf("Failed to parse args: %v\n", err) + return + } + + // Fetch bookmarks from database + filterOptions := database.GetBookmarksOptions{IDs: ids} + bookmarks, err := db.GetBookmarks(filterOptions) + if err != nil { + cError.Printf("Failed to get bookmarks: %v\n", err) + return + } + + // Create HTTP client + httpClient := &http.Client{Timeout: time.Minute} + + // Test each bookmark item + unreachableIDs := []int{} + + wg := sync.WaitGroup{} + chDone := make(chan struct{}) + chProblem := make(chan int, 10) + chMessage := make(chan interface{}, 10) + semaphore := make(chan struct{}, 10) + + for i, book := range bookmarks { + wg.Add(1) + + go func(i int, book model.Bookmark) { + // Make sure to finish the WG + defer wg.Done() + + // Register goroutine to semaphore + semaphore <- struct{}{} + defer func() { + <-semaphore + }() + + // Ping bookmark's URL + _, err := httpClient.Get(book.URL) + if err != nil { + chProblem <- book.ID + chMessage <- fmt.Errorf("Failed to reach %s: %v", book.URL, err) + return + } + + // Send success message + chMessage <- fmt.Sprintf("Reached %s", book.URL) + }(i, book) + } + + // Watch messages from channels + go func(nBookmark int) { + logIndex := 0 + + for { + select { + case <-chDone: + cInfo.Println("Check finished") + return + case id := <-chProblem: + unreachableIDs = append(unreachableIDs, id) + case msg := <-chMessage: + logIndex++ + + switch msg.(type) { + case error: + cError.Printf("[%d/%d] %v\n", logIndex, nBookmark, msg) + case string: + cInfo.Printf("[%d/%d] %s\n", logIndex, nBookmark, msg) + } + } + } + }(len(bookmarks)) + + // Wait until all download finished + wg.Wait() + close(chDone) + + // Print the unreachable bookmarks + fmt.Println() + + if len(unreachableIDs) == 0 { + cInfo.Println("All bookmarks is reachable.") + } else { + sort.Ints(unreachableIDs) + + cError.Println("Encountered some unreachable bookmarks:") + for _, id := range unreachableIDs { + cError.Printf("%d ", id) + } + fmt.Println() + } +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 1c54860..5a0a7f9 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -34,6 +34,7 @@ func ShioriCmd() *cobra.Command { exportCmd(), pocketCmd(), serveCmd(), + checkCmd(), ) return rootCmd