mirror of
https://github.com/go-shiori/shiori.git
synced 2024-11-15 21:55:31 +08:00
187 lines
4.5 KiB
Go
187 lines
4.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
fp "path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-shiori/shiori/internal/database"
|
|
"github.com/go-shiori/shiori/pkg/warc"
|
|
"github.com/julienschmidt/httprouter"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func openCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "open [indices]",
|
|
Short: "Open the saved bookmarks",
|
|
Long: "Open bookmarks in browser. " +
|
|
"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 bookmarks will be opened.",
|
|
Run: openHandler,
|
|
}
|
|
|
|
cmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and open ALL bookmarks")
|
|
cmd.Flags().BoolP("archive", "a", false, "Open the bookmark's archived content")
|
|
cmd.Flags().IntP("archive-port", "p", 0, "Port number that used to serve archive")
|
|
cmd.Flags().BoolP("text-cache", "t", false, "Open the bookmark's text cache in terminal")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func openHandler(cmd *cobra.Command, args []string) {
|
|
// Parse flags
|
|
skipConfirm, _ := cmd.Flags().GetBool("yes")
|
|
archiveMode, _ := cmd.Flags().GetBool("archive")
|
|
archivePort, _ := cmd.Flags().GetInt("archive-port")
|
|
textCacheMode, _ := cmd.Flags().GetBool("text-cache")
|
|
|
|
// Convert args to ids
|
|
ids, err := parseStrIndices(args)
|
|
if err != nil {
|
|
cError.Println(err)
|
|
return
|
|
}
|
|
|
|
// If in archive mode, only one bookmark allowed
|
|
if len(ids) > 1 && archiveMode {
|
|
cError.Println("In archive mode, only one bookmark allowed")
|
|
return
|
|
}
|
|
|
|
// If no arguments (i.e all bookmarks will be opened),
|
|
// confirm to user
|
|
if len(args) == 0 && !skipConfirm {
|
|
confirmOpen := ""
|
|
fmt.Print("Open ALL bookmarks? (y/N): ")
|
|
fmt.Scanln(&confirmOpen)
|
|
|
|
if confirmOpen != "y" {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Read bookmarks from database
|
|
getOptions := database.GetBookmarksOptions{
|
|
IDs: ids,
|
|
WithContent: true,
|
|
}
|
|
|
|
bookmarks, err := db.GetBookmarks(getOptions)
|
|
if err != nil {
|
|
cError.Printf("Failed to get bookmarks: %v\n", err)
|
|
return
|
|
}
|
|
|
|
if len(bookmarks) == 0 {
|
|
if len(ids) > 0 {
|
|
cError.Println("No matching index found")
|
|
} else {
|
|
cError.Println("No bookmarks saved yet")
|
|
}
|
|
return
|
|
}
|
|
|
|
// If not text cache mode nor archive mode, open bookmarks in browser
|
|
if !textCacheMode && !archiveMode {
|
|
for _, book := range bookmarks {
|
|
err = openBrowser(book.URL)
|
|
if err != nil {
|
|
cError.Printf("Failed to open %s: %v\n", book.URL, err)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Show bookmarks content in terminal
|
|
if textCacheMode {
|
|
termWidth := getTerminalWidth()
|
|
|
|
for _, book := range bookmarks {
|
|
cIndex.Printf("%d. ", book.ID)
|
|
cTitle.Println(book.Title)
|
|
fmt.Println()
|
|
|
|
if book.Content == "" {
|
|
cError.Println("This bookmark doesn't have any cached content")
|
|
} else {
|
|
book.Content = strings.Join(strings.Fields(book.Content), " ")
|
|
fmt.Println(book.Content)
|
|
}
|
|
|
|
fmt.Println()
|
|
cSymbol.Println(strings.Repeat("=", termWidth))
|
|
fmt.Println()
|
|
}
|
|
return
|
|
}
|
|
|
|
// Open archive
|
|
id := strconv.Itoa(bookmarks[0].ID)
|
|
archivePath := fp.Join(dataDir, "archive", id)
|
|
|
|
archive, err := warc.Open(archivePath)
|
|
if err != nil {
|
|
cError.Printf("Failed to open archive: %v\n", err)
|
|
return
|
|
}
|
|
defer archive.Close()
|
|
|
|
// Create simple server
|
|
router := httprouter.New()
|
|
router.GET("/*filename", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
filename := ps.ByName("filename")
|
|
resourceName := fp.Base(filename)
|
|
if resourceName == "/" {
|
|
resourceName = ""
|
|
}
|
|
|
|
content, contentType, err := archive.Read(resourceName)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", contentType)
|
|
w.Header().Set("Content-Encoding", "gzip")
|
|
if _, err = w.Write(content); err != nil {
|
|
panic(err)
|
|
}
|
|
})
|
|
|
|
router.PanicHandler = func(w http.ResponseWriter, r *http.Request, arg interface{}) {
|
|
http.Error(w, fmt.Sprint(arg), 500)
|
|
}
|
|
|
|
// Choose random port
|
|
listenerAddr := fmt.Sprintf(":%d", archivePort)
|
|
listener, err := net.Listen("tcp", listenerAddr)
|
|
if err != nil {
|
|
cError.Printf("Failed to serve archive: %v\n", err)
|
|
return
|
|
}
|
|
|
|
portNumber := listener.Addr().(*net.TCPAddr).Port
|
|
localhostAddr := fmt.Sprintf("http://localhost:%d", portNumber)
|
|
cInfo.Printf("Archive served in %s\n", localhostAddr)
|
|
|
|
// Open browser
|
|
go func() {
|
|
time.Sleep(time.Second)
|
|
|
|
err := openBrowser(localhostAddr)
|
|
if err != nil {
|
|
cError.Printf("Failed to open browser: %v\n", err)
|
|
}
|
|
}()
|
|
|
|
// Serve archive
|
|
err = http.Serve(listener, router)
|
|
if err != nil {
|
|
cError.Printf("Failed to serve archive: %v\n", err)
|
|
}
|
|
}
|