Commit 322b0616 authored by Max Kellermann's avatar Max Kellermann

DetachedSong: fork of struct Song

From now on, struct Song will be used by the database only, and DetachedSong will be used by everybody else. DetachedSong is easier to use, but Song has lower overhead.
parent 43847f22
......@@ -192,6 +192,7 @@ src_mpd_SOURCES = \
src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \
src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \
src/SignalHandlers.cxx src/SignalHandlers.hxx \
src/DetachedSong.cxx src/DetachedSong.hxx \
src/Song.cxx src/Song.hxx \
src/SongUpdate.cxx \
src/SongPrint.cxx src/SongPrint.hxx \
......@@ -206,7 +207,6 @@ src_mpd_SOURCES = \
src/TextInputStream.cxx \
src/Volume.cxx src/Volume.hxx \
src/SongFilter.cxx src/SongFilter.hxx \
src/SongPointer.hxx \
src/PlaylistFile.cxx src/PlaylistFile.hxx \
src/Timer.cxx
......@@ -1271,7 +1271,7 @@ test_dump_playlist_SOURCES = test/dump_playlist.cxx \
$(DECODER_SRC) \
src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/Song.cxx src/TagSave.cxx \
src/TagSave.cxx \
src/TagFile.cxx \
src/CheckAudioFormat.cxx \
src/TextInputStream.cxx \
......
......@@ -23,6 +23,7 @@
#include "PlaylistFile.hxx"
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"
#include "DetachedSong.hxx"
#include <functional>
......@@ -30,7 +31,7 @@ static bool
AddSong(const char *playlist_path_utf8,
Song &song, Error &error)
{
return spl_append_song(playlist_path_utf8, song, error);
return spl_append_song(playlist_path_utf8, DetachedSong(song), error);
}
bool
......
......@@ -55,26 +55,24 @@ PrintDirectoryFull(Client &client, const Directory &directory)
static void
print_playlist_in_directory(Client &client,
const Directory &directory,
const Directory *directory,
const char *name_utf8)
{
if (directory.IsRoot())
if (directory == nullptr || directory->IsRoot())
client_printf(client, "playlist: %s\n", name_utf8);
else
client_printf(client, "playlist: %s/%s\n",
directory.GetPath(), name_utf8);
directory->GetPath(), name_utf8);
}
static bool
PrintSongBrief(Client &client, const Song &song)
{
assert(song.parent != nullptr);
song_print_uri(client, song);
if (song.tag != nullptr && song.tag->has_playlist)
/* this song file has an embedded CUE sheet */
print_playlist_in_directory(client, *song.parent, song.uri);
print_playlist_in_directory(client, song.parent, song.uri);
return true;
}
......@@ -82,13 +80,11 @@ PrintSongBrief(Client &client, const Song &song)
static bool
PrintSongFull(Client &client, const Song &song)
{
assert(song.parent != nullptr);
song_print_info(client, song);
if (song.tag != nullptr && song.tag->has_playlist)
/* this song file has an embedded CUE sheet */
print_playlist_in_directory(client, *song.parent, song.uri);
print_playlist_in_directory(client, song.parent, song.uri);
return true;
}
......@@ -98,7 +94,7 @@ PrintPlaylistBrief(Client &client,
const PlaylistInfo &playlist,
const Directory &directory)
{
print_playlist_in_directory(client, directory, playlist.name.c_str());
print_playlist_in_directory(client, &directory, playlist.name.c_str());
return true;
}
......@@ -107,7 +103,7 @@ PrintPlaylistFull(Client &client,
const PlaylistInfo &playlist,
const Directory &directory)
{
print_playlist_in_directory(client, directory, playlist.name.c_str());
print_playlist_in_directory(client, &directory, playlist.name.c_str());
if (playlist.mtime > 0)
time_print(client, "Last-Modified", playlist.mtime);
......
......@@ -23,14 +23,16 @@
#include "DatabasePlugin.hxx"
#include "Partition.hxx"
#include "util/Error.hxx"
#include "DetachedSong.hxx"
#include <functional>
static bool
AddToQueue(Partition &partition, Song &song, Error &error)
AddToQueue(Partition &partition, const Song &song, Error &error)
{
PlaylistResult result =
partition.playlist.AppendSong(partition.pc, &song, nullptr);
partition.playlist.AppendSong(partition.pc, DetachedSong(song),
nullptr);
if (result != PlaylistResult::SUCCESS) {
error.Set(playlist_domain, int(result), "Playlist error");
return false;
......
......@@ -28,7 +28,7 @@
#include "MusicPipe.hxx"
#include "DecoderControl.hxx"
#include "DecoderInternal.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
......@@ -467,7 +467,7 @@ decoder_data(Decoder &decoder,
const auto dest =
chunk->Write(dc.out_audio_format,
decoder.timestamp -
dc.song->start_ms / 1000.0,
dc.song->GetStartMS() / 1000.0,
kbit_rate);
if (dest.IsNull()) {
/* the chunk is full, flush it */
......
......@@ -20,7 +20,7 @@
#include "config.h"
#include "DecoderControl.hxx"
#include "MusicPipe.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include <assert.h>
......@@ -36,8 +36,7 @@ DecoderControl::~DecoderControl()
{
ClearError();
if (song != nullptr)
song->Free();
delete song;
}
void
......@@ -53,7 +52,7 @@ DecoderControl::WaitForDecoder()
}
bool
DecoderControl::IsCurrentSong(const Song &_song) const
DecoderControl::IsCurrentSong(const DetachedSong &_song) const
{
switch (state) {
case DecoderState::STOP:
......@@ -62,7 +61,7 @@ DecoderControl::IsCurrentSong(const Song &_song) const
case DecoderState::START:
case DecoderState::DECODE:
return SongEquals(*song, _song);
return song->IsSame(_song);
}
assert(false);
......@@ -70,16 +69,14 @@ DecoderControl::IsCurrentSong(const Song &_song) const
}
void
DecoderControl::Start(Song *_song,
DecoderControl::Start(DetachedSong *_song,
unsigned _start_ms, unsigned _end_ms,
MusicBuffer &_buffer, MusicPipe &_pipe)
{
assert(_song != nullptr);
assert(_pipe.IsEmpty());
if (song != nullptr)
song->Free();
delete song;
song = _song;
start_ms = _start_ms;
end_ms = _end_ms;
......
......@@ -36,7 +36,7 @@
#undef ERROR
#endif
struct Song;
class DetachedSong;
class MusicBuffer;
class MusicPipe;
......@@ -123,7 +123,7 @@ struct DecoderControl {
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
Song *song;
DetachedSong *song;
/**
* The initial seek position (in milliseconds), e.g. to the
......@@ -293,10 +293,10 @@ struct DecoderControl {
* Caller must lock the object.
*/
gcc_pure
bool IsCurrentSong(const Song &_song) const;
bool IsCurrentSong(const DetachedSong &_song) const;
gcc_pure
bool LockIsCurrentSong(const Song &_song) const {
bool LockIsCurrentSong(const DetachedSong &_song) const {
Lock();
const bool result = IsCurrentSong(_song);
Unlock();
......@@ -360,7 +360,7 @@ public:
* @param pipe the pipe which receives the decoded chunks (owned by
* the caller)
*/
void Start(Song *song, unsigned start_ms, unsigned end_ms,
void Start(DetachedSong *song, unsigned start_ms, unsigned end_ms,
MusicBuffer &buffer, MusicPipe &pipe);
void Stop();
......
......@@ -23,7 +23,7 @@
#include "DecoderInternal.hxx"
#include "DecoderError.hxx"
#include "DecoderPlugin.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "system/FatalError.hxx"
#include "Mapper.hxx"
#include "fs/Traits.hxx"
......@@ -347,11 +347,10 @@ decoder_run_file(Decoder &decoder, const char *path_fs)
static void
decoder_run_song(DecoderControl &dc,
const Song &song, const char *uri)
const DetachedSong &song, const char *uri)
{
Decoder decoder(dc, dc.start_ms > 0,
song.tag != nullptr && song.IsFile()
? new Tag(*song.tag) : nullptr);
new Tag(song.GetTag()));
int ret;
dc.state = DecoderState::START;
......@@ -381,7 +380,7 @@ decoder_run_song(DecoderControl &dc,
else {
dc.state = DecoderState::ERROR;
const char *error_uri = song.uri;
const char *error_uri = song.GetURI();
const std::string allocated = uri_remove_auth(error_uri);
if (!allocated.empty())
error_uri = allocated.c_str();
......@@ -399,10 +398,10 @@ decoder_run(DecoderControl &dc)
dc.ClearError();
assert(dc.song != nullptr);
const Song &song = *dc.song;
const DetachedSong &song = *dc.song;
const std::string uri = song.IsFile()
? std::string(map_song_fs(song).c_str())
? map_song_fs(song).c_str()
: song.GetURI();
if (uri.empty()) {
......
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DetachedSong.hxx"
#include "Song.hxx"
#include "util/UriUtil.hxx"
#include "fs/Traits.hxx"
DetachedSong::DetachedSong(const Song &other)
:uri(other.GetURI().c_str()),
tag(other.tag != nullptr ? *other.tag : Tag()),
mtime(other.mtime),
start_ms(other.start_ms), end_ms(other.end_ms) {}
bool
DetachedSong::IsRemote() const
{
return uri_has_scheme(uri.c_str());
}
bool
DetachedSong::IsAbsoluteFile() const
{
return PathTraitsUTF8::IsAbsolute(uri.c_str());
}
double
DetachedSong::GetDuration() const
{
if (end_ms > 0)
return (end_ms - start_ms) / 1000.0;
return tag.time - start_ms / 1000.0;
}
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DETACHED_SONG_HXX
#define MPD_DETACHED_SONG_HXX
#include "check.h"
#include "tag/Tag.hxx"
#include "Compiler.h"
#include <string>
#include <utility>
#include <time.h>
struct Song;
class DetachedSong {
/**
* An UTF-8-encoded URI referring to the song file. This can
* be one of:
*
* - an absolute URL with a scheme
* (e.g. "http://example.com/foo.mp3")
*
* - an absolute file name
*
* - a file name relative to the music directory
*/
std::string uri;
Tag tag;
time_t mtime;
/**
* Start of this sub-song within the file in milliseconds.
*/
unsigned start_ms;
/**
* End of this sub-song within the file in milliseconds.
* Unused if zero.
*/
unsigned end_ms;
public:
explicit DetachedSong(const DetachedSong &other)
:uri(other.uri),
tag(other.tag),
mtime(other.mtime),
start_ms(other.start_ms), end_ms(other.end_ms) {}
explicit DetachedSong(const Song &other);
explicit DetachedSong(const char *_uri)
:uri(_uri),
mtime(0), start_ms(0), end_ms(0) {}
explicit DetachedSong(const std::string &_uri)
:uri(std::move(_uri)),
mtime(0), start_ms(0), end_ms(0) {}
explicit DetachedSong(std::string &&_uri)
:uri(std::move(_uri)),
mtime(0), start_ms(0), end_ms(0) {}
template<typename U>
DetachedSong(U &&_uri, Tag &&_tag)
:uri(std::forward<U>(_uri)),
tag(std::move(_tag)),
mtime(0), start_ms(0), end_ms(0) {}
DetachedSong(DetachedSong &&other)
:uri(std::move(other.uri)),
tag(std::move(other.tag)),
mtime(other.mtime),
start_ms(other.start_ms), end_ms(other.end_ms) {}
gcc_pure
const char *GetURI() const {
return uri.c_str();
}
template<typename T>
void SetURI(T &&_uri) {
uri = std::forward<T>(_uri);
}
/**
* Returns true if both objects refer to the same physical
* song.
*/
gcc_pure
bool IsSame(const DetachedSong &other) const {
return uri == other.uri;
}
gcc_pure gcc_nonnull_all
bool IsURI(const char *other_uri) const {
return uri == other_uri;
}
gcc_pure
bool IsRemote() const;
gcc_pure
bool IsFile() const {
return !IsRemote();
}
gcc_pure
bool IsAbsoluteFile() const;
gcc_pure
bool IsInDatabase() const {
return IsFile() && !IsAbsoluteFile();
}
const Tag &GetTag() const {
return tag;
}
Tag &WritableTag() {
return tag;
}
void SetTag(const Tag &_tag) {
tag = Tag(_tag);
}
void SetTag(Tag &&_tag) {
tag = std::move(_tag);
}
void MoveTagFrom(DetachedSong &&other) {
tag = std::move(other.tag);
}
time_t GetLastModified() const {
return mtime;
}
void SetLastModified(time_t _value) {
mtime = _value;
}
unsigned GetStartMS() const {
return start_ms;
}
void SetStartMS(unsigned _value) {
start_ms = _value;
}
unsigned GetEndMS() const {
return end_ms;
}
void SetEndMS(unsigned _value) {
end_ms = _value;
}
gcc_pure
double GetDuration() const;
};
#endif
......@@ -48,14 +48,6 @@ Directory::Allocate(const char *path)
path);
}
Directory::Directory()
{
INIT_LIST_HEAD(&children);
INIT_LIST_HEAD(&songs);
path[0] = 0;
}
Directory::Directory(const char *_path)
:mtime(0), have_stat(false)
{
......
......@@ -95,10 +95,6 @@ protected:
static Directory *Allocate(const char *path);
public:
/**
* Default constructor, needed for #detached_root.
*/
Directory();
~Directory();
/**
......
......@@ -22,6 +22,7 @@
#include "Directory.hxx"
#include "Song.hxx"
#include "SongSave.hxx"
#include "DetachedSong.hxx"
#include "PlaylistDatabase.hxx"
#include "fs/TextFile.hxx"
#include "util/StringUtil.hxx"
......@@ -132,7 +133,6 @@ directory_load(TextFile &file, Directory &directory, Error &error)
return false;
} else if (StringStartsWith(line, SONG_BEGIN)) {
const char *name = line + sizeof(SONG_BEGIN) - 1;
Song *song;
if (directory.FindSong(name) != nullptr) {
error.Format(directory_domain,
......@@ -140,11 +140,13 @@ directory_load(TextFile &file, Directory &directory, Error &error)
return false;
}
song = song_load(file, &directory, name, error);
DetachedSong *song = song_load(file, name, error);
if (song == nullptr)
return false;
directory.AddSong(song);
directory.AddSong(Song::NewFrom(std::move(*song),
&directory));
delete song;
} else if (StringStartsWith(line, PLAYLIST_META_BEGIN)) {
const char *name = line + sizeof(PLAYLIST_META_BEGIN) - 1;
if (!playlist_metadata_load(file, directory.playlists,
......
......@@ -23,9 +23,9 @@
#include "Idle.hxx"
void
Instance::DeleteSong(const Song &song)
Instance::DeleteSong(const char *uri)
{
partition->DeleteSong(song);
partition->DeleteSong(uri);
}
void
......
......@@ -24,14 +24,13 @@
class ClientList;
struct Partition;
struct Song;
struct Instance {
ClientList *client_list;
Partition *partition;
void DeleteSong(const Song &song);
void DeleteSong(const char *uri);
/**
* The database has been modified. Propagate the change to
......
......@@ -25,6 +25,7 @@
#include "Mapper.hxx"
#include "Directory.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/Charset.hxx"
......@@ -219,14 +220,18 @@ map_detached_song_fs(const char *uri_utf8)
AllocatedPath
map_song_fs(const Song &song)
{
assert(song.IsFile());
return song.parent == nullptr
? map_detached_song_fs(song.uri)
: map_directory_child_fs(*song.parent, song.uri);
}
if (song.IsInDatabase())
return song.IsDetached()
? map_detached_song_fs(song.uri)
: map_directory_child_fs(*song.parent, song.uri);
AllocatedPath
map_song_fs(const DetachedSong &song)
{
if (song.IsAbsoluteFile())
return AllocatedPath::FromUTF8(song.GetURI());
else
return AllocatedPath::FromUTF8(song.uri);
return map_uri_fs(song.GetURI());
}
std::string
......
......@@ -33,6 +33,7 @@
class AllocatedPath;
struct Directory;
struct Song;
class DetachedSong;
void
mapper_init(AllocatedPath &&music_dir, AllocatedPath &&playlist_dir);
......@@ -116,6 +117,10 @@ gcc_pure
AllocatedPath
map_song_fs(const Song &song);
gcc_pure
AllocatedPath
map_song_fs(const DetachedSong &song);
/**
* Maps a file system path (relative to the music directory or
* absolute) to a relative path in UTF-8 encoding.
......
......@@ -20,13 +20,13 @@
#include "config.h"
#include "MemorySongEnumerator.hxx"
Song *
DetachedSong *
MemorySongEnumerator::NextSong()
{
if (songs.empty())
return nullptr;
auto result = songs.front().Steal();
auto result = new DetachedSong(std::move(songs.front()));
songs.pop_front();
return result;
}
......@@ -21,18 +21,18 @@
#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX
#include "SongEnumerator.hxx"
#include "SongPointer.hxx"
#include "DetachedSong.hxx"
#include <forward_list>
class MemorySongEnumerator final : public SongEnumerator {
std::forward_list<SongPointer> songs;
std::forward_list<DetachedSong> songs;
public:
MemorySongEnumerator(std::forward_list<SongPointer> &&_songs)
MemorySongEnumerator(std::forward_list<DetachedSong> &&_songs)
:songs(std::move(_songs)) {}
virtual Song *NextSong() override;
virtual DetachedSong *NextSong() override;
};
#endif
......@@ -19,7 +19,7 @@
#include "config.h"
#include "Partition.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
void
Partition::DatabaseModified()
......@@ -30,10 +30,10 @@ Partition::DatabaseModified()
void
Partition::TagModified()
{
Song *song = pc.LockReadTaggedSong();
DetachedSong *song = pc.LockReadTaggedSong();
if (song != nullptr) {
playlist.TagModified(std::move(*song));
song->Free();
delete song;
}
}
......
......@@ -76,8 +76,8 @@ struct Partition {
return playlist.DeleteRange(pc, start, end);
}
void DeleteSong(const Song &song) {
playlist.DeleteSong(pc, song);
void DeleteSong(const char *uri) {
playlist.DeleteSong(pc, uri);
}
void Shuffle(unsigned start, unsigned end) {
......
......@@ -20,7 +20,7 @@
#include "config.h"
#include "PlayerControl.hxx"
#include "Idle.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include <algorithm>
......@@ -42,15 +42,12 @@ PlayerControl::PlayerControl(unsigned _buffer_chunks,
PlayerControl::~PlayerControl()
{
if (next_song != nullptr)
next_song->Free();
if (tagged_song != nullptr)
tagged_song->Free();
delete next_song;
delete tagged_song;
}
void
PlayerControl::Play(Song *song)
PlayerControl::Play(DetachedSong *song)
{
assert(song != nullptr);
......@@ -195,26 +192,23 @@ PlayerControl::ClearError()
}
void
PlayerControl::LockSetTaggedSong(const Song &song)
PlayerControl::LockSetTaggedSong(const DetachedSong &song)
{
Lock();
if (tagged_song != nullptr)
tagged_song->Free();
tagged_song = song.DupDetached();
delete tagged_song;
tagged_song = new DetachedSong(song);
Unlock();
}
void
PlayerControl::ClearTaggedSong()
{
if (tagged_song != nullptr) {
tagged_song->Free();
tagged_song = nullptr;
}
delete tagged_song;
tagged_song = nullptr;
}
void
PlayerControl::EnqueueSong(Song *song)
PlayerControl::EnqueueSong(DetachedSong *song)
{
assert(song != nullptr);
......@@ -224,15 +218,13 @@ PlayerControl::EnqueueSong(Song *song)
}
bool
PlayerControl::Seek(Song *song, float seek_time)
PlayerControl::Seek(DetachedSong *song, float seek_time)
{
assert(song != nullptr);
Lock();
if (next_song != nullptr)
next_song->Free();
delete next_song;
next_song = song;
seek_where = seek_time;
SynchronousCommand(PlayerCommand::SEEK);
......
......@@ -29,7 +29,7 @@
#include <stdint.h>
struct Song;
class DetachedSong;
enum class PlayerState : uint8_t {
STOP,
......@@ -131,16 +131,16 @@ struct PlayerControl {
Error error;
/**
* A copy of the current #Song after its tags have been
* updated by the decoder (for example, a radio stream that
* has sent a new tag after switching to the next song). This
* shall be used by the GlobalEvents::TAG handler to update
* the current #Song in the queue.
* A copy of the current #DetachedSong after its tags have
* been updated by the decoder (for example, a radio stream
* that has sent a new tag after switching to the next song).
* This shall be used by the GlobalEvents::TAG handler to
* update the current #DetachedSong in the queue.
*
* Protected by #mutex. Set by the PlayerThread and consumed
* by the main thread.
*/
Song *tagged_song;
DetachedSong *tagged_song;
uint16_t bit_rate;
AudioFormat audio_format;
......@@ -153,7 +153,7 @@ struct PlayerControl {
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
Song *next_song;
DetachedSong *next_song;
double seek_where;
......@@ -299,7 +299,7 @@ public:
* @param song the song to be queued; the given instance will
* be owned and freed by the player
*/
void Play(Song *song);
void Play(DetachedSong *song);
/**
* see PlayerCommand::CANCEL
......@@ -371,9 +371,9 @@ public:
/**
* Set the #tagged_song attribute to a newly allocated copy of
* the given #Song. Locks and unlocks the object.
* the given #DetachedSong. Locks and unlocks the object.
*/
void LockSetTaggedSong(const Song &song);
void LockSetTaggedSong(const DetachedSong &song);
void ClearTaggedSong();
......@@ -382,8 +382,8 @@ public:
*
* Caller must lock the object.
*/
Song *ReadTaggedSong() {
Song *result = tagged_song;
DetachedSong *ReadTaggedSong() {
DetachedSong *result = tagged_song;
tagged_song = nullptr;
return result;
}
......@@ -391,9 +391,9 @@ public:
/**
* Like ReadTaggedSong(), but locks and unlocks the object.
*/
Song *LockReadTaggedSong() {
DetachedSong *LockReadTaggedSong() {
Lock();
Song *result = ReadTaggedSong();
DetachedSong *result = ReadTaggedSong();
Unlock();
return result;
}
......@@ -403,7 +403,7 @@ public:
void UpdateAudio();
private:
void EnqueueSongLocked(Song *song) {
void EnqueueSongLocked(DetachedSong *song) {
assert(song != nullptr);
assert(next_song == nullptr);
......@@ -416,7 +416,7 @@ public:
* @param song the song to be queued; the given instance will be owned
* and freed by the player
*/
void EnqueueSong(Song *song);
void EnqueueSong(DetachedSong *song);
/**
* Makes the player thread seek the specified song to a position.
......@@ -426,7 +426,7 @@ public:
* @return true on success, false on failure (e.g. if MPD isn't
* playing currently)
*/
bool Seek(Song *song, float seek_time);
bool Seek(DetachedSong *song, float seek_time);
void SetCrossFade(float cross_fade_seconds);
......
......@@ -24,7 +24,7 @@
#include "MusicPipe.hxx"
#include "MusicBuffer.hxx"
#include "MusicChunk.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "system/FatalError.hxx"
#include "CrossFade.hxx"
#include "PlayerControl.hxx"
......@@ -92,7 +92,7 @@ class Player {
/**
* the song currently being played
*/
Song *song;
DetachedSong *song;
/**
* is cross fading enabled?
......@@ -290,12 +290,12 @@ Player::StartDecoder(MusicPipe &_pipe)
assert(queued || pc.command == PlayerCommand::SEEK);
assert(pc.next_song != nullptr);
unsigned start_ms = pc.next_song->start_ms;
unsigned start_ms = pc.next_song->GetStartMS();
if (pc.command == PlayerCommand::SEEK)
start_ms += (unsigned)(pc.seek_where * 1000);
dc.Start(pc.next_song->DupDetached(),
start_ms, pc.next_song->end_ms,
dc.Start(new DetachedSong(*pc.next_song),
start_ms, pc.next_song->GetEndMS(),
buffer, _pipe);
}
......@@ -329,7 +329,7 @@ Player::WaitForDecoder()
if (error.IsDefined()) {
pc.SetError(PlayerError::DECODER, std::move(error));
pc.next_song->Free();
delete pc.next_song;
pc.next_song = nullptr;
pc.Unlock();
......@@ -339,9 +339,7 @@ Player::WaitForDecoder()
pc.ClearTaggedSong();
if (song != nullptr)
song->Free();
delete song;
song = pc.next_song;
elapsed_time = 0.0;
......@@ -370,17 +368,20 @@ Player::WaitForDecoder()
* indicated by the decoder plugin.
*/
static double
real_song_duration(const Song &song, double decoder_duration)
real_song_duration(const DetachedSong &song, double decoder_duration)
{
if (decoder_duration <= 0.0)
/* the decoder plugin didn't provide information; fall
back to Song::GetDuration() */
return song.GetDuration();
if (song.end_ms > 0 && song.end_ms / 1000.0 < decoder_duration)
return (song.end_ms - song.start_ms) / 1000.0;
const unsigned start_ms = song.GetStartMS();
const unsigned end_ms = song.GetEndMS();
if (end_ms > 0 && end_ms / 1000.0 < decoder_duration)
return (end_ms - start_ms) / 1000.0;
return decoder_duration - song.start_ms / 1000.0;
return decoder_duration - start_ms / 1000.0;
}
bool
......@@ -458,10 +459,10 @@ Player::CheckDecoderStartup()
decoder_starting = false;
if (!paused && !OpenOutput()) {
const auto uri = dc.song->GetURI();
FormatError(player_domain,
"problems opening audio device "
"while playing \"%s\"", uri.c_str());
"while playing \"%s\"",
dc.song->GetURI());
return true;
}
......@@ -516,7 +517,7 @@ Player::SeekDecoder()
{
assert(pc.next_song != nullptr);
const unsigned start_ms = pc.next_song->start_ms;
const unsigned start_ms = pc.next_song->GetStartMS();
if (!dc.LockIsCurrentSong(*pc.next_song)) {
/* the decoder is already decoding the "next" song -
......@@ -542,7 +543,7 @@ Player::SeekDecoder()
ClearAndReplacePipe(dc.pipe);
}
pc.next_song->Free();
delete pc.next_song;
pc.next_song = nullptr;
queued = false;
}
......@@ -658,7 +659,7 @@ Player::ProcessCommand()
pc.Lock();
}
pc.next_song->Free();
delete pc.next_song;
pc.next_song = nullptr;
queued = false;
pc.CommandFinished();
......@@ -681,17 +682,14 @@ Player::ProcessCommand()
}
static void
update_song_tag(PlayerControl &pc, Song &song, const Tag &new_tag)
update_song_tag(PlayerControl &pc, DetachedSong &song, const Tag &new_tag)
{
if (song.IsFile())
/* don't update tags of local files, only remote
streams may change tags dynamically */
return;
Tag *old_tag = song.tag;
song.tag = new Tag(new_tag);
delete old_tag;
song.SetTag(new_tag);
pc.LockSetTaggedSong(song);
......@@ -713,7 +711,7 @@ update_song_tag(PlayerControl &pc, Song &song, const Tag &new_tag)
*/
static bool
play_chunk(PlayerControl &pc,
Song &song, struct music_chunk *chunk,
DetachedSong &song, struct music_chunk *chunk,
MusicBuffer &buffer,
const AudioFormat format,
Error &error)
......@@ -880,10 +878,7 @@ Player::SongBorder()
{
xfade_state = CrossFadeState::UNKNOWN;
{
const auto uri = song->GetURI();
FormatDefault(player_domain, "played \"%s\"", uri.c_str());
}
FormatDefault(player_domain, "played \"%s\"", song->GetURI());
ReplacePipe(dc.pipe);
......@@ -1079,9 +1074,8 @@ Player::Run()
delete cross_fade_tag;
if (song != nullptr) {
const auto uri = song->GetURI();
FormatDefault(player_domain, "played \"%s\"", uri.c_str());
song->Free();
FormatDefault(player_domain, "played \"%s\"", song->GetURI());
delete song;
}
pc.Lock();
......@@ -1090,7 +1084,7 @@ Player::Run()
if (queued) {
assert(pc.next_song != nullptr);
pc.next_song->Free();
delete pc.next_song;
pc.next_song = nullptr;
}
......@@ -1139,10 +1133,8 @@ player_task(void *arg)
/* fall through */
case PlayerCommand::PAUSE:
if (pc.next_song != nullptr) {
pc.next_song->Free();
pc.next_song = nullptr;
}
delete pc.next_song;
pc.next_song = nullptr;
pc.CommandFinished();
break;
......@@ -1177,10 +1169,8 @@ player_task(void *arg)
return;
case PlayerCommand::CANCEL:
if (pc.next_song != nullptr) {
pc.next_song->Free();
pc.next_song = nullptr;
}
delete pc.next_song;
pc.next_song = nullptr;
pc.CommandFinished();
break;
......
......@@ -21,23 +21,23 @@
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "PlayerControl.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "Idle.hxx"
#include "Log.hxx"
#include <assert.h>
void
playlist::TagModified(Song &&song)
playlist::TagModified(DetachedSong &&song)
{
if (!playing || song.tag == nullptr)
if (!playing)
return;
assert(current >= 0);
Song &current_song = queue.GetOrder(current);
if (SongEquals(song, current_song))
current_song.ReplaceTag(std::move(*song.tag));
DetachedSong &current_song = queue.GetOrder(current);
if (song.IsSame(current_song))
current_song.MoveTagFrom(std::move(song));
queue.ModifyAtOrder(current);
queue.IncrementVersion();
......@@ -55,15 +55,12 @@ playlist_queue_song_order(playlist &playlist, PlayerControl &pc,
playlist.queued = order;
Song *song = playlist.queue.GetOrder(order).DupDetached();
const DetachedSong &song = playlist.queue.GetOrder(order);
{
const auto uri = song->GetURI();
FormatDebug(playlist_domain, "queue song %i:\"%s\"",
playlist.queued, uri.c_str());
}
FormatDebug(playlist_domain, "queue song %i:\"%s\"",
playlist.queued, song.GetURI());
pc.EnqueueSong(song);
pc.EnqueueSong(new DetachedSong(song));
}
/**
......@@ -88,7 +85,7 @@ playlist_song_started(playlist &playlist, PlayerControl &pc)
idle_add(IDLE_PLAYER);
}
const Song *
const DetachedSong *
playlist::GetQueuedSong() const
{
return playing && queued >= 0
......@@ -97,7 +94,7 @@ playlist::GetQueuedSong() const
}
void
playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev)
{
if (!playing)
return;
......@@ -124,7 +121,7 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
current = queue.PositionToOrder(current_position);
}
const Song *const next_song = next_order >= 0
const DetachedSong *const next_song = next_order >= 0
? &queue.GetOrder(next_order)
: nullptr;
......@@ -148,15 +145,11 @@ playlist::PlayOrder(PlayerControl &pc, int order)
playing = true;
queued = -1;
Song *song = queue.GetOrder(order).DupDetached();
const DetachedSong &song = queue.GetOrder(order);
{
const auto uri = song->GetURI();
FormatDebug(playlist_domain, "play %i:\"%s\"",
order, uri.c_str());
}
FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI());
pc.Play(song);
pc.Play(new DetachedSong(song));
current = order;
}
......@@ -173,7 +166,7 @@ playlist::SyncWithPlayer(PlayerControl &pc)
pc.Lock();
const PlayerState pc_state = pc.GetState();
const Song *pc_next_song = pc.next_song;
const DetachedSong *pc_next_song = pc.next_song;
pc.Unlock();
if (pc_state == PlayerState::STOP)
......@@ -286,7 +279,7 @@ playlist::SetRandom(PlayerControl &pc, bool status)
if (status == queue.random)
return;
const Song *const queued_song = GetQueuedSong();
const DetachedSong *const queued_song = GetQueuedSong();
queue.random = status;
......
......@@ -25,7 +25,7 @@
enum TagType : uint8_t;
struct PlayerControl;
struct Song;
class DetachedSong;
class Error;
struct playlist {
......@@ -100,7 +100,7 @@ struct playlist {
* none if there is none (yet?) or if MPD isn't playing.
*/
gcc_pure
const Song *GetQueuedSong() const;
const DetachedSong *GetQueuedSong() const;
/**
* This is the "PLAYLIST" event handler. It is invoked by the
......@@ -125,7 +125,7 @@ protected:
* @param prev the song which was previously queued, as
* determined by playlist_get_queued_song()
*/
void UpdateQueuedSong(PlayerControl &pc, const Song *prev);
void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev);
public:
void Clear(PlayerControl &pc);
......@@ -135,7 +135,7 @@ public:
* thread. Apply the given song's tag to the current song if
* the song matches.
*/
void TagModified(Song &&song);
void TagModified(DetachedSong &&song);
/**
* The database has been modified. Pull all updates.
......@@ -143,7 +143,7 @@ public:
void DatabaseModified();
PlaylistResult AppendSong(PlayerControl &pc,
Song *song,
DetachedSong &&song,
unsigned *added_id=nullptr);
/**
......@@ -162,7 +162,7 @@ public:
protected:
void DeleteInternal(PlayerControl &pc,
unsigned song, const Song **queued_p);
unsigned song, const DetachedSong **queued_p);
public:
PlaylistResult DeletePosition(PlayerControl &pc,
......@@ -184,7 +184,7 @@ public:
PlaylistResult DeleteRange(PlayerControl &pc,
unsigned start, unsigned end);
void DeleteSong(PlayerControl &pc, const Song &song);
void DeleteSong(PlayerControl &pc, const char *uri);
void Shuffle(PlayerControl &pc, unsigned start, unsigned end);
......
......@@ -26,7 +26,7 @@
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "PlayerControl.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "Log.hxx"
void
......@@ -195,7 +195,7 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
if (!queue.IsValidPosition(song))
return PlaylistResult::BAD_RANGE;
const Song *queued_song = GetQueuedSong();
const DetachedSong *queued_song = GetQueuedSong();
unsigned i = queue.random
? queue.PositionToOrder(song)
......@@ -215,8 +215,7 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
queued_song = nullptr;
}
Song *the_song = queue.GetOrder(i).DupDetached();
if (!pc.Seek(the_song, seek_time)) {
if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) {
UpdateQueuedSong(pc, queued_song);
return PlaylistResult::NOT_PLAYING;
......
......@@ -30,6 +30,7 @@
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "Idle.hxx"
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"
......@@ -64,23 +65,23 @@ playlist::AppendFile(PlayerControl &pc,
if (song == nullptr)
return PlaylistResult::NO_SUCH_SONG;
const auto result = AppendSong(pc, song, added_id);
const auto result = AppendSong(pc, DetachedSong(*song), added_id);
song->Free();
return result;
}
PlaylistResult
playlist::AppendSong(PlayerControl &pc,
Song *song, unsigned *added_id)
DetachedSong &&song, unsigned *added_id)
{
unsigned id;
if (queue.IsFull())
return PlaylistResult::TOO_LARGE;
const Song *const queued_song = GetQueuedSong();
const DetachedSong *const queued_song = GetQueuedSong();
id = queue.Append(song, 0);
id = queue.Append(std::move(song), 0);
if (queue.random) {
/* shuffle the new song into the list of remaining
......@@ -110,25 +111,24 @@ playlist::AppendURI(PlayerControl &pc,
{
FormatDebug(playlist_domain, "add to playlist: %s", uri);
const Database *db = nullptr;
Song *song;
DetachedSong *song;
if (uri_has_scheme(uri)) {
song = Song::NewRemote(uri);
song = new DetachedSong(uri);
} else {
db = GetDatabase();
const Database *db = GetDatabase();
if (db == nullptr)
return PlaylistResult::NO_SUCH_SONG;
song = db->GetSong(uri, IgnoreError());
if (song == nullptr)
Song *tmp = db->GetSong(uri, IgnoreError());
if (tmp == nullptr)
return PlaylistResult::NO_SUCH_SONG;
song = new DetachedSong(*tmp);
db->ReturnSong(tmp);
}
PlaylistResult result = AppendSong(pc, song, added_id);
if (db != nullptr)
db->ReturnSong(song);
else
song->Free();
PlaylistResult result = AppendSong(pc, std::move(*song), added_id);
delete song;
return result;
}
......@@ -139,7 +139,7 @@ playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2)
if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2))
return PlaylistResult::BAD_RANGE;
const Song *const queued_song = GetQueuedSong();
const DetachedSong *const queued_song = GetQueuedSong();
queue.SwapPositions(song1, song2);
......@@ -193,7 +193,7 @@ playlist::SetPriorityRange(PlayerControl &pc,
/* remember "current" and "queued" */
const int current_position = GetCurrentPosition();
const Song *const queued_song = GetQueuedSong();
const DetachedSong *const queued_song = GetQueuedSong();
/* apply the priority changes */
......@@ -225,7 +225,7 @@ playlist::SetPriorityId(PlayerControl &pc,
void
playlist::DeleteInternal(PlayerControl &pc,
unsigned song, const Song **queued_p)
unsigned song, const DetachedSong **queued_p)
{
assert(song < GetLength());
......@@ -275,7 +275,7 @@ playlist::DeletePosition(PlayerControl &pc, unsigned song)
if (song >= queue.GetLength())
return PlaylistResult::BAD_RANGE;
const Song *queued_song = GetQueuedSong();
const DetachedSong *queued_song = GetQueuedSong();
DeleteInternal(pc, song, &queued_song);
......@@ -297,7 +297,7 @@ playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end)
if (start >= end)
return PlaylistResult::SUCCESS;
const Song *queued_song = GetQueuedSong();
const DetachedSong *queued_song = GetQueuedSong();
do {
DeleteInternal(pc, --end, &queued_song);
......@@ -320,10 +320,10 @@ playlist::DeleteId(PlayerControl &pc, unsigned id)
}
void
playlist::DeleteSong(PlayerControl &pc, const struct Song &song)
playlist::DeleteSong(PlayerControl &pc, const char *uri)
{
for (int i = queue.GetLength() - 1; i >= 0; --i)
if (SongEquals(song, queue.Get(i)))
if (queue.Get(i).IsURI(uri))
DeletePosition(pc, i);
}
......@@ -341,7 +341,7 @@ playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
/* nothing happens */
return PlaylistResult::SUCCESS;
const Song *const queued_song = GetQueuedSong();
const DetachedSong *const queued_song = GetQueuedSong();
/*
* (to < 0) => move to offset from current song
......@@ -401,7 +401,7 @@ playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end)
/* needs at least two entries. */
return;
const Song *const queued_song = GetQueuedSong();
const DetachedSong *const queued_song = GetQueuedSong();
if (playing && current >= 0) {
unsigned current_position = queue.OrderToPosition(current);
......
......@@ -24,7 +24,7 @@
#include "PlaylistVector.hxx"
#include "DatabasePlugin.hxx"
#include "DatabaseGlue.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "Mapper.hxx"
#include "fs/TextFile.hxx"
#include "ConfigGlobal.hxx"
......@@ -361,7 +361,7 @@ spl_remove_index(const char *utf8path, unsigned pos, Error &error)
}
bool
spl_append_song(const char *utf8path, const Song &song, Error &error)
spl_append_song(const char *utf8path, const DetachedSong &song, Error &error)
{
if (spl_map(error).IsNull())
return false;
......@@ -402,22 +402,21 @@ bool
spl_append_uri(const char *url, const char *utf8file, Error &error)
{
if (uri_has_scheme(url)) {
Song *song = Song::NewRemote(url);
bool success = spl_append_song(utf8file, *song, error);
song->Free();
return success;
return spl_append_song(utf8file, DetachedSong(url),
error);
} else {
const Database *db = GetDatabase(error);
if (db == nullptr)
return false;
Song *song = db->GetSong(url, error);
if (song == nullptr)
Song *tmp = db->GetSong(url, error);
if (tmp == nullptr)
return false;
bool success = spl_append_song(utf8file, *song, error);
db->ReturnSong(song);
return success;
const DetachedSong song(*tmp);
db->ReturnSong(tmp);
return spl_append_song(utf8file, song, error);
}
}
......
......@@ -23,7 +23,7 @@
#include <vector>
#include <string>
struct Song;
class DetachedSong;
class PlaylistVector;
class Error;
......@@ -68,7 +68,7 @@ bool
spl_remove_index(const char *utf8path, unsigned pos, Error &error);
bool
spl_append_song(const char *utf8path, const Song &song, Error &error);
spl_append_song(const char *utf8path, const DetachedSong &song, Error &error);
bool
spl_append_uri(const char *file, const char *utf8file, Error &error);
......
......@@ -31,6 +31,7 @@
#include "Client.hxx"
#include "InputStream.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "fs/Traits.hxx"
#include "util/Error.hxx"
#include "thread/Cond.hxx"
......@@ -151,7 +152,7 @@ playlist_provider_print(Client &client, const char *uri,
? PathTraitsUTF8::GetParent(uri)
: std::string(".");
Song *song;
DetachedSong *song;
while ((song = e.NextSong()) != nullptr) {
song = playlist_check_translate_song(song, base_uri.c_str(),
false);
......@@ -163,7 +164,7 @@ playlist_provider_print(Client &client, const char *uri,
else
song_print_uri(client, *song);
song->Free();
delete song;
}
}
......
......@@ -24,7 +24,7 @@
#include "Playlist.hxx"
#include "InputStream.hxx"
#include "SongEnumerator.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "thread/Cond.hxx"
#include "fs/Traits.hxx"
......@@ -38,13 +38,13 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e,
? PathTraitsUTF8::GetParent(uri)
: std::string(".");
Song *song;
DetachedSong *song;
for (unsigned i = 0;
i < end_index && (song = e.NextSong()) != nullptr;
++i) {
if (i < start_index) {
/* skip songs before the start index */
song->Free();
delete song;
continue;
}
......@@ -53,8 +53,8 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e,
if (song == nullptr)
continue;
PlaylistResult result = dest.AppendSong(pc, song);
song->Free();
PlaylistResult result = dest.AppendSong(pc, std::move(*song));
delete song;
if (result != PlaylistResult::SUCCESS)
return result;
}
......
......@@ -23,6 +23,7 @@
#include "PlaylistError.hxx"
#include "Playlist.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "Mapper.hxx"
#include "Idle.hxx"
#include "fs/AllocatedPath.hxx"
......@@ -36,7 +37,7 @@
#include <string.h>
void
playlist_print_song(FILE *file, const Song &song)
playlist_print_song(FILE *file, const DetachedSong &song)
{
if (playlist_saveAbsolutePaths && song.IsInDatabase()) {
const auto path = map_song_fs(song);
......@@ -44,7 +45,7 @@ playlist_print_song(FILE *file, const Song &song)
fprintf(file, "%s\n", path.c_str());
} else {
const auto uri_utf8 = song.GetURI();
const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8.c_str());
const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8);
if (!uri_fs.IsNull())
fprintf(file, "%s\n", uri_fs.c_str());
......
......@@ -24,14 +24,14 @@
#include <stdio.h>
struct Song;
struct queue;
struct playlist;
struct PlayerControl;
class DetachedSong;
class Error;
void
playlist_print_song(FILE *fp, const Song &song);
playlist_print_song(FILE *file, const DetachedSong &song);
void
playlist_print_uri(FILE *fp, const char *uri);
......
......@@ -24,41 +24,42 @@
#include "DatabaseGlue.hxx"
#include "ls.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "DetachedSong.hxx"
#include "Song.hxx"
#include <assert.h>
#include <string.h>
static void
merge_song_metadata(Song *dest, const Song *base,
const Song *add)
merge_song_metadata(DetachedSong *dest, const DetachedSong *base,
const DetachedSong *add)
{
dest->tag = base->tag != nullptr
? (add->tag != nullptr
? Tag::Merge(*base->tag, *add->tag)
: new Tag(*base->tag))
: (add->tag != nullptr
? new Tag(*add->tag)
: nullptr);
dest->mtime = base->mtime;
dest->start_ms = add->start_ms;
dest->end_ms = add->end_ms;
{
TagBuilder builder(add->GetTag());
builder.Complement(base->GetTag());
dest->SetTag(builder.Commit());
}
dest->SetLastModified(base->GetLastModified());
dest->SetStartMS(add->GetStartMS());
dest->SetEndMS(add->GetEndMS());
}
static Song *
apply_song_metadata(Song *dest, const Song *src)
static DetachedSong *
apply_song_metadata(DetachedSong *dest, const DetachedSong *src)
{
Song *tmp;
DetachedSong *tmp;
assert(dest != nullptr);
assert(src != nullptr);
if (src->tag == nullptr && src->start_ms == 0 && src->end_ms == 0)
if (!src->GetTag().IsDefined() &&
src->GetStartMS() == 0 && src->GetEndMS() == 0)
return dest;
if (dest->IsInDatabase()) {
......@@ -70,37 +71,41 @@ apply_song_metadata(Song *dest, const Song *src)
if (path_utf8.empty())
path_utf8 = path_fs.c_str();
tmp = Song::NewFile(path_utf8.c_str(), nullptr);
tmp = new DetachedSong(std::move(path_utf8));
merge_song_metadata(tmp, dest, src);
} else {
tmp = Song::NewFile(dest->uri, nullptr);
tmp = new DetachedSong(dest->GetURI());
merge_song_metadata(tmp, dest, src);
}
if (dest->tag != nullptr && dest->tag->time > 0 &&
src->start_ms > 0 && src->end_ms == 0 &&
src->start_ms / 1000 < (unsigned)dest->tag->time)
if (dest->GetTag().IsDefined() && dest->GetTag().time > 0 &&
src->GetStartMS() > 0 && src->GetEndMS() == 0 &&
src->GetStartMS() / 1000 < (unsigned)dest->GetTag().time)
/* the range is open-ended, and the playlist plugin
did not know the total length of the song file
(e.g. last track on a CUE file); fix it up here */
tmp->tag->time = dest->tag->time - src->start_ms / 1000;
tmp->WritableTag().time =
dest->GetTag().time - src->GetStartMS() / 1000;
dest->Free();
delete dest;
return tmp;
}
static Song *
playlist_check_load_song(const Song *song, const char *uri, bool secure)
static DetachedSong *
playlist_check_load_song(const DetachedSong *song, const char *uri, bool secure)
{
Song *dest;
DetachedSong *dest;
if (uri_has_scheme(uri)) {
dest = Song::NewRemote(uri);
dest = new DetachedSong(uri);
} else if (PathTraitsUTF8::IsAbsolute(uri) && secure) {
dest = Song::LoadFile(uri, nullptr);
if (dest == nullptr)
Song *tmp = Song::LoadFile(uri, nullptr);
if (tmp == nullptr)
return nullptr;
dest = new DetachedSong(*tmp);
delete tmp;
} else {
const Database *db = GetDatabase();
if (db == nullptr)
......@@ -111,22 +116,22 @@ playlist_check_load_song(const Song *song, const char *uri, bool secure)
/* not found in database */
return nullptr;
dest = tmp->DupDetached();
dest = new DetachedSong(*tmp);
db->ReturnSong(tmp);
}
return apply_song_metadata(dest, song);
}
Song *
playlist_check_translate_song(Song *song, const char *base_uri,
DetachedSong *
playlist_check_translate_song(DetachedSong *song, const char *base_uri,
bool secure)
{
if (song->IsInDatabase())
/* already ok */
return song;
const char *uri = song->uri;
const char *uri = song->GetURI();
if (uri_has_scheme(uri)) {
if (uri_supported_scheme(uri))
......@@ -134,7 +139,7 @@ playlist_check_translate_song(Song *song, const char *base_uri,
return song;
else {
/* unsupported remote song */
song->Free();
delete song;
return nullptr;
}
}
......@@ -156,7 +161,7 @@ playlist_check_translate_song(Song *song, const char *base_uri,
else if (!secure) {
/* local files must be relative to the music
directory when "secure" is enabled */
song->Free();
delete song;
return nullptr;
}
......@@ -169,8 +174,8 @@ playlist_check_translate_song(Song *song, const char *base_uri,
uri = full_uri.c_str();
}
Song *dest = playlist_check_load_song(song, uri, secure);
song->Free();
DetachedSong *dest = playlist_check_load_song(song, uri, secure);
delete song;
return dest;
}
......@@ -20,7 +20,7 @@
#ifndef MPD_PLAYLIST_SONG_HXX
#define MPD_PLAYLIST_SONG_HXX
struct Song;
class DetachedSong;
/**
* Verifies the song, returns nullptr if it is unsafe. Translate the
......@@ -30,8 +30,8 @@ struct Song;
* @param secure if true, then local files are only allowed if they
* are relative to base_uri
*/
Song *
playlist_check_translate_song(Song *song, const char *base_uri,
DetachedSong *
playlist_check_translate_song(DetachedSong *song, const char *base_uri,
bool secure);
#endif
......@@ -26,7 +26,7 @@
#include "config.h"
#include "Playlist.hxx"
#include "PlaylistError.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
......@@ -42,22 +42,19 @@ playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value,
return false;
}
Song &song = queue.Get(position);
DetachedSong &song = queue.Get(position);
if (song.IsFile()) {
error.Set(playlist_domain, int(PlaylistResult::DENIED),
"Cannot edit tags of local file");
return false;
}
TagBuilder tag;
if (song.tag != nullptr) {
tag = std::move(*song.tag);
delete song.tag;
{
TagBuilder tag(std::move(song.WritableTag()));
tag.AddItem(tag_type, value);
song.SetTag(tag.Commit());
}
tag.AddItem(tag_type, value);
song.tag = tag.CommitNew();
queue.ModifyAtPosition(position);
OnModified();
return true;
......@@ -74,24 +71,21 @@ playlist::ClearSongIdTag(unsigned id, TagType tag_type,
return false;
}
Song &song = queue.Get(position);
DetachedSong &song = queue.Get(position);
if (song.IsFile()) {
error.Set(playlist_domain, int(PlaylistResult::DENIED),
"Cannot edit tags of local file");
return false;
}
if (song.tag == nullptr)
return true;
TagBuilder tag(std::move(*song.tag));
delete song.tag;
if (tag_type == TAG_NUM_OF_ITEM_TYPES)
tag.RemoveAll();
else
tag.RemoveType(tag_type);
song.tag = tag.CommitNew();
{
TagBuilder tag(std::move(song.WritableTag()));
if (tag_type == TAG_NUM_OF_ITEM_TYPES)
tag.RemoveAll();
else
tag.RemoveType(tag_type);
song.SetTag(tag.Commit());
}
queue.ModifyAtPosition(position);
OnModified();
......
......@@ -22,35 +22,36 @@
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
#include "util/Error.hxx"
static bool
UpdatePlaylistSong(const Database &db, Song &song)
UpdatePlaylistSong(const Database &db, DetachedSong &song)
{
if (!song.IsInDatabase() || !song.IsDetached())
if (!song.IsInDatabase())
/* only update Songs instances that are "detached"
from the Database */
return false;
Song *original = db.GetSong(song.uri, IgnoreError());
Song *original = db.GetSong(song.GetURI(), IgnoreError());
if (original == nullptr)
/* not found - shouldn't happen, because the update
thread should ensure that all stale Song instances
have been purged */
return false;
if (original->mtime == song.mtime) {
if (original->mtime == song.GetLastModified()) {
/* not modified */
db.ReturnSong(original);
return false;
}
song.mtime = original->mtime;
song.SetLastModified(original->mtime);
if (original->tag != nullptr)
song.ReplaceTag(Tag(*original->tag));
song.SetTag(*original->tag);
db.ReturnSong(original);
return true;
......
......@@ -19,7 +19,7 @@
#include "config.h"
#include "Queue.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
queue::queue(unsigned _max_length)
:max_length(_max_length), length(0),
......@@ -84,7 +84,7 @@ queue::ModifyAtOrder(unsigned _order)
}
unsigned
queue::Append(Song *song, uint8_t priority)
queue::Append(DetachedSong &&song, uint8_t priority)
{
assert(!IsFull());
......@@ -92,7 +92,7 @@ queue::Append(Song *song, uint8_t priority)
const unsigned id = id_table.Insert(position);
auto &item = items[position];
item.song = song->DupDetached();
item.song = new DetachedSong(std::move(song));
item.id = id;
item.version = version;
item.priority = priority;
......@@ -219,11 +219,7 @@ queue::DeletePosition(unsigned position)
{
assert(position < length);
{
Song &song = Get(position);
assert(!song.IsInDatabase() || song.IsDetached());
song.Free();
}
delete items[position].song;
const unsigned id = PositionToId(position);
const unsigned _order = PositionToOrder(position);
......@@ -257,9 +253,7 @@ queue::Clear()
for (unsigned i = 0; i < length; i++) {
Item *item = &items[i];
assert(!item->song->IsInDatabase() ||
item->song->IsDetached());
item->song->Free();
delete item->song;
id_table.Erase(item->id);
}
......
......@@ -29,7 +29,7 @@
#include <assert.h>
#include <stdint.h>
struct Song;
class DetachedSong;
/**
* A queue of songs. This is the backend of the playlist: it contains
......@@ -53,7 +53,7 @@ struct queue {
* information attached.
*/
struct Item {
Song *song;
DetachedSong *song;
/** the unique id of this item in the queue */
unsigned id;
......@@ -200,7 +200,7 @@ struct queue {
/**
* Returns the song at the specified position.
*/
Song &Get(unsigned position) const {
DetachedSong &Get(unsigned position) const {
assert(position < length);
return *items[position].song;
......@@ -209,7 +209,7 @@ struct queue {
/**
* Returns the song at the specified order number.
*/
Song &GetOrder(unsigned _order) const {
DetachedSong &GetOrder(unsigned _order) const {
return Get(OrderToPosition(_order));
}
......@@ -268,7 +268,7 @@ struct queue {
*
* @param priority the priority of this new queue item
*/
unsigned Append(Song *song, uint8_t priority);
unsigned Append(DetachedSong &&song, uint8_t priority);
/**
* Swaps two songs, addressed by their position.
......
......@@ -94,7 +94,7 @@ queue_find(Client &client, const queue &queue,
const SongFilter &filter)
{
for (unsigned i = 0; i < queue.GetLength(); i++) {
const Song &song = queue.Get(i);
const DetachedSong &song = queue.Get(i);
if (filter.Match(song))
queue_print_song_info(client, queue, i);
......
......@@ -21,7 +21,7 @@
#include "QueueSave.hxx"
#include "Queue.hxx"
#include "PlaylistError.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "SongSave.hxx"
#include "DatabasePlugin.hxx"
#include "DatabaseGlue.hxx"
......@@ -37,20 +37,19 @@
#define PRIO_LABEL "Prio: "
static void
queue_save_database_song(FILE *fp, int idx, const Song &song)
queue_save_database_song(FILE *fp, int idx, const DetachedSong &song)
{
const auto uri = song.GetURI();
fprintf(fp, "%i:%s\n", idx, uri.c_str());
fprintf(fp, "%i:%s\n", idx, song.GetURI());
}
static void
queue_save_full_song(FILE *fp, const Song &song)
queue_save_full_song(FILE *fp, const DetachedSong &song)
{
song_save(fp, song);
}
static void
queue_save_song(FILE *fp, int idx, const Song &song)
queue_save_song(FILE *fp, int idx, const DetachedSong &song)
{
if (song.IsInDatabase())
queue_save_database_song(fp, idx, song);
......@@ -85,8 +84,7 @@ queue_load_song(TextFile &file, const char *line, queue &queue)
return;
}
const Database *db = nullptr;
Song *song;
DetachedSong *song;
if (StringStartsWith(line, SONG_BEGIN)) {
const char *uri = line + sizeof(SONG_BEGIN) - 1;
......@@ -94,7 +92,7 @@ queue_load_song(TextFile &file, const char *line, queue &queue)
return;
Error error;
song = song_load(file, nullptr, uri, error);
song = song_load(file, uri, error);
if (song == nullptr) {
LogError(error);
return;
......@@ -111,22 +109,21 @@ queue_load_song(TextFile &file, const char *line, queue &queue)
const char *uri = endptr + 1;
if (uri_has_scheme(uri)) {
song = Song::NewRemote(uri);
song = new DetachedSong(uri);
} else {
db = GetDatabase();
const Database *db = GetDatabase();
if (db == nullptr)
return;
song = db->GetSong(uri, IgnoreError());
if (song == nullptr)
Song *tmp = db->GetSong(uri, IgnoreError());
if (tmp == nullptr)
return;
song = new DetachedSong(*tmp);
db->ReturnSong(tmp);
}
}
queue.Append(song, priority);
if (db != nullptr)
db->ReturnSong(song);
else
song->Free();
queue.Append(std::move(*song), priority);
delete song;
}
......@@ -22,13 +22,12 @@
#include "Directory.hxx"
#include "tag/Tag.hxx"
#include "util/Alloc.hxx"
#include "DetachedSong.hxx"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
Directory detached_root;
static Song *
song_alloc(const char *uri, Directory *parent)
{
......@@ -51,57 +50,22 @@ song_alloc(const char *uri, Directory *parent)
}
Song *
Song::NewRemote(const char *uri)
Song::NewFrom(DetachedSong &&other, Directory *parent)
{
return song_alloc(uri, nullptr);
Song *song = song_alloc(other.GetURI(), parent);
song->tag = new Tag(std::move(other.WritableTag()));
song->mtime = other.GetLastModified();
song->start_ms = other.GetStartMS();
song->end_ms = other.GetEndMS();
return song;
}
Song *
Song::NewFile(const char *path, Directory *parent)
{
assert((parent == nullptr) == (*path == '/'));
return song_alloc(path, parent);
}
Song *
Song::ReplaceURI(const char *new_uri)
{
Song *new_song = song_alloc(new_uri, parent);
new_song->tag = tag;
new_song->mtime = mtime;
new_song->start_ms = start_ms;
new_song->end_ms = end_ms;
free(this);
return new_song;
}
Song *
Song::NewDetached(const char *uri)
{
assert(uri != nullptr);
return song_alloc(uri, &detached_root);
}
Song *
Song::DupDetached() const
{
Song *song;
if (IsInDatabase()) {
const auto new_uri = GetURI();
song = NewDetached(new_uri.c_str());
} else
song = song_alloc(uri, nullptr);
song->tag = tag != nullptr ? new Tag(*tag) : nullptr;
song->mtime = mtime;
song->start_ms = start_ms;
song->end_ms = end_ms;
return song;
}
void
Song::Free()
{
......@@ -109,54 +73,12 @@ Song::Free()
free(this);
}
void
Song::ReplaceTag(Tag &&_tag)
{
if (tag == nullptr)
tag = new Tag();
*tag = std::move(_tag);
}
gcc_pure
static inline bool
directory_equals(const Directory &a, const Directory &b)
{
return strcmp(a.path, b.path) == 0;
}
gcc_pure
static inline bool
directory_is_same(const Directory *a, const Directory *b)
{
return a == b ||
(a != nullptr && b != nullptr &&
directory_equals(*a, *b));
}
bool
SongEquals(const Song &a, const Song &b)
{
if (a.parent != nullptr && b.parent != nullptr &&
!directory_equals(*a.parent, *b.parent) &&
(a.parent == &detached_root || b.parent == &detached_root)) {
/* must compare the full URI if one of the objects is
"detached" */
const auto au = a.GetURI();
const auto bu = b.GetURI();
return au == bu;
}
return directory_is_same(a.parent, b.parent) &&
strcmp(a.uri, b.uri) == 0;
}
std::string
Song::GetURI() const
{
assert(*uri);
if (!IsInDatabase() || parent->IsRoot())
if (parent == nullptr || parent->IsRoot())
return std::string(uri);
else {
const char *path = parent->GetPath();
......
......@@ -32,13 +32,12 @@
#define SONG_TIME "Time: "
struct Tag;
struct Directory;
class DetachedSong;
/**
* A dummy #directory instance that is used for "detached" song
* copies.
* A song file inside the configured music directory.
*/
extern struct Directory detached_root;
struct Song {
/**
* Pointers to the siblings of this directory within the
......@@ -51,7 +50,14 @@ struct Song {
struct list_head siblings;
Tag *tag;
/**
* The #Directory that contains this song. May be nullptr if
* the current database plugin does not manage the parent
* directory this way.
*/
Directory *parent;
time_t mtime;
/**
......@@ -65,11 +71,14 @@ struct Song {
*/
unsigned end_ms;
/**
* The file name. If #parent is nullptr, then this is the URI
* relative to the music directory.
*/
char uri[sizeof(int)];
/** allocate a new song with a remote URL */
gcc_malloc
static Song *NewRemote(const char *uri);
static Song *NewFrom(DetachedSong &&other, Directory *parent);
/** allocate a new song with a local file name */
gcc_malloc
......@@ -87,47 +96,8 @@ struct Song {
return LoadFile(path_utf8, &parent);
}
/**
* Replaces the URI of a song object. The given song object
* is destroyed, and a newly allocated one is returned. It
* does not update the reference within the parent directory;
* the caller is responsible for doing that.
*/
gcc_malloc
Song *ReplaceURI(const char *uri);
/**
* Creates a "detached" song object.
*/
gcc_malloc
static Song *NewDetached(const char *uri);
/**
* Creates a duplicate of the song object. If the object is
* in the database, it creates a "detached" copy of this song,
* see Song::IsDetached().
*/
gcc_malloc
Song *DupDetached() const;
void Free();
bool IsInDatabase() const {
return parent != nullptr;
}
bool IsFile() const {
return IsInDatabase() || uri[0] == '/';
}
bool IsDetached() const {
assert(IsInDatabase());
return parent == &detached_root;
}
void ReplaceTag(Tag &&tag);
bool UpdateFile();
bool UpdateFileInArchive();
......@@ -142,11 +112,4 @@ struct Song {
double GetDuration() const;
};
/**
* Returns true if both objects refer to the same physical song.
*/
gcc_pure
bool
SongEquals(const Song &a, const Song &b);
#endif
......@@ -20,7 +20,7 @@
#ifndef MPD_SONG_ENUMERATOR_HXX
#define MPD_SONG_ENUMERATOR_HXX
struct Song;
class DetachedSong;
/**
* An object which provides serial access to a number of #Song
......@@ -35,7 +35,7 @@ public:
* freeing the returned #Song object. Returns nullptr if
* there are no more songs.
*/
virtual Song *NextSong() = 0;
virtual DetachedSong *NextSong() = 0;
};
#endif
......@@ -20,6 +20,7 @@
#include "config.h"
#include "SongFilter.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include "util/ASCII.hxx"
#include "util/UriUtil.hxx"
......@@ -151,6 +152,18 @@ SongFilter::Item::Match(const Song &song) const
return song.tag != NULL && Match(*song.tag);
}
bool
SongFilter::Item::Match(const DetachedSong &song) const
{
if (tag == LOCATE_TAG_BASE_TYPE)
return uri_is_child_or_same(value.c_str(), song.GetURI());
if (tag == LOCATE_TAG_FILE_TYPE)
return StringMatch(song.GetURI());
return Match(song.GetTag());
}
SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case)
{
items.push_back(Item(tag, value, fold_case));
......@@ -203,6 +216,16 @@ SongFilter::Match(const Song &song) const
return true;
}
bool
SongFilter::Match(const DetachedSong &song) const
{
for (const auto &i : items)
if (!i.Match(song))
return false;
return true;
}
std::string
SongFilter::GetBase() const
{
......
......@@ -38,6 +38,7 @@
struct Tag;
struct TagItem;
struct Song;
class DetachedSong;
class SongFilter {
public:
......@@ -80,6 +81,9 @@ public:
gcc_pure
bool Match(const Song &song) const;
gcc_pure
bool Match(const DetachedSong &song) const;
};
private:
......@@ -105,6 +109,9 @@ public:
gcc_pure
bool Match(const Song &song) const;
gcc_pure
bool Match(const DetachedSong &song) const;
const std::list<Item> &GetItems() const {
return items;
}
......
/*
* Copyright (C) 2003-2013 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_SONG_POINTER_HXX
#define MPD_SONG_POINTER_HXX
#include "Song.hxx"
#include <utility>
class SongPointer {
Song *song;
public:
explicit SongPointer(Song *_song)
:song(_song) {}
SongPointer(const SongPointer &) = delete;
SongPointer(SongPointer &&other):song(other.song) {
other.song = nullptr;
}
~SongPointer() {
if (song != nullptr)
song->Free();
}
SongPointer &operator=(const SongPointer &) = delete;
SongPointer &operator=(SongPointer &&other) {
std::swap(song, other.song);
return *this;
}
operator const Song *() const {
return song;
}
Song *Steal() {
auto result = song;
song = nullptr;
return result;
}
};
#endif
......@@ -20,6 +20,7 @@
#include "config.h"
#include "SongPrint.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "Directory.hxx"
#include "TimePrint.hxx"
#include "TagPrint.hxx"
......@@ -27,21 +28,31 @@
#include "Client.hxx"
#include "util/UriUtil.hxx"
static void
song_print_uri(Client &client, const char *uri)
{
const std::string allocated = uri_remove_auth(uri);
if (!allocated.empty())
uri = allocated.c_str();
client_printf(client, "%s%s\n", SONG_FILE,
map_to_relative_path(uri));
}
void
song_print_uri(Client &client, const Song &song)
{
if (song.IsInDatabase() && !song.parent->IsRoot()) {
if (song.parent != nullptr && !song.parent->IsRoot()) {
client_printf(client, "%s%s/%s\n", SONG_FILE,
song.parent->GetPath(), song.uri);
} else {
const char *uri = song.uri;
const std::string allocated = uri_remove_auth(uri);
if (!allocated.empty())
uri = allocated.c_str();
} else
song_print_uri(client, song.uri);
}
client_printf(client, "%s%s\n", SONG_FILE,
map_to_relative_path(uri));
}
void
song_print_uri(Client &client, const DetachedSong &song)
{
song_print_uri(client, song.GetURI());
}
void
......@@ -66,3 +77,28 @@ song_print_info(Client &client, const Song &song)
if (song.tag != nullptr)
tag_print(client, *song.tag);
}
void
song_print_info(Client &client, const DetachedSong &song)
{
song_print_uri(client, song);
const unsigned start_ms = song.GetStartMS();
const unsigned end_ms = song.GetEndMS();
if (end_ms > 0)
client_printf(client, "Range: %u.%03u-%u.%03u\n",
start_ms / 1000,
start_ms % 1000,
end_ms / 1000,
end_ms % 1000);
else if (start_ms > 0)
client_printf(client, "Range: %u.%03u-\n",
start_ms / 1000,
start_ms % 1000);
if (song.GetLastModified() > 0)
time_print(client, "Last-Modified", song.GetLastModified());
tag_print(client, song.GetTag());
}
......@@ -21,12 +21,19 @@
#define MPD_SONG_PRINT_HXX
struct Song;
class DetachedSong;
class Client;
void
song_print_info(Client &client, const DetachedSong &song);
void
song_print_info(Client &client, const Song &song);
void
song_print_uri(Client &client, const Song &song);
void
song_print_uri(Client &client, const DetachedSong &song);
#endif
......@@ -20,6 +20,7 @@
#include "config.h"
#include "SongSave.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "TagSave.hxx"
#include "fs/TextFile.hxx"
#include "tag/Tag.hxx"
......@@ -36,15 +37,21 @@
static constexpr Domain song_save_domain("song_save");
static void
range_save(FILE *file, unsigned start_ms, unsigned end_ms)
{
if (end_ms > 0)
fprintf(file, "Range: %u-%u\n", start_ms, end_ms);
else if (start_ms > 0)
fprintf(file, "Range: %u-\n", start_ms);
}
void
song_save(FILE *fp, const Song &song)
{
fprintf(fp, SONG_BEGIN "%s\n", song.uri);
if (song.end_ms > 0)
fprintf(fp, "Range: %u-%u\n", song.start_ms, song.end_ms);
else if (song.start_ms > 0)
fprintf(fp, "Range: %u-\n", song.start_ms);
range_save(fp, song.start_ms, song.end_ms);
if (song.tag != nullptr)
tag_save(fp, *song.tag);
......@@ -53,13 +60,24 @@ song_save(FILE *fp, const Song &song)
fprintf(fp, SONG_END "\n");
}
Song *
song_load(TextFile &file, Directory *parent, const char *uri,
void
song_save(FILE *fp, const DetachedSong &song)
{
fprintf(fp, SONG_BEGIN "%s\n", song.GetURI());
range_save(fp, song.GetStartMS(), song.GetEndMS());
tag_save(fp, song.GetTag());
fprintf(fp, SONG_MTIME ": %li\n", (long)song.GetLastModified());
fprintf(fp, SONG_END "\n");
}
DetachedSong *
song_load(TextFile &file, const char *uri,
Error &error)
{
Song *song = parent != nullptr
? Song::NewFile(uri, parent)
: Song::NewRemote(uri);
DetachedSong *song = new DetachedSong(uri);
TagBuilder tag;
......@@ -68,7 +86,7 @@ song_load(TextFile &file, Directory *parent, const char *uri,
strcmp(line, SONG_END) != 0) {
char *colon = strchr(line, ':');
if (colon == nullptr || colon == line) {
song->Free();
delete song;
error.Format(song_save_domain,
"unknown line in db: %s", line);
......@@ -86,15 +104,19 @@ song_load(TextFile &file, Directory *parent, const char *uri,
} else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) {
song->mtime = atoi(value);
song->SetLastModified(atoi(value));
} else if (strcmp(line, "Range") == 0) {
char *endptr;
song->start_ms = strtoul(value, &endptr, 10);
if (*endptr == '-')
song->end_ms = strtoul(endptr + 1, nullptr, 10);
unsigned start_ms = strtoul(value, &endptr, 10);
unsigned end_ms = *endptr == '-'
? strtoul(endptr + 1, nullptr, 10)
: 0;
song->SetStartMS(start_ms);
song->SetEndMS(end_ms);
} else {
song->Free();
delete song;
error.Format(song_save_domain,
"unknown line in db: %s", line);
......@@ -102,8 +124,6 @@ song_load(TextFile &file, Directory *parent, const char *uri,
}
}
if (tag.IsDefined())
song->tag = tag.CommitNew();
song->SetTag(tag.Commit());
return song;
}
......@@ -26,12 +26,16 @@
struct Song;
struct Directory;
class DetachedSong;
class TextFile;
class Error;
void
song_save(FILE *fp, const Song &song);
void
song_save(FILE *fp, const DetachedSong &song);
/**
* Loads a song from the input file. Reading stops after the
* "song_end" line.
......@@ -39,8 +43,8 @@ song_save(FILE *fp, const Song &song);
* @param error location to store the error occurring
* @return true on success, false on error
*/
Song *
song_load(TextFile &file, Directory *parent, const char *uri,
DetachedSong *
song_load(TextFile &file, const char *uri,
Error &error);
#endif
......@@ -31,8 +31,6 @@
std::string
sticker_song_get_value(const Song &song, const char *name)
{
assert(song.IsInDatabase());
const auto uri = song.GetURI();
return sticker_load_value("song", uri.c_str(), name);
}
......@@ -41,8 +39,6 @@ bool
sticker_song_set_value(const Song &song,
const char *name, const char *value)
{
assert(song.IsInDatabase());
const auto uri = song.GetURI();
return sticker_store_value("song", uri.c_str(), name, value);
}
......@@ -50,8 +46,6 @@ sticker_song_set_value(const Song &song,
bool
sticker_song_delete(const Song &song)
{
assert(song.IsInDatabase());
const auto uri = song.GetURI();
return sticker_delete("song", uri.c_str());
}
......@@ -59,8 +53,6 @@ sticker_song_delete(const Song &song)
bool
sticker_song_delete_value(const Song &song, const char *name)
{
assert(song.IsInDatabase());
const auto uri = song.GetURI();
return sticker_delete_value("song", uri.c_str(), name);
}
......@@ -68,8 +60,6 @@ sticker_song_delete_value(const Song &song, const char *name)
struct sticker *
sticker_song_get(const Song &song)
{
assert(song.IsInDatabase());
const auto uri = song.GetURI();
return sticker_load("song", uri.c_str());
}
......
......@@ -78,8 +78,6 @@ tag_scan_fallback(Path path,
bool
Song::UpdateFile()
{
assert(IsFile());
const auto path_fs = map_song_fs(*this);
if (path_fs.IsNull())
return false;
......@@ -107,13 +105,9 @@ Song::UpdateFile()
bool
Song::UpdateFileInArchive()
{
const char *suffix;
assert(IsFile());
/* check if there's a suffix and a plugin */
suffix = uri_get_suffix(uri);
const char *suffix = uri_get_suffix(uri);
if (suffix == nullptr)
return false;
......
......@@ -60,7 +60,10 @@ song_remove_event(void)
sticker_song_delete(*removed_song);
#endif
instance->DeleteSong(*removed_song);
{
const auto uri = removed_song->GetURI();
instance->DeleteSong(uri.c_str());
}
/* clear "removed_song" and send signal to update thread */
remove_mutex.lock();
......
......@@ -22,7 +22,7 @@
#include "util/Alloc.hxx"
#include "util/StringUtil.hxx"
#include "util/CharUtil.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include <assert.h>
......@@ -38,14 +38,9 @@ CueParser::CueParser()
CueParser::~CueParser()
{
if (current != nullptr)
current->Free();
if (previous != nullptr)
previous->Free();
if (finished != nullptr)
finished->Free();
delete current;
delete previous;
delete finished;
}
static const char *
......@@ -169,8 +164,8 @@ CueParser::Commit()
if (current == nullptr)
return;
assert(current->tag == nullptr);
current->tag = song_tag.CommitNew();
assert(!current->GetTag().IsDefined());
current->SetTag(song_tag.Commit());
finished = previous;
previous = current;
......@@ -249,8 +244,8 @@ CueParser::Feed2(char *p)
}
state = TRACK;
current = Song::NewRemote(filename.c_str());
assert(current->tag == nullptr);
current = new DetachedSong(std::move(filename));
assert(!current->GetTag().IsDefined());
song_tag = header_tag;
song_tag.AddItem(TAG_TRACK, nr);
......@@ -272,14 +267,14 @@ CueParser::Feed2(char *p)
return;
if (!last_updated && previous != nullptr &&
previous->start_ms < (unsigned)position_ms) {
previous->GetStartMS() < (unsigned)position_ms) {
last_updated = true;
previous->end_ms = position_ms;
previous->tag->time =
(previous->end_ms - previous->start_ms + 500) / 1000;
previous->SetEndMS(position_ms);
previous->WritableTag().time =
(previous->GetEndMS() - previous->GetStartMS() + 500) / 1000;
}
current->start_ms = position_ms;
current->SetStartMS(position_ms);
}
}
......@@ -305,7 +300,7 @@ CueParser::Finish()
end = true;
}
Song *
DetachedSong *
CueParser::Get()
{
if (finished == nullptr && end) {
......@@ -317,7 +312,7 @@ CueParser::Get()
previous = nullptr;
}
Song *song = finished;
DetachedSong *song = finished;
finished = nullptr;
return song;
}
......@@ -26,7 +26,7 @@
#include <string>
struct Song;
class DetachedSong;
struct Tag;
class CueParser {
......@@ -74,19 +74,19 @@ class CueParser {
/**
* The song currently being edited.
*/
Song *current;
DetachedSong *current;
/**
* The previous song. It is remembered because its end_time
* will be set to the current song's start time.
*/
Song *previous;
DetachedSong *previous;
/**
* A song that is completely finished and can be returned to
* the caller via cue_parser_get().
*/
Song *finished;
DetachedSong *finished;
/**
* Set to true after previous.end_time has been updated to the
......@@ -125,7 +125,7 @@ public:
* @return a song object that must be freed by the caller, or NULL if
* no song was finished at this time
*/
Song *Get();
DetachedSong *Get();
private:
gcc_pure
......
......@@ -340,20 +340,19 @@ void
ProxyDatabase::ReturnSong(Song *song) const
{
assert(song != nullptr);
assert(song->IsInDatabase());
assert(song->IsDetached());
assert(song->parent == nullptr);
song->Free();
}
static bool
Visit(struct mpd_connection *connection, const char *uri,
Visit(struct mpd_connection *connection, Directory &root, const char *uri,
bool recursive, const SongFilter *filter,
VisitDirectory visit_directory, VisitSong visit_song,
VisitPlaylist visit_playlist, Error &error);
static bool
Visit(struct mpd_connection *connection,
Visit(struct mpd_connection *connection, Directory &root,
bool recursive, const SongFilter *filter,
const struct mpd_directory *directory,
VisitDirectory visit_directory, VisitSong visit_song,
......@@ -362,7 +361,7 @@ Visit(struct mpd_connection *connection,
const char *path = mpd_directory_get_path(directory);
if (visit_directory) {
Directory *d = Directory::NewGeneric(path, &detached_root);
Directory *d = Directory::NewGeneric(path, &root);
bool success = visit_directory(*d, error);
d->Free();
if (!success)
......@@ -370,7 +369,7 @@ Visit(struct mpd_connection *connection,
}
if (recursive &&
!Visit(connection, path, recursive, filter,
!Visit(connection, root, path, recursive, filter,
visit_directory, visit_song, visit_playlist, error))
return false;
......@@ -394,7 +393,7 @@ Copy(TagBuilder &tag, TagType d_tag,
static Song *
Convert(const struct mpd_song *song)
{
Song *s = Song::NewDetached(mpd_song_get_uri(song));
Song *s = Song::NewFile(mpd_song_get_uri(song), nullptr);
s->mtime = mpd_song_get_last_modified(song);
s->start_ms = mpd_song_get_start(song) * 1000;
......@@ -434,7 +433,7 @@ Visit(const SongFilter *filter,
}
static bool
Visit(const struct mpd_playlist *playlist,
Visit(const struct mpd_playlist *playlist, Directory &root,
VisitPlaylist visit_playlist, Error &error)
{
if (!visit_playlist)
......@@ -443,7 +442,7 @@ Visit(const struct mpd_playlist *playlist,
PlaylistInfo p(mpd_playlist_get_path(playlist),
mpd_playlist_get_last_modified(playlist));
return visit_playlist(p, detached_root, error);
return visit_playlist(p, root, error);
}
class ProxyEntity {
......@@ -485,7 +484,7 @@ ReceiveEntities(struct mpd_connection *connection)
}
static bool
Visit(struct mpd_connection *connection, const char *uri,
Visit(struct mpd_connection *connection, Directory &root, const char *uri,
bool recursive, const SongFilter *filter,
VisitDirectory visit_directory, VisitSong visit_song,
VisitPlaylist visit_playlist, Error &error)
......@@ -503,7 +502,7 @@ Visit(struct mpd_connection *connection, const char *uri,
break;
case MPD_ENTITY_TYPE_DIRECTORY:
if (!Visit(connection, recursive, filter,
if (!Visit(connection, root, recursive, filter,
mpd_entity_get_directory(entity),
visit_directory, visit_song, visit_playlist,
error))
......@@ -518,7 +517,7 @@ Visit(struct mpd_connection *connection, const char *uri,
break;
case MPD_ENTITY_TYPE_PLAYLIST:
if (!Visit(mpd_entity_get_playlist(entity),
if (!Visit(mpd_entity_get_playlist(entity), root,
visit_playlist, error))
return false;
break;
......@@ -577,7 +576,7 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
return ::SearchSongs(connection, selection, visit_song, error);
/* fall back to recursive walk (slow!) */
return ::Visit(connection, selection.uri.c_str(),
return ::Visit(connection, *root, selection.uri.c_str(),
selection.recursive, selection.filter,
visit_directory, visit_song, visit_playlist,
error);
......
......@@ -43,7 +43,7 @@ struct AsxParser {
* The list of songs (in reverse order because that's faster
* while adding).
*/
std::forward_list<SongPointer> songs;
std::forward_list<DetachedSong> songs;
/**
* The current position in the XML file.
......@@ -60,10 +60,9 @@ struct AsxParser {
TagType tag_type;
/**
* The current song. It is allocated after the "location"
* element.
* The current song URI. It is set by the "ref" element.
*/
Song *song;
std::string location;
TagBuilder tag_builder;
......@@ -96,7 +95,7 @@ asx_start_element(gcc_unused GMarkupParseContext *context,
case AsxParser::ROOT:
if (StringEqualsCaseASCII(element_name, "entry")) {
parser->state = AsxParser::ENTRY;
parser->song = Song::NewRemote("asx:");
parser->location.clear();
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
......@@ -107,17 +106,8 @@ asx_start_element(gcc_unused GMarkupParseContext *context,
const gchar *href = get_attribute(attribute_names,
attribute_values,
"href");
if (href != nullptr) {
/* create new song object; we cannot
replace the existing song's URI,
because that attribute is
immutable */
Song *song = Song::NewRemote(href);
if (parser->song != nullptr)
parser->song->Free();
parser->song = song;
}
if (href != nullptr)
parser->location = href;
} else if (StringEqualsCaseASCII(element_name, "author"))
/* is that correct? or should it be COMPOSER
or PERFORMER? */
......@@ -142,12 +132,9 @@ asx_end_element(gcc_unused GMarkupParseContext *context,
case AsxParser::ENTRY:
if (StringEqualsCaseASCII(element_name, "entry")) {
if (strcmp(parser->song->uri, "asx:") != 0) {
assert(parser->song->tag == nullptr);
parser->song->tag = parser->tag_builder.CommitNew();
parser->songs.emplace_front(parser->song);
} else
parser->song->Free();
if (!parser->location.empty())
parser->songs.emplace_front(std::move(parser->location),
parser->tag_builder.Commit());
parser->state = AsxParser::ROOT;
} else
......@@ -186,15 +173,6 @@ static const GMarkupParser asx_parser = {
nullptr,
};
static void
asx_parser_destroy(gpointer data)
{
AsxParser *parser = (AsxParser *)data;
if (parser->state >= AsxParser::ENTRY)
parser->song->Free();
}
/*
* The playlist object
*
......@@ -215,7 +193,7 @@ asx_open_stream(InputStream &is)
context = g_markup_parse_context_new(&asx_parser,
G_MARKUP_TREAT_CDATA_AS_TEXT,
&parser, asx_parser_destroy);
&parser, nullptr);
while (true) {
nbytes = is.LockRead(buffer, sizeof(buffer), error2);
......
......@@ -36,7 +36,7 @@ class CuePlaylist final : public SongEnumerator {
:is(_is), tis(is) {
}
virtual Song *NextSong() override;
virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
......@@ -45,10 +45,10 @@ cue_playlist_open_stream(InputStream &is)
return new CuePlaylist(is);
}
Song *
DetachedSong *
CuePlaylist::NextSong()
{
Song *song = parser.Get();
DetachedSong *song = parser.Get();
if (song != nullptr)
return song;
......
......@@ -23,7 +23,7 @@
#include "PlaylistPlugin.hxx"
#include "MemorySongEnumerator.hxx"
#include "tag/Tag.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "Log.hxx"
extern "C" {
......@@ -34,10 +34,9 @@ extern "C" {
#include <stdlib.h>
static void
add_song(std::forward_list<SongPointer> &songs, ds_track &track)
add_song(std::forward_list<DetachedSong> &songs, ds_track &track)
{
const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
Song *song;
char uri[128];
char *ds_uri;
......@@ -52,15 +51,12 @@ add_song(std::forward_list<SongPointer> &songs, ds_track &track)
return;
}
song = Song::NewRemote(uri);
song->tag = new Tag(mpd_despotify_tag_from_track(track));
songs.emplace_front(song);
songs.emplace_front(uri, mpd_despotify_tag_from_track(track));
}
static bool
parse_track(struct despotify_session *session,
std::forward_list<SongPointer> &songs,
std::forward_list<DetachedSong> &songs,
struct ds_link *link)
{
struct ds_track *track = despotify_link_get_track(session, link);
......@@ -73,7 +69,7 @@ parse_track(struct despotify_session *session,
static bool
parse_playlist(struct despotify_session *session,
std::forward_list<SongPointer> &songs,
std::forward_list<DetachedSong> &songs,
struct ds_link *link)
{
ds_playlist *playlist = despotify_link_get_playlist(session, link);
......@@ -103,7 +99,7 @@ despotify_playlist_open_uri(const char *url,
return nullptr;
}
std::forward_list<SongPointer> songs;
std::forward_list<DetachedSong> songs;
bool parse_result;
switch (link->type) {
......
......@@ -30,7 +30,7 @@
#include "tag/TagHandler.hxx"
#include "tag/TagId3.hxx"
#include "tag/ApeTag.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "TagFile.hxx"
#include "cue/CueParser.hxx"
#include "fs/Traits.hxx"
......@@ -69,7 +69,7 @@ public:
delete parser;
}
virtual Song *NextSong() override;
virtual DetachedSong *NextSong() override;
};
static void
......@@ -124,10 +124,10 @@ embcue_playlist_open_uri(const char *uri,
return playlist;
}
Song *
DetachedSong *
EmbeddedCuePlaylist::NextSong()
{
Song *song = parser->Get();
DetachedSong *song = parser->Get();
if (song != nullptr)
return song;
......@@ -145,14 +145,16 @@ EmbeddedCuePlaylist::NextSong()
parser->Feed(line);
song = parser->Get();
if (song != nullptr)
return song->ReplaceURI(filename.c_str());
if (song != nullptr) {
song->SetURI(filename);
return song;
}
}
parser->Finish();
song = parser->Get();
if (song != nullptr)
song = song->ReplaceURI(filename.c_str());
song->SetURI(filename);
return song;
}
......
......@@ -21,7 +21,7 @@
#include "ExtM3uPlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "SongEnumerator.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
......@@ -44,7 +44,7 @@ public:
strcmp(line.c_str(), "#EXTM3U") == 0;
}
virtual Song *NextSong() override;
virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
......@@ -101,20 +101,19 @@ extm3u_parse_tag(const char *line)
return tag.CommitNew();
}
Song *
DetachedSong *
ExtM3uPlaylist::NextSong()
{
Tag *tag = NULL;
std::string line;
const char *line_s;
Song *song;
do {
if (!tis.ReadLine(line)) {
delete tag;
return NULL;
}
line_s = line.c_str();
if (StringStartsWith(line_s, "#EXTINF:")) {
......@@ -126,8 +125,8 @@ ExtM3uPlaylist::NextSong()
line_s = strchug_fast(line_s);
} while (line_s[0] == '#' || *line_s == 0);
song = Song::NewRemote(line_s);
song->tag = tag;
DetachedSong *song = new DetachedSong(line_s, std::move(*tag));
delete tag;
return song;
}
......
......@@ -21,7 +21,7 @@
#include "M3uPlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "SongEnumerator.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "util/StringUtil.hxx"
#include "TextInputStream.hxx"
......@@ -33,7 +33,7 @@ public:
:tis(is) {
}
virtual Song *NextSong() override;
virtual DetachedSong *NextSong() override;
};
static SongEnumerator *
......@@ -42,7 +42,7 @@ m3u_open_stream(InputStream &is)
return new M3uPlaylist(is);
}
Song *
DetachedSong *
M3uPlaylist::NextSong()
{
std::string line;
......@@ -56,7 +56,7 @@ M3uPlaylist::NextSong()
line_s = strchug_fast(line_s);
} while (line_s[0] == '#' || *line_s == 0);
return Song::NewRemote(line_s);
return new DetachedSong(line_s);
}
static const char *const m3u_suffixes[] = {
......
......@@ -22,7 +22,7 @@
#include "PlaylistPlugin.hxx"
#include "MemorySongEnumerator.hxx"
#include "InputStream.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
......@@ -37,7 +37,7 @@
static constexpr Domain pls_domain("pls");
static void
pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
{
gchar *value;
GError *error = nullptr;
......@@ -61,8 +61,8 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
for (; num_entries > 0; --num_entries) {
char key[64];
sprintf(key, "File%u", num_entries);
value = g_key_file_get_string(keyfile, "playlist", key,
&error);
char *uri = g_key_file_get_string(keyfile, "playlist", key,
&error);
if(error) {
FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
key, error->message);
......@@ -70,9 +70,6 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
return;
}
Song *song = Song::NewRemote(value);
g_free(value);
TagBuilder tag;
sprintf(key, "Title%u", num_entries);
......@@ -89,8 +86,8 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
if (length > 0)
tag.SetTime(length);
song->tag = tag.CommitNew();
songs.emplace_front(song);
songs.emplace_front(uri, tag.Commit());
g_free(uri);
}
}
......@@ -135,7 +132,7 @@ pls_open_stream(InputStream &is)
return nullptr;
}
std::forward_list<SongPointer> songs;
std::forward_list<DetachedSong> songs;
pls_parser(keyfile, songs);
g_key_file_free(keyfile);
......
......@@ -43,7 +43,7 @@ struct RssParser {
* The list of songs (in reverse order because that's faster
* while adding).
*/
std::forward_list<SongPointer> songs;
std::forward_list<DetachedSong> songs;
/**
* The current position in the XML file.
......@@ -60,10 +60,10 @@ struct RssParser {
TagType tag_type;
/**
* The current song. It is allocated after the "location"
* The current song URI. It is set by the "enclosure"
* element.
*/
Song *song;
std::string location;
TagBuilder tag_builder;
......@@ -95,7 +95,7 @@ rss_start_element(gcc_unused GMarkupParseContext *context,
case RssParser::ROOT:
if (StringEqualsCaseASCII(element_name, "item")) {
parser->state = RssParser::ITEM;
parser->song = Song::NewRemote("rss:");
parser->location.clear();
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
......@@ -106,18 +106,8 @@ rss_start_element(gcc_unused GMarkupParseContext *context,
const gchar *href = get_attribute(attribute_names,
attribute_values,
"url");
if (href != nullptr) {
/* create new song object; we cannot
replace the existing song's URI,
because that attribute is
immutable */
Song *song = Song::NewRemote(href);
if (parser->song != nullptr)
parser->song->Free();
parser->song = song;
}
if (href != nullptr)
parser->location = href;
} else if (StringEqualsCaseASCII(element_name, "title"))
parser->tag_type = TAG_TITLE;
else if (StringEqualsCaseASCII(element_name, "itunes:author"))
......@@ -140,12 +130,9 @@ rss_end_element(gcc_unused GMarkupParseContext *context,
case RssParser::ITEM:
if (StringEqualsCaseASCII(element_name, "item")) {
if (strcmp(parser->song->uri, "rss:") != 0) {
assert(parser->song->tag == nullptr);
parser->song->tag = parser->tag_builder.CommitNew();
parser->songs.emplace_front(parser->song);
} else
parser->song->Free();
if (!parser->location.empty())
parser->songs.emplace_front(std::move(parser->location),
parser->tag_builder.Commit());
parser->state = RssParser::ROOT;
} else
......@@ -183,15 +170,6 @@ static const GMarkupParser rss_parser = {
nullptr,
};
static void
rss_parser_destroy(gpointer data)
{
RssParser *parser = (RssParser *)data;
if (parser->state >= RssParser::ITEM)
parser->song->Free();
}
/*
* The playlist object
*
......@@ -212,7 +190,7 @@ rss_open_stream(InputStream &is)
context = g_markup_parse_context_new(&rss_parser,
G_MARKUP_TREAT_CDATA_AS_TEXT,
&parser, rss_parser_destroy);
&parser, nullptr);
while (true) {
nbytes = is.LockRead(buffer, sizeof(buffer), error2);
......
......@@ -108,7 +108,7 @@ struct parse_data {
char* title;
int got_url; /* nesting level of last stream_url */
std::forward_list<SongPointer> songs;
std::forward_list<DetachedSong> songs;
};
static int
......@@ -214,16 +214,14 @@ handle_end_map(void *ctx)
char *u = g_strconcat(data->stream_url, "?client_id=",
soundcloud_config.apikey.c_str(), nullptr);
Song *s = Song::NewRemote(u);
g_free(u);
TagBuilder tag;
tag.SetTime(data->duration / 1000);
if (data->title != nullptr)
tag.AddItem(TAG_NAME, data->title);
s->tag = tag.CommitNew();
data->songs.emplace_front(s);
data->songs.emplace_front(u, tag.Commit());
g_free(u);
return 1;
}
......
......@@ -21,6 +21,7 @@
#include "XspfPlaylistPlugin.hxx"
#include "PlaylistPlugin.hxx"
#include "MemorySongEnumerator.hxx"
#include "DetachedSong.hxx"
#include "InputStream.hxx"
#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
......@@ -41,7 +42,7 @@ struct XspfParser {
* The list of songs (in reverse order because that's faster
* while adding).
*/
std::forward_list<SongPointer> songs;
std::forward_list<DetachedSong> songs;
/**
* The current position in the XML file.
......@@ -59,10 +60,9 @@ struct XspfParser {
TagType tag_type;
/**
* The current song. It is allocated after the "location"
* element.
* The current song URI. It is set by the "location" element.
*/
Song *song;
std::string location;
TagBuilder tag_builder;
......@@ -95,7 +95,7 @@ xspf_start_element(gcc_unused GMarkupParseContext *context,
case XspfParser::TRACKLIST:
if (strcmp(element_name, "track") == 0) {
parser->state = XspfParser::TRACK;
parser->song = nullptr;
parser->location.clear();
parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
}
......@@ -149,11 +149,9 @@ xspf_end_element(gcc_unused GMarkupParseContext *context,
case XspfParser::TRACK:
if (strcmp(element_name, "track") == 0) {
if (parser->song != nullptr) {
assert(parser->song->tag == nullptr);
parser->song->tag = parser->tag_builder.CommitNew();
parser->songs.emplace_front(parser->song);
}
if (!parser->location.empty())
parser->songs.emplace_front(std::move(parser->location),
parser->tag_builder.Commit());
parser->state = XspfParser::TRACKLIST;
} else
......@@ -181,7 +179,7 @@ xspf_text(gcc_unused GMarkupParseContext *context,
break;
case XspfParser::TRACK:
if (parser->song != nullptr &&
if (!parser->location.empty() &&
parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
parser->tag_builder.AddItem(parser->tag_type,
text, text_len);
......@@ -189,11 +187,7 @@ xspf_text(gcc_unused GMarkupParseContext *context,
break;
case XspfParser::LOCATION:
if (parser->song == nullptr) {
char *uri = g_strndup(text, text_len);
parser->song = Song::NewRemote(uri);
g_free(uri);
}
parser->location.assign(text, text_len);
break;
}
......@@ -207,15 +201,6 @@ static const GMarkupParser xspf_parser = {
nullptr,
};
static void
xspf_parser_destroy(gpointer data)
{
XspfParser *parser = (XspfParser *)data;
if (parser->state >= XspfParser::TRACK && parser->song != nullptr)
parser->song->Free();
}
/*
* The playlist object
*
......@@ -236,7 +221,7 @@ xspf_open_stream(InputStream &is)
context = g_markup_parse_context_new(&xspf_parser,
G_MARKUP_TREAT_CDATA_AS_TEXT,
&parser, xspf_parser_destroy);
&parser, nullptr);
while (true) {
nbytes = is.LockRead(buffer, sizeof(buffer), error2);
......
......@@ -49,7 +49,10 @@ DumpDirectory(const Directory &directory, Error &)
static bool
DumpSong(Song &song, Error &)
{
cout << "S " << song.parent->path << "/" << song.uri << endl;
cout << "S ";
if (song.parent != nullptr && !song.parent->IsRoot())
cout << song.parent->path << "/";
cout << song.uri << endl;
return true;
}
......
......@@ -19,9 +19,8 @@
#include "config.h"
#include "TagSave.hxx"
#include "Song.hxx"
#include "DetachedSong.hxx"
#include "SongEnumerator.hxx"
#include "Directory.hxx"
#include "InputStream.hxx"
#include "ConfigGlobal.hxx"
#include "DecoderList.hxx"
......@@ -39,14 +38,10 @@
#include <unistd.h>
#include <stdlib.h>
Directory::Directory() {}
Directory::~Directory() {}
int main(int argc, char **argv)
{
const char *uri;
InputStream *is = NULL;
Song *song;
if (argc != 3) {
fprintf(stderr, "Usage: dump_playlist CONFIG URI\n");
......@@ -114,24 +109,27 @@ int main(int argc, char **argv)
/* dump the playlist */
DetachedSong *song;
while ((song = playlist->NextSong()) != NULL) {
printf("%s\n", song->uri);
printf("%s\n", song->GetURI());
const unsigned start_ms = song->GetStartMS();
const unsigned end_ms = song->GetEndMS();
if (song->end_ms > 0)
if (end_ms > 0)
printf("range: %u:%02u..%u:%02u\n",
song->start_ms / 60000,
(song->start_ms / 1000) % 60,
song->end_ms / 60000,
(song->end_ms / 1000) % 60);
else if (song->start_ms > 0)
start_ms / 60000,
(start_ms / 1000) % 60,
end_ms / 60000,
(end_ms / 1000) % 60);
else if (start_ms > 0)
printf("range: %u:%02u..\n",
song->start_ms / 60000,
(song->start_ms / 1000) % 60);
start_ms / 60000,
(start_ms / 1000) % 60);
if (song->tag != NULL)
tag_save(stdout, *song->tag);
tag_save(stdout, song->GetTag());
song->Free();
delete song;
}
/* deinitialize everything */
......
#include "config.h"
#include "Queue.hxx"
#include "Song.hxx"
#include "Directory.hxx"
#include "DetachedSong.hxx"
#include "util/Macros.hxx"
#include <cppunit/TestFixture.h>
......@@ -9,21 +8,8 @@
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
Directory detached_root;
Directory::Directory() {}
Directory::~Directory() {}
Song *
Song::DupDetached() const
{
return const_cast<Song *>(this);
}
void
Song::Free()
{
}
Tag::Tag(const Tag &) {}
void Tag::Clear() {}
static void
check_descending_priority(const struct queue *queue,
......@@ -53,12 +39,29 @@ public:
void
QueuePriorityTest::TestPriority()
{
static Song songs[16];
DetachedSong songs[16] = {
DetachedSong("0.ogg"),
DetachedSong("1.ogg"),
DetachedSong("2.ogg"),
DetachedSong("3.ogg"),
DetachedSong("4.ogg"),
DetachedSong("5.ogg"),
DetachedSong("6.ogg"),
DetachedSong("7.ogg"),
DetachedSong("8.ogg"),
DetachedSong("9.ogg"),
DetachedSong("a.ogg"),
DetachedSong("b.ogg"),
DetachedSong("c.ogg"),
DetachedSong("d.ogg"),
DetachedSong("e.ogg"),
DetachedSong("f.ogg"),
};
struct queue queue(32);
for (unsigned i = 0; i < ARRAY_SIZE(songs); ++i)
queue.Append(&songs[i], 0);
queue.Append(DetachedSong(songs[i]), 0);
CPPUNIT_ASSERT_EQUAL(unsigned(ARRAY_SIZE(songs)), queue.GetLength());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment