gropple/main.go

326 lines
7.2 KiB
Go
Raw Normal View History

2021-09-21 08:33:24 +09:30
package main
import (
"embed"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
"strings"
"time"
"strconv"
"github.com/gorilla/mux"
2021-09-28 21:17:54 +09:30
"github.com/tardisx/gropple/config"
"github.com/tardisx/gropple/download"
2021-09-26 21:13:33 +09:30
"github.com/tardisx/gropple/version"
2021-09-21 08:33:24 +09:30
)
var downloads []*download.Download
2021-09-21 08:33:24 +09:30
var downloadId = 0
var conf *config.Config
var versionInfo = version.Info{CurrentVersion: "v0.5.2"}
2021-09-21 08:33:24 +09:30
//go:embed web
var webFS embed.FS
type successResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
}
type errorResponse struct {
Success bool `json:"success"`
Error string `json:"error"`
}
2021-09-21 08:33:24 +09:30
func main() {
if !config.ConfigFileExists() {
log.Print("No config file - creating default config")
conf = config.DefaultConfig()
conf.WriteConfig()
} else {
loadedConfig, err := config.LoadConfig()
if err != nil {
log.Fatal(err)
}
conf = loadedConfig
}
2021-09-21 08:33:24 +09:30
r := mux.NewRouter()
r.HandleFunc("/", homeHandler)
r.HandleFunc("/config", configHandler)
r.HandleFunc("/fetch", fetchHandler)
r.HandleFunc("/fetch/{id}", fetchHandler)
// info for the list
r.HandleFunc("/rest/fetch", fetchInfoRESTHandler)
// info for one, including update
r.HandleFunc("/rest/fetch/{id}", fetchInfoOneRESTHandler)
r.HandleFunc("/rest/version", versionRESTHandler)
r.HandleFunc("/rest/config", configRESTHandler)
2021-09-21 08:33:24 +09:30
http.Handle("/", r)
srv := &http.Server{
Handler: r,
2021-09-28 21:17:54 +09:30
Addr: fmt.Sprintf(":%d", conf.Server.Port),
2021-09-21 08:33:24 +09:30
// Good practice: enforce timeouts for servers you create!
2021-09-21 17:59:30 +09:30
WriteTimeout: 5 * time.Second,
ReadTimeout: 5 * time.Second,
2021-09-21 08:33:24 +09:30
}
2021-09-26 21:13:33 +09:30
// check for a new version every 4 hours
go func() {
for {
versionInfo.UpdateGitHubVersion()
time.Sleep(time.Hour * 4)
}
}()
log.Printf("starting gropple %s - https://github.com/tardisx/gropple", versionInfo.CurrentVersion)
2021-09-28 21:17:54 +09:30
log.Printf("go to %s for details on installing the bookmarklet and to check status", conf.Server.Address)
2021-09-21 08:33:24 +09:30
log.Fatal(srv.ListenAndServe())
2021-09-26 21:13:33 +09:30
}
2021-09-21 08:33:24 +09:30
// versionRESTHandler returns the version information, if we have up-to-date info from github
func versionRESTHandler(w http.ResponseWriter, r *http.Request) {
2021-09-26 21:13:33 +09:30
if versionInfo.GithubVersionFetched {
b, _ := json.Marshal(versionInfo)
w.Write(b)
} else {
w.WriteHeader(400)
}
2021-09-21 08:33:24 +09:30
}
// homeHandler returns the main index page
func homeHandler(w http.ResponseWriter, r *http.Request) {
2021-09-21 08:33:24 +09:30
w.WriteHeader(http.StatusOK)
bookmarkletURL := fmt.Sprintf("javascript:(function(f,s,n,o){window.open(f+encodeURIComponent(s),n,o)}('%s/fetch?url=',window.location,'yourform','width=%d,height=%d'));", conf.Server.Address, conf.UI.PopupWidth, conf.UI.PopupHeight)
2021-09-21 08:33:24 +09:30
t, err := template.ParseFS(webFS, "web/layout.tmpl", "web/menu.tmpl", "web/index.html")
2021-09-21 08:33:24 +09:30
if err != nil {
panic(err)
}
type Info struct {
Downloads []*download.Download
2021-09-21 08:33:24 +09:30
BookmarkletURL template.URL
Config *config.Config
2021-09-21 08:33:24 +09:30
}
info := Info{
Downloads: downloads,
BookmarkletURL: template.URL(bookmarkletURL),
Config: conf,
2021-09-21 08:33:24 +09:30
}
err = t.ExecuteTemplate(w, "layout", info)
2021-09-21 08:33:24 +09:30
if err != nil {
panic(err)
}
}
// configHandler returns the configuration page
func configHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
t, err := template.ParseFS(webFS, "web/layout.tmpl", "web/menu.tmpl", "web/config.html")
if err != nil {
panic(err)
}
err = t.ExecuteTemplate(w, "layout", nil)
if err != nil {
panic(err)
}
}
// configRESTHandler handles both reading and writing of the configuration
func configRESTHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
log.Printf("Updating config")
b, err := io.ReadAll(r.Body)
if err != nil {
panic(err)
}
err = conf.UpdateFromJSON(b)
if err != nil {
errorRes := errorResponse{Success: false, Error: err.Error()}
errorResB, _ := json.Marshal(errorRes)
w.WriteHeader(400)
w.Write(errorResB)
return
}
conf.WriteConfig()
}
b, _ := json.Marshal(conf)
w.Write(b)
}
//
func fetchInfoOneRESTHandler(w http.ResponseWriter, r *http.Request) {
2021-09-21 08:33:24 +09:30
vars := mux.Vars(r)
idString := vars["id"]
if idString != "" {
id, err := strconv.Atoi(idString)
if err != nil {
http.NotFound(w, r)
return
}
// find the download
var thisDownload *download.Download
for _, dl := range downloads {
if dl.Id == id {
thisDownload = dl
}
}
if thisDownload == nil {
http.NotFound(w, r)
return
}
if r.Method == "POST" {
type updateRequest struct {
Action string `json:"action"`
Profile string `json:"profile"`
}
thisReq := updateRequest{}
b, err := io.ReadAll(r.Body)
if err != nil {
panic(err)
}
err = json.Unmarshal(b, &thisReq)
if err != nil {
errorRes := errorResponse{Success: false, Error: err.Error()}
errorResB, _ := json.Marshal(errorRes)
w.WriteHeader(400)
w.Write(errorResB)
return
}
if thisReq.Action == "start" {
// find the profile they asked for
profile := conf.ProfileCalled(thisReq.Profile)
if profile == nil {
panic("bad profile name?")
}
// set the profile
thisDownload.DownloadProfile = *profile
go func() { thisDownload.Begin() }()
succRes := successResponse{Success: true, Message: "download started"}
succResB, _ := json.Marshal(succRes)
w.Write(succResB)
return
}
}
// just a get, return the object
b, _ := json.Marshal(thisDownload)
w.Write(b)
return
2021-09-21 08:33:24 +09:30
} else {
http.NotFound(w, r)
}
}
func fetchInfoRESTHandler(w http.ResponseWriter, r *http.Request) {
2021-09-26 12:33:31 +09:30
b, _ := json.Marshal(downloads)
w.Write(b)
}
func fetchHandler(w http.ResponseWriter, r *http.Request) {
2021-09-21 08:33:24 +09:30
// if they refreshed the popup, just load the existing object, don't
// create a new one
vars := mux.Vars(r)
idString := vars["id"]
idInt, err := strconv.ParseInt(idString, 10, 32)
if err == nil && idInt > 0 {
for _, dl := range downloads {
if dl.Id == int(idInt) {
t, err := template.ParseFS(webFS, "web/layout.tmpl", "web/popup.html")
if err != nil {
panic(err)
}
templateData := map[string]interface{}{"dl": dl, "config": conf}
err = t.ExecuteTemplate(w, "layout", templateData)
if err != nil {
panic(err)
}
return
}
}
}
2021-09-21 08:33:24 +09:30
query := r.URL.Query()
2021-09-26 12:48:42 +09:30
url, present := query["url"]
2021-09-21 08:33:24 +09:30
if !present {
2021-09-26 12:48:42 +09:30
w.WriteHeader(400)
fmt.Fprint(w, "No url supplied")
return
2021-09-21 08:33:24 +09:30
} else {
2021-09-26 12:48:42 +09:30
// check the URL for a sudden but inevitable betrayal
2021-09-28 21:17:54 +09:30
if strings.Contains(url[0], conf.Server.Address) {
2021-09-26 12:48:42 +09:30
w.WriteHeader(400)
fmt.Fprint(w, "you musn't gropple your gropple :-)")
return
}
2021-09-21 08:33:24 +09:30
// create the record
// XXX should be atomic!
downloadId++
newDownload := download.Download{
Config: conf,
Id: downloadId,
Url: url[0],
PopupUrl: fmt.Sprintf("/fetch/%d", downloadId),
State: "choose profile",
Finished: false,
Eta: "?",
Percent: 0.0,
Log: make([]string, 0, 1000),
2021-09-21 08:33:24 +09:30
}
downloads = append(downloads, &newDownload)
2021-09-21 08:33:24 +09:30
// XXX atomic ^^
2021-09-21 08:33:24 +09:30
newDownload.Log = append(newDownload.Log, "start of log...")
// go func() {
// newDownload.Begin()
// }()
2021-09-26 12:48:42 +09:30
t, err := template.ParseFS(webFS, "web/layout.tmpl", "web/popup.html")
2021-09-21 08:33:24 +09:30
if err != nil {
panic(err)
}
templateData := map[string]interface{}{"dl": newDownload, "config": conf}
err = t.ExecuteTemplate(w, "layout", templateData)
2021-09-21 08:33:24 +09:30
if err != nil {
panic(err)
}
}
}