forked from CWAD/relibre
164 lines
3.7 KiB
Go
164 lines
3.7 KiB
Go
// main.go
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
webview "github.com/webview/webview_go"
|
|
)
|
|
|
|
//go:embed index.html ui/* images/*
|
|
var content embed.FS
|
|
|
|
type saveReq struct {
|
|
Filename string `json:"filename"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
type saveResp struct {
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
func main() {
|
|
// Static files
|
|
subFS, err := fs.Sub(content, ".")
|
|
if err != nil {
|
|
log.Fatalf("fs.Sub error: %v", err)
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/", http.FileServer(http.FS(subFS)))
|
|
|
|
// /save endpoint writes to Downloads
|
|
mux.HandleFunc("/save", func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
defer func() { log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start)) }()
|
|
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
var req saveReq
|
|
if err := json.NewDecoder(io.LimitReader(r.Body, 10<<20)).Decode(&req); err != nil {
|
|
http.Error(w, "bad json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Filename == "" {
|
|
req.Filename = fmt.Sprintf("relibre-%d.html", time.Now().Unix())
|
|
}
|
|
req.Filename = sanitizeFilename(req.Filename)
|
|
|
|
dir := downloadsDir()
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
http.Error(w, "mkdir failed", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
path := filepath.Join(dir, req.Filename)
|
|
if err := os.WriteFile(path, []byte(req.Content), 0o644); err != nil {
|
|
http.Error(w, "write failed", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
log.Printf("Saved HTML -> %s", path)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(saveResp{Path: path})
|
|
})
|
|
|
|
// Simple request logger
|
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
mux.ServeHTTP(w, r)
|
|
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
|
|
})
|
|
|
|
// Start server on ephemeral port
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
log.Fatalf("listen: %v", err)
|
|
}
|
|
addr := ln.Addr().String()
|
|
srv := &http.Server{Handler: handler, ReadHeaderTimeout: 5 * time.Second}
|
|
|
|
go func() {
|
|
log.Printf("HTTP listening on http://%s", addr)
|
|
if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("http serve: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Native webview window (debug true for dev tools/log signals)
|
|
debug := true
|
|
w := webview.New(debug)
|
|
defer w.Destroy()
|
|
w.SetTitle("Relibre")
|
|
w.SetSize(1100, 750, webview.HintNone)
|
|
w.Navigate(fmt.Sprintf("http://%s", addr))
|
|
w.Run()
|
|
|
|
_ = srv.Close()
|
|
if runtime.GOOS == "linux" {
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func sanitizeFilename(name string) string {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return "relibre.html"
|
|
}
|
|
if !strings.HasSuffix(strings.ToLower(name), ".html") {
|
|
name += ".html"
|
|
}
|
|
re := regexp.MustCompile(`[<>:"/\\|?*\x00-\x1F]`)
|
|
return re.ReplaceAllString(name, "_")
|
|
}
|
|
|
|
func downloadsDir() string {
|
|
if p := readXDGDownloads(); p != "" {
|
|
return p
|
|
}
|
|
home, _ := os.UserHomeDir()
|
|
if home == "" {
|
|
return "./"
|
|
}
|
|
return filepath.Join(home, "Downloads")
|
|
}
|
|
|
|
func readXDGDownloads() string {
|
|
home, _ := os.UserHomeDir()
|
|
if home == "" {
|
|
return ""
|
|
}
|
|
path := filepath.Join(home, ".config", "user-dirs.dirs")
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
defer f.Close()
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
line := sc.Text()
|
|
if strings.HasPrefix(line, "XDG_DOWNLOAD_DIR=") {
|
|
raw := strings.TrimPrefix(line, "XDG_DOWNLOAD_DIR=")
|
|
raw = strings.Trim(raw, `"`)
|
|
raw = strings.ReplaceAll(raw, "$HOME", home)
|
|
return raw
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|