refactor(playlist): use sanity check code for every method

fixes #436
This commit is contained in:
sentriz
2023-12-22 01:38:26 +00:00
parent 2f40ceea04
commit 6b5195c583

View File

@@ -26,40 +26,6 @@ const (
extM3U8 = ".m3u8" extM3U8 = ".m3u8"
) )
type Store struct {
basePath string
mu sync.Mutex
}
func NewStore(basePath string) (*Store, error) {
if basePath == "" {
return nil, ErrInvalidBasePath
}
// sanity check layout, just in case someone tries to use an existing folder
entries, err := os.ReadDir(basePath)
if err != nil {
return nil, fmt.Errorf("sanity checking: reading dir: %w", err)
}
var found string
for _, entry := range entries {
if !entry.IsDir() {
continue
}
if _, err := userIDFromPath(entry.Name()); err != nil {
found = entry.Name()
break
}
}
if found != "" {
return nil, fmt.Errorf("sanity checking: %w: item %q in playlists directory is not a user id. see wiki for details on layout of the playlists dir", ErrNoUserPrefix, found)
}
return &Store{
basePath: basePath,
}, nil
}
type Playlist struct { type Playlist struct {
UpdatedAt time.Time UpdatedAt time.Time
UserID int UserID int
@@ -69,18 +35,33 @@ type Playlist struct {
IsPublic bool IsPublic bool
} }
func NewPath(userID int, playlistName string) string { type Store struct {
playlistName = fileutil.Safe(playlistName) basePath string
if playlistName == "" { mu sync.Mutex
playlistName = "pl" }
func NewStore(basePath string) (*Store, error) {
if basePath == "" {
return nil, ErrInvalidBasePath
} }
playlistName = fmt.Sprintf("%s-%d%s", playlistName, time.Now().UnixMilli(), extM3U) if err := sanityCheck(basePath); err != nil {
return filepath.Join(fmt.Sprint(userID), playlistName) return nil, err
}
return &Store{
basePath: basePath,
}, nil
} }
// List finds playlist items in s.basePath. // List finds playlist items in s.basePath.
// the expected format is <base path>/<user id>/**/<playlist name>.m3u // the expected format is <base path>/<user id>/**/<playlist name>.m3u
func (s *Store) List() ([]string, error) { func (s *Store) List() ([]string, error) {
defer lock(&s.mu)()
if err := sanityCheck(s.basePath); err != nil {
return nil, err
}
var relPaths []string var relPaths []string
return relPaths, filepath.WalkDir(s.basePath, func(path string, d fs.DirEntry, err error) error { return relPaths, filepath.WalkDir(s.basePath, func(path string, d fs.DirEntry, err error) error {
if err != nil { if err != nil {
@@ -100,30 +81,13 @@ func (s *Store) List() ([]string, error) {
}) })
} }
const (
attrPrefix = "#GONIC-"
attrName = "NAME"
attrCommment = "COMMENT"
attrIsPublic = "IS-PUBLIC"
)
func encodeAttr(name, value string) string {
return fmt.Sprintf("%s%s:%s", attrPrefix, name, strconv.Quote(value))
}
func decodeAttr(line string) (name, value string) {
if !strings.HasPrefix(line, attrPrefix) {
return "", ""
}
prefixAndName, rawValue, _ := strings.Cut(line, ":")
name = strings.TrimPrefix(prefixAndName, attrPrefix)
value, _ = strconv.Unquote(rawValue)
return name, value
}
func (s *Store) Read(relPath string) (*Playlist, error) { func (s *Store) Read(relPath string) (*Playlist, error) {
defer lock(&s.mu)() defer lock(&s.mu)()
if err := sanityCheck(s.basePath); err != nil {
return nil, err
}
absPath := filepath.Join(s.basePath, relPath) absPath := filepath.Join(s.basePath, relPath)
stat, err := os.Stat(absPath) stat, err := os.Stat(absPath)
if err != nil { if err != nil {
@@ -171,6 +135,10 @@ func (s *Store) Read(relPath string) (*Playlist, error) {
func (s *Store) Write(relPath string, playlist *Playlist) error { func (s *Store) Write(relPath string, playlist *Playlist) error {
defer lock(&s.mu)() defer lock(&s.mu)()
if err := sanityCheck(s.basePath); err != nil {
return err
}
absPath := filepath.Join(s.basePath, relPath) absPath := filepath.Join(s.basePath, relPath)
if err := os.MkdirAll(filepath.Dir(absPath), 0o777); err != nil { if err := os.MkdirAll(filepath.Dir(absPath), 0o777); err != nil {
return fmt.Errorf("make m3u base dir: %w", err) return fmt.Errorf("make m3u base dir: %w", err)
@@ -217,6 +185,10 @@ func (s *Store) Write(relPath string, playlist *Playlist) error {
} }
func (s *Store) Delete(relPath string) error { func (s *Store) Delete(relPath string) error {
if err := sanityCheck(s.basePath); err != nil {
return err
}
return os.Remove(filepath.Join(s.basePath, relPath)) return os.Remove(filepath.Join(s.basePath, relPath))
} }
@@ -237,3 +209,55 @@ func lock(mu *sync.Mutex) func() {
mu.Lock() mu.Lock()
return mu.Unlock return mu.Unlock
} }
func sanityCheck(basePath string) error {
// sanity check layout, just in case someone tries to use an existing folder
entries, err := os.ReadDir(basePath)
if err != nil {
return fmt.Errorf("sanity checking: reading dir: %w", err)
}
var found string
for _, entry := range entries {
if !entry.IsDir() {
continue
}
if _, err := userIDFromPath(entry.Name()); err != nil {
found = entry.Name()
break
}
}
if found != "" {
return fmt.Errorf("sanity checking: %w: item %q in playlists directory is not a user id. see wiki for details on layout of the playlists dir", ErrNoUserPrefix, found)
}
return nil
}
const (
attrPrefix = "#GONIC-"
attrName = "NAME"
attrCommment = "COMMENT"
attrIsPublic = "IS-PUBLIC"
)
func encodeAttr(name, value string) string {
return fmt.Sprintf("%s%s:%s", attrPrefix, name, strconv.Quote(value))
}
func decodeAttr(line string) (name, value string) {
if !strings.HasPrefix(line, attrPrefix) {
return "", ""
}
prefixAndName, rawValue, _ := strings.Cut(line, ":")
name = strings.TrimPrefix(prefixAndName, attrPrefix)
value, _ = strconv.Unquote(rawValue)
return name, value
}
func NewPath(userID int, playlistName string) string {
playlistName = fileutil.Safe(playlistName)
if playlistName == "" {
playlistName = "pl"
}
playlistName = fmt.Sprintf("%s-%d%s", playlistName, time.Now().UnixMilli(), extM3U)
return filepath.Join(fmt.Sprint(userID), playlistName)
}