package main import ( "embed" "flag" "fmt" "html/template" "log" "math/rand" "net" "net/http" "os" "strings" "sync" "time" "github.com/gorilla/mux" "riedstra.dev/mitch/steam-export/steam" ) type App struct { Library *steamLib Status *statusInfo Templates *template.Template // Sending to this channel triggers the downloader in the background download chan string } func NewApp(libPath string) (*App, error) { lib, err := steam.NewLibrary(libPath) if err != nil { return nil, err } a := &App{ Library: &steamLib{}, Status: &statusInfo{}, download: make(chan string), } a.Library.Library = *lib a.Templates = template.Must(template.New("index").Parse(indexTemplate)) return a, nil } type steamLib struct { steam.Library sync.Mutex } type statusInfo struct { sync.RWMutex Running bool Error error Url string Transferred int64 Size int64 Start *time.Time } var ( Version = "Development" Logger = log.New(os.Stderr, "", log.LstdFlags) Listen = ":8899" //go:embed static/* embeddedStatic embed.FS //go:embed templates/index.html indexTemplate string ) func (a *App) LibrarySet(path string) { Logger.Println("Starting library reload") a.Library.Lock() defer a.Library.Unlock() var err error l2, err := steam.NewLibrary(path) if err != nil { Logger.Printf("Error reopening lib: %s", err) return } a.Library.Library = *l2 Logger.Println("Done reloading lib") } func (a *App) LibraryReload() { cur := a.Library.Folder a.LibrarySet(cur) return } func (a *App) HandleSetLib(w http.ResponseWriter, r *http.Request) { if unauthorizedIfNotLocal(w, r) { return } err := r.ParseForm() if err != nil { Logger.Printf("Setlib: While parsing form: %s", err) http.Error(w, fmt.Sprintf("Invalid form: %s", err), 400) return } a.LibrarySet(r.Form.Get("path")) http.Redirect(w, r, "/", 302) } func (a *App) HandleQuit(w http.ResponseWriter, r *http.Request) { if unauthorizedIfNotLocal(w, r) { return } Logger.Println("Quit was called, exiting") w.Header().Add("Content-type", "text/plain") w.Write([]byte("Shutting down...")) go func() { time.Sleep(time.Second * 2) os.Exit(0) }() return } func unauthorizedIfNotLocal(w http.ResponseWriter, r *http.Request) bool { if !isLocal(r.RemoteAddr) { http.Error(w, "Unauthorized", http.StatusUnauthorized) Logger.Printf("Unauthorized request from: %s for %s", r.RemoteAddr, r.RequestURI) return true } return false } func isLocal(addr string) bool { _, localNet, _ := net.ParseCIDR("127.0.0.1/8") return localNet.Contains(net.ParseIP(strings.Split(addr, ":")[0])) } // getHostIP attempts to guess the IP address of the current machine and // returns that. Simply bails at the first non sane looking IP and returns it. // Not ideal but it should work well enough most of the time func getHostIP() string { iFaces, err := net.Interfaces() if err != nil { return "127.0.0.1" } // RFC 3927 _, ipv4LinkLocal, _ := net.ParseCIDR("169.254.0.0/16") for _, iFace := range iFaces { addrs, err := iFace.Addrs() if err != nil { return "127.0.0.1" } for _, a := range addrs { n, ok := a.(*net.IPNet) if !ok { continue } if n.IP.To4() != nil && !n.IP.IsLoopback() && !ipv4LinkLocal.Contains(n.IP.To4()) { return n.IP.String() } } } return "127.0.0.1" } func getPort() string { s := strings.Split(Listen, ":") if len(s) != 2 { return Listen } return s[1] } func main() { fl := flag.NewFlagSet("steam-export", flag.ExitOnError) debug := fl.Bool("d", false, "Print line numbers in log") fl.StringVar(&Listen, "l", Listen, "What address do we listen on?") fl.StringVar(&DefaultLib, "L", DefaultLib, "Full path to default library") fl.Parse(os.Args[1:]) if *debug { Logger.SetFlags(log.LstdFlags | log.Llongfile) } a, err := NewApp(DefaultLib) if err != nil { Logger.Fatal(err) } go a.installer() r := mux.NewRouter() r.HandleFunc("/quit", a.HandleQuit) r.HandleFunc("/setLib", a.HandleSetLib) r.HandleFunc("/delete", a.HandleDelete) r.HandleFunc("/install", a.HandleInstall) r.HandleFunc("/steam-export-web.exe", serveSelf) r.HandleFunc("/download/{game}", a.HandleDownload) r.HandleFunc("/status", a.HandleStats) r.PathPrefix("/static").Handler( http.FileServer(http.FS(embeddedStatic))) r.HandleFunc("/", a.HandleIndex) s := http.Server{ Handler: r, Addr: Listen, } go startBrowser() for i := 0; i < 5; i++ { err = s.ListenAndServe() if err != nil { Logger.Printf("Encountered: %s", err) rand.Seed(time.Now().UnixNano()) Listen = fmt.Sprintf(":%d", rand.Intn(63000)+1024) Logger.Printf("Trying: %s", Listen) s.Addr = Listen } } }