/
handlers_playlist.go
131 lines (121 loc) · 3.5 KB
/
handlers_playlist.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package ctrladmin
import (
"bufio"
"errors"
"fmt"
"mime/multipart"
"net/http"
"strconv"
"strings"
"github.com/jinzhu/gorm"
"go.senan.xyz/gonic/db"
)
var (
errPlaylistNoMatch = errors.New("couldn't match track")
)
func playlistParseLine(c *Controller, absPath string) (int, error) {
if strings.HasPrefix(absPath, "#") || strings.TrimSpace(absPath) == "" {
return 0, nil
}
var track db.Track
query := c.DB.Raw(`
SELECT tracks.id FROM TRACKS
JOIN albums ON tracks.album_id=albums.id
WHERE (albums.root_dir || '/' || albums.left_path || albums.right_path || '/' || tracks.filename)=?`,
absPath)
err := query.First(&track).Error
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
return 0, fmt.Errorf("%v: %w", err, errPlaylistNoMatch)
case err != nil:
return 0, fmt.Errorf("while matching: %w", err)
default:
return track.ID, nil
}
}
func playlistCheckContentType(contentType string) bool {
contentType = strings.ToLower(contentType)
known := map[string]struct{}{
"audio/x-mpegurl": {},
"audio/mpegurl": {},
"application/octet-stream": {},
}
_, ok := known[contentType]
return ok
}
func playlistParseUpload(c *Controller, userID int, header *multipart.FileHeader) ([]string, bool) {
file, err := header.Open()
if err != nil {
return []string{fmt.Sprintf("couldn't open file %q", header.Filename)}, false
}
playlistName := strings.TrimSuffix(header.Filename, ".m3u8")
if playlistName == "" {
return []string{fmt.Sprintf("invalid filename %q", header.Filename)}, false
}
contentType := header.Header.Get("Content-Type")
if !playlistCheckContentType(contentType) {
return []string{fmt.Sprintf("invalid content-type %q", contentType)}, false
}
var trackIDs []int
var errors []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
trackID, err := playlistParseLine(c, scanner.Text())
if err != nil {
// trim length of error to not overflow cookie flash
errors = append(errors, fmt.Sprintf("%.100s", err.Error()))
}
if trackID != 0 {
trackIDs = append(trackIDs, trackID)
}
}
if err := scanner.Err(); err != nil {
return []string{fmt.Sprintf("iterating playlist file: %v", err)}, true
}
playlist := &db.Playlist{}
c.DB.FirstOrCreate(playlist, db.Playlist{
Name: playlistName,
UserID: userID,
})
playlist.SetItems(trackIDs)
c.DB.Save(playlist)
return errors, true
}
func (c *Controller) ServeUploadPlaylist(r *http.Request) *Response {
return &Response{template: "upload_playlist.tmpl"}
}
func (c *Controller) ServeUploadPlaylistDo(r *http.Request) *Response {
if err := r.ParseMultipartForm((1 << 10) * 24); err != nil {
return &Response{code: 500, err: "couldn't parse mutlipart"}
}
user := r.Context().Value(CtxUser).(*db.User)
var playlistCount int
var errors []string
for _, headers := range r.MultipartForm.File {
for _, header := range headers {
headerErrors, created := playlistParseUpload(c, user.ID, header)
if created {
playlistCount++
}
errors = append(errors, headerErrors...)
}
}
return &Response{
redirect: "/admin/home",
flashN: []string{fmt.Sprintf("%d playlist(s) created", playlistCount)},
flashW: errors,
}
}
func (c *Controller) ServeDeletePlaylistDo(r *http.Request) *Response {
user := r.Context().Value(CtxUser).(*db.User)
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
return &Response{code: 400, err: "please provide a valid id"}
}
c.DB.
Where("user_id=? AND id=?", user.ID, id).
Delete(db.Playlist{})
return &Response{
redirect: "/admin/home",
}
}