/* Copyright © 2023-2025 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 dump import ( "bytes" "context" "fmt" "io" "os" "strings" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) type DumpBackend struct { buffer io.ReadWriter print bool // Whether to print the output to stdout } func (b *DumpBackend) Name() string { return "dump" } func (b *DumpBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "file-path", Label: i18n.Tr("File path"), Type: models.String, }, { Name: "append", Label: i18n.Tr("Append to file"), Type: models.Bool, Default: "true", }} } func (b *DumpBackend) InitConfig(config *config.ServiceConfig) error { filePath := config.GetString("file-path") append := config.GetBool("append", true) if strings.TrimSpace(filePath) != "" { mode := os.O_WRONLY | os.O_CREATE if !append { mode |= os.O_TRUNC // Truncate the file if not appending } f, err := os.OpenFile(filePath, mode, 0644) if err != nil { return err } b.buffer = f b.print = false // If a file path is specified, we don't print to stdout } else { // If no file path is specified, use a bytes.Buffer for in-memory dumping b.buffer = new(bytes.Buffer) b.print = true // Print to stdout } return nil } func (b *DumpBackend) StartImport() error { return nil } func (b *DumpBackend) FinishImport() error { if b.print { out := new(strings.Builder) _, err := io.Copy(out, b.buffer) if err != nil { return err } fmt.Println(out.String()) } // Close the io writer if it is closable if closer, ok := b.buffer.(io.Closer); ok { if err := closer.Close(); err != nil { return fmt.Errorf("failed to close output file: %w", err) } } return nil } func (b *DumpBackend) ImportListens(ctx context.Context, export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { for _, listen := range export.Items { if err := ctx.Err(); err != nil { return importResult, err } importResult.UpdateTimestamp(listen.ListenedAt) importResult.ImportCount += 1 _, err := fmt.Fprintf(b.buffer, "🎶 %v: \"%v\" by %v (%v)\n", listen.ListenedAt, listen.TrackName, listen.ArtistName(), listen.RecordingMBID) if err != nil { return importResult, err } progress <- models.TransferProgress{}.FromImportResult(importResult, false) } return importResult, nil } func (b *DumpBackend) ImportLoves(ctx context.Context, export models.LovesResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { for _, love := range export.Items { if err := ctx.Err(); err != nil { return importResult, err } importResult.UpdateTimestamp(love.Created) importResult.ImportCount += 1 _, err := fmt.Fprintf(b.buffer, "❤️ %v: \"%v\" by %v (%v)\n", love.Created, love.TrackName, love.ArtistName(), love.RecordingMBID) if err != nil { return importResult, err } progress <- models.TransferProgress{}.FromImportResult(importResult, false) } return importResult, nil }