-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmain.go
215 lines (181 loc) · 5.31 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package main
import (
"context"
"embed"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gorilla/mux"
)
var library map[string]interface{}
var Games map[string]interface{}
var gameFiles []FileDesc
var rootShop string
//go:embed assets/*
var assetData embed.FS
type HostType string
const (
LocalFile HostType = "localFile"
NFSShare HostType = "NFS"
)
type FileDesc struct {
url string
size int64
gameInfo string
path string
hostType HostType
}
type GameId struct {
FullId string
ShortId string
Extension string
}
func main() {
initServer()
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/games/{game}", GamesHandler)
r.NotFoundHandler = http.HandlerFunc(notFound)
r.MethodNotAllowedHandler = http.HandlerFunc(notAllowed)
r.Use(tinfoilMiddleware)
http.Handle("/", r)
srv := &http.Server{
Handler: r,
Addr: "0.0.0.0:3000",
// Good practice to set timeouts to avoid Slowloris attacks.
WriteTimeout: 0, // Installing large game can take a lot of time
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
}
// Run our server in a goroutine so that it doesn't block.
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Println(err)
}
}()
log.Printf("Total of %d files in your library (%d in titledb section)\n", len(Games["files"].([]interface{})), len(Games["titledb"].(map[string]interface{})))
var uniqueGames int
for _, entry := range Games["titledb"].(map[string]interface{}) {
if entry.(map[string]interface{})["iconUrl"] != nil {
uniqueGames += 1
}
}
log.Printf("Total of %d unique games in your library\n", uniqueGames)
log.Printf("Tinshop available at %s !\n", rootShop)
c := make(chan os.Signal, 1)
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)
// Block until we receive our signal.
<-c
// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()
// Doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
_ = srv.Shutdown(ctx)
// Optionally, you could run srv.Shutdown in a goroutine and block on
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
log.Println("shutting down")
os.Exit(0)
}
func initServer() {
// Loading config
loadConfig()
// Load JSON library
loadTitlesLibrary()
// Load Games
gameFiles = make([]FileDesc, 0)
initGamesCollection()
loadGamesDirectories(len(nfsShares) == 0)
loadGamesNfsShares()
}
func notFound(w http.ResponseWriter, r *http.Request) {
log.Println("notFound")
log.Println(r.Header)
log.Println(r.RequestURI)
w.WriteHeader(http.StatusNotFound)
}
func notAllowed(w http.ResponseWriter, r *http.Request) {
log.Println("notAllowed")
log.Println(r.Header)
log.Println(r.RequestURI)
w.WriteHeader(http.StatusMethodNotAllowed)
}
func loadTitlesLibrary() {
// Open our jsonFile
jsonFile, err := os.Open("titles.US.en.json")
if err != nil {
if err.Error() == "open titles.US.en.json: no such file or directory" {
log.Println("Missing 'titles.US.en.json'! Start downloading it.")
downloadErr := DownloadFile("https://github.com/AdamK2003/titledb/releases/download/latest/titles.US.en.json", "titles.US.en.json")
if downloadErr != nil {
log.Fatalln(err, downloadErr)
} else {
jsonFile, err = os.Open("titles.US.en.json")
if err != nil {
log.Fatalln("Error while parsing downloaded json file.\nPlease remove the file and start again the program.\n", err)
}
}
} else {
log.Fatalln(err)
}
}
log.Println("Successfully Opened titles library")
// defer the closing of our jsonFile so that we can parse it later on
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
err = json.Unmarshal([]byte(byteValue), &library)
if err != nil {
log.Println("Error while loading titles library", err)
} else {
log.Println("Successfully Loaded titles library")
}
}
func initGamesCollection() {
// Build games object
Games = make(map[string]interface{})
Games["success"] = "Welcome to your own shop!"
Games["titledb"] = make(map[string]interface{})
Games["files"] = make([]interface{}, 0)
}
// Handle list of games
func HomeHandler(w http.ResponseWriter, r *http.Request) {
jsonResponse, jsonError := json.Marshal(Games)
if jsonError != nil {
log.Println("Unable to encode JSON")
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(jsonResponse)
}
// Handle downloading games
func GamesHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
log.Println("Requesting game", vars["game"])
idx := Search(len(gameFiles), func(index int) bool {
return gameFiles[index].url == vars["game"]
})
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
log.Printf("Game '%s' not found!", vars["game"])
return
} else {
log.Println(gameFiles[idx].path)
switch gameFiles[idx].hostType {
case LocalFile:
downloadLocalFile(w, r, vars["game"], gameFiles[idx].path)
case NFSShare:
downloadNfsFile(w, r, gameFiles[idx].path)
default:
w.WriteHeader(http.StatusNotImplemented)
log.Printf("The type '%s' is not implemented to download game", gameFiles[idx].hostType)
}
}
}