Moved generic archive abstraction into separate package

This commit is contained in:
Philipp Wolfer 2025-05-24 00:35:55 +02:00
parent 424305518b
commit 1025277ba9
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
2 changed files with 196 additions and 147 deletions

View file

@ -22,19 +22,16 @@ THE SOFTWARE.
package listenbrainz
import (
"archive/zip"
"encoding/json"
"fmt"
"io"
"iter"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"time"
"github.com/simonfrey/jsonl"
"go.uploadedlobster.com/scotty/internal/archive"
)
// Represents a ListenBrainz export archive.
@ -42,7 +39,17 @@ import (
// The export contains the user's listen history, favorite tracks and
// user information.
type Archive struct {
backend archiveBackend
backend archive.Archive
}
// Open a ListenBrainz archive from file path.
func OpenArchive(path string) (*Archive, error) {
backend, err := archive.OpenArchive(path)
if err != nil {
return nil, err
}
return &Archive{backend: backend}, nil
}
// Close the archive and release any resources.
@ -137,166 +144,27 @@ func (a *Archive) IterListens(minTimestamp time.Time) iter.Seq2[Listen, error] {
}
}
// Open a ListenBrainz archive from file path.
func OpenArchive(path string) (*Archive, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err
}
switch mode := fi.Mode(); {
case mode.IsRegular():
backend := &zipArchive{}
err := backend.Open(path)
if err != nil {
return nil, err
}
return &Archive{backend: backend}, nil
case mode.IsDir():
backend := &dirArchive{}
err := backend.Open(path)
if err != nil {
return nil, err
}
return &Archive{backend: backend}, nil
default:
return nil, fmt.Errorf("unsupported file mode: %s", mode)
}
}
type UserInfo struct {
ID string `json:"user_id"`
Name string `json:"username"`
}
type archiveBackend interface {
Close() error
OpenFile(path string) (io.ReadCloser, error)
Glob(pattern string) ([]FileInfo, error)
}
type timeRange struct {
Start time.Time
End time.Time
}
type OpenableFile interface {
Open() (io.ReadCloser, error)
}
type FileInfo struct {
Name string
File OpenableFile
}
type FilesystemFile struct {
path string
}
func (f *FilesystemFile) Open() (io.ReadCloser, error) {
return os.Open(f.path)
}
type ListenExportFileInfo struct {
Name string
TimeRange timeRange
f OpenableFile
}
// An implementation of the archiveBackend interface for zip files.
type zipArchive struct {
zip *zip.ReadCloser
}
func (a *zipArchive) Open(path string) error {
zip, err := zip.OpenReader(path)
if err != nil {
return err
}
a.zip = zip
return nil
}
func (a *zipArchive) Close() error {
if a.zip == nil {
return nil
}
return a.zip.Close()
}
func (a *zipArchive) Glob(pattern string) ([]FileInfo, error) {
result := make([]FileInfo, 0)
for _, file := range a.zip.File {
if matched, err := filepath.Match(pattern, file.Name); matched {
if err != nil {
return nil, err
}
info := FileInfo{
Name: file.Name,
File: file,
}
result = append(result, info)
}
}
return result, nil
}
func (a *zipArchive) OpenFile(path string) (io.ReadCloser, error) {
file, err := a.zip.Open(path)
if err != nil {
return nil, err
}
return file, nil
}
// An implementation of the archiveBackend interface for directories.
type dirArchive struct {
dir string
}
func (a *dirArchive) Open(path string) error {
a.dir = filepath.Clean(path)
return nil
}
func (a *dirArchive) Close() error {
return nil
}
func (a *dirArchive) OpenFile(path string) (io.ReadCloser, error) {
file, err := os.Open(filepath.Join(a.dir, path))
if err != nil {
return nil, err
}
return file, nil
}
func (a *dirArchive) Glob(pattern string) ([]FileInfo, error) {
files, err := filepath.Glob(filepath.Join(a.dir, pattern))
if err != nil {
return nil, err
}
result := make([]FileInfo, 0)
for _, filename := range files {
name, err := filepath.Rel(a.dir, filename)
if err != nil {
return nil, err
}
info := FileInfo{
Name: name,
File: &FilesystemFile{path: filename},
}
result = append(result, info)
}
return result, nil
f archive.OpenableFile
}
type ListenExportFile struct {
file OpenableFile
file archive.OpenableFile
}
func NewExportFile(f OpenableFile) ListenExportFile {
func NewExportFile(f archive.OpenableFile) ListenExportFile {
return ListenExportFile{file: f}
}