forked from go/golangs_learn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
6.0 KiB
220 lines
6.0 KiB
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"os/user"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
. "ytd/clipboard"
|
|
. "ytd/models"
|
|
. "ytd/plugins"
|
|
|
|
_ "embed"
|
|
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
|
)
|
|
|
|
var plugins []Plugin = []Plugin{&Yt{Name: "youtube"}}
|
|
|
|
//go:embed .version
|
|
var version string
|
|
|
|
//go:embed frontend/dist/assets/*
|
|
var static embed.FS
|
|
|
|
var pwaUrl = "https://ytd.surge.sh"
|
|
var appState *AppState
|
|
var newEntries = make(chan *GenericEntry)
|
|
var maxConvertJobs = runtime.NumCPU()
|
|
|
|
type JsonError struct {
|
|
Msg string `json:"msg"`
|
|
}
|
|
|
|
func (e *JsonError) ToJSON() string {
|
|
j, err := json.Marshal(e)
|
|
if err != nil {
|
|
return `{"msg": "json.Marshal() failed"}`
|
|
}
|
|
return string(j)
|
|
}
|
|
|
|
func panicHandler() error {
|
|
if panicPayload := recover(); panicPayload != nil {
|
|
|
|
stack := string(debug.Stack())
|
|
fmt.Fprintln(os.Stderr, "")
|
|
fmt.Fprintln(os.Stderr, "================================================================================")
|
|
fmt.Fprintln(os.Stderr, "Ytd has encountered a fatal error. This is a bug!")
|
|
fmt.Fprintln(os.Stderr, "We would appreciate a report: https://github.com/marcio199226/ytd/issues/")
|
|
fmt.Fprintln(os.Stderr, "Please provide all of the below text in your report.")
|
|
fmt.Fprintln(os.Stderr, "================================================================================")
|
|
|
|
fmt.Fprintf(os.Stderr, "Ytd Version: %s\n", version)
|
|
fmt.Fprintf(os.Stderr, "Go Version: %s\n", runtime.Version())
|
|
fmt.Fprintf(os.Stderr, "Go Compiler: %s\n", runtime.Compiler)
|
|
fmt.Fprintf(os.Stderr, "Architecture: %s\n", runtime.GOARCH)
|
|
fmt.Fprintf(os.Stderr, "Operating System: %s\n", runtime.GOOS)
|
|
fmt.Fprintf(os.Stderr, "Panic: %s\n\n", panicPayload)
|
|
fmt.Fprintln(os.Stderr, stack)
|
|
return fmt.Errorf("panic: %s", panicPayload)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cors(fs http.Handler) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
fs.ServeHTTP(w, r)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(2)
|
|
app := &AppState{PwaUrl: pwaUrl}
|
|
ctx, cancelCtx := context.WithCancel(context.Background())
|
|
app.Context = ctx
|
|
exitChan := make(chan os.Signal, 1)
|
|
signal.Notify(exitChan,
|
|
os.Interrupt,
|
|
os.Kill,
|
|
syscall.SIGHUP,
|
|
syscall.SIGINT,
|
|
syscall.SIGTERM,
|
|
syscall.SIGQUIT,
|
|
)
|
|
changes := make(chan string, 10)
|
|
stopCh := make(chan struct{}, 1)
|
|
go MonitorClipboard(time.Second, ctx, wg, stopCh, changes)
|
|
|
|
go func(wg *sync.WaitGroup) {
|
|
sig := <-exitChan
|
|
fmt.Println("Received signal", sig)
|
|
// send the shutdown signal through the context.Context and to the global context passed down
|
|
cancelCtx()
|
|
wg.Done()
|
|
stopCh <- struct{}{}
|
|
return
|
|
}(wg)
|
|
|
|
// initialize plugins
|
|
usr, _ := user.Current()
|
|
currentDir := fmt.Sprintf("%v/songs", usr.HomeDir)
|
|
for _, plugin := range plugins {
|
|
log.Println("download to dir=", fmt.Sprintf("%s/%s", currentDir, plugin.GetName()))
|
|
plugin.SetDir(fmt.Sprintf("%s/%s", currentDir, plugin.GetName()))
|
|
plugin.Initialize()
|
|
}
|
|
|
|
go func() {
|
|
// Watch for changes of local clipboard
|
|
for {
|
|
select {
|
|
case <-stopCh:
|
|
fmt.Printf("stopped manually")
|
|
time.Sleep(time.Second)
|
|
wg.Wait()
|
|
os.Exit(0)
|
|
return
|
|
default:
|
|
change, ok := <-changes /* */
|
|
if ok && change != "" {
|
|
log.Printf("change received: '%s'", change)
|
|
if app.Config.ClipboardWatch {
|
|
app.AddToDownload(change, true)
|
|
}
|
|
} else {
|
|
log.Println("channel has been closed. exiting...")
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
fs := http.StripPrefix("/static/", http.FileServer(http.FS(static)))
|
|
http.Handle("/tracks/", http.StripPrefix("/tracks/", http.FileServer(http.Dir(currentDir))))
|
|
http.Handle("/static/", cors(fs))
|
|
|
|
// api for mobile/pwa app
|
|
ytTracks := http.StripPrefix("/app/download", http.FileServer(http.Dir(currentDir)))
|
|
http.Handle("/app/download/", cors(ytTracks))
|
|
http.HandleFunc("/app/state", func(w http.ResponseWriter, r *http.Request) {
|
|
var buffer bytes.Buffer
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
|
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Authorization, X-API-KEY")
|
|
w.Header().Set("Access-Control-Expose-Headers", "Authorization, X-API-KEY")
|
|
|
|
// check api-key if needed
|
|
var xApiKey = r.Header.Get("X-API-KEY")
|
|
if xApiKey == "" {
|
|
xApiKey = r.URL.Query().Get("api-key")
|
|
}
|
|
if appState.Config.IsNgrokApiKeyEnabled() && (appState.Config.GetNgrokApiKkey() == "" || xApiKey != appState.Config.GetNgrokApiKkey()) {
|
|
jsonErr := &JsonError{Msg: "Wrong api key"}
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(&buffer).Encode(&jsonErr)
|
|
w.Write(buffer.Bytes())
|
|
return
|
|
}
|
|
|
|
err := json.NewEncoder(&buffer).Encode(&app)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 400)
|
|
return
|
|
}
|
|
w.Write(buffer.Bytes())
|
|
})
|
|
|
|
http.ListenAndServe(":8080", nil)
|
|
}()
|
|
|
|
defer panicHandler()
|
|
app.PreWailsInit(ctx)
|
|
err := wails.Run(&options.App{
|
|
Width: 1024,
|
|
Height: 768,
|
|
MinWidth: 1024,
|
|
MinHeight: 768,
|
|
StartHidden: app.canStartAtLogin && app.Config.StartAtLogin,
|
|
HideWindowOnClose: app.Config.RunInBackgroundOnClose,
|
|
DisableResize: false,
|
|
Fullscreen: false,
|
|
Startup: app.WailsInit,
|
|
Shutdown: app.WailsShutdown,
|
|
Mac: &mac.Options{
|
|
WebviewIsTransparent: true,
|
|
WindowBackgroundIsTranslucent: true,
|
|
TitleBar: mac.TitleBarHiddenInset(),
|
|
ActivationPolicy: mac.NSApplicationActivationPolicyAccessory,
|
|
},
|
|
Title: "ytd",
|
|
Bind: []interface{}{
|
|
app,
|
|
app.offlinePlaylistService,
|
|
app.ngrok,
|
|
},
|
|
Frameless: false,
|
|
})
|
|
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}
|
|
|