diff --git a/src/http/static/component-oriented-design/v1/main.go b/src/http/static/component-oriented-design/v1/main.go deleted file mode 100644 index 490a516..0000000 --- a/src/http/static/component-oriented-design/v1/main.go +++ /dev/null @@ -1,314 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "log" - "math/rand" - "net" - "net/http" - "os" - "sort" - "strconv" - "sync" - "time" -) - -// Logger describes a simple component used for printing log lines. -type Logger interface { - Printf(string, ...interface{}) -} - -//////////////////////////////////////////////////////////////////////////////// -// The scoreboard component - -// File wraps the standard os.File type. -type File interface { - io.ReadWriter - Truncate(int64) error - Seek(int64, int) (int64, error) -} - -// scoreboard loads player scores from a save file, tracks score updates, and -// periodically saves those scores back to the save file. -type scoreboard struct { - file File - scoresM map[string]int - scoresLock sync.Mutex - - // this field will only be set in tests, and is used to synchronize with the - // the for-select loop in saveLoop. - saveLoopWaitCh chan struct{} -} - -// newScoreboard initializes a scoreboard using scores saved in the given File -// (which may be empty). The scoreboard will rewrite the save file with the -// latest scores everytime saveTicker is written to. -func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger) (*scoreboard, error) { - fileBody, err := ioutil.ReadAll(file) - if err != nil { - return nil, fmt.Errorf("reading saved scored: %w", err) - } - - scoresM := map[string]int{} - if len(fileBody) > 0 { - if err := json.Unmarshal(fileBody, &scoresM); err != nil { - return nil, fmt.Errorf("decoding saved scores: %w", err) - } - } - - scoreboard := &scoreboard{ - file: file, - scoresM: scoresM, - saveLoopWaitCh: make(chan struct{}), - } - - go scoreboard.saveLoop(saveTicker, logger) - - return scoreboard, nil -} - -func (s *scoreboard) guessedCorrect(name string) int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - s.scoresM[name] += 1000 - return s.scoresM[name] -} - -func (s *scoreboard) guessedIncorrect(name string) int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - s.scoresM[name] -= 1 - return s.scoresM[name] -} - -func (s *scoreboard) scores() map[string]int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - scoresCp := map[string]int{} - for name, score := range s.scoresM { - scoresCp[name] = score - } - - return scoresCp -} - -func (s *scoreboard) save() error { - scores := s.scores() - if _, err := s.file.Seek(0, 0); err != nil { - return fmt.Errorf("seeking to start of save file: %w", err) - } else if err := s.file.Truncate(0); err != nil { - return fmt.Errorf("truncating save file: %w", err) - } else if err := json.NewEncoder(s.file).Encode(scores); err != nil { - return fmt.Errorf("encoding scores to save file: %w", err) - } - return nil -} - -func (s *scoreboard) saveLoop(ticker <-chan time.Time, logger Logger) { - for { - select { - case <-ticker: - if err := s.save(); err != nil { - logger.Printf("error saving scoreboard to file: %v", err) - } - case <-s.saveLoopWaitCh: - // test will unblock, nothing to do here. - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// The httpHandlers component - -// Scoreboard describes the scoreboard component from the point of view of the -// httpHandler component (which only needs a subset of scoreboard's methods). -type Scoreboard interface { - guessedCorrect(name string) int - guessedIncorrect(name string) int - scores() map[string]int -} - -// RandSrc describes a randomness component which can produce random integers. -type RandSrc interface { - Int() int -} - -// httpHandlers implements the http.HandlerFuncs used by the httpServer. -type httpHandlers struct { - scoreboard Scoreboard - randSrc RandSrc - logger Logger - - mux *http.ServeMux - n int - nLock sync.Mutex -} - -func newHTTPHandlers(scoreboard Scoreboard, randSrc RandSrc, logger Logger) *httpHandlers { - n := randSrc.Int() - logger.Printf("first n is %v", n) - - httpHandlers := &httpHandlers{ - scoreboard: scoreboard, - randSrc: randSrc, - logger: logger, - mux: http.NewServeMux(), - n: n, - } - - httpHandlers.mux.HandleFunc("/guess", httpHandlers.handleGuess) - httpHandlers.mux.HandleFunc("/scores", httpHandlers.handleScores) - - return httpHandlers -} - -func (h *httpHandlers) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - h.mux.ServeHTTP(rw, r) -} - -func (h *httpHandlers) handleGuess(rw http.ResponseWriter, r *http.Request) { - r.Header.Set("Content-Type", "text/plain") - - name := r.FormValue("name") - nStr := r.FormValue("n") - if name == "" || nStr == "" { - http.Error(rw, `"name" and "n" GET args are required`, http.StatusBadRequest) - return - } - - n, err := strconv.Atoi(nStr) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - h.nLock.Lock() - defer h.nLock.Unlock() - - if h.n == n { - newScore := h.scoreboard.guessedCorrect(name) - h.n = h.randSrc.Int() - h.logger.Printf("new n is %v", h.n) - rw.WriteHeader(http.StatusOK) - fmt.Fprintf(rw, "Correct! Your score is now %d\n", newScore) - return - } - - hint := "higher" - if h.n < n { - hint = "lower" - } - - newScore := h.scoreboard.guessedIncorrect(name) - rw.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(rw, "Try %s. Your score is now %d\n", hint, newScore) -} - -func (h *httpHandlers) handleScores(rw http.ResponseWriter, r *http.Request) { - r.Header.Set("Content-Type", "text/plain") - - h.nLock.Lock() - defer h.nLock.Unlock() - - type scoreTup struct { - name string - score int - } - - scores := h.scoreboard.scores() - scoresTups := make([]scoreTup, 0, len(scores)) - for name, score := range scores { - scoresTups = append(scoresTups, scoreTup{name, score}) - } - - sort.Slice(scoresTups, func(i, j int) bool { - return scoresTups[i].score > scoresTups[j].score - }) - - for _, scoresTup := range scoresTups { - fmt.Fprintf(rw, "%s: %d\n", scoresTup.name, scoresTup.score) - } -} - -//////////////////////////////////////////////////////////////////////////////// -// The httpServer component. - -type httpServer struct { - httpServer *http.Server - errCh chan error -} - -func newHTTPServer(listener net.Listener, httpHandlers *httpHandlers, logger Logger) *httpServer { - loggingHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - ip, _, _ := net.SplitHostPort(r.RemoteAddr) - logger.Printf("HTTP request -> %s %s %s", ip, r.Method, r.URL.String()) - httpHandlers.ServeHTTP(rw, r) - }) - - server := &httpServer{ - httpServer: &http.Server{ - Handler: loggingHandler, - }, - errCh: make(chan error, 1), - } - - go func() { - err := server.httpServer.Serve(listener) - if errors.Is(err, http.ErrServerClosed) { - err = nil - } - server.errCh <- err - }() - - return server -} - -//////////////////////////////////////////////////////////////////////////////// -// main - -const ( - saveFilePath = "./save.json" - listenAddr = ":8888" - saveInterval = 5 * time.Second -) - -func main() { - logger := log.New(os.Stdout, "", log.LstdFlags) - - logger.Printf("opening scoreboard save file %q", saveFilePath) - file, err := os.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - logger.Fatalf("failed to open file %q: %v", saveFilePath, err) - } - - saveTicker := time.NewTicker(saveInterval) - randSrc := rand.New(rand.NewSource(time.Now().UnixNano())) - - logger.Printf("initializing scoreboard") - scoreboard, err := newScoreboard(file, saveTicker.C, logger) - if err != nil { - logger.Fatalf("failed to initialize scoreboard: %v", err) - } - - logger.Printf("listening on %q", listenAddr) - listener, err := net.Listen("tcp", listenAddr) - if err != nil { - logger.Fatalf("failed to listen on %q: %v", listenAddr, err) - } - - logger.Printf("setting up HTTP handlers") - httpHandlers := newHTTPHandlers(scoreboard, randSrc, logger) - - logger.Printf("serving HTTP requests") - newHTTPServer(listener, httpHandlers, logger) - - logger.Printf("initialization done") - select {} // block forever -} diff --git a/src/http/static/component-oriented-design/v1/main_test.go b/src/http/static/component-oriented-design/v1/main_test.go deleted file mode 100644 index 6cfd9fb..0000000 --- a/src/http/static/component-oriented-design/v1/main_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package main - -import ( - "bytes" - "net/http" - "net/http/httptest" - "reflect" - "testing" - "time" -) - -type nullLogger struct{} - -func (nullLogger) Printf(string, ...interface{}) {} - -//////////////////////////////////////////////////////////////////////////////// -// Test scoreboard component - -type fileStub struct { - *bytes.Buffer -} - -func newFileStub(init string) *fileStub { - return &fileStub{Buffer: bytes.NewBufferString(init)} -} - -func (fs *fileStub) Truncate(i int64) error { - fs.Buffer.Truncate(int(i)) - return nil -} - -func (fs *fileStub) Seek(i int64, whence int) (int64, error) { - return i, nil -} - -func TestScoreboard(t *testing.T) { - newScoreboard := func(t *testing.T, fileStub *fileStub, saveTicker <-chan time.Time) *scoreboard { - t.Helper() - scoreboard, err := newScoreboard(fileStub, saveTicker, nullLogger{}) - if err != nil { - t.Errorf("unexpected error checking saved scored: %v", err) - } - return scoreboard - } - - assertScores := func(t *testing.T, expScores, gotScores map[string]int) { - t.Helper() - if !reflect.DeepEqual(expScores, gotScores) { - t.Errorf("expected scores of %+v, but instead got %+v", expScores, gotScores) - } - } - - assertSavedScores := func(t *testing.T, expScores map[string]int, fileStub *fileStub) { - t.Helper() - fileStubCp := newFileStub(fileStub.String()) - tmpScoreboard := newScoreboard(t, fileStubCp, nil) - assertScores(t, expScores, tmpScoreboard.scores()) - } - - t.Run("loading", func(t *testing.T) { - // make sure loading scoreboards with various file contents works - assertSavedScores(t, map[string]int{}, newFileStub("")) - assertSavedScores(t, map[string]int{"foo": 1}, newFileStub(`{"foo":1}`)) - assertSavedScores(t, map[string]int{"foo": 1, "bar": -2}, newFileStub(`{"foo":1,"bar":-2}`)) - }) - - t.Run("tracking", func(t *testing.T) { - scoreboard := newScoreboard(t, newFileStub(""), nil) - assertScores(t, map[string]int{}, scoreboard.scores()) // sanity check - - scoreboard.guessedCorrect("foo") - assertScores(t, map[string]int{"foo": 1000}, scoreboard.scores()) - - scoreboard.guessedIncorrect("bar") - assertScores(t, map[string]int{"foo": 1000, "bar": -1}, scoreboard.scores()) - - scoreboard.guessedIncorrect("foo") - assertScores(t, map[string]int{"foo": 999, "bar": -1}, scoreboard.scores()) - }) - - t.Run("saving", func(t *testing.T) { - // this test tests scoreboard's periodic save feature using a ticker - // channel which will be written to manually. The saveLoopWaitCh is used - // here to ensure that each ticker has been fully processed. - ticker := make(chan time.Time) - fileStub := newFileStub("") - scoreboard := newScoreboard(t, fileStub, ticker) - - tick := func() { - ticker <- time.Time{} - scoreboard.saveLoopWaitCh <- struct{}{} - } - - // this should not effect the save file at first - scoreboard.guessedCorrect("foo") - assertSavedScores(t, map[string]int{}, fileStub) - - // after the ticker the new score should get saved - tick() - assertSavedScores(t, map[string]int{"foo": 1000}, fileStub) - - // ticker again after no changes should save the same thing as before - tick() - assertSavedScores(t, map[string]int{"foo": 1000}, fileStub) - - // buffer a bunch of changes, shouldn't get saved till after tick - scoreboard.guessedCorrect("foo") - scoreboard.guessedCorrect("bar") - scoreboard.guessedCorrect("bar") - assertSavedScores(t, map[string]int{"foo": 1000}, fileStub) - tick() - assertSavedScores(t, map[string]int{"foo": 2000, "bar": 2000}, fileStub) - }) -} - -//////////////////////////////////////////////////////////////////////////////// -// Test httpHandlers component - -type mockScoreboard map[string]int - -func (mockScoreboard) guessedCorrect(name string) int { return 1 } - -func (mockScoreboard) guessedIncorrect(name string) int { return -1 } - -func (m mockScoreboard) scores() map[string]int { return m } - -type mockRandSrc struct{} - -func (m mockRandSrc) Int() int { return 666 } - -func TestHTTPHandlers(t *testing.T) { - mockScoreboard := mockScoreboard{"foo": 1, "bar": 2} - httpHandlers := newHTTPHandlers(mockScoreboard, mockRandSrc{}, nullLogger{}) - - assertRequest := func(t *testing.T, expCode int, expBody string, r *http.Request) { - t.Helper() - rw := httptest.NewRecorder() - httpHandlers.ServeHTTP(rw, r) - if rw.Code != expCode { - t.Errorf("expected HTTP response code %d, got %d", expCode, rw.Code) - } else if rw.Body.String() != expBody { - t.Errorf("expected HTTP response body %q, got %q", expBody, rw.Body.String()) - } - } - - r := httptest.NewRequest("GET", "/guess?name=foo&n=665", nil) - assertRequest(t, 400, "Try higher. Your score is now -1\n", r) - - r = httptest.NewRequest("GET", "/guess?name=foo&n=667", nil) - assertRequest(t, 400, "Try lower. Your score is now -1\n", r) - - r = httptest.NewRequest("GET", "/guess?name=foo&n=666", nil) - assertRequest(t, 200, "Correct! Your score is now 1\n", r) - - r = httptest.NewRequest("GET", "/scores", nil) - assertRequest(t, 200, "bar: 2\nfoo: 1\n", r) -} - -//////////////////////////////////////////////////////////////////////////////// -// -// httpServer is NOT tested, for the following reasons: -// * It depends on a `net.Listener`, which is not trivial to mock. -// * It does very little besides passing an httpHandlers along to an http.Server -// and managing cleanup. -// * It isn't likely to be changed often. -// * If it were to break it would be very apparent in subsequent testing stages. -// diff --git a/src/http/static/component-oriented-design/v2/main.go b/src/http/static/component-oriented-design/v2/main.go deleted file mode 100644 index fb5773c..0000000 --- a/src/http/static/component-oriented-design/v2/main.go +++ /dev/null @@ -1,320 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "log" - "math/rand" - "net" - "net/http" - "os" - "sort" - "strconv" - "sync" - "time" -) - -// Logger describes a simple component used for printing log lines. -type Logger interface { - Printf(string, ...interface{}) -} - -//////////////////////////////////////////////////////////////////////////////// -// The scoreboard component - -// File wraps the standard os.File type. -type File interface { - io.ReadWriter - Truncate(int64) error - Seek(int64, int) (int64, error) -} - -// scoreboard loads player scores from a save file, tracks score updates, and -// periodically saves those scores back to the save file. -type scoreboard struct { - file File - scoresM map[string]int - scoresLock sync.Mutex - - pointsOnCorrect, pointsOnIncorrect int - - // this field will only be set in tests, and is used to synchronize with the - // the for-select loop in saveLoop. - saveLoopWaitCh chan struct{} -} - -// newScoreboard initializes a scoreboard using scores saved in the given File -// (which may be empty). The scoreboard will rewrite the save file with the -// latest scores everytime saveTicker is written to. -func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger, pointsOnCorrect, pointsOnIncorrect int) (*scoreboard, error) { - fileBody, err := ioutil.ReadAll(file) - if err != nil { - return nil, fmt.Errorf("reading saved scored: %w", err) - } - - scoresM := map[string]int{} - if len(fileBody) > 0 { - if err := json.Unmarshal(fileBody, &scoresM); err != nil { - return nil, fmt.Errorf("decoding saved scores: %w", err) - } - } - - scoreboard := &scoreboard{ - file: file, - scoresM: scoresM, - pointsOnCorrect: pointsOnCorrect, - pointsOnIncorrect: pointsOnIncorrect, - saveLoopWaitCh: make(chan struct{}), - } - - go scoreboard.saveLoop(saveTicker, logger) - - return scoreboard, nil -} - -func (s *scoreboard) guessedCorrect(name string) int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - s.scoresM[name] += s.pointsOnCorrect - return s.scoresM[name] -} - -func (s *scoreboard) guessedIncorrect(name string) int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - s.scoresM[name] += s.pointsOnIncorrect - return s.scoresM[name] -} - -func (s *scoreboard) scores() map[string]int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - scoresCp := map[string]int{} - for name, score := range s.scoresM { - scoresCp[name] = score - } - - return scoresCp -} - -func (s *scoreboard) save() error { - scores := s.scores() - if _, err := s.file.Seek(0, 0); err != nil { - return fmt.Errorf("seeking to start of save file: %w", err) - } else if err := s.file.Truncate(0); err != nil { - return fmt.Errorf("truncating save file: %w", err) - } else if err := json.NewEncoder(s.file).Encode(scores); err != nil { - return fmt.Errorf("encoding scores to save file: %w", err) - } - return nil -} - -func (s *scoreboard) saveLoop(ticker <-chan time.Time, logger Logger) { - for { - select { - case <-ticker: - if err := s.save(); err != nil { - logger.Printf("error saving scoreboard to file: %v", err) - } - case <-s.saveLoopWaitCh: - // test will unblock, nothing to do here. - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// The httpHandlers component - -// Scoreboard describes the scoreboard component from the point of view of the -// httpHandler component (which only needs a subset of scoreboard's methods). -type Scoreboard interface { - guessedCorrect(name string) int - guessedIncorrect(name string) int - scores() map[string]int -} - -// RandSrc describes a randomness component which can produce random integers. -type RandSrc interface { - Int() int -} - -// httpHandlers implements the http.HandlerFuncs used by the httpServer. -type httpHandlers struct { - scoreboard Scoreboard - randSrc RandSrc - logger Logger - - mux *http.ServeMux - n int - nLock sync.Mutex -} - -func newHTTPHandlers(scoreboard Scoreboard, randSrc RandSrc, logger Logger) *httpHandlers { - n := randSrc.Int() - logger.Printf("first n is %v", n) - - httpHandlers := &httpHandlers{ - scoreboard: scoreboard, - randSrc: randSrc, - logger: logger, - mux: http.NewServeMux(), - n: n, - } - - httpHandlers.mux.HandleFunc("/guess", httpHandlers.handleGuess) - httpHandlers.mux.HandleFunc("/scores", httpHandlers.handleScores) - - return httpHandlers -} - -func (h *httpHandlers) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - h.mux.ServeHTTP(rw, r) -} - -func (h *httpHandlers) handleGuess(rw http.ResponseWriter, r *http.Request) { - r.Header.Set("Content-Type", "text/plain") - - name := r.FormValue("name") - nStr := r.FormValue("n") - if name == "" || nStr == "" { - http.Error(rw, `"name" and "n" GET args are required`, http.StatusBadRequest) - return - } - - n, err := strconv.Atoi(nStr) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - h.nLock.Lock() - defer h.nLock.Unlock() - - if h.n == n { - newScore := h.scoreboard.guessedCorrect(name) - h.n = h.randSrc.Int() - h.logger.Printf("new n is %v", h.n) - rw.WriteHeader(http.StatusOK) - fmt.Fprintf(rw, "Correct! Your score is now %d\n", newScore) - return - } - - hint := "higher" - if h.n < n { - hint = "lower" - } - - newScore := h.scoreboard.guessedIncorrect(name) - rw.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(rw, "Try %s. Your score is now %d\n", hint, newScore) -} - -func (h *httpHandlers) handleScores(rw http.ResponseWriter, r *http.Request) { - r.Header.Set("Content-Type", "text/plain") - - h.nLock.Lock() - defer h.nLock.Unlock() - - type scoreTup struct { - name string - score int - } - - scores := h.scoreboard.scores() - scoresTups := make([]scoreTup, 0, len(scores)) - for name, score := range scores { - scoresTups = append(scoresTups, scoreTup{name, score}) - } - - sort.Slice(scoresTups, func(i, j int) bool { - return scoresTups[i].score > scoresTups[j].score - }) - - for _, scoresTup := range scoresTups { - fmt.Fprintf(rw, "%s: %d\n", scoresTup.name, scoresTup.score) - } -} - -//////////////////////////////////////////////////////////////////////////////// -// The httpServer component. - -type httpServer struct { - httpServer *http.Server - errCh chan error -} - -func newHTTPServer(listener net.Listener, httpHandlers *httpHandlers, logger Logger) *httpServer { - loggingHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - ip, _, _ := net.SplitHostPort(r.RemoteAddr) - logger.Printf("HTTP request -> %s %s %s", ip, r.Method, r.URL.String()) - httpHandlers.ServeHTTP(rw, r) - }) - - server := &httpServer{ - httpServer: &http.Server{ - Handler: loggingHandler, - }, - errCh: make(chan error, 1), - } - - go func() { - err := server.httpServer.Serve(listener) - if errors.Is(err, http.ErrServerClosed) { - err = nil - } - server.errCh <- err - }() - - return server -} - -//////////////////////////////////////////////////////////////////////////////// -// main - -func main() { - saveFilePath := flag.String("save-file", "./save.json", "File used to save scores") - listenAddr := flag.String("listen-addr", ":8888", "Address to listen for HTTP requests on") - saveInterval := flag.Duration("save-interval", 5*time.Second, "How often to resave scores") - pointsOnCorrect := flag.Int("points-on-correct", 1000, "Amount to change a user's score by upon a correct score") - pointsOnIncorrect := flag.Int("points-on-incorrect", -1, "Amount to change a user's score by upon an incorrect score") - flag.Parse() - - logger := log.New(os.Stdout, "", log.LstdFlags) - - logger.Printf("opening scoreboard save file %q", *saveFilePath) - file, err := os.OpenFile(*saveFilePath, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - logger.Fatalf("failed to open file %q: %v", *saveFilePath, err) - } - - saveTicker := time.NewTicker(*saveInterval) - randSrc := rand.New(rand.NewSource(time.Now().UnixNano())) - - logger.Printf("initializing scoreboard") - scoreboard, err := newScoreboard(file, saveTicker.C, logger, *pointsOnCorrect, *pointsOnIncorrect) - if err != nil { - logger.Fatalf("failed to initialize scoreboard: %v", err) - } - - logger.Printf("listening on %q", *listenAddr) - listener, err := net.Listen("tcp", *listenAddr) - if err != nil { - logger.Fatalf("failed to listen on %q: %v", *listenAddr, err) - } - - logger.Printf("setting up HTTP handlers") - httpHandlers := newHTTPHandlers(scoreboard, randSrc, logger) - - logger.Printf("serving HTTP requests") - newHTTPServer(listener, httpHandlers, logger) - - logger.Printf("initialization done") - select {} // block forever -} diff --git a/src/http/static/component-oriented-design/v3/main.go b/src/http/static/component-oriented-design/v3/main.go deleted file mode 100644 index afe8bab..0000000 --- a/src/http/static/component-oriented-design/v3/main.go +++ /dev/null @@ -1,390 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "log" - "math/rand" - "net" - "net/http" - "os" - "os/signal" - "sort" - "strconv" - "sync" - "time" -) - -// Logger describes a simple component used for printing log lines. -type Logger interface { - Printf(string, ...interface{}) -} - -//////////////////////////////////////////////////////////////////////////////// -// The scoreboard component - -// File wraps the standard os.File type. -type File interface { - io.ReadWriter - Truncate(int64) error - Seek(int64, int) (int64, error) -} - -// scoreboard loads player scores from a save file, tracks score updates, and -// periodically saves those scores back to the save file. -type scoreboard struct { - file File - scoresM map[string]int - scoresLock sync.Mutex - - pointsOnCorrect, pointsOnIncorrect int - - // The cleanup method closes cleanupCh to signal to all scoreboard's running - // go-routines to clean themselves up, and cleanupWG is then used to wait - // for those goroutines to do so. - cleanupCh chan struct{} - cleanupWG sync.WaitGroup - - // this field will only be set in tests, and is used to synchronize with the - // the for-select loop in saveLoop. - saveLoopWaitCh chan struct{} -} - -// newScoreboard initializes a scoreboard using scores saved in the given File -// (which may be empty). The scoreboard will rewrite the save file with the -// latest scores everytime saveTicker is written to. -func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger, pointsOnCorrect, pointsOnIncorrect int) (*scoreboard, error) { - fileBody, err := ioutil.ReadAll(file) - if err != nil { - return nil, fmt.Errorf("reading saved scored: %w", err) - } - - scoresM := map[string]int{} - if len(fileBody) > 0 { - if err := json.Unmarshal(fileBody, &scoresM); err != nil { - return nil, fmt.Errorf("decoding saved scores: %w", err) - } - } - - scoreboard := &scoreboard{ - file: file, - scoresM: scoresM, - pointsOnCorrect: pointsOnCorrect, - pointsOnIncorrect: pointsOnIncorrect, - cleanupCh: make(chan struct{}), - saveLoopWaitCh: make(chan struct{}), - } - - scoreboard.cleanupWG.Add(1) - go func() { - scoreboard.saveLoop(saveTicker, logger) - scoreboard.cleanupWG.Done() - }() - - return scoreboard, nil -} - -func (s *scoreboard) cleanup() error { - close(s.cleanupCh) - s.cleanupWG.Wait() - - if err := s.save(); err != nil { - return fmt.Errorf("saving scores during cleanup: %w", err) - } - return nil -} - -func (s *scoreboard) guessedCorrect(name string) int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - s.scoresM[name] += s.pointsOnCorrect - return s.scoresM[name] -} - -func (s *scoreboard) guessedIncorrect(name string) int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - s.scoresM[name] += s.pointsOnIncorrect - return s.scoresM[name] -} - -func (s *scoreboard) scores() map[string]int { - s.scoresLock.Lock() - defer s.scoresLock.Unlock() - - scoresCp := map[string]int{} - for name, score := range s.scoresM { - scoresCp[name] = score - } - - return scoresCp -} - -func (s *scoreboard) save() error { - scores := s.scores() - if _, err := s.file.Seek(0, 0); err != nil { - return fmt.Errorf("seeking to start of save file: %w", err) - } else if err := s.file.Truncate(0); err != nil { - return fmt.Errorf("truncating save file: %w", err) - } else if err := json.NewEncoder(s.file).Encode(scores); err != nil { - return fmt.Errorf("encoding scores to save file: %w", err) - } - return nil -} - -func (s *scoreboard) saveLoop(ticker <-chan time.Time, logger Logger) { - for { - select { - case <-ticker: - if err := s.save(); err != nil { - logger.Printf("error saving scoreboard to file: %v", err) - } - case <-s.cleanupCh: - return - case <-s.saveLoopWaitCh: - // test will unblock, nothing to do here. - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// The httpHandlers component - -// Scoreboard describes the scoreboard component from the point of view of the -// httpHandler component (which only needs a subset of scoreboard's methods). -type Scoreboard interface { - guessedCorrect(name string) int - guessedIncorrect(name string) int - scores() map[string]int -} - -// RandSrc describes a randomness component which can produce random integers. -type RandSrc interface { - Int() int -} - -// httpHandlers implements the http.HandlerFuncs used by the httpServer. -type httpHandlers struct { - scoreboard Scoreboard - randSrc RandSrc - logger Logger - - mux *http.ServeMux - n int - nLock sync.Mutex -} - -func newHTTPHandlers(scoreboard Scoreboard, randSrc RandSrc, logger Logger) *httpHandlers { - n := randSrc.Int() - logger.Printf("first n is %v", n) - - httpHandlers := &httpHandlers{ - scoreboard: scoreboard, - randSrc: randSrc, - logger: logger, - mux: http.NewServeMux(), - n: n, - } - - httpHandlers.mux.HandleFunc("/guess", httpHandlers.handleGuess) - httpHandlers.mux.HandleFunc("/scores", httpHandlers.handleScores) - - return httpHandlers -} - -func (h *httpHandlers) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - h.mux.ServeHTTP(rw, r) -} - -func (h *httpHandlers) handleGuess(rw http.ResponseWriter, r *http.Request) { - r.Header.Set("Content-Type", "text/plain") - - name := r.FormValue("name") - nStr := r.FormValue("n") - if name == "" || nStr == "" { - http.Error(rw, `"name" and "n" GET args are required`, http.StatusBadRequest) - return - } - - n, err := strconv.Atoi(nStr) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - h.nLock.Lock() - defer h.nLock.Unlock() - - if h.n == n { - newScore := h.scoreboard.guessedCorrect(name) - h.n = h.randSrc.Int() - h.logger.Printf("new n is %v", h.n) - rw.WriteHeader(http.StatusOK) - fmt.Fprintf(rw, "Correct! Your score is now %d\n", newScore) - return - } - - hint := "higher" - if h.n < n { - hint = "lower" - } - - newScore := h.scoreboard.guessedIncorrect(name) - rw.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(rw, "Try %s. Your score is now %d\n", hint, newScore) -} - -func (h *httpHandlers) handleScores(rw http.ResponseWriter, r *http.Request) { - r.Header.Set("Content-Type", "text/plain") - - h.nLock.Lock() - defer h.nLock.Unlock() - - type scoreTup struct { - name string - score int - } - - scores := h.scoreboard.scores() - scoresTups := make([]scoreTup, 0, len(scores)) - for name, score := range scores { - scoresTups = append(scoresTups, scoreTup{name, score}) - } - - sort.Slice(scoresTups, func(i, j int) bool { - return scoresTups[i].score > scoresTups[j].score - }) - - for _, scoresTup := range scoresTups { - fmt.Fprintf(rw, "%s: %d\n", scoresTup.name, scoresTup.score) - } -} - -//////////////////////////////////////////////////////////////////////////////// -// The httpServer component. - -type httpServer struct { - httpServer *http.Server - errCh chan error -} - -func newHTTPServer(listener net.Listener, httpHandlers *httpHandlers, logger Logger) *httpServer { - loggingHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - ip, _, _ := net.SplitHostPort(r.RemoteAddr) - logger.Printf("HTTP request -> %s %s %s", ip, r.Method, r.URL.String()) - httpHandlers.ServeHTTP(rw, r) - }) - - server := &httpServer{ - httpServer: &http.Server{ - Handler: loggingHandler, - }, - errCh: make(chan error, 1), - } - - go func() { - err := server.httpServer.Serve(listener) - if errors.Is(err, http.ErrServerClosed) { - err = nil - } - server.errCh <- err - }() - - return server -} - -func (s *httpServer) cleanup() error { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - if err := s.httpServer.Shutdown(ctx); err != nil { - return fmt.Errorf("shutting down http server: %w", err) - } - return <-s.errCh -} - -//////////////////////////////////////////////////////////////////////////////// -// main - -func main() { - saveFilePath := flag.String("save-file", "./save.json", "File used to save scores") - listenAddr := flag.String("listen-addr", ":8888", "Address to listen for HTTP requests on") - saveInterval := flag.Duration("save-interval", 5*time.Second, "How often to resave scores") - pointsOnCorrect := flag.Int("points-on-correct", 1000, "Amount to change a user's score by upon a correct score") - pointsOnIncorrect := flag.Int("points-on-incorrect", -1, "Amount to change a user's score by upon an incorrect score") - flag.Parse() - - logger := log.New(os.Stdout, "", log.LstdFlags) - - logger.Printf("opening scoreboard save file %q", *saveFilePath) - file, err := os.OpenFile(*saveFilePath, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - logger.Fatalf("failed to open file %q: %v", *saveFilePath, err) - } - - saveTicker := time.NewTicker(*saveInterval) - randSrc := rand.New(rand.NewSource(time.Now().UnixNano())) - - logger.Printf("initializing scoreboard") - scoreboard, err := newScoreboard(file, saveTicker.C, logger, *pointsOnCorrect, *pointsOnIncorrect) - if err != nil { - logger.Fatalf("failed to initialize scoreboard: %v", err) - } - - logger.Printf("listening on %q", *listenAddr) - listener, err := net.Listen("tcp", *listenAddr) - if err != nil { - logger.Fatalf("failed to listen on %q: %v", *listenAddr, err) - } - - logger.Printf("setting up HTTP handlers") - httpHandlers := newHTTPHandlers(scoreboard, randSrc, logger) - - logger.Printf("serving HTTP requests") - httpServer := newHTTPServer(listener, httpHandlers, logger) - - logger.Printf("initialization done, waiting for interrupt signal") - sigCh := make(chan os.Signal) - signal.Notify(sigCh, os.Interrupt) - <-sigCh - logger.Printf("interrupt signal received, cleaning up") - go func() { - <-sigCh - log.Fatalf("interrupt signal received again, forcing shutdown") - }() - - if err := httpServer.cleanup(); err != nil { - logger.Fatalf("cleaning up http server: %v", err) - } - - // NOTE go's builtin http server does not follow component property 5a, and - // instead closes the net.Listener given to it as a parameter when Shutdown - // is called. Because of that inconsistency this Close would error if it - // were called. - // - // While there are ways to work around this, it's instead highlighted in - // this example as an instance of a language making the component-oriented - // pattern more difficult. - // - //if err := listener.Close(); err != nil { - // logger.Fatalf("closing listener %q: %v", listenAddr, err) - //} - - if err := scoreboard.cleanup(); err != nil { - logger.Fatalf("cleaning up scoreboard: %v", err) - } - - saveTicker.Stop() - - if err := file.Close(); err != nil { - logger.Fatalf("closing file %q: %v", *saveFilePath, err) - } - - os.Stdout.Sync() -} diff --git a/src/http/static/markov/Makefile b/src/http/static/markov/Makefile deleted file mode 100644 index 4195baf..0000000 --- a/src/http/static/markov/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -bundle: - nix-build markov.nix -A nixBundle - ./result/bin/nix-bundle '((import ./markov.nix) {}).entrypoint' '/bin/markov' diff --git a/src/http/static/markov/markov.nix b/src/http/static/markov/markov.nix deleted file mode 100644 index 042ab8d..0000000 --- a/src/http/static/markov/markov.nix +++ /dev/null @@ -1,63 +0,0 @@ -{ - pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/d50923ab2d308a1ddb21594ba6ae064cab65d8ae.tar.gz") {} -}: - -rec { - - markov = pkgs.buildGoModule { - pname = "markov"; - version = "618b666484566de71f2d59114d011ff4621cf375"; - src = pkgs.fetchFromGitHub { - owner = "mediocregopher"; - repo = "markov"; - rev = "618b666484566de71f2d59114d011ff4621cf375"; - sha256 = "1sx9dr1q3vr3q8nyx3965x6259iyl85591vx815g1xacygv4i4fg"; - }; - vendorSha256 = "048wygrmv26fsnypsp6vxf89z3j0gs9f1w4i63khx7h134yxhbc6"; - }; - - entrypoint = pkgs.writeScriptBin "markov" '' - #!${pkgs.stdenv.shell} - - # On every run we create new, temporary, configuration files for redis and - # circus. To do this we create a new config directory. - markovCfgDir=$(${pkgs.coreutils}/bin/mktemp -d) - echo "generating configuration to $markovCfgDir" - - ${pkgs.coreutils}/bin/cat >$markovCfgDir/redis.conf <$markovCfgDir/circus.ini < { - console.log("CW websocket connected"); - if (this.onconnect) this.onconnect(); - } - - let decoder = new TextDecoder(); - this.conn.onmessage = (msg) => { - let d = JSON.parse(decoder.decode(msg.data)); - - // The server will always send an AUTHENTICATED signal when you establish a valid connection - // At this point you can subscribe to resources - if (d.authenticationResult && d.authenticationResult.status === 'AUTHENTICATED') { - if (this.onauth) this.onauth(); - this.conn.send(JSON.stringify({ - subscribe: { - subscriptions: [ - {streamSubscription: {resource: resource}}, - ], - } - })); - return; - } - - // Market data comes in a marketUpdate - // In this case, we're expecting trades so we look for marketUpdate.tradesUpdate - if (!d.marketUpdate || !d.marketUpdate.tradesUpdate) { - return; - } - - let trades = d.marketUpdate.tradesUpdate.trades; - for (let i in trades) { - trades[i].price = parseFloat(trades[i].priceStr); - trades[i].volume = parseFloat(trades[i].amountStr); - } - if (this.ontrades) this.ontrades(trades); - } - - this.close = () => this.conn.close(); -} diff --git a/src/http/static/trading-in-the-rain/Distributor.js b/src/http/static/trading-in-the-rain/Distributor.js deleted file mode 100644 index fa6e9f2..0000000 --- a/src/http/static/trading-in-the-rain/Distributor.js +++ /dev/null @@ -1,42 +0,0 @@ -function distribute(val, minOld, maxOld, minNew, maxNew) { - let scalar = (val - minOld) / (maxOld - minOld); - return minNew + ((maxNew - minNew) * scalar); -} - -function Distributor(capacity) { - this.cap = capacity; - - this.reset = () => { - this.arr = []; - this.arrSorted = []; - this.length = 0; - }; - this.reset(); - - // add adds the given value into the series, shifting off the oldest value if - // the series is at capacity. - this.add = (val) => { - this.arr.push(val); - if (this.arr.length >= this.cap) this.arr.shift(); - this.arrSorted = this.arr.slice(); // copy array - this.arrSorted.sort(); - this.length = this.arr.length; - }; - - // distribute finds where the given value falls within the series, and then - // scales that into the given range (inclusive). - this.distribute = (val, min, max) => { - if (this.length == 0) throw "cannot locate within empty Distributor"; - - let idx = this.length; - for (i in this.arrSorted) { - if (val < this.arrSorted[i]) { - idx = i; - break; - } - } - - return distribute(idx, 0, this.length, min, max); - }; -} - diff --git a/src/http/static/trading-in-the-rain/MIDI.js/LICENSE.txt b/src/http/static/trading-in-the-rain/MIDI.js/LICENSE.txt deleted file mode 100644 index 0dca6fb..0000000 --- a/src/http/static/trading-in-the-rain/MIDI.js/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2010-2013 MIDI.js Authors. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/Base64.js b/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/Base64.js deleted file mode 100644 index b5a59ce..0000000 --- a/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/Base64.js +++ /dev/null @@ -1,61 +0,0 @@ -//https://github.com/davidchambers/Base64.js - -;(function () { - var object = typeof exports != 'undefined' ? exports : this; // #8: web workers - var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; - - function InvalidCharacterError(message) { - this.message = message; - } - InvalidCharacterError.prototype = new Error; - InvalidCharacterError.prototype.name = 'InvalidCharacterError'; - - // encoder - // [https://gist.github.com/999166] by [https://github.com/nignag] - object.btoa || ( - object.btoa = function (input) { - for ( - // initialize result and counter - var block, charCode, idx = 0, map = chars, output = ''; - // if the next input index does not exist: - // change the mapping table to "=" - // check if d has no fractional digits - input.charAt(idx | 0) || (map = '=', idx % 1); - // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 - output += map.charAt(63 & block >> 8 - idx % 1 * 8) - ) { - charCode = input.charCodeAt(idx += 3/4); - if (charCode > 0xFF) { - throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."); - } - block = block << 8 | charCode; - } - return output; - }); - - // decoder - // [https://gist.github.com/1020396] by [https://github.com/atk] - object.atob || ( - object.atob = function (input) { - input = input.replace(/=+$/, '') - if (input.length % 4 == 1) { - throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); - } - for ( - // initialize result and counters - var bc = 0, bs, buffer, idx = 0, output = ''; - // get next character - buffer = input.charAt(idx++); - // character found in table? initialize bit storage and add its ascii value; - ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, - // and if not first of each 4 characters, - // convert the first 8 bits to one ascii character - bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 - ) { - // try to find character in table (0-63, not found => -1) - buffer = chars.indexOf(buffer); - } - return output; - }); - -}()); \ No newline at end of file diff --git a/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/Base64binary.js b/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/Base64binary.js deleted file mode 100644 index 2c59f8f..0000000 --- a/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/Base64binary.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @license ------------------------------------------------------------------- - * module: Base64Binary - * src: http://blog.danguer.com/2011/10/24/base64-binary-decoding-in-javascript/ - * license: Simplified BSD License - * ------------------------------------------------------------------- - * Copyright 2011, Daniel Guerrero. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL DANIEL GUERRERO BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -var Base64Binary = { - _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", - - /* will return a Uint8Array type */ - decodeArrayBuffer: function(input) { - var bytes = Math.ceil( (3*input.length) / 4.0); - var ab = new ArrayBuffer(bytes); - this.decode(input, ab); - - return ab; - }, - - decode: function(input, arrayBuffer) { - //get last chars to see if are valid - var lkey1 = this._keyStr.indexOf(input.charAt(input.length-1)); - var lkey2 = this._keyStr.indexOf(input.charAt(input.length-1)); - - var bytes = Math.ceil( (3*input.length) / 4.0); - if (lkey1 == 64) bytes--; //padding chars, so skip - if (lkey2 == 64) bytes--; //padding chars, so skip - - var uarray; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - var j = 0; - - if (arrayBuffer) - uarray = new Uint8Array(arrayBuffer); - else - uarray = new Uint8Array(bytes); - - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - - for (i=0; i> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - uarray[i] = chr1; - if (enc3 != 64) uarray[i+1] = chr2; - if (enc4 != 64) uarray[i+2] = chr3; - } - - return uarray; - } -}; \ No newline at end of file diff --git a/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/WebAudioAPI.js b/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/WebAudioAPI.js deleted file mode 100644 index 17e9eb9..0000000 --- a/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/WebAudioAPI.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * @license ------------------------------------------------------------------- - * module: WebAudioShim - Fix naming issues for WebAudioAPI supports - * src: https://github.com/Dinahmoe/webaudioshim - * author: Dinahmoe AB - * ------------------------------------------------------------------- - * Copyright (c) 2012 DinahMoe AB - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -window.AudioContext = window.AudioContext || window.webkitAudioContext || null; -window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext || null; - -(function (Context) { - var isFunction = function (f) { - return Object.prototype.toString.call(f) === "[object Function]" || - Object.prototype.toString.call(f) === "[object AudioContextConstructor]"; - }; - var contextMethods = [ - ["createGainNode", "createGain"], - ["createDelayNode", "createDelay"], - ["createJavaScriptNode", "createScriptProcessor"] - ]; - /// - var proto; - var instance; - var sourceProto; - /// - if (!isFunction(Context)) { - return; - } - instance = new Context(); - if (!instance.destination || !instance.sampleRate) { - return; - } - proto = Context.prototype; - sourceProto = Object.getPrototypeOf(instance.createBufferSource()); - - if (!isFunction(sourceProto.start)) { - if (isFunction(sourceProto.noteOn)) { - sourceProto.start = function (when, offset, duration) { - switch (arguments.length) { - case 0: - throw new Error("Not enough arguments."); - case 1: - this.noteOn(when); - break; - case 2: - if (this.buffer) { - this.noteGrainOn(when, offset, this.buffer.duration - offset); - } else { - throw new Error("Missing AudioBuffer"); - } - break; - case 3: - this.noteGrainOn(when, offset, duration); - } - }; - } - } - - if (!isFunction(sourceProto.noteOn)) { - sourceProto.noteOn = sourceProto.start; - } - - if (!isFunction(sourceProto.noteGrainOn)) { - sourceProto.noteGrainOn = sourceProto.start; - } - - if (!isFunction(sourceProto.stop)) { - sourceProto.stop = sourceProto.noteOff; - } - - if (!isFunction(sourceProto.noteOff)) { - sourceProto.noteOff = sourceProto.stop; - } - - contextMethods.forEach(function (names) { - var name1; - var name2; - while (names.length) { - name1 = names.pop(); - if (isFunction(this[name1])) { - this[names.pop()] = this[name1]; - } else { - name2 = names.pop(); - this[name1] = this[name2]; - } - } - }, proto); -})(window.AudioContext); \ No newline at end of file diff --git a/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/WebMIDIAPI.js b/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/WebMIDIAPI.js deleted file mode 100644 index 000a916..0000000 --- a/src/http/static/trading-in-the-rain/MIDI.js/inc/shim/WebMIDIAPI.js +++ /dev/null @@ -1,421 +0,0 @@ -/* Copyright 2013 Chris Wilson - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -// Initialize the MIDI library. -(function (global) { - 'use strict'; - var midiIO, _requestMIDIAccess, MIDIAccess, _onReady, MIDIPort, MIDIInput, MIDIOutput, _midiProc; - - function Promise() { - - } - - Promise.prototype.then = function(accept, reject) { - this.accept = accept; - this.reject = reject; - } - - Promise.prototype.succeed = function(access) { - if (this.accept) - this.accept(access); - } - - Promise.prototype.fail = function(error) { - if (this.reject) - this.reject(error); - } - - function _JazzInstance() { - this.inputInUse = false; - this.outputInUse = false; - - // load the Jazz plugin - var o1 = document.createElement("object"); - o1.id = "_Jazz" + Math.random() + "ie"; - o1.classid = "CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90"; - - this.activeX = o1; - - var o2 = document.createElement("object"); - o2.id = "_Jazz" + Math.random(); - o2.type="audio/x-jazz"; - o1.appendChild(o2); - - this.objRef = o2; - - var e = document.createElement("p"); - e.appendChild(document.createTextNode("This page requires the ")); - - var a = document.createElement("a"); - a.appendChild(document.createTextNode("Jazz plugin")); - a.href = "http://jazz-soft.net/"; - - e.appendChild(a); - e.appendChild(document.createTextNode(".")); - o2.appendChild(e); - - var insertionPoint = document.getElementById("MIDIPlugin"); - if (!insertionPoint) { - // Create hidden element - var insertionPoint = document.createElement("div"); - insertionPoint.id = "MIDIPlugin"; - insertionPoint.style.position = "absolute"; - insertionPoint.style.visibility = "hidden"; - insertionPoint.style.left = "-9999px"; - insertionPoint.style.top = "-9999px"; - document.body.appendChild(insertionPoint); - } - insertionPoint.appendChild(o1); - - if (this.objRef.isJazz) - this._Jazz = this.objRef; - else if (this.activeX.isJazz) - this._Jazz = this.activeX; - else - this._Jazz = null; - if (this._Jazz) { - this._Jazz._jazzTimeZero = this._Jazz.Time(); - this._Jazz._perfTimeZero = window.performance.now(); - } - } - - _requestMIDIAccess = function _requestMIDIAccess() { - var access = new MIDIAccess(); - return access._promise; - }; - - // API Methods - - MIDIAccess = function() { - this._jazzInstances = new Array(); - this._jazzInstances.push( new _JazzInstance() ); - this._promise = new Promise; - - if (this._jazzInstances[0]._Jazz) { - this._Jazz = this._jazzInstances[0]._Jazz; - window.setTimeout( _onReady.bind(this), 3 ); - } else { - window.setTimeout( _onNotReady.bind(this), 3 ); - } - }; - - _onReady = function _onReady() { - if (this._promise) - this._promise.succeed(this); - }; - - function _onNotReady() { - if (this._promise) - this._promise.fail( { code: 1 } ); - }; - - MIDIAccess.prototype.inputs = function( ) { - if (!this._Jazz) - return null; - var list=this._Jazz.MidiInList(); - var inputs = new Array( list.length ); - - for ( var i=0; i1)) { - var sendObj = new Object(); - sendObj.jazz = this._jazzInstance; - sendObj.data = data; - - window.setTimeout( _sendLater.bind(sendObj), delayBeforeSend ); - } else { - this._jazzInstance.MidiOutLong( data ); - } - return true; - }; - - //init: create plugin - if (!window.navigator.requestMIDIAccess) - window.navigator.requestMIDIAccess = _requestMIDIAccess; - -}(window)); - -// Polyfill window.performance.now() if necessary. -(function (exports) { - var perf = {}, props; - - function findAlt() { - var prefix = ['moz', 'webkit', 'o', 'ms'], - i = prefix.length, - //worst case, we use Date.now() - props = { - value: (function (start) { - return function () { - return Date.now() - start; - }; - }(Date.now())) - }; - - //seach for vendor prefixed version - for (; i >= 0; i--) { - if ((prefix[i] + "Now") in exports.performance) { - props.value = function (method) { - return function () { - exports.performance[method](); - } - }(prefix[i] + "Now"); - return props; - } - } - - //otherwise, try to use connectionStart - if ("timing" in exports.performance && "connectStart" in exports.performance.timing) { - //this pretty much approximates performance.now() to the millisecond - props.value = (function (start) { - return function() { - Date.now() - start; - }; - }(exports.performance.timing.connectStart)); - } - return props; - } - - //if already defined, bail - if (("performance" in exports) && ("now" in exports.performance)) - return; - if (!("performance" in exports)) - Object.defineProperty(exports, "performance", { - get: function () { - return perf; - }}); - //otherwise, performance is there, but not "now()" - - props = findAlt(); - Object.defineProperty(exports.performance, "now", props); -}(window)); diff --git a/src/http/static/trading-in-the-rain/MIDI.js/js/midi/audioDetect.js b/src/http/static/trading-in-the-rain/MIDI.js/js/midi/audioDetect.js deleted file mode 100644 index 957605d..0000000 --- a/src/http/static/trading-in-the-rain/MIDI.js/js/midi/audioDetect.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - ---------------------------------------------------------- - MIDI.audioDetect : 0.3.2 : 2015-03-26 - ---------------------------------------------------------- - https://github.com/mudcube/MIDI.js - ---------------------------------------------------------- - Probably, Maybe, No... Absolutely! - Test to see what types of