db/update: convert to OO API

Move global variables into the new classes.  That may allow multiple
update threads for multiple databases one day.
This commit is contained in:
Max Kellermann 2014-01-29 20:16:43 +01:00
parent a31738f6f1
commit c6725884bc
29 changed files with 514 additions and 520 deletions

View File

@ -124,15 +124,15 @@ src_mpd_SOURCES = \
src/filter/FilterInternal.hxx \ src/filter/FilterInternal.hxx \
src/filter/FilterRegistry.cxx src/filter/FilterRegistry.hxx \ src/filter/FilterRegistry.cxx src/filter/FilterRegistry.hxx \
src/db/update/UpdateDomain.cxx src/db/update/UpdateDomain.hxx \ src/db/update/UpdateDomain.cxx src/db/update/UpdateDomain.hxx \
src/db/update/UpdateGlue.cxx src/db/update/UpdateGlue.hxx \ src/db/update/Service.cxx src/db/update/Service.hxx \
src/db/update/UpdateQueue.cxx src/db/update/UpdateQueue.hxx \ src/db/update/UpdateGlue.cxx \
src/db/update/Queue.cxx src/db/update/Queue.hxx \
src/db/update/UpdateIO.cxx src/db/update/UpdateIO.hxx \ src/db/update/UpdateIO.cxx src/db/update/UpdateIO.hxx \
src/db/update/UpdateDatabase.cxx src/db/update/UpdateDatabase.hxx \ src/db/update/Editor.cxx src/db/update/Editor.hxx \
src/db/update/UpdateWalk.cxx src/db/update/UpdateWalk.hxx \ src/db/update/Walk.cxx src/db/update/Walk.hxx \
src/db/update/UpdateSong.cxx src/db/update/UpdateSong.hxx \ src/db/update/UpdateSong.cxx \
src/db/update/UpdateContainer.cxx src/db/update/UpdateContainer.hxx \ src/db/update/Container.cxx \
src/db/update/UpdateInternal.hxx \ src/db/update/Remove.cxx src/db/update/Remove.hxx \
src/db/update/UpdateRemove.cxx src/db/update/UpdateRemove.hxx \
src/db/update/ExcludeList.cxx src/db/update/ExcludeList.hxx \ src/db/update/ExcludeList.cxx src/db/update/ExcludeList.hxx \
src/client/Client.cxx src/client/Client.hxx \ src/client/Client.cxx src/client/Client.hxx \
src/client/ClientInternal.hxx \ src/client/ClientInternal.hxx \
@ -486,7 +486,7 @@ if ENABLE_ARCHIVE
noinst_LIBRARIES += libarchive.a noinst_LIBRARIES += libarchive.a
src_mpd_SOURCES += \ src_mpd_SOURCES += \
src/db/update/UpdateArchive.cxx src/db/update/UpdateArchive.hxx src/db/update/Archive.cxx
libarchive_a_SOURCES = \ libarchive_a_SOURCES = \
src/archive/ArchiveDomain.cxx src/archive/ArchiveDomain.hxx \ src/archive/ArchiveDomain.cxx src/archive/ArchiveDomain.hxx \

View File

