From eb846695264d871edffb0e09b6d57a1a85793f48 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Sun, 21 May 2023 16:08:23 +0200 Subject: implement incremental search results TODO: ensure late search results do not show up for future searches --- TODO | 6 ++++-- main.go | 49 ++++++++++++++++++++++++++++++------------------- web/client.html | 25 ++++++++++++++++--------- wrms.go | 32 +++++++++++++++++++++++++------- 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/TODO b/TODO index ed29307..1568877 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,6 @@ ## Fronted -- [ ] support incremental search results +- [X] support incremental search results + - [ ] distinguish different search queries ## Web Backend @@ -8,7 +9,8 @@ ## Backends - [X] paralyze search - - [ ] report results incrementally + - [X] report results incrementally + - [ ] distinguish different search queries - [X] Implement local storage backend - [X] Implement upload - [ ] Remove uploaded songs after were played diff --git a/main.go b/main.go index 94e11b9..69808bf 100644 --- a/main.go +++ b/main.go @@ -32,38 +32,49 @@ func landingPage(w http.ResponseWriter, r *http.Request) { } } +func getConnId(w http.ResponseWriter, r *http.Request) string { + uuidCookie, err := r.Cookie("UUID") + if err != nil { + http.Error(w, "No connection ID cookie set", http.StatusUnauthorized) + return "" + } + return uuidCookie.Value +} + func searchHandler(w http.ResponseWriter, r *http.Request) { + connId := getConnId(w, r) + if connId == "" { + return + } + + conn := wrms.GetConn(connId) + pattern := r.URL.Query().Get("pattern") llog.Debug("Searching for %s", pattern) start := time.Now() resultsChan := wrms.Player.Search(pattern) - results := []Song{} - for result := range resultsChan { - results = append(results, result...) - } - - llog.Debug("searching for %s took %v", pattern, time.Since(start)) + go func() { + for result := range resultsChan { + if len(result) > 0 { + conn.Send(Event{"search", result}) + } + } - data, err := json.Marshal(Event{"search", results}) - if err != nil { - llog.Error("Encoding the search result failed with %s", err) - return - } + conn.Send(newNotification("finish-search")) + llog.Debug("searching for %s took %v", pattern, time.Since(start)) + }() - llog.Info("Search returned: %s", string(data)) - w.Write(data) + fmt.Fprintf(w, "Starting search for %s", pattern) } func genericVoteHandler(w http.ResponseWriter, r *http.Request, vote string) { - uuidCookie, err := r.Cookie("UUID") - if err != nil { - http.Error(w, "No connection ID cookie set", http.StatusUnauthorized) + connId := getConnId(w, r) + if connId == "" { return } - connId := uuidCookie.Value songUri := r.URL.Query().Get("song") llog.Info("%s song %s via url %s", vote, songUri, r.URL) @@ -131,8 +142,8 @@ func wsEndpoint(w http.ResponseWriter, r *http.Request) { cancel() }() - conn := Connection{id, make(chan Event), c} - wrms.Connections = append(wrms.Connections, conn) + conn := &Connection{id, make(chan Event), c} + wrms.Connections[id] = conn llog.Info("New websocket connection with id %d", id) diff --git a/web/client.html b/web/client.html index f0ec6ed..878bd48 100644 --- a/web/client.html +++ b/web/client.html @@ -224,6 +224,12 @@ case "downvoted": handleVotes("down", cmd.songs) break; + case "search": + handleSearch(cmd.songs) + break; + case "finish-search": + handleFinishSearch() + break; } }; @@ -232,6 +238,10 @@ }; function submitSearch() { + // Clear search Results + let searchResults = document.getElementById("searchResults"); + searchResults.innerHTML = ""; + let searchingIndicator = document.getElementById("searching"); searchingIndicator.style.display = "block"; let searchResultsOverlay = document.getElementById("searchResultsOverlay"); @@ -239,7 +249,7 @@ const form = new FormData(document.getElementById("searchForm")); const pattern = new URLSearchParams(form).toString(); - new HttpClient().get("/search?" + pattern, showSearchResults); + new HttpClient().get("/search?" + pattern, console.log); return false; } @@ -259,20 +269,17 @@ new HttpClient().post("/add", JSON.stringify(song), console.log); } - function showSearchResults(responseText) { - response = JSON.parse(responseText) - if (!response) - return - + function handleFinishSearch() { let searchingIndicator = document.getElementById("searching"); searchingIndicator.style.display = "none"; let searchResultsOverlay = document.getElementById("searchResultsOverlay"); searchResultsOverlay['aria-busy'] = false; + } + function handleSearch(songs) { + let searchResultsOverlay = document.getElementById("searchResultsOverlay"); let searchResults = document.getElementById("searchResults"); - // Clear search Results - searchResults.innerHTML = ""; - for (const song of response.songs) { + for (const song of songs) { let listItem = document.createElement("li"); listItem.addEventListener("click", function() { addSong(song); diff --git a/wrms.go b/wrms.go index 9cd0ac4..22cd62a 100644 --- a/wrms.go +++ b/wrms.go @@ -44,12 +44,20 @@ type Event struct { Songs []Song `json:"songs"` } +func newNotification(notification string) Event { + return Event{notification, nil} +} + type Connection struct { Id uuid.UUID Events chan Event C *websocket.Conn } +func (c *Connection) Send(ev Event) { + c.Events <- ev +} + func (c *Connection) Close() { llog.Info("Closing connection %s", c.Id) close(c.Events) @@ -57,7 +65,7 @@ func (c *Connection) Close() { } type Wrms struct { - Connections []Connection + Connections map[uuid.UUID]*Connection Songs []Song queue Playlist CurrentSong Song @@ -69,10 +77,20 @@ type Wrms struct { func NewWrms(config Config) *Wrms { wrms := Wrms{} wrms.Config = config + wrms.Connections = make(map[uuid.UUID]*Connection) wrms.Player = NewPlayer(&wrms, strings.Split(config.Backends, " ")) return &wrms } +func (wrms *Wrms) GetConn(connId string) *Connection { + id, err := uuid.Parse(connId) + if err != nil { + llog.Fatal("Failed to parse connId %s: %v", connId, err) + } + + return wrms.Connections[id] +} + func (wrms *Wrms) Close() { llog.Info("Closing WRMS") for _, conn := range wrms.Connections { @@ -80,10 +98,10 @@ func (wrms *Wrms) Close() { } } -func (wrms *Wrms) Broadcast(cmd Event) { - llog.Info("Broadcasting %v", cmd) - for i := 0; i < len(wrms.Connections); i++ { - wrms.Connections[i].Events <- cmd +func (wrms *Wrms) Broadcast(ev Event) { + llog.Info("Broadcasting %v", ev) + for _, conn := range wrms.Connections { + conn.Send(ev) } } @@ -122,14 +140,14 @@ func (wrms *Wrms) PlayPause() { wrms.Next() if wrms.CurrentSong.Uri == "" { llog.Info("No song left to play") - wrms.Broadcast(Event{"stop", []Song{}}) + wrms.Broadcast(newNotification("stop")) return } } ev = Event{"play", []Song{wrms.CurrentSong}} wrms.Player.Play(&wrms.CurrentSong) } else { - ev = Event{"pause", []Song{}} + ev = newNotification("pause") wrms.Player.Pause() } -- cgit v1.2.3