Files
mpd/src/db/plugins/simple/DirectorySave.cxx
datasone d4f3dd49b4 db/SimpleDatabasePlugin: store in_playlist value of songs into database
Fixes hide_playlist_targets not working after server restart

Currently, `hide_playlists_targets` works by skipping songs with
`in_playlist` value set to true in
[`Directory::Walk`](a57bcd0238/src/db/plugins/simple/Directory.cxx (L237)). But
`in_playlist` is not stored and only updated in
[`UpdateWalk::PurgeDanglingFromPlaylists`](a57bcd0238/src/db/update/Playlist.cxx (L139)),
which will only be executed while updating DB.

This causes the problem that playlist target songs are correctly
hidden after database update, but will remain visible after mpd server
restarted. This pr solves the problem by storing `in_playlist` value
of songs into the `SimpleDatabase` file.
2023-05-21 20:51:47 +02:00

189 lines
4.8 KiB
C++

/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "DirectorySave.hxx"
#include "Directory.hxx"
#include "Song.hxx"
#include "SongSave.hxx"
#include "song/DetachedSong.hxx"
#include "PlaylistDatabase.hxx"
#include "io/LineReader.hxx"
#include "io/BufferedOutputStream.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringAPI.hxx"
#include "util/StringCompare.hxx"
#include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx"
#include <string.h>
#define DIRECTORY_DIR "directory: "
#define DIRECTORY_TYPE "type: "
#define DIRECTORY_MTIME "mtime: "
#define DIRECTORY_BEGIN "begin: "
#define DIRECTORY_END "end: "
gcc_const
static const char *
DeviceToTypeString(unsigned device) noexcept
{
switch (device) {
case DEVICE_INARCHIVE:
return "archive";
case DEVICE_CONTAINER:
return "container";
case DEVICE_PLAYLIST:
return "playlist";
default:
return nullptr;
}
}
gcc_pure
static unsigned
ParseTypeString(const char *type) noexcept
{
if (StringIsEqual(type, "archive"))
return DEVICE_INARCHIVE;
else if (StringIsEqual(type, "container"))
return DEVICE_CONTAINER;
else if (StringIsEqual(type, "playlist"))
return DEVICE_PLAYLIST;
else
return 0;
}
void
directory_save(BufferedOutputStream &os, const Directory &directory)
{
if (!directory.IsRoot()) {
const char *type = DeviceToTypeString(directory.device);
if (type != nullptr)
os.Format(DIRECTORY_TYPE "%s\n", type);
if (!IsNegative(directory.mtime))
os.Format(DIRECTORY_MTIME "%lu\n",
(unsigned long)std::chrono::system_clock::to_time_t(directory.mtime));
os.Format("%s%s\n", DIRECTORY_BEGIN, directory.GetPath());
}
for (const auto &child : directory.children) {
if (child.IsMount())
continue;
os.Format(DIRECTORY_DIR "%s\n", child.GetName());
directory_save(os, child);
}
for (const auto &song : directory.songs)
song_save(os, song);
playlist_vector_save(os, directory.playlists);
if (!directory.IsRoot())
os.Format(DIRECTORY_END "%s\n", directory.GetPath());
}
static bool
ParseLine(Directory &directory, const char *line)
{
const char *p;
if ((p = StringAfterPrefix(line, DIRECTORY_MTIME))) {
const auto mtime = ParseUint64(p);
if (mtime > 0)
directory.mtime = std::chrono::system_clock::from_time_t(mtime);
} else if ((p = StringAfterPrefix(line, DIRECTORY_TYPE))) {
directory.device = ParseTypeString(p);
} else
return false;
return true;
}
static Directory *
directory_load_subdir(LineReader &file, Directory &parent, std::string_view name)
{
if (parent.FindChild(name) != nullptr)
throw FormatRuntimeError("Duplicate subdirectory '%.*s'",
int(name.size()), name.data());
Directory *directory = parent.CreateChild(name);
try {
while (true) {
const char *line = file.ReadLine();
if (line == nullptr)
throw std::runtime_error("Unexpected end of file");
if (StringStartsWith(line, DIRECTORY_BEGIN))
break;
if (!ParseLine(*directory, line))
throw FormatRuntimeError("Malformed line: %s", line);
}
directory_load(file, *directory);
} catch (...) {
directory->Delete();
throw;
}
return directory;
}
void
directory_load(LineReader &file, Directory &directory)
{
const char *line;
while ((line = file.ReadLine()) != nullptr &&
!StringStartsWith(line, DIRECTORY_END)) {
const char *p;
if ((p = StringAfterPrefix(line, DIRECTORY_DIR))) {
directory_load_subdir(file, directory, p);
} else if ((p = StringAfterPrefix(line, SONG_BEGIN))) {
const char *name = p;
if (directory.FindSong(name) != nullptr)
throw FormatRuntimeError("Duplicate song '%s'", name);
std::string target;
bool in_playlist = false;
auto detached_song = song_load(file, name,
&target, &in_playlist);
auto song = std::make_unique<Song>(std::move(detached_song),
directory);
song->target = std::move(target);
song->in_playlist = in_playlist;
directory.AddSong(std::move(song));
} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
const char *name = p;
playlist_metadata_load(file, directory.playlists, name);
} else {
throw FormatRuntimeError("Malformed line: %s", line);
}
}
}