github.com/derat/nup@v0.0.0-20230418113745-15592ba7c620/example/serve_example_data.go (about) 1 // Copyright 2021 Daniel Erat. 2 // All rights reserved. 3 4 // Package main runs dev_appserver with example data. 5 package main 6 7 import ( 8 "flag" 9 "fmt" 10 "io" 11 "log" 12 "os" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "syscall" 17 18 "github.com/derat/nup/server/config" 19 "github.com/derat/nup/server/db" 20 "github.com/derat/nup/test" 21 22 gops "github.com/mitchellh/go-ps" 23 24 "golang.org/x/sys/unix" 25 ) 26 27 func main() { 28 code, err := run() 29 if err != nil { 30 log.Print("Failed serving example data: ", err) 31 } 32 os.Exit(code) 33 } 34 35 func run() (int, error) { 36 email := flag.String("email", "test@example.com", "Email address for login") 37 logToStderr := flag.Bool("log-to-stderr", true, "Write noisy dev_appserver output to stderr") 38 minify := flag.Bool("minify", false, "Minify HTML, JavaScript, and CSS") 39 numSongs := flag.Int("num-songs", 5, "Number of songs to insert") 40 port := flag.Int("port", 8080, "HTTP port for app") 41 flag.Parse() 42 43 tmpDir, _, err := test.OutputDir("example") 44 if err != nil { 45 return -1, err 46 } 47 defer os.RemoveAll(tmpDir) 48 49 // Unlike actual tests, we expect to receive SIGINT in normal usage, 50 // so none of these defer statements are actually going to run. 51 // Delete the temp dir in the signal handler to avoid leaving a mess. 52 test.HandleSignals([]os.Signal{unix.SIGINT, unix.SIGTERM}, func() { 53 // dev_appserver.py seems to frequently hang if its storage directory 54 // gets deleted while it's still shutting down. Send SIGKILL to make 55 // sure it really goes away. 56 if err := killProcs(regexp.MustCompile("^python2?$")); err != nil { 57 log.Print("Failed killing processes: ", err) 58 } 59 os.RemoveAll(tmpDir) 60 }) 61 62 exampleDir, err := test.CallerDir() 63 if err != nil { 64 return -1, err 65 } 66 fileSrv := test.ServeFiles(exampleDir) 67 defer fileSrv.Close() 68 log.Print("File server is listening at ", fileSrv.URL) 69 70 log.Print("Starting dev_appserver") 71 var appOut io.Writer // discard by default 72 if *logToStderr { 73 appOut = os.Stderr 74 } 75 cfg := &config.Config{ 76 Users: []config.User{ 77 {Email: *email}, 78 {Username: test.Username, Password: test.Password, Admin: true}, 79 }, 80 SongBaseURL: fileSrv.URL + "/music/", 81 CoverBaseURL: fileSrv.URL + "/covers/", 82 Presets: presets, 83 Minify: minify, 84 } 85 appSrv, err := test.NewDevAppserver(cfg, filepath.Join(tmpDir, "app_storage"), appOut, 86 test.DevAppserverPort(*port), test.DevAppserverWatchForChanges(true)) 87 if err != nil { 88 return -1, fmt.Errorf("dev_appserver: %v", err) 89 } 90 defer appSrv.Close() 91 appURL := appSrv.URL() 92 log.Print("dev_appserver is listening at ", appURL) 93 94 tester := test.NewTester(nil, appURL, filepath.Join(tmpDir, "tester"), test.TesterConfig{ 95 MusicDir: filepath.Join(exampleDir, "music"), 96 CoverDir: filepath.Join(exampleDir, "covers"), 97 }) 98 tester.ImportSongsFromJSONFile(getSongs(*numSongs)) 99 tester.UpdateStats() 100 101 // Block until we get killed. 102 <-make(chan struct{}) 103 return 0, nil 104 } 105 106 // killProcs sends SIGKILL to all processes in the same process group as us 107 // with an executable name matched by re. 108 func killProcs(re *regexp.Regexp) error { 109 self := os.Getpid() 110 pgid, err := unix.Getpgid(self) 111 if err != nil { 112 return err 113 } 114 procs, err := gops.Processes() 115 if err != nil { 116 return err 117 } 118 for _, p := range procs { 119 if p.Pid() == self || !re.MatchString(p.Executable()) { 120 continue 121 } 122 if pg, err := unix.Getpgid(p.Pid()); err != nil || pg != pgid { 123 continue 124 } 125 log.Printf("Sending SIGKILL to process %d (%v)", p.Pid(), p.Executable()) 126 if err := unix.Kill(p.Pid(), syscall.SIGKILL); err != nil { 127 log.Printf("Killing %d failed: %v", p.Pid(), err) 128 } 129 } 130 return nil 131 } 132 133 // MaxPlays needs to be set explicitly since Go's zero value (i.e. 0) isn't the default. 134 // The server handles this automatically when unmarshaling from JSON. 135 var presets = []config.SearchPreset{ 136 { 137 Name: "old", 138 MinRating: 4, 139 LastPlayed: 6, 140 MaxPlays: -1, 141 Shuffle: true, 142 Play: true, 143 }, 144 { 145 Name: "new albums", 146 FirstPlayed: 3, 147 MaxPlays: -1, 148 FirstTrack: true, 149 }, 150 { 151 Name: "unrated", 152 Unrated: true, 153 MaxPlays: -1, 154 Play: true, 155 }, 156 } 157 158 const ( 159 songsPerArtist = 10 160 songsPerAlbum = 5 161 ) 162 163 // getSongs returns num songs to serve, creating fake ones if needed. 164 func getSongs(num int) []db.Song { 165 songs := make([]db.Song, num) 166 copy(songs, baseSongs) 167 for i := len(baseSongs); i < num; i++ { 168 n := i - len(baseSongs) 169 artistID := n/songsPerArtist + 1 170 albumID := n/songsPerAlbum + 1 171 songs[i] = db.Song{ 172 SHA1: fmt.Sprintf("%040x", i), 173 SongID: strconv.Itoa(i), 174 Filename: baseSongs[0].Filename, 175 Artist: fmt.Sprintf("Artist %d", artistID), 176 Title: fmt.Sprintf("Song %d", i), 177 Album: fmt.Sprintf("Album %d", albumID), 178 AlbumID: strconv.Itoa(albumID), 179 Track: (n % songsPerAlbum) + 1, 180 Disc: 1, 181 Length: baseSongs[0].Length, 182 } 183 } 184 return songs 185 } 186 187 var baseSongs = []db.Song{ 188 { 189 SHA1: "5439c23b4eae55f9dcd145fc3284cd8fa05696ff", 190 SongID: "1", 191 Filename: "400x400.mp3", 192 Artist: "Artist", 193 Title: "400x400", 194 Album: "400x400", 195 AlbumID: "400-400", 196 Track: 1, 197 Disc: 1, 198 Date: test.Date(2015, 3, 4), 199 Length: 1, 200 Rating: 5, 201 Tags: []string{"electronic", "instrumental", "drum-and-bass"}, 202 }, 203 { 204 SHA1: "74057828e637cdaa60338c220ad3f59e4262c3f2", 205 SongID: "2", 206 Filename: "800x800.mp3", 207 Artist: "Artist", 208 Title: "800x800", 209 Album: "800x800", 210 AlbumID: "800-800", 211 Track: 1, 212 Disc: 1, 213 Date: test.Date(2012, 1, 23), 214 Length: 1, 215 Rating: 4, 216 Tags: []string{"drums", "guitar", "rock", "vocals"}, 217 }, 218 { 219 SHA1: "0358287496e475b2e812e882b7885be665b604d1", 220 SongID: "3", 221 Filename: "40x40.mp3", 222 Artist: "Artist", 223 Title: "40x40", 224 Album: "40x40", 225 AlbumID: "40-40", 226 Track: 1, 227 Disc: 1, 228 Date: test.Date(1995, 12, 25), 229 Length: 1, 230 Rating: 3, 231 Tags: []string{"electronic", "vocals"}, 232 }, 233 { 234 SHA1: "22aa5c0ad793e7a86852cfb7e0aa6b41aa98e99c", 235 SongID: "4", 236 Filename: "360x400.mp3", 237 Artist: "Artist", 238 Title: "360x400", 239 Album: "360x400", 240 AlbumID: "360-400", 241 Track: 1, 242 Disc: 1, 243 Date: test.Date(1984, 4, 1), 244 Length: 1, 245 Rating: 2, 246 Tags: []string{"annoying", "pop", "vocals"}, 247 }, 248 { 249 SHA1: "11551e3ebd919e5ef2329d9d3716c3e453d98c7d", 250 SongID: "5", 251 Filename: "400x360.mp3", 252 Artist: "Artist", 253 Title: "400x360", 254 Album: "400x360", 255 AlbumID: "400-360", 256 Track: 1, 257 Disc: 1, 258 Length: 1, 259 Rating: 0, 260 }, 261 }