Skip to content

Commit e1d29d1

Browse files
committed
feat: allow enqueuing whole folders
1 parent bbcab36 commit e1d29d1

File tree

7 files changed

+133
-45
lines changed

7 files changed

+133
-45
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package helpers
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"go-api/infrastructure/reqRes"
7+
"io/fs"
8+
"net/http"
9+
"os"
10+
)
11+
12+
func ReadFolder(w reqRes.MyResponseWriter, fileSystemFolder string) (bool, []os.DirEntry) {
13+
dirAsFile, err := os.Open(fileSystemFolder)
14+
if err != nil {
15+
if errors.Is(err, os.ErrNotExist) {
16+
message := fmt.Sprintf("%s does not exist", fileSystemFolder)
17+
w.Error(message, http.StatusBadRequest)
18+
return false, nil
19+
}
20+
21+
message := fmt.Sprintf("Failure to open folder '%s': \n%v", fileSystemFolder, err)
22+
w.Error(message, http.StatusInternalServerError)
23+
return false, nil
24+
}
25+
26+
dirEntries, err := dirAsFile.ReadDir(-1)
27+
if err != nil {
28+
// here I would check if this error is "NotADir" or something, but there seems to
29+
// not be a consistent / sane way to do this?
30+
// https://github.com/golang/go/issues/46734
31+
32+
if errors.Is(err, fs.ErrNotExist) {
33+
message := fmt.Sprintf("Failure to read folder '%s': \n%v", fileSystemFolder, err)
34+
w.Error(message, http.StatusInternalServerError)
35+
return false, nil
36+
}
37+
38+
message := fmt.Sprintf("Failure to read folder '%s': \n%v", fileSystemFolder, err)
39+
w.Error(message, http.StatusInternalServerError)
40+
return false, nil
41+
}
42+
43+
err = dirAsFile.Close()
44+
if err != nil {
45+
message := fmt.Sprintf("Failure to close folder '%s': \n%v", fileSystemFolder, err)
46+
w.Error(message, http.StatusInternalServerError)
47+
return false, nil
48+
}
49+
50+
return true, dirEntries
51+
}

