/* Copyright © 2023 Philipp Wolfer This file is part of Scotty. Scotty is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Scotty is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Scotty. If not, see . */ package scrobblerlog import ( "bufio" "os" "sort" "time" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) type ScrobblerLogBackend struct { filePath string includeSkipped bool append bool file *os.File log ScrobblerLog } func (b *ScrobblerLogBackend) Name() string { return "scrobbler-log" } func (b *ScrobblerLogBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "file-path", Label: i18n.Tr("File path"), Type: models.String, }, { Name: "include-skipped", Label: i18n.Tr("Include skipped listens"), Type: models.Bool, }, { Name: "append", Label: i18n.Tr("Append to file"), Type: models.Bool, Default: "true", }} } func (b *ScrobblerLogBackend) FromConfig(config *config.ServiceConfig) models.Backend { b.filePath = config.GetString("file-path") b.includeSkipped = config.GetBool("include-skipped", false) b.append = config.GetBool("append", true) b.log = ScrobblerLog{ Timezone: "UNKNOWN", Client: "Rockbox unknown $Revision$", } return b } func (b *ScrobblerLogBackend) StartImport() error { flags := os.O_RDWR | os.O_CREATE if !b.append { flags |= os.O_TRUNC } file, err := os.OpenFile(b.filePath, flags, 0666) if err != nil { return err } if b.append { stat, err := file.Stat() if err != nil { file.Close() return err } if stat.Size() == 0 { // Zero length file, treat as a new file b.append = false } else { // Verify existing file is a scrobbler log reader := bufio.NewReader(file) if err = ReadHeader(reader, &b.log); err != nil { file.Close() return err } if _, err = file.Seek(0, 2); err != nil { return err } } } if !b.append { if err = WriteHeader(file, &b.log); err != nil { file.Close() return err } } b.file = file return nil } func (b *ScrobblerLogBackend) FinishImport() error { return b.file.Close() } func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { defer close(results) file, err := os.Open(b.filePath) if err != nil { progress <- models.Progress{}.Complete() results <- models.ListensResult{Error: err} return } defer file.Close() log, err := Parse(file, b.includeSkipped) if err != nil { progress <- models.Progress{}.Complete() results <- models.ListensResult{Error: err} return } listens := log.Listens.NewerThan(oldestTimestamp) sort.Sort(listens) progress <- models.Progress{Elapsed: int64(len(listens))}.Complete() results <- models.ListensResult{Items: listens} } func (b *ScrobblerLogBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { lastTimestamp, err := Write(b.file, export.Items) if err != nil { return importResult, err } importResult.UpdateTimestamp(lastTimestamp) importResult.ImportCount += len(export.Items) progress <- models.Progress{}.FromImportResult(importResult) return importResult, nil }