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  }