@ -33,9 +33,6 @@ class EventLoop;
namespace GlobalEvents { namespace GlobalEvents {
enum Event { enum Event {
/** database update was finished */
UPDATE,
/** during database update, a song was deleted */ /** during database update, a song was deleted */
DELETE, DELETE,

View File

@ -29,6 +29,7 @@
class NeighborGlue; class NeighborGlue;
#endif #endif
class UpdateService;
class ClientList; class ClientList;
struct Partition; struct Partition;
@ -42,6 +43,8 @@ struct Instance final
NeighborGlue *neighbors; NeighborGlue *neighbors;
#endif #endif
UpdateService *update;
ClientList *client_list; ClientList *client_list;
Partition *partition; Partition *partition;

View File

@ -23,7 +23,7 @@
#include "CommandLine.hxx" #include "CommandLine.hxx"
#include "PlaylistFile.hxx" #include "PlaylistFile.hxx"
#include "PlaylistGlobal.hxx" #include "PlaylistGlobal.hxx"
#include "db/update/UpdateGlue.hxx" #include "db/update/Service.hxx"
#include "MusicChunk.hxx" #include "MusicChunk.hxx"
#include "StateFile.hxx" #include "StateFile.hxx"
#include "PlayerThread.hxx" #include "PlayerThread.hxx"
@ -448,10 +448,13 @@ int mpd_main(int argc, char *argv[])
} }
decoder_plugin_init_all(); decoder_plugin_init_all();
update_global_init();
const bool create_db = !glue_db_init_and_load(); const bool create_db = !glue_db_init_and_load();
instance->update = db_is_simple()
? new UpdateService(*main_loop)
: nullptr;
glue_sticker_init(); glue_sticker_init();
command_init(); command_init();
@ -490,7 +493,7 @@ int mpd_main(int argc, char *argv[])
if (create_db) { if (create_db) {
/* the database failed to load: recreate the /* the database failed to load: recreate the
database */ database */
unsigned job = update_enqueue("", true); unsigned job = instance->update->Enqueue("", true);
if (job == 0) if (job == 0)
FatalError("directory update failed"); FatalError("directory update failed");
} }
@ -504,8 +507,9 @@ int mpd_main(int argc, char *argv[])
if (config_get_bool(CONF_AUTO_UPDATE, false)) { if (config_get_bool(CONF_AUTO_UPDATE, false)) {
#ifdef ENABLE_INOTIFY #ifdef ENABLE_INOTIFY
if (mapper_has_music_directory()) if (mapper_has_music_directory() &&
mpd_inotify_init(*main_loop, instance->update != nullptr)
mpd_inotify_init(*main_loop, *instance->update,
config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
G_MAXUINT)); G_MAXUINT));
#else #else
@ -558,6 +562,8 @@ int mpd_main(int argc, char *argv[])
} }
#endif #endif
delete instance->update;
const clock_t start = clock(); const clock_t start = clock();
DatabaseGlobalDeinit(); DatabaseGlobalDeinit();
FormatDebug(main_domain, FormatDebug(main_domain,
@ -575,7 +581,6 @@ int mpd_main(int argc, char *argv[])
mapper_finish(); mapper_finish();
delete instance->partition; delete instance->partition;
command_finish(); command_finish();
update_global_finish();
decoder_plugin_deinit_all(); decoder_plugin_deinit_all();
#ifdef ENABLE_ARCHIVE #ifdef ENABLE_ARCHIVE
archive_plugin_deinit_all(); archive_plugin_deinit_all();

View File

@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "OtherCommands.hxx" #include "OtherCommands.hxx"
#include "DatabaseCommands.hxx" #include "DatabaseCommands.hxx"
#include "db/update/UpdateGlue.hxx" #include "db/update/Service.hxx"
#include "CommandError.hxx" #include "CommandError.hxx"
#include "db/Uri.hxx" #include "db/Uri.hxx"
#include "DetachedSong.hxx" #include "DetachedSong.hxx"
@ -46,6 +46,7 @@
#include "client/ClientFile.hxx" #include "client/ClientFile.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include <assert.h> #include <assert.h>
@ -186,7 +187,6 @@ CommandResult
handle_update(Client &client, gcc_unused int argc, char *argv[]) handle_update(Client &client, gcc_unused int argc, char *argv[])
{ {
const char *path = ""; const char *path = "";
unsigned ret;
assert(argc <= 2); assert(argc <= 2);
if (argc == 2) { if (argc == 2) {
@ -202,7 +202,13 @@ handle_update(Client &client, gcc_unused int argc, char *argv[])
} }
} }
ret = update_enqueue(path, false); UpdateService *update = client.partition.instance.update;
if (update == nullptr) {
command_error(client, ACK_ERROR_NO_EXIST, "No database");
return CommandResult::ERROR;
}
unsigned ret = update->Enqueue(path, false);
if (ret > 0) { if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret); client_printf(client, "updating_db: %i\n", ret);
return CommandResult::OK; return CommandResult::OK;
@ -217,7 +223,6 @@ CommandResult
handle_rescan(Client &client, gcc_unused int argc, char *argv[]) handle_rescan(Client &client, gcc_unused int argc, char *argv[])
{ {
const char *path = ""; const char *path = "";
unsigned ret;
assert(argc <= 2); assert(argc <= 2);
if (argc == 2) { if (argc == 2) {
@ -230,7 +235,13 @@ handle_rescan(Client &client, gcc_unused int argc, char *argv[])
} }
} }
ret = update_enqueue(path, true); UpdateService *update = client.partition.instance.update;
if (update == nullptr) {
command_error(client, ACK_ERROR_NO_EXIST, "No database");
return CommandResult::ERROR;
}
unsigned ret = update->Enqueue(path, true);
if (ret > 0) { if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret); client_printf(client, "updating_db: %i\n", ret);
return CommandResult::OK; return CommandResult::OK;

View File

@ -22,10 +22,11 @@
#include "CommandError.hxx" #include "CommandError.hxx"
#include "Playlist.hxx" #include "Playlist.hxx"
#include "PlaylistPrint.hxx" #include "PlaylistPrint.hxx"
#include "db/update/UpdateGlue.hxx" #include "db/update/Service.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
#include "mixer/Volume.hxx" #include "mixer/Volume.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx"
#include "protocol/Result.hxx" #include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx" #include "protocol/ArgParser.hxx"
#include "AudioFormat.hxx" #include "AudioFormat.hxx"
@ -111,7 +112,6 @@ handle_status(Client &client,
gcc_unused int argc, gcc_unused char *argv[]) gcc_unused int argc, gcc_unused char *argv[])
{ {
const char *state = nullptr; const char *state = nullptr;
int updateJobId;
int song; int song;
const auto player_status = client.player_control.GetStatus(); const auto player_status = client.player_control.GetStatus();
@ -187,7 +187,11 @@ handle_status(Client &client,
} }
} }
if ((updateJobId = isUpdatingDB())) { const UpdateService *update_service = client.partition.instance.update;
unsigned updateJobId = update_service != nullptr
? update_service->GetId()
: 0;
if (updateJobId != 0) {
client_printf(client, client_printf(client,
COMMAND_STATUS_UPDATING_DB ": %i\n", COMMAND_STATUS_UPDATING_DB ": %i\n",
updateJobId); updateJobId);

View File

@ -18,8 +18,7 @@
*/ */
#include "config.h" /* must be first for large file support */ #include "config.h" /* must be first for large file support */
#include "UpdateArchive.hxx" #include "Walk.hxx"
#include "UpdateInternal.hxx"
#include "UpdateDomain.hxx" #include "UpdateDomain.hxx"
#include "db/DatabaseLock.hxx" #include "db/DatabaseLock.hxx"
#include "db/Directory.hxx" #include "db/Directory.hxx"
@ -35,10 +34,11 @@
#include <string> #include <string>
#include <sys/stat.h>
#include <string.h> #include <string.h>
static void void
update_archive_tree(Directory &directory, const char *name) UpdateWalk::UpdateArchiveTree(Directory &directory, const char *name)
{ {
const char *tmp = strchr(name, '/'); const char *tmp = strchr(name, '/');
if (tmp) { if (tmp) {
@ -51,7 +51,7 @@ update_archive_tree(Directory &directory, const char *name)
db_unlock(); db_unlock();
//create directories first //create directories first
update_archive_tree(*subdir, tmp+1); UpdateArchiveTree(*subdir, tmp + 1);
} else { } else {
if (strlen(name) == 0) { if (strlen(name) == 0) {
LogWarning(update_domain, LogWarning(update_domain,
@ -78,6 +78,21 @@ update_archive_tree(Directory &directory, const char *name)
} }
} }
class UpdateArchiveVisitor final : public ArchiveVisitor {
UpdateWalk &walk;
Directory *directory;
public:
UpdateArchiveVisitor(UpdateWalk &_walk, Directory *_directory)
:walk(_walk), directory(_directory) {}
virtual void VisitArchiveEntry(const char *path_utf8) override {
FormatDebug(update_domain,
"adding archive file: %s", path_utf8);
walk.UpdateArchiveTree(*directory, path_utf8);
}
};
/** /**
* Updates the file listing from an archive file. * Updates the file listing from an archive file.
* *
@ -86,10 +101,10 @@ update_archive_tree(Directory &directory, const char *name)
* @param st stat() information on the archive file * @param st stat() information on the archive file
* @param plugin the archive plugin which fits this archive type * @param plugin the archive plugin which fits this archive type
*/ */
static void void
update_archive_file2(Directory &parent, const char *name, UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name,
const struct stat *st, const struct stat *st,
const struct archive_plugin *plugin) const archive_plugin &plugin)
{ {
db_lock(); db_lock();
Directory *directory = parent.FindChild(name); Directory *directory = parent.FindChild(name);
@ -105,7 +120,7 @@ update_archive_file2(Directory &parent, const char *name,
/* open archive */ /* open archive */
Error error; Error error;
ArchiveFile *file = archive_file_open(plugin, path_fs.c_str(), error); ArchiveFile *file = archive_file_open(&plugin, path_fs.c_str(), error);
if (file == nullptr) { if (file == nullptr) {
LogError(error); LogError(error);
return; return;
@ -126,44 +141,21 @@ update_archive_file2(Directory &parent, const char *name,
directory->mtime = st->st_mtime; directory->mtime = st->st_mtime;
class UpdateArchiveVisitor final : public ArchiveVisitor { UpdateArchiveVisitor visitor(*this, directory);
Directory *directory;
public:
UpdateArchiveVisitor(Directory *_directory)
:directory(_directory) {}
virtual void VisitArchiveEntry(const char *path_utf8) override {
FormatDebug(update_domain,
"adding archive file: %s", path_utf8);
update_archive_tree(*directory, path_utf8);
}
};
UpdateArchiveVisitor visitor(directory);
file->Visit(visitor); file->Visit(visitor);
file->Close(); file->Close();
} }
bool bool
update_archive_file(Directory &directory, UpdateWalk::UpdateArchiveFile(Directory &directory,
const char *name, const char *suffix, const char *name, const char *suffix,
const struct stat *st) const struct stat *st)
{ {
#ifdef ENABLE_ARCHIVE
const struct archive_plugin *plugin = const struct archive_plugin *plugin =
archive_plugin_from_suffix(suffix); archive_plugin_from_suffix(suffix);
if (plugin == nullptr) if (plugin == nullptr)
return false; return false;
update_archive_file2(directory, name, st, plugin); UpdateArchiveFile(directory, name, st, *plugin);
return true; return true;
#else
(void)directory;
(void)name;
(void)suffix;
(void)st;
return false;
#endif
} }

View File

@ -18,9 +18,7 @@
*/ */
#include "config.h" /* must be first for large file support */ #include "config.h" /* must be first for large file support */
#include "UpdateContainer.hxx" #include "Walk.hxx"
#include "UpdateInternal.hxx"
#include "UpdateDatabase.hxx"
#include "UpdateDomain.hxx" #include "UpdateDomain.hxx"
#include "db/DatabaseLock.hxx" #include "db/DatabaseLock.hxx"
#include "db/Directory.hxx" #include "db/Directory.hxx"
@ -33,19 +31,13 @@
#include "tag/TagBuilder.hxx" #include "tag/TagBuilder.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <sys/stat.h>
#include <glib.h> #include <glib.h>
/** Directory *
* Create the specified directory object if it does not exist already UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name,
* or if the #stat object indicates that it has been modified since const struct stat *st)
* the last update. Returns nullptr when it exists already and is
* unmodified.
*
* The caller must lock the database.
*/
static Directory *
make_directory_if_modified(Directory &parent, const char *name,
const struct stat *st)
{ {
Directory *directory = parent.FindChild(name); Directory *directory = parent.FindChild(name);
@ -56,7 +48,7 @@ make_directory_if_modified(Directory &parent, const char *name,
return nullptr; return nullptr;
} }
delete_directory(directory); editor.DeleteDirectory(directory);
modified = true; modified = true;
} }
@ -73,10 +65,9 @@ SupportsContainerSuffix(const DecoderPlugin &plugin, const char *suffix)
} }
bool bool
update_container_file(Directory &directory, UpdateWalk::UpdateContainerFile(Directory &directory,
const char *name, const char *name, const char *suffix,
const struct stat *st, const struct stat *st)
const char *suffix)
{ {
const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){ const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){
return SupportsContainerSuffix(plugin, suffix); return SupportsContainerSuffix(plugin, suffix);
@ -86,7 +77,7 @@ update_container_file(Directory &directory,
const DecoderPlugin &plugin = *_plugin; const DecoderPlugin &plugin = *_plugin;
db_lock(); db_lock();
Directory *contdir = make_directory_if_modified(directory, name, st); Directory *contdir = MakeDirectoryIfModified(directory, name, st);
if (contdir == nullptr) { if (contdir == nullptr) {
/* not modified */ /* not modified */
db_unlock(); db_unlock();
@ -128,7 +119,7 @@ update_container_file(Directory &directory,
if (tnum == 1) { if (tnum == 1) {
db_lock(); db_lock();
delete_directory(contdir); editor.DeleteDirectory(contdir);
db_unlock(); db_unlock();
return false; return false;
} else } else

View File

@ -18,8 +18,8 @@
*/ */
#include "config.h" /* must be first for large file support */ #include "config.h" /* must be first for large file support */
#include "UpdateDatabase.hxx" #include "Editor.hxx"
#include "UpdateRemove.hxx" #include "Remove.hxx"
#include "db/PlaylistVector.hxx" #include "db/PlaylistVector.hxx"
#include "db/Directory.hxx" #include "db/Directory.hxx"
#include "db/Song.hxx" #include "db/Song.hxx"
@ -29,7 +29,7 @@
#include <stddef.h> #include <stddef.h>
void void
delete_song(Directory &dir, Song *del) DatabaseEditor::DeleteSong(Directory &dir, Song *del)
{ {
assert(del->parent == &dir); assert(del->parent == &dir);
@ -39,7 +39,7 @@ delete_song(Directory &dir, Song *del)
db_unlock(); /* temporary unlock, because update_remove_song() blocks */ db_unlock(); /* temporary unlock, because update_remove_song() blocks */
/* now take it out of the playlist (in the main_task) */ /* now take it out of the playlist (in the main_task) */
update_remove_song(del); remove.Remove(del);
/* finally, all possible references gone, free it */ /* finally, all possible references gone, free it */
del->Free(); del->Free();
@ -53,32 +53,32 @@ delete_song(Directory &dir, Song *del)
* *
* Caller must lock the #db_mutex. * Caller must lock the #db_mutex.
*/ */
static void inline void
clear_directory(Directory &directory) DatabaseEditor::ClearDirectory(Directory &directory)
{ {
Directory *child, *n; Directory *child, *n;
directory_for_each_child_safe(child, n, directory) directory_for_each_child_safe(child, n, directory)
delete_directory(child); DeleteDirectory(child);
Song *song, *ns; Song *song, *ns;
directory_for_each_song_safe(song, ns, directory) { directory_for_each_song_safe(song, ns, directory) {
assert(song->parent == &directory); assert(song->parent == &directory);
delete_song(directory, song); DeleteSong(directory, song);
} }
} }
void void
delete_directory(Directory *directory) DatabaseEditor::DeleteDirectory(Directory *directory)
{ {
assert(directory->parent != nullptr); assert(directory->parent != nullptr);
clear_directory(*directory); ClearDirectory(*directory);
directory->Delete(); directory->Delete();
} }
bool bool
delete_name_in(Directory &parent, const char *name) DatabaseEditor::DeleteNameIn(Directory &parent, const char *name)
{ {
bool modified = false; bool modified = false;
@ -86,13 +86,13 @@ delete_name_in(Directory &parent, const char *name)
Directory *directory = parent.FindChild(name); Directory *directory = parent.FindChild(name);
if (directory != nullptr) { if (directory != nullptr) {
delete_directory(directory); DeleteDirectory(directory);
modified = true; modified = true;
} }
Song *song = parent.FindSong(name); Song *song = parent.FindSong(name);
if (song != nullptr) { if (song != nullptr) {
delete_song(parent, song); DeleteSong(parent, song);
modified = true; modified = true;
} }

View File

@ -21,30 +21,40 @@
#define MPD_UPDATE_DATABASE_HXX #define MPD_UPDATE_DATABASE_HXX
#include "check.h" #include "check.h"
#include "Remove.hxx"
struct Directory; struct Directory;
struct Song; struct Song;
class UpdateRemoveService;
/** class DatabaseEditor final {
* Caller must lock the #db_mutex. UpdateRemoveService remove;
*/
void
delete_song(Directory &parent, Song *song);
/** public:
* Recursively free a directory and all its contents. DatabaseEditor(EventLoop &_loop)
* :remove(_loop) {}
* Caller must lock the #db_mutex.
*/
void
delete_directory(Directory *directory);
/** /**
* Caller must NOT lock the #db_mutex. * Caller must lock the #db_mutex.
* */
* @return true if the database was modified void DeleteSong(Directory &parent, Song *song);
*/
bool /**
delete_name_in(Directory &parent, const char *name); * Recursively free a directory and all its contents.
*
* Caller must lock the #db_mutex.
*/
void DeleteDirectory(Directory *directory);
/**
* Caller must NOT lock the #db_mutex.
*
* @return true if the database was modified
*/
bool DeleteNameIn(Directory &parent, const char *name);
private:
void ClearDirectory(Directory &directory);
};
#endif #endif

View File

@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "InotifyQueue.hxx" #include "InotifyQueue.hxx"
#include "InotifyDomain.hxx" #include "InotifyDomain.hxx"
#include "UpdateGlue.hxx" #include "Service.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <string.h> #include <string.h>
@ -40,7 +40,7 @@ InotifyQueue::OnTimeout()
while (!queue.empty()) { while (!queue.empty()) {
const char *uri_utf8 = queue.front().c_str(); const char *uri_utf8 = queue.front().c_str();
id = update_enqueue(uri_utf8, false); id = update.Enqueue(uri_utf8, false);
if (id == 0) { if (id == 0) {
/* retry later */ /* retry later */
ScheduleSeconds(INOTIFY_UPDATE_DELAY_S); ScheduleSeconds(INOTIFY_UPDATE_DELAY_S);

View File

@ -26,11 +26,16 @@
#include <list> #include <list>
#include <string> #include <string>
class UpdateService;
class InotifyQueue final : private TimeoutMonitor { class InotifyQueue final : private TimeoutMonitor {
UpdateService &update;
std::list<std::string> queue; std::list<std::string> queue;
public: public:
InotifyQueue(EventLoop &_loop):TimeoutMonitor(_loop) {} InotifyQueue(EventLoop &_loop, UpdateService &_update)
:TimeoutMonitor(_loop), update(_update) {}
void Enqueue(const char *uri_utf8); void Enqueue(const char *uri_utf8);

View File

@ -285,7 +285,7 @@ mpd_inotify_callback(int wd, unsigned mask,
} }
void void
mpd_inotify_init(EventLoop &loop, unsigned max_depth) mpd_inotify_init(EventLoop &loop, UpdateService &update, unsigned max_depth)
{ {
LogDebug(inotify_domain, "initializing inotify"); LogDebug(inotify_domain, "initializing inotify");
@ -320,7 +320,7 @@ mpd_inotify_init(EventLoop &loop, unsigned max_depth)
recursive_watch_subdirectories(inotify_root, path, 0); recursive_watch_subdirectories(inotify_root, path, 0);
inotify_queue = new InotifyQueue(loop); inotify_queue = new InotifyQueue(loop, update);
LogDebug(inotify_domain, "watching music directory"); LogDebug(inotify_domain, "watching music directory");
} }

View File

@ -24,11 +24,12 @@
#include "Compiler.h" #include "Compiler.h"
class EventLoop; class EventLoop;
class UpdateService;
#ifdef HAVE_INOTIFY_INIT #ifdef HAVE_INOTIFY_INIT
void void
mpd_inotify_init(EventLoop &loop, unsigned max_depth); mpd_inotify_init(EventLoop &loop, UpdateService &update, unsigned max_depth);
void void
mpd_inotify_finish(void); mpd_inotify_finish(void);
@ -36,7 +37,9 @@ mpd_inotify_finish(void);
#else /* !HAVE_INOTIFY_INIT */ #else /* !HAVE_INOTIFY_INIT */
static inline void static inline void
mpd_inotify_init(gcc_unused EventLoop &loop, gcc_unused unsigned max_depth) mpd_inotify_init(gcc_unused EventLoop &loop,
gcc_unused UpdateService &update,
gcc_unused unsigned max_depth)
{ {
} }

View File

@ -18,17 +18,10 @@
*/ */
#include "config.h" #include "config.h"
#include "UpdateQueue.hxx" #include "Queue.hxx"
#include <queue>
#include <list>
static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32;
static std::queue<UpdateQueueItem, std::list<UpdateQueueItem>> update_queue;
bool bool
update_queue_push(const char *path, bool discard, unsigned id) UpdateQueue::Push(const char *path, bool discard, unsigned id)
{ {
if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE) if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE)
return false; return false;
@ -38,7 +31,7 @@ update_queue_push(const char *path, bool discard, unsigned id)
} }
UpdateQueueItem UpdateQueueItem
update_queue_shift() UpdateQueue::Pop()
{ {
if (update_queue.empty()) if (update_queue.empty())
return UpdateQueueItem(); return UpdateQueueItem();

View File

@ -23,6 +23,8 @@
#include "check.h" #include "check.h"
#include <string> #include <string>
#include <queue>
#include <list>
struct UpdateQueueItem { struct UpdateQueueItem {
std::string path_utf8; std::string path_utf8;
@ -39,10 +41,15 @@ struct UpdateQueueItem {
} }
}; };
bool class UpdateQueue {
update_queue_push(const char *path, bool discard, unsigned id); static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32;
UpdateQueueItem std::queue<UpdateQueueItem, std::list<UpdateQueueItem>> update_queue;
update_queue_shift();
public:
bool Push(const char *path, bool discard, unsigned id);
UpdateQueueItem Pop();
};
#endif #endif

View File

@ -18,7 +18,7 @@
*/ */
#include "config.h" /* must be first for large file support */ #include "config.h" /* must be first for large file support */
#include "UpdateRemove.hxx" #include "Remove.hxx"
#include "UpdateDomain.hxx" #include "UpdateDomain.hxx"
#include "GlobalEvents.hxx" #include "GlobalEvents.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
@ -36,17 +36,12 @@
#include <assert.h> #include <assert.h>
static const Song *removed_song;
static Mutex remove_mutex;
static Cond remove_cond;
/** /**
* Safely remove a song from the database. This must be done in the * Safely remove a song from the database. This must be done in the
* main task, to be sure that there is no pointer left to it. * main task, to be sure that there is no pointer left to it.
*/ */
static void void
song_remove_event(void) UpdateRemoveService::RunDeferred()
{ {
assert(removed_song != nullptr); assert(removed_song != nullptr);
@ -74,19 +69,13 @@ song_remove_event(void)
} }
void void
update_remove_global_init(void) UpdateRemoveService::Remove(const Song *song)
{
GlobalEvents::Register(GlobalEvents::DELETE, song_remove_event);
}
void
update_remove_song(const Song *song)
{ {
assert(removed_song == nullptr); assert(removed_song == nullptr);
removed_song = song; removed_song = song;
GlobalEvents::Emit(GlobalEvents::DELETE); DeferredMonitor::Schedule();
remove_mutex.lock(); remove_mutex.lock();

View File

@ -21,18 +21,37 @@
#define MPD_UPDATE_REMOVE_HXX #define MPD_UPDATE_REMOVE_HXX
#include "check.h" #include "check.h"
#include "event/DeferredMonitor.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
struct Song; struct Song;
void
update_remove_global_init(void);
/** /**
* Sends a signal to the main thread which will in turn remove the * This class handles #Song removal. It defers the action to the main
* song: from the sticker database and from the playlist. This * thread to ensure that all references to the #Song are gone.
* serialized access is implemented to avoid excessive locking.
*/ */
void class UpdateRemoveService final : DeferredMonitor {
update_remove_song(const Song *song); Mutex remove_mutex;
Cond remove_cond;
const Song *removed_song;
public:
UpdateRemoveService(EventLoop &_loop)
:DeferredMonitor(_loop) {}
/**
* Sends a signal to the main thread which will in turn remove
* the song: from the sticker database and from the playlist.
* This serialized access is implemented to avoid excessive
* locking.
*/
void Remove(const Song *song);
private:
/* virtual methods from class DeferredMonitor */
virtual void RunDeferred() override;
};
#endif #endif

View File

@ -17,12 +17,5 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef MPD_UPDATE_INTERNAL_H #include "config.h"
#define MPD_UPDATE_INTERNAL_H #include "Service.hxx"
#include "check.h"
extern bool walk_discard;
extern bool modified;
#endif

89
src/db/update/Service.hxx Normal file
View File

@ -0,0 +1,89 @@
/*
* 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_UPDATE_SERVICE_HXX
#define MPD_UPDATE_SERVICE_HXX
#include "check.h"
#include "Queue.hxx"
#include "Walk.hxx"
#include "event/DeferredMonitor.hxx"
#include "thread/Thread.hxx"
/**
* This class manages the update queue and runs the update thread.
*/
class UpdateService final : DeferredMonitor {
enum Progress {
UPDATE_PROGRESS_IDLE = 0,
UPDATE_PROGRESS_RUNNING = 1,
UPDATE_PROGRESS_DONE = 2
};
Progress progress;
bool modified;
Thread update_thread;
static const unsigned update_task_id_max = 1 << 15;
unsigned update_task_id;
UpdateQueue queue;
UpdateQueueItem next;
UpdateWalk walk;
public:
UpdateService(EventLoop &_loop);
/**
* Returns a non-zero job id when we are currently updating
* the database.
*/
unsigned GetId() const {
return next.id;
}
/**
* Add this path to the database update queue.
*
* @param path a path to update; if an empty string,
* the whole music directory is updated
* @return the job id, or 0 on error
*/
gcc_nonnull_all
unsigned Enqueue(const char *path, bool discard);
private:
/* virtual methods from class DeferredMonitor */
virtual void RunDeferred() override;
/* the update thread */
void Task();
static void Task(void *ctx);
void StartThread(UpdateQueueItem &&i);
unsigned GenerateId();
};
#endif

View File

@ -1,50 +0,0 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_UPDATE_ARCHIVE_HXX
#define MPD_UPDATE_ARCHIVE_HXX
#include "check.h"
#include "Compiler.h"
#include <sys/stat.h>
struct Directory;
#ifdef ENABLE_ARCHIVE
bool
update_archive_file(Directory &directory,
const char *name, const char *suffix,
const struct stat *st);
#else
static inline bool
update_archive_file(gcc_unused Directory &directory,
gcc_unused const char *name,
gcc_unused const char *suffix,
gcc_unused const struct stat *st)
{
return false;
}
#endif
#endif

View File

@ -1,36 +0,0 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_UPDATE_CONTAINER_HXX
#define MPD_UPDATE_CONTAINER_HXX
#include "check.h"
#include <sys/stat.h>
struct Directory;
struct DecoderPlugin;
bool
update_container_file(Directory &directory,
const char *name,
const struct stat *st,
const char *suffix);
#endif

View File

@ -18,15 +18,11 @@
*/ */
#include "config.h" #include "config.h"
#include "UpdateGlue.hxx" #include "Service.hxx"
#include "UpdateQueue.hxx"
#include "UpdateWalk.hxx"
#include "UpdateRemove.hxx"
#include "UpdateDomain.hxx" #include "UpdateDomain.hxx"
#include "Mapper.hxx" #include "Mapper.hxx"
#include "db/DatabaseSimple.hxx" #include "db/DatabaseSimple.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include "GlobalEvents.hxx"
#include "util/Error.hxx" #include "util/Error.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "Main.hxx" #include "Main.hxx"
@ -38,30 +34,8 @@
#include <assert.h> #include <assert.h>
static enum update_progress { inline void
UPDATE_PROGRESS_IDLE = 0, UpdateService::Task()
UPDATE_PROGRESS_RUNNING = 1,
UPDATE_PROGRESS_DONE = 2
} progress;
static bool modified;
static Thread update_thread;
static const unsigned update_task_id_max = 1 << 15;
static unsigned update_task_id;
static UpdateQueueItem next;
unsigned
isUpdatingDB(void)
{
return next.id;
}
static void
update_task(gcc_unused void *ctx)
{ {
if (!next.path_utf8.empty()) if (!next.path_utf8.empty())
FormatDebug(update_domain, "starting: %s", FormatDebug(update_domain, "starting: %s",
@ -71,7 +45,7 @@ update_task(gcc_unused void *ctx)
SetThreadIdlePriority(); SetThreadIdlePriority();
modified = update_walk(next.path_utf8.c_str(), next.discard); modified = walk.Walk(next.path_utf8.c_str(), next.discard);
if (modified || !db_exists()) { if (modified || !db_exists()) {
Error error; Error error;
@ -86,11 +60,18 @@ update_task(gcc_unused void *ctx)
LogDebug(update_domain, "finished"); LogDebug(update_domain, "finished");
progress = UPDATE_PROGRESS_DONE; progress = UPDATE_PROGRESS_DONE;
GlobalEvents::Emit(GlobalEvents::UPDATE); DeferredMonitor::Schedule();
} }
static void void
spawn_update_task(UpdateQueueItem &&i) UpdateService::Task(void *ctx)
{
UpdateService &service = *(UpdateService *)ctx;
return service.Task();
}
void
UpdateService::StartThread(UpdateQueueItem &&i)
{ {
assert(main_thread.IsInside()); assert(main_thread.IsInside());
@ -100,15 +81,15 @@ spawn_update_task(UpdateQueueItem &&i)
next = std::move(i); next = std::move(i);
Error error; Error error;
if (!update_thread.Start(update_task, nullptr, error)) if (!update_thread.Start(Task, this, error))
FatalError(error); FatalError(error);
FormatDebug(update_domain, FormatDebug(update_domain,
"spawned thread for update job id %i", next.id); "spawned thread for update job id %i", next.id);
} }
static unsigned unsigned
generate_update_id() UpdateService::GenerateId()
{ {
unsigned id = update_task_id + 1; unsigned id = update_task_id + 1;
if (id > update_task_id_max) if (id > update_task_id_max)
@ -117,7 +98,7 @@ generate_update_id()
} }
unsigned unsigned
update_enqueue(const char *path, bool discard) UpdateService::Enqueue(const char *path, bool discard)
{ {
assert(main_thread.IsInside()); assert(main_thread.IsInside());
@ -125,16 +106,16 @@ update_enqueue(const char *path, bool discard)
return 0; return 0;
if (progress != UPDATE_PROGRESS_IDLE) { if (progress != UPDATE_PROGRESS_IDLE) {
const unsigned id = generate_update_id(); const unsigned id = GenerateId();
if (!update_queue_push(path, discard, id)) if (!queue.Push(path, discard, id))
return 0; return 0;
update_task_id = id; update_task_id = id;
return id; return id;
} }
const unsigned id = update_task_id = generate_update_id(); const unsigned id = update_task_id = GenerateId();
spawn_update_task(UpdateQueueItem(path, discard, id)); StartThread(UpdateQueueItem(path, discard, id));
idle_add(IDLE_UPDATE); idle_add(IDLE_UPDATE);
@ -144,7 +125,8 @@ update_enqueue(const char *path, bool discard)
/** /**
* Called in the main thread after the database update is finished. * Called in the main thread after the database update is finished.
*/ */
static void update_finished_event(void) void
UpdateService::RunDeferred()
{ {
assert(progress == UPDATE_PROGRESS_DONE); assert(progress == UPDATE_PROGRESS_DONE);
assert(next.IsDefined()); assert(next.IsDefined());
@ -158,24 +140,16 @@ static void update_finished_event(void)
/* send "idle" events */ /* send "idle" events */
instance->DatabaseModified(); instance->DatabaseModified();
auto i = update_queue_shift(); auto i = queue.Pop();
if (i.IsDefined()) { if (i.IsDefined()) {
/* schedule the next path */ /* schedule the next path */
spawn_update_task(std::move(i)); StartThread(std::move(i));
} else { } else {
progress = UPDATE_PROGRESS_IDLE; progress = UPDATE_PROGRESS_IDLE;
} }
} }
void update_global_init(void) UpdateService::UpdateService(EventLoop &_loop)
:DeferredMonitor(_loop), walk(_loop)
{ {
GlobalEvents::Register(GlobalEvents::UPDATE, update_finished_event);
update_remove_global_init();
update_walk_global_init();
}
void update_global_finish(void)
{
update_walk_global_finish();
} }

View File

@ -1,43 +0,0 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_UPDATE_GLUE_HXX
#define MPD_UPDATE_GLUE_HXX
#include "Compiler.h"
void update_global_init(void);
void update_global_finish(void);
unsigned
isUpdatingDB(void);
/**
* Add this path to the database update queue.
*
* @param path a path to update; if an empty string,
* the whole music directory is updated
* @return the job id, or 0 on error
*/
gcc_nonnull_all
unsigned
update_enqueue(const char *path, bool discard);
#endif

View File

@ -18,11 +18,8 @@
*/ */
#include "config.h" /* must be first for large file support */ #include "config.h" /* must be first for large file support */
#include "UpdateSong.hxx" #include "Service.hxx"
#include "UpdateInternal.hxx"
#include "UpdateIO.hxx" #include "UpdateIO.hxx"
#include "UpdateDatabase.hxx"
#include "UpdateContainer.hxx"
#include "UpdateDomain.hxx" #include "UpdateDomain.hxx"
#include "db/DatabaseLock.hxx" #include "db/DatabaseLock.hxx"
#include "db/Directory.hxx" #include "db/Directory.hxx"
@ -32,10 +29,10 @@
#include <unistd.h> #include <unistd.h>
static void inline void
update_song_file2(Directory &directory, UpdateWalk::UpdateSongFile2(Directory &directory,
const char *name, const struct stat *st, const char *name, const char *suffix,
const char *suffix) const struct stat *st)
{ {
db_lock(); db_lock();
Song *song = directory.FindSong(name); Song *song = directory.FindSong(name);
@ -47,7 +44,7 @@ update_song_file2(Directory &directory,
directory.GetPath(), name); directory.GetPath(), name);
if (song != nullptr) { if (song != nullptr) {
db_lock(); db_lock();
delete_song(directory, song); editor.DeleteSong(directory, song);
db_unlock(); db_unlock();
} }
@ -56,10 +53,10 @@ update_song_file2(Directory &directory,
if (!(song != nullptr && st->st_mtime == song->mtime && if (!(song != nullptr && st->st_mtime == song->mtime &&
!walk_discard) && !walk_discard) &&
update_container_file(directory, name, st, suffix)) { UpdateContainerFile(directory, name, suffix, st)) {
if (song != nullptr) { if (song != nullptr) {
db_lock(); db_lock();
delete_song(directory, song); editor.DeleteSong(directory, song);
db_unlock(); db_unlock();
} }
@ -92,7 +89,7 @@ update_song_file2(Directory &directory,
"deleting unrecognized file %s/%s", "deleting unrecognized file %s/%s",
directory.GetPath(), name); directory.GetPath(), name);
db_lock(); db_lock();
delete_song(directory, song); editor.DeleteSong(directory, song);
db_unlock(); db_unlock();
} }
@ -101,13 +98,13 @@ update_song_file2(Directory &directory,
} }
bool bool
update_song_file(Directory &directory, UpdateWalk::UpdateSongFile(Directory &directory,
const char *name, const char *suffix, const char *name, const char *suffix,
const struct stat *st) const struct stat *st)
{ {
if (!decoder_plugins_supports_suffix(suffix)) if (!decoder_plugins_supports_suffix(suffix))
return false; return false;
update_song_file2(directory, name, st, suffix); UpdateSongFile2(directory, name, suffix, st);
return true; return true;
} }

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_UPDATE_SONG_HXX
#define MPD_UPDATE_SONG_HXX
#include "check.h"
#include <sys/stat.h>
struct Directory;
bool
update_song_file(Directory &directory,
const char *name, const char *suffix,
const struct stat *st);
#endif

View File

@ -1,37 +0,0 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_UPDATE_WALK_HXX
#define MPD_UPDATE_WALK_HXX
#include "check.h"
void
update_walk_global_init(void);
void
update_walk_global_finish(void);
/**
* Returns true if the database was modified.
*/
bool
update_walk(const char *path, bool discard);
#endif

View File

@ -18,11 +18,9 @@
*/ */
#include "config.h" /* must be first for large file support */ #include "config.h" /* must be first for large file support */
#include "UpdateWalk.hxx" #include "Walk.hxx"
#include "UpdateIO.hxx" #include "UpdateIO.hxx"
#include "UpdateDatabase.hxx" #include "Editor.hxx"
#include "UpdateSong.hxx"
#include "UpdateArchive.hxx"
#include "UpdateDomain.hxx" #include "UpdateDomain.hxx"
#include "db/DatabaseLock.hxx" #include "db/DatabaseLock.hxx"
#include "db/DatabaseSimple.hxx" #include "db/DatabaseSimple.hxx"
@ -49,21 +47,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <errno.h> #include <errno.h>
bool walk_discard; UpdateWalk::UpdateWalk(EventLoop &_loop)
bool modified; :editor(_loop)
#ifndef WIN32
static constexpr bool DEFAULT_FOLLOW_INSIDE_SYMLINKS = true;
static constexpr bool DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true;
static bool follow_inside_symlinks;
static bool follow_outside_symlinks;
#endif
void
update_walk_global_init(void)
{ {
#ifndef WIN32 #ifndef WIN32
follow_inside_symlinks = follow_inside_symlinks =
@ -76,11 +61,6 @@ update_walk_global_init(void)
#endif #endif
} }
void
update_walk_global_finish(void)
{
}
static void static void
directory_set_stat(Directory &dir, const struct stat *st) directory_set_stat(Directory &dir, const struct stat *st)
{ {
@ -89,9 +69,9 @@ directory_set_stat(Directory &dir, const struct stat *st)
dir.have_stat = true; dir.have_stat = true;
} }
static void inline void
remove_excluded_from_directory(Directory &directory, UpdateWalk::RemoveExcludedFromDirectory(Directory &directory,
const ExcludeList &exclude_list) const ExcludeList &exclude_list)
{ {
db_lock(); db_lock();
@ -100,7 +80,7 @@ remove_excluded_from_directory(Directory &directory,
const auto name_fs = AllocatedPath::FromUTF8(child->GetName()); const auto name_fs = AllocatedPath::FromUTF8(child->GetName());
if (name_fs.IsNull() || exclude_list.Check(name_fs)) { if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
delete_directory(child); editor.DeleteDirectory(child);
modified = true; modified = true;
} }
} }
@ -111,7 +91,7 @@ remove_excluded_from_directory(Directory &directory,
const auto name_fs = AllocatedPath::FromUTF8(song->uri); const auto name_fs = AllocatedPath::FromUTF8(song->uri);
if (name_fs.IsNull() || exclude_list.Check(name_fs)) { if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
delete_song(directory, song); editor.DeleteSong(directory, song);
modified = true; modified = true;
} }
} }
@ -119,8 +99,8 @@ remove_excluded_from_directory(Directory &directory,
db_unlock(); db_unlock();
} }
static void inline void
purge_deleted_from_directory(Directory &directory) UpdateWalk::PurgeDeletedFromDirectory(Directory &directory)
{ {
Directory *child, *n; Directory *child, *n;
directory_for_each_child_safe(child, n, directory) { directory_for_each_child_safe(child, n, directory) {
@ -128,7 +108,7 @@ purge_deleted_from_directory(Directory &directory)
continue; continue;
db_lock(); db_lock();
delete_directory(child); editor.DeleteDirectory(child);
db_unlock(); db_unlock();
modified = true; modified = true;
@ -139,7 +119,7 @@ purge_deleted_from_directory(Directory &directory)
const auto path = map_song_fs(*song); const auto path = map_song_fs(*song);
if (path.IsNull() || !FileExists(path)) { if (path.IsNull() || !FileExists(path)) {
db_lock(); db_lock();
delete_song(directory, song); editor.DeleteSong(directory, song);
db_unlock(); db_unlock();
modified = true; modified = true;
@ -195,10 +175,10 @@ find_inode_ancestor(Directory *parent, ino_t inode, dev_t device)
return 0; return 0;
} }
static bool inline bool
update_playlist_file2(Directory &directory, UpdateWalk::UpdatePlaylistFile(Directory &directory,
const char *name, const char *suffix, const char *name, const char *suffix,
const struct stat *st) const struct stat *st)
{ {
if (!playlist_suffix_supported(suffix)) if (!playlist_suffix_supported(suffix))
return false; return false;
@ -212,30 +192,27 @@ update_playlist_file2(Directory &directory,
return true; return true;
} }
static bool inline bool
update_regular_file(Directory &directory, UpdateWalk::UpdateRegularFile(Directory &directory,
const char *name, const struct stat *st) const char *name, const struct stat *st)
{ {
const char *suffix = uri_get_suffix(name); const char *suffix = uri_get_suffix(name);
if (suffix == nullptr) if (suffix == nullptr)
return false; return false;
return update_song_file(directory, name, suffix, st) || return UpdateSongFile(directory, name, suffix, st) ||
update_archive_file(directory, name, suffix, st) || UpdateArchiveFile(directory, name, suffix, st) ||
update_playlist_file2(directory, name, suffix, st); UpdatePlaylistFile(directory, name, suffix, st);
} }
static bool void
update_directory(Directory &directory, const struct stat *st); UpdateWalk::UpdateDirectoryChild(Directory &directory,
const char *name, const struct stat *st)
static void
update_directory_child(Directory &directory,
const char *name, const struct stat *st)
{ {
assert(strchr(name, '/') == nullptr); assert(strchr(name, '/') == nullptr);
if (S_ISREG(st->st_mode)) { if (S_ISREG(st->st_mode)) {
update_regular_file(directory, name, st); UpdateRegularFile(directory, name, st);
} else if (S_ISDIR(st->st_mode)) { } else if (S_ISDIR(st->st_mode)) {
if (find_inode_ancestor(&directory, st->st_ino, st->st_dev)) if (find_inode_ancestor(&directory, st->st_ino, st->st_dev))
return; return;
@ -246,9 +223,9 @@ update_directory_child(Directory &directory,
assert(&directory == subdir->parent); assert(&directory == subdir->parent);
if (!update_directory(*subdir, st)) { if (!UpdateDirectory(*subdir, st)) {
db_lock(); db_lock();
delete_directory(subdir); editor.DeleteDirectory(subdir);
db_unlock(); db_unlock();
} }
} else { } else {
@ -268,8 +245,9 @@ static bool skip_path(Path path_fs)
} }
gcc_pure gcc_pure
static bool bool
skip_symlink(const Directory *directory, const char *utf8_name) UpdateWalk::SkipSymlink(const Directory *directory,
const char *utf8_name) const
{ {
#ifndef WIN32 #ifndef WIN32
const auto path_fs = map_directory_child_fs(*directory, utf8_name); const auto path_fs = map_directory_child_fs(*directory, utf8_name);
@ -333,8 +311,8 @@ skip_symlink(const Directory *directory, const char *utf8_name)
#endif #endif
} }
static bool bool
update_directory(Directory &directory, const struct stat *st) UpdateWalk::UpdateDirectory(Directory &directory, const struct stat *st)
{ {
assert(S_ISDIR(st->st_mode)); assert(S_ISDIR(st->st_mode));
@ -358,9 +336,9 @@ update_directory(Directory &directory, const struct stat *st)
exclude_list.LoadFile(AllocatedPath::Build(path_fs, ".mpdignore")); exclude_list.LoadFile(AllocatedPath::Build(path_fs, ".mpdignore"));
if (!exclude_list.IsEmpty()) if (!exclude_list.IsEmpty())
remove_excluded_from_directory(directory, exclude_list); RemoveExcludedFromDirectory(directory, exclude_list);
purge_deleted_from_directory(directory); PurgeDeletedFromDirectory(directory);
while (reader.ReadEntry()) { while (reader.ReadEntry()) {
std::string utf8; std::string utf8;
@ -375,15 +353,15 @@ update_directory(Directory &directory, const struct stat *st)
if (utf8.empty()) if (utf8.empty())
continue; continue;
if (skip_symlink(&directory, utf8.c_str())) { if (SkipSymlink(&directory, utf8.c_str())) {
modified |= delete_name_in(directory, utf8.c_str()); modified |= editor.DeleteNameIn(directory, utf8.c_str());
continue; continue;
} }
if (stat_directory_child(directory, utf8.c_str(), &st2) == 0) if (stat_directory_child(directory, utf8.c_str(), &st2) == 0)
update_directory_child(directory, utf8.c_str(), &st2); UpdateDirectoryChild(directory, utf8.c_str(), &st2);
else else
modified |= delete_name_in(directory, utf8.c_str()); modified |= editor.DeleteNameIn(directory, utf8.c_str());
} }
directory.mtime = st->st_mtime; directory.mtime = st->st_mtime;
@ -391,8 +369,8 @@ update_directory(Directory &directory, const struct stat *st)
return true; return true;
} }
static Directory * inline Directory *
directory_make_child_checked(Directory &parent, const char *name_utf8) UpdateWalk::DirectoryMakeChildChecked(Directory &parent, const char *name_utf8)
{ {
db_lock(); db_lock();
Directory *directory = parent.FindChild(name_utf8); Directory *directory = parent.FindChild(name_utf8);
@ -406,7 +384,7 @@ directory_make_child_checked(Directory &parent, const char *name_utf8)
find_inode_ancestor(&parent, st.st_ino, st.st_dev)) find_inode_ancestor(&parent, st.st_ino, st.st_dev))
return nullptr; return nullptr;
if (skip_symlink(&parent, name_utf8)) if (SkipSymlink(&parent, name_utf8))
return nullptr; return nullptr;
/* if we're adding directory paths, make sure to delete filenames /* if we're adding directory paths, make sure to delete filenames
@ -414,7 +392,7 @@ directory_make_child_checked(Directory &parent, const char *name_utf8)
db_lock(); db_lock();
Song *conflicting = parent.FindSong(name_utf8); Song *conflicting = parent.FindSong(name_utf8);
if (conflicting) if (conflicting)
delete_song(parent, conflicting); editor.DeleteSong(parent, conflicting);
directory = parent.CreateChild(name_utf8); directory = parent.CreateChild(name_utf8);
db_unlock(); db_unlock();
@ -423,8 +401,8 @@ directory_make_child_checked(Directory &parent, const char *name_utf8)
return directory; return directory;
} }
static Directory * inline Directory *
directory_make_uri_parent_checked(const char *uri) UpdateWalk::DirectoryMakeUriParentChecked(const char *uri)
{ {
Directory *directory = db_get_root(); Directory *directory = db_get_root();
char *duplicated = xstrdup(uri); char *duplicated = xstrdup(uri);
@ -436,8 +414,8 @@ directory_make_uri_parent_checked(const char *uri)
if (*name_utf8 == 0) if (*name_utf8 == 0)
continue; continue;
directory = directory_make_child_checked(*directory, directory = DirectoryMakeChildChecked(*directory,
name_utf8); name_utf8);
if (directory == nullptr) if (directory == nullptr)
break; break;
@ -448,37 +426,37 @@ directory_make_uri_parent_checked(const char *uri)
return directory; return directory;
} }
static void inline void
update_uri(const char *uri) UpdateWalk::UpdateUri(const char *uri)
{ {
Directory *parent = directory_make_uri_parent_checked(uri); Directory *parent = DirectoryMakeUriParentChecked(uri);
if (parent == nullptr) if (parent == nullptr)
return; return;
const char *name = PathTraitsUTF8::GetBase(uri); const char *name = PathTraitsUTF8::GetBase(uri);
struct stat st; struct stat st;
if (!skip_symlink(parent, name) && if (!SkipSymlink(parent, name) &&
stat_directory_child(*parent, name, &st) == 0) stat_directory_child(*parent, name, &st) == 0)
update_directory_child(*parent, name, &st); UpdateDirectoryChild(*parent, name, &st);
else else
modified |= delete_name_in(*parent, name); modified |= editor.DeleteNameIn(*parent, name);
} }
bool bool
update_walk(const char *path, bool discard) UpdateWalk::Walk(const char *path, bool discard)
{ {
walk_discard = discard; walk_discard = discard;
modified = false; modified = false;
if (path != nullptr && !isRootDirectory(path)) { if (path != nullptr && !isRootDirectory(path)) {
update_uri(path); UpdateUri(path);
} else { } else {
Directory *directory = db_get_root(); Directory *directory = db_get_root();
struct stat st; struct stat st;
if (stat_directory(*directory, &st) == 0) if (stat_directory(*directory, &st) == 0)
update_directory(*directory, &st); UpdateDirectory(*directory, &st);
} }
return modified; return modified;

134
src/db/update/Walk.hxx Normal file
View File

@ -0,0 +1,134 @@
/*
* 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_UPDATE_WALK_HXX
#define MPD_UPDATE_WALK_HXX
#include "check.h"
#include "Editor.hxx"
#include <sys/stat.h>
struct stat;
struct Directory;
struct archive_plugin;
class ExcludeList;
class UpdateWalk final {
#ifdef ENABLE_ARCHIVE
friend class UpdateArchiveVisitor;
#endif
#ifndef WIN32
static constexpr bool DEFAULT_FOLLOW_INSIDE_SYMLINKS = true;
static constexpr bool DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true;
bool follow_inside_symlinks;
bool follow_outside_symlinks;
#endif
bool walk_discard;
bool modified;
DatabaseEditor editor;
public:
UpdateWalk(EventLoop &_loop);
/**
* Returns true if the database was modified.
*/
bool Walk(const char *path, bool discard);
private:
gcc_pure
bool SkipSymlink(const Directory *directory,
const char *utf8_name) const;
void RemoveExcludedFromDirectory(Directory &directory,
const ExcludeList &exclude_list);
void PurgeDeletedFromDirectory(Directory &directory);
void UpdateSongFile2(Directory &directory,
const char *name, const char *suffix,
const struct stat *st);
bool UpdateSongFile(Directory &directory,
const char *name, const char *suffix,
const struct stat *st);
bool UpdateContainerFile(Directory &directory,
const char *name, const char *suffix,
const struct stat *st);
#ifdef ENABLE_ARCHIVE
void UpdateArchiveTree(Directory &parent, const char *name);
bool UpdateArchiveFile(Directory &directory,
const char *name, const char *suffix,
const struct stat *st);
void UpdateArchiveFile(Directory &directory, const char *name,
const struct stat *st,
const archive_plugin &plugin);
#else
bool UpdateArchiveFile(gcc_unused Directory &directory,
gcc_unused const char *name,
gcc_unused const char *suffix,
gcc_unused const struct stat *st) {
return false;
}
#endif
bool UpdatePlaylistFile(Directory &directory,
const char *name, const char *suffix,
const struct stat *st);
bool UpdateRegularFile(Directory &directory,
const char *name, const struct stat *st);
void UpdateDirectoryChild(Directory &directory,
const char *name, const struct stat *st);
bool UpdateDirectory(Directory &directory, const struct stat *st);
/**
* Create the specified directory object if it does not exist
* already or if the #stat object indicates that it has been
* modified since the last update. Returns nullptr when it
* exists already and is unmodified.
*
* The caller must lock the database.
*/
Directory *MakeDirectoryIfModified(Directory &parent, const char *name,
const struct stat *st);
Directory *DirectoryMakeChildChecked(Directory &parent,
const char *name_utf8);
Directory *DirectoryMakeUriParentChecked(const char *uri);
void UpdateUri(const char *uri);
};
#endif