infrastructure/setup/app.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ func (app *App) MapRoutes() {
9595

9696
var playerController = player.NewController(app.Config, app.Db)
9797
app.handleFunc("GET /player", authRequired(playerController.GetPlayer))
98-
app.handleFunc("POST /addSong/{path...}", authRequired(playerController.AddSong))
98+
app.handleFunc("POST /enqueueSong/{path...}", authRequired(playerController.EnqueueSong))
99+
app.handleFunc("POST /enqueueFolder/{path...}", authRequired(playerController.EnqueueFolder))
99100
app.handleFunc("DELETE /removeSong/{id}", authRequired(playerController.RemoveSong))
100101
app.handleFunc("POST /reportSongDuration/{queuedSongId}", authRequired(playerController.ReportSongDuration))
101102

pages/fileExplorer/ExploreAt.go

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package fileExplorer
22

33
import (
4-
"errors"
54
"fmt"
5+
"go-api/infrastructure/helpers"
66
"go-api/infrastructure/myLog"
77
"go-api/infrastructure/reqRes"
88
"html/template"
9-
"io/fs"
109
"mime"
11-
"net/http"
1210
"os"
1311
"path"
1412
"path/filepath"
@@ -41,40 +39,8 @@ func (controller *Controller) ExploreAt(w reqRes.MyResponseWriter, r *reqRes.MyR
4139
}
4240

4341
func renderExplorer(w reqRes.MyResponseWriter, fileSystemFolder string, queryFolder string, resultMessage string) {
44-
dirAsFile, err := os.Open(fileSystemFolder)
45-
if err != nil {
46-
if errors.Is(err, os.ErrNotExist) {
47-
message := fmt.Sprintf("%s does not exist", fileSystemFolder)
48-
w.Error(message, http.StatusBadRequest)
49-
return
50-
}
51-
52-
message := fmt.Sprintf("Failure to open folder '%s': \n%v", fileSystemFolder, err)
53-
w.Error(message, http.StatusInternalServerError)
54-
return
55-
}
56-
57-
dirEntries, err := dirAsFile.ReadDir(-1)
58-
if err != nil {
59-
// here I would check if this error is "NotADir" or something, but there seems to
60-
// not be a consistent / sane way to do this?
61-
// https://github.com/golang/go/issues/46734
62-
63-
if errors.Is(err, fs.ErrNotExist) {
64-
message := fmt.Sprintf("Failure to read folder '%s': \n%v", fileSystemFolder, err)
65-
w.Error(message, http.StatusInternalServerError)
66-
return
67-
}
68-
69-
message := fmt.Sprintf("Failure to read folder '%s': \n%v", fileSystemFolder, err)
70-
w.Error(message, http.StatusInternalServerError)
71-
return
72-
}
73-
74-
err = dirAsFile.Close()
75-
if err != nil {
76-
message := fmt.Sprintf("Failure to close folder '%s': \n%v", fileSystemFolder, err)
77-
w.Error(message, http.StatusInternalServerError)
42+
ok, dirEntries := helpers.ReadFolder(w, fileSystemFolder)
43+
if !ok {
7844
return
7945
}
8046

pages/fileExplorer/fileExplorerPartial.gohtml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
{{- /*gotype: go-api/pages/music.FilesData*/ -}}
22
<div id="files-partial" class="card">
33
<div class="grow">
4-
<h3>/{{.Path}}</h3>
4+
<div class="files-header">
5+
<h3>/{{.Path}}</h3>
6+
<button class="secondary-button small" hx-post="/enqueueFolder/{{.Path}}" hx-target="#player">
7+
Enqueue folder
8+
</button>
9+
</div>
510
<table>
611
<tbody>
712
{{range .Items}}
@@ -27,7 +32,8 @@
2732
</td>
2833
<td class="align-end short">
2934
{{if .IsSong}}
30-
<button class="icon-button" hx-post="/addSong/{{.Path}}" hx-target="#player">
35+
<button class="icon-button" hx-post="/enqueueSong/{{.Path}}" hx-target="#player"
36+
title="Enqueue song">
3137
<img src="/public/icons/add.svg" width="20" height="20" alt="Add"/>
3238
</button>
3339
{{end}}
@@ -36,7 +42,8 @@
3642
{{if not .IsGoUp}}
3743
<button class="icon-button" hx-delete="/file/{{.Path}}"
3844
hx-confirm='Are you sure you want to delete {{if .IsDir}}folder{{else}}file{{end}} "{{.Name}}"?'
39-
hx-target="#files-partial" hx-swap="outerHTML">
45+
hx-target="#files-partial" hx-swap="outerHTML"
46+
title="Delete {{if .IsDir}}folder{{else}}file{{end}}">
4047
<img src="/public/icons/delete.svg" width="20" height="20" alt="Delete"/>
4148
</button>
4249
{{end}}

pages/index/index.gohtml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,14 @@
3030
flex-grow: 1;
3131
}
3232

33+
.files-header {
34+
display: flex;
35+
justify-content: space-between;
36+
margin-bottom: 8px;
37+
}
38+
3339
h3 {
34-
font-size: 16px;
35-
margin-bottom: 4px;
40+
font-size: 20px;
3641
}
3742

3843
hr {

pages/player/GetPlayer.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package player
22

33
import (
44
"fmt"
5+
"go-api/infrastructure/helpers"
56
"go-api/infrastructure/models"
67
"go-api/infrastructure/reqRes"
78
"go-api/pages/fileExplorer"
@@ -50,7 +51,7 @@ func (controller *Controller) GetPlayer(w reqRes.MyResponseWriter, r *reqRes.MyR
5051
w.RenderTemplate(playerTemplate, pageData)
5152
}
5253

53-
func (controller *Controller) AddSong(w reqRes.MyResponseWriter, r *reqRes.MyRequest) {
54+
func (controller *Controller) EnqueueSong(w reqRes.MyResponseWriter, r *reqRes.MyRequest) {
5455
pathQueryParam := r.PathValue("path")
5556
pathToFile := filepath.Join(controller.explorerRoot, pathQueryParam)
5657

@@ -86,6 +87,39 @@ func (controller *Controller) AddSong(w reqRes.MyResponseWriter, r *reqRes.MyReq
8687
controller.GetPlayer(w, r)
8788
}
8889

90+
func (controller *Controller) EnqueueFolder(w reqRes.MyResponseWriter, r *reqRes.MyRequest) {
91+
pathQueryParam := r.PathValue("path")
92+
folder := filepath.Join(controller.explorerRoot, pathQueryParam)
93+
94+
ok, dirEntries := helpers.ReadFolder(w, folder)
95+
if !ok {
96+
return
97+
}
98+
99+
songsToAdd := make([]models.QueuedSong, 0)
100+
for _, file := range dirEntries {
101+
fileName := file.Name()
102+
isSong := fileExplorer.IsSong(fileName)
103+
if !isSong {
104+
continue
105+
}
106+
107+
songsToAdd = append(songsToAdd, models.QueuedSong{
108+
Path: filepath.Join(pathQueryParam, fileName),
109+
})
110+
}
111+
112+
result := controller.db.Create(&songsToAdd)
113+
114+
if result.Error != nil {
115+
message := fmt.Sprintf("Failed to insert song into queue: \n%v", result.Error)
116+
w.Error(message, http.StatusBadRequest)
117+
return
118+
}
119+
120+
controller.GetPlayer(w, r)
121+
}
122+
89123
// ReportSongDuration should be called by client to tell the server when the song actually ends
90124
// It would have been nice to be able to figure out duration server-side, but that seems to not be that easy
91125
func (controller *Controller) ReportSongDuration(w reqRes.MyResponseWriter, r *reqRes.MyRequest) {

public/styles.css

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,30 @@ button.primary-button {
104104
}
105105
}
106106

107+
button.secondary-button {
108+
background: none;
109+
color: var(--foreground);
110+
border: 1px solid var(--primary);
111+
padding: 0.5rem 0.75rem;
112+
border-radius: 99px;
113+
cursor: pointer;
114+
font-size: 0.875rem;
115+
116+
transition: background-color 0.125s ease-in;
117+
118+
&:hover {
119+
background-color: color-mix(in srgb, transparent, var(--primary) 20%);
120+
}
121+
122+
&:active {
123+
background-color: color-mix(in srgb, transparent, var(--primary) 40%);
124+
}
125+
}
126+
127+
button.small {
128+
padding: 0.25rem 0.5rem;
129+
}
130+
107131
button.icon-button {
108132
background-color: transparent;
109133
padding: 0.25rem;
@@ -186,4 +210,4 @@ button.icon-button {
186210
}
187211
}
188212
}
189-
}
213+
}

0 commit comments

Comments
 (0)