From b48f89d67c3102fa413da36418c413abaa5c6abb Mon Sep 17 00:00:00 2001
From: comp500 <comp500@users.noreply.github.com>
Date: Wed, 18 Sep 2019 17:32:37 +0100
Subject: [PATCH] Add serve command

---
 cmd/serve.go  | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++
 core/index.go |   2 +
 2 files changed, 155 insertions(+)
 create mode 100644 cmd/serve.go

diff --git a/cmd/serve.go b/cmd/serve.go
new file mode 100644
index 0000000..08fb5fc
--- /dev/null
+++ b/cmd/serve.go
@@ -0,0 +1,153 @@
+package cmd
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"sync"
+
+	"github.com/comp500/packwiz/core"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+)
+
+var refreshMutex sync.RWMutex
+
+// serveCmd represents the server 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")), pack.Index.File)
+
+			http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+				path := strings.TrimPrefix(req.URL.Path, "/")
+
+				found := false
+				if path == pack.Index.File {
+					found = true
+					path = indexPath
+					// Must be done here, to ensure all paths gain the lock at some point
+					refreshMutex.RLock()
+				} else if path == 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()
+				} else {
+					refreshMutex.RLock()
+					// Only allow indexed files
+					for _, v := range index.Files {
+						if path == v.File {
+							found = true
+							break
+						}
+					}
+					if found {
+						path = filepath.Join(filepath.Dir(indexPath), path)
+					}
+				}
+				defer refreshMutex.RUnlock()
+				if found {
+					f, err := os.Open(path)
+					if err != nil {
+						fmt.Printf("Error reading file \"%s\": %s\n", path, err)
+						w.WriteHeader(404)
+						w.Write([]byte("File not found"))
+						return
+					}
+					defer f.Close()
+					_, err = io.Copy(w, f)
+					if err != nil {
+						fmt.Printf("Error reading file \"%s\": %s\n", path, err)
+						w.WriteHeader(500)
+						w.Write([]byte("Failed to read file"))
+						return
+					}
+				} else {
+					fmt.Printf("File not found: %s\n", path)
+					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"))
+}
diff --git a/core/index.go b/core/index.go
index 1f65672..5d4fcf8 100644
--- a/core/index.go
+++ b/core/index.go
@@ -235,6 +235,8 @@ func (in *Index) Refresh() error {
 
 		progress.Increment(time.Since(start))
 	}
+	// Close bar
+	progressContainer.Wait()
 
 	// Check all the files exist, remove them if they don't
 	i := 0