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.
This commit is contained in:
parent
43847f2244
commit
322b061632
|
@ -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()) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2003-2013 The Music Player Daemon Project
|
||||
* 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
|
||||
|
@ -17,47 +17,35 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_SONG_POINTER_HXX
|
||||
#define MPD_SONG_POINTER_HXX
|
||||
|
||||
#include "config.h"
|
||||
#include "DetachedSong.hxx"
|
||||
#include "Song.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
|
||||
#include <utility>
|
||||
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) {}
|
||||
|
||||
class SongPointer {
|
||||
Song *song;
|
||||
bool
|
||||
DetachedSong::IsRemote() const
|
||||
{
|
||||
return uri_has_scheme(uri.c_str());
|
||||
}
|
||||
|
||||
public:
|
||||
explicit SongPointer(Song *_song)
|
||||
:song(_song) {}
|
||||
bool
|
||||
DetachedSong::IsAbsoluteFile() const
|
||||
{
|
||||
return PathTraitsUTF8::IsAbsolute(uri.c_str());
|
||||
}
|
||||
|
||||
SongPointer(const SongPointer &) = delete;
|
||||
double
|
||||
DetachedSong::GetDuration() const
|
||||
{
|
||||
if (end_ms > 0)
|
||||
return (end_ms - start_ms) / 1000.0;
|
||||
|
||||
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
|
||||
return tag.time - start_ms / 1000.0;
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* 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();
|
||||
|
||||
return decoder_duration - song.start_ms / 1000.0;
|
||||
if (end_ms > 0 && end_ms / 1000.0 < decoder_duration)
|
||||
return (end_ms - 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 ¤t_song = queue.GetOrder(current);
|
||||
if (SongEquals(song, current_song))
|
||||
current_song.ReplaceTag(std::move(*song.tag));
|
||||
DetachedSong ¤t_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);
|
||||
{
|
||||
TagBuilder builder(add->GetTag());
|
||||
builder.Complement(base->GetTag());
|
||||
dest->SetTag(builder.Commit());
|
||||
}
|
||||
|
||||
dest->mtime = base->mtime;
|
||||
dest->start_ms = add->start_ms;
|
||||
dest->end_ms = add->end_ms;
|
||||
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;
|
||||
}
|
||||
|
|
96
src/Song.cxx
96
src/Song.cxx
|
@ -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();
|
||||
|
|
67
src/Song.hxx
67
src/Song.hxx
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
if (song->end_ms > 0)
|
||||
const unsigned start_ms = song->GetStartMS();
|
||||
const unsigned end_ms = song->GetEndMS();
|
||||
|
||||
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 */
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#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());
|
||||
|
||||
|
|
Loading…
Reference in New Issue