packwiz/cmd/serve.go
2021-12-28 16:28:34 +00:00

167 lines
4.3 KiB
Go

package cmd
import (
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/packwiz/packwiz/core"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var refreshMutex sync.RWMutex
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Run a local development server",
Long: `Run a local HTTP server for development, automatically refreshing the index when it is queried`,
Aliases: []string{"server"},
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
if viper.GetBool("serve.basic") {
http.Handle("/", http.FileServer(http.Dir(".")))
} else {
fmt.Println("Loading modpack...")
pack, err := core.LoadPack()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
index, err := pack.LoadIndex()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
indexPath := filepath.Join(filepath.Dir(viper.GetString("pack-file")), filepath.FromSlash(pack.Index.File))
indexDir := filepath.Dir(indexPath)
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
urlPath := strings.TrimPrefix(path.Clean("/"+strings.TrimPrefix(req.URL.Path, "/")), "/")
indexRelPath, err := filepath.Rel(indexDir, filepath.FromSlash(urlPath))
if err != nil {
fmt.Println(err)
return
}
indexRelPathSlash := path.Clean(filepath.ToSlash(indexRelPath))
var destPath string
found := false
if urlPath == filepath.ToSlash(indexPath) {
found = true
destPath = indexPath
// Must be done here, to ensure all paths gain the lock at some point
refreshMutex.RLock()
} else if urlPath == filepath.ToSlash(viper.GetString("pack-file")) {
found = true
if viper.GetBool("serve.refresh") {
// Get write lock, to do a refresh
refreshMutex.Lock()
// Reload pack and index (might have changed on disk)
pack, err = core.LoadPack()
if err != nil {
fmt.Println(err)
return
}
index, err = pack.LoadIndex()
if err != nil {
fmt.Println(err)
return
}
err = index.Refresh()
if err != nil {
fmt.Println(err)
return
}
err = index.Write()
if err != nil {
fmt.Println(err)
return
}
err = pack.UpdateIndexHash()
if err != nil {
fmt.Println(err)
return
}
err = pack.Write()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Index refreshed!")
// Downgrade to a read lock
refreshMutex.Unlock()
}
refreshMutex.RLock()
destPath = viper.GetString("pack-file")
} else {
refreshMutex.RLock()
// Only allow indexed files
for _, v := range index.Files {
if indexRelPathSlash == v.File {
found = true
break
}
}
if found {
destPath = filepath.FromSlash(urlPath)
}
}
defer refreshMutex.RUnlock()
if found {
f, err := os.Open(destPath)
if err != nil {
fmt.Printf("Error reading file \"%s\": %s\n", destPath, err)
w.WriteHeader(404)
_, _ = w.Write([]byte("File not found"))
return
}
_, err = io.Copy(w, f)
err2 := f.Close()
if err == nil {
err = err2
}
if err != nil {
fmt.Printf("Error reading file \"%s\": %s\n", destPath, err)
w.WriteHeader(500)
_, _ = w.Write([]byte("Failed to read file"))
return
}
} else {
fmt.Printf("File not found: %s\n", destPath)
w.WriteHeader(404)
_, _ = w.Write([]byte("File not found"))
return
}
})
}
port := strconv.Itoa(viper.GetInt("serve.port"))
fmt.Println("Running on port " + port)
err := http.ListenAndServe(":"+port, nil)
if err != nil {
fmt.Printf("Error running server: %s\n", err)
os.Exit(1)
}
},
}
func init() {
rootCmd.AddCommand(serveCmd)
serveCmd.Flags().IntP("port", "p", 8080, "The port to run the server on")
_ = viper.BindPFlag("serve.port", serveCmd.Flags().Lookup("port"))
serveCmd.Flags().BoolP("refresh", "r", true, "Automatically refresh the index file")
_ = viper.BindPFlag("serve.refresh", serveCmd.Flags().Lookup("refresh"))
serveCmd.Flags().Bool("basic", false, "Disable refreshing and allow all files in the directory, rather than just files listed in the index")
_ = viper.BindPFlag("serve.basic", serveCmd.Flags().Lookup("basic"))
}