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