diff --git a/playlist/playlist.go b/playlist/playlist.go index 6f9f60e..b229619 100644 --- a/playlist/playlist.go +++ b/playlist/playlist.go @@ -26,40 +26,6 @@ const ( 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 { UpdatedAt time.Time UserID int @@ -69,18 +35,33 @@ type Playlist struct { IsPublic bool } -func NewPath(userID int, playlistName string) string { - playlistName = fileutil.Safe(playlistName) - if playlistName == "" { - playlistName = "pl" +type Store struct { + basePath string + mu sync.Mutex +} + +func NewStore(basePath string) (*Store, error) { + if basePath == "" { + return nil, ErrInvalidBasePath } - playlistName = fmt.Sprintf("%s-%d%s", playlistName, time.Now().UnixMilli(), extM3U) - return filepath.Join(fmt.Sprint(userID), playlistName) + if err := sanityCheck(basePath); err != nil { + return nil, err + } + + return &Store{ + basePath: basePath, + }, nil } // List finds playlist items in s.basePath. // the expected format is //**/.m3u func (s *Store) List() ([]string, error) { + defer lock(&s.mu)() + + if err := sanityCheck(s.basePath); err != nil { + return nil, err + } + var relPaths []string return relPaths, filepath.WalkDir(s.basePath, func(path string, d fs.DirEntry, err error) error { 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) { defer lock(&s.mu)() + if err := sanityCheck(s.basePath); err != nil { + return nil, err + } + absPath := filepath.Join(s.basePath, relPath) stat, err := os.Stat(absPath) if err != nil { @@ -171,6 +135,10 @@ func (s *Store) Read(relPath string) (*Playlist, error) { func (s *Store) Write(relPath string, playlist *Playlist) error { defer lock(&s.mu)() + if err := sanityCheck(s.basePath); err != nil { + return err + } + absPath := filepath.Join(s.basePath, relPath) if err := os.MkdirAll(filepath.Dir(absPath), 0o777); err != nil { 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 { + if err := sanityCheck(s.basePath); err != nil { + return err + } + return os.Remove(filepath.Join(s.basePath, relPath)) } @@ -237,3 +209,55 @@ func lock(mu *sync.Mutex) func() { mu.Lock() 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) +}