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() } }