Commit c6725884 authored by Max Kellermann's avatar Max Kellermann

db/update: convert to OO API

Move global variables into the new classes. That may allow multiple update threads for multiple databases one day.
parent a31738f6
......@@ -124,15 +124,15 @@ src_mpd_SOURCES = \
src/filter/FilterInternal.hxx \
src/filter/FilterRegistry.cxx src/filter/FilterRegistry.hxx \
src/db/update/UpdateDomain.cxx src/db/update/UpdateDomain.hxx \
src/db/update/UpdateGlue.cxx src/db/update/UpdateGlue.hxx \
src/db/update/UpdateQueue.cxx src/db/update/UpdateQueue.hxx \
src/db/update/Service.cxx src/db/update/Service.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/UpdateDatabase.cxx src/db/update/UpdateDatabase.hxx \
src/db/update/UpdateWalk.cxx src/db/update/UpdateWalk.hxx \
src/db/update/UpdateSong.cxx src/db/update/UpdateSong.hxx \
src/db/update/UpdateContainer.cxx src/db/update/UpdateContainer.hxx \
src/db/update/UpdateInternal.hxx \
src/db/update/UpdateRemove.cxx src/db/update/UpdateRemove.hxx \
src/db/update/Editor.cxx src/db/update/Editor.hxx \
src/db/update/Walk.cxx src/db/update/Walk.hxx \
src/db/update/UpdateSong.cxx \
src/db/update/Container.cxx \
src/db/update/Remove.cxx src/db/update/Remove.hxx \
src/db/update/ExcludeList.cxx src/db/update/ExcludeList.hxx \
src/client/Client.cxx src/client/Client.hxx \
src/client/ClientInternal.hxx \
......@@ -486,7 +486,7 @@ if ENABLE_ARCHIVE
noinst_LIBRARIES += libarchive.a
src_mpd_SOURCES += \
src/db/update/UpdateArchive.cxx src/db/update/UpdateArchive.hxx
src/db/update/Archive.cxx
libarchive_a_SOURCES = \
src/archive/ArchiveDomain.cxx src/archive/ArchiveDomain.hxx \
......
......@@ -33,9 +33,6 @@ class EventLoop;
namespace GlobalEvents {
enum Event {
/** database update was finished */
UPDATE,
/** during database update, a song was deleted */
DELETE,
......
......@@ -29,6 +29,7 @@
class NeighborGlue;
#endif
class UpdateService;
class ClientList;
struct Partition;
......@@ -42,6 +43,8 @@ struct Instance final
NeighborGlue *neighbors;
#endif
UpdateService *update;
ClientList *client_list;
Partition *partition;
......
......@@ -23,7 +23,7 @@
#include "CommandLine.hxx"
#include "PlaylistFile.hxx"
#include "PlaylistGlobal.hxx"
#include "db/update/UpdateGlue.hxx"
#include "db/update/Service.hxx"
#include "MusicChunk.hxx"
#include "StateFile.hxx"
#include "PlayerThread.hxx"
......@@ -448,10 +448,13 @@ int mpd_main(int argc, char *argv[])
}
decoder_plugin_init_all();
update_global_init();
const bool create_db = !glue_db_init_and_load();
instance->update = db_is_simple()
? new UpdateService(*main_loop)
: nullptr;
glue_sticker_init();
command_init();
......@@ -490,7 +493,7 @@ int mpd_main(int argc, char *argv[])
if (create_db) {
/* the database failed to load: recreate the
database */
unsigned job = update_enqueue("", true);
unsigned job = instance->update->Enqueue("", true);
if (job == 0)
FatalError("directory update failed");
}
......@@ -504,8 +507,9 @@ int mpd_main(int argc, char *argv[])
if (config_get_bool(CONF_AUTO_UPDATE, false)) {
#ifdef ENABLE_INOTIFY
if (mapper_has_music_directory())
mpd_inotify_init(*main_loop,
if (mapper_has_music_directory() &&
instance->update != nullptr)
mpd_inotify_init(*main_loop, *instance->update,
config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
G_MAXUINT));
#else
......@@ -558,6 +562,8 @@ int mpd_main(int argc, char *argv[])
}
#endif
delete instance->update;
const clock_t start = clock();
DatabaseGlobalDeinit();
FormatDebug(main_domain,
......@@ -575,7 +581,6 @@ int mpd_main(int argc, char *argv[])
mapper_finish();
delete instance->partition;
command_finish();
update_global_finish();
decoder_plugin_deinit_all();
#ifdef ENABLE_ARCHIVE
archive_plugin_deinit_all();
......
......@@ -20,7 +20,7 @@
#include "config.h"
#include "OtherCommands.hxx"
#include "DatabaseCommands.hxx"
#include "db/update/UpdateGlue.hxx"
#include "db/update/Service.hxx"
#include "CommandError.hxx"
#include "db/Uri.hxx"
#include "DetachedSong.hxx"
......@@ -46,6 +46,7 @@
#include "client/ClientFile.hxx"
#include "client/Client.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
#include "Idle.hxx"
#include <assert.h>
......@@ -186,7 +187,6 @@ CommandResult
handle_update(Client &client, gcc_unused int argc, char *argv[])
{
const char *path = "";
unsigned ret;
assert(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) {
client_printf(client, "updating_db: %i\n", ret);
return CommandResult::OK;
......@@ -217,7 +223,6 @@ CommandResult
handle_rescan(Client &client, gcc_unused int argc, char *argv[])
{
const char *path = "";
unsigned ret;
assert(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) {
client_printf(client, "updating_db: %i\n", ret);
return CommandResult::OK;
......
......@@ -22,10 +22,11 @@
#include "CommandError.hxx"
#include "Playlist.hxx"
#include "PlaylistPrint.hxx"
#include "db/update/UpdateGlue.hxx"
#include "db/update/Service.hxx"
#include "client/Client.hxx"
#include "mixer/Volume.hxx"
#include "Partition.hxx"
#include "Instance.hxx"
#include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx"
#include "AudioFormat.hxx"
......@@ -111,7 +112,6 @@ handle_status(Client &client,
gcc_unused int argc, gcc_unused char *argv[])
{
const char *state = nullptr;
int updateJobId;
int song;
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,
COMMAND_STATUS_UPDATING_DB ": %i\n",
updateJobId);
......
......@@ -18,8 +18,7 @@
*/
#include "config.h" /* must be first for large file support */
#include "UpdateArchive.hxx"
#include "UpdateInternal.hxx"
#include "Walk.hxx"
#include "UpdateDomain.hxx"
#include "db/DatabaseLock.hxx"
#include "db/Directory.hxx"
......@@ -35,10 +34,11 @@
#include <string>
#include <sys/stat.h>
#include <string.h>
static void
update_archive_tree(Directory &directory, const char *name)
void
UpdateWalk::UpdateArchiveTree(Directory &directory, const char *name)
{
const char *tmp = strchr(name, '/');
if (tmp) {
......@@ -51,7 +51,7 @@ update_archive_tree(Directory &directory, const char *name)
db_unlock();
//create directories first
update_archive_tree(*subdir, tmp+1);
UpdateArchiveTree(*subdir, tmp + 1);
} else {
if (strlen(name) == 0) {
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.
*
......@@ -86,10 +101,10 @@ update_archive_tree(Directory &directory, const char *name)
* @param st stat() information on the archive file
* @param plugin the archive plugin which fits this archive type
*/
static void
update_archive_file2(Directory &parent, const char *name,
const struct stat *st,
const struct archive_plugin *plugin)
void
UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name,
const struct stat *st,
const archive_plugin &plugin)
{
db_lock();
Directory *directory = parent.FindChild(name);
......@@ -105,7 +120,7 @@ update_archive_file2(Directory &parent, const char *name,
/* open archive */
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) {
LogError(error);
return;
......@@ -126,44 +141,21 @@ update_archive_file2(Directory &parent, const char *name,
directory->mtime = st->st_mtime;
class UpdateArchiveVisitor final : public ArchiveVisitor {
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);
UpdateArchiveVisitor visitor(*this, directory);
file->Visit(visitor);
file->Close();
}
bool
update_archive_file(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
UpdateWalk::UpdateArchiveFile(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
{
#ifdef ENABLE_ARCHIVE
const struct archive_plugin *plugin =
archive_plugin_from_suffix(suffix);
if (plugin == nullptr)
return false;
update_archive_file2(directory, name, st, plugin);
UpdateArchiveFile(directory, name, st, *plugin);
return true;
#else
(void)directory;
(void)name;
(void)suffix;
(void)st;
return false;
#endif
}
......@@ -18,9 +18,7 @@
*/
#include "config.h" /* must be first for large file support */
#include "UpdateContainer.hxx"
#include "UpdateInternal.hxx"
#include "UpdateDatabase.hxx"
#include "Walk.hxx"
#include "UpdateDomain.hxx"
#include "db/DatabaseLock.hxx"
#include "db/Directory.hxx"
......@@ -33,19 +31,13 @@
#include "tag/TagBuilder.hxx"
#include "Log.hxx"
#include <sys/stat.h>
#include <glib.h>
/**
* 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.
*/
static Directory *
make_directory_if_modified(Directory &parent, const char *name,
const struct stat *st)
Directory *
UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name,
const struct stat *st)
{
Directory *directory = parent.FindChild(name);
......@@ -56,7 +48,7 @@ make_directory_if_modified(Directory &parent, const char *name,
return nullptr;
}
delete_directory(directory);
editor.DeleteDirectory(directory);
modified = true;
}
......@@ -73,10 +65,9 @@ SupportsContainerSuffix(const DecoderPlugin &plugin, const char *suffix)
}
bool
update_container_file(Directory &directory,
const char *name,
const struct stat *st,
const char *suffix)
UpdateWalk::UpdateContainerFile(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
{
const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){
return SupportsContainerSuffix(plugin, suffix);
......@@ -86,7 +77,7 @@ update_container_file(Directory &directory,
const DecoderPlugin &plugin = *_plugin;
db_lock();
Directory *contdir = make_directory_if_modified(directory, name, st);
Directory *contdir = MakeDirectoryIfModified(directory, name, st);
if (contdir == nullptr) {
/* not modified */
db_unlock();
......@@ -128,7 +119,7 @@ update_container_file(Directory &directory,
if (tnum == 1) {
db_lock();
delete_directory(contdir);
editor.DeleteDirectory(contdir);
db_unlock();
return false;
} else
......
......@@ -18,8 +18,8 @@
*/
#include "config.h" /* must be first for large file support */
#include "UpdateDatabase.hxx"
#include "UpdateRemove.hxx"
#include "Editor.hxx"
#include "Remove.hxx"
#include "db/PlaylistVector.hxx"
#include "db/Directory.hxx"
#include "db/Song.hxx"
......@@ -29,7 +29,7 @@
#include <stddef.h>
void
delete_song(Directory &dir, Song *del)
DatabaseEditor::DeleteSong(Directory &dir, Song *del)
{
assert(del->parent == &dir);
......@@ -39,7 +39,7 @@ delete_song(Directory &dir, Song *del)
db_unlock(); /* temporary unlock, because update_remove_song() blocks */
/* 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 */
del->Free();
......@@ -53,32 +53,32 @@ delete_song(Directory &dir, Song *del)
*
* Caller must lock the #db_mutex.
*/
static void
clear_directory(Directory &directory)
inline void
DatabaseEditor::ClearDirectory(Directory &directory)
{
Directory *child, *n;
directory_for_each_child_safe(child, n, directory)
delete_directory(child);
DeleteDirectory(child);
Song *song, *ns;
directory_for_each_song_safe(song, ns, directory) {
assert(song->parent == &directory);
delete_song(directory, song);
DeleteSong(directory, song);
}
}
void
delete_directory(Directory *directory)
DatabaseEditor::DeleteDirectory(Directory *directory)
{
assert(directory->parent != nullptr);
clear_directory(*directory);
ClearDirectory(*directory);
directory->Delete();
}
bool
delete_name_in(Directory &parent, const char *name)
DatabaseEditor::DeleteNameIn(Directory &parent, const char *name)
{
bool modified = false;
......@@ -86,13 +86,13 @@ delete_name_in(Directory &parent, const char *name)
Directory *directory = parent.FindChild(name);
if (directory != nullptr) {
delete_directory(directory);
DeleteDirectory(directory);
modified = true;
}
Song *song = parent.FindSong(name);
if (song != nullptr) {
delete_song(parent, song);
DeleteSong(parent, song);
modified = true;
}
......
......@@ -21,30 +21,40 @@
#define MPD_UPDATE_DATABASE_HXX
#include "check.h"
#include "Remove.hxx"
struct Directory;
struct Song;
class UpdateRemoveService;
/**
* Caller must lock the #db_mutex.
*/
void
delete_song(Directory &parent, Song *song);
class DatabaseEditor final {
UpdateRemoveService remove;
/**
* Recursively free a directory and all its contents.
*
* Caller must lock the #db_mutex.
*/
void
delete_directory(Directory *directory);
public:
DatabaseEditor(EventLoop &_loop)
:remove(_loop) {}
/**
* Caller must NOT lock the #db_mutex.
*
* @return true if the database was modified
*/
bool
delete_name_in(Directory &parent, const char *name);
/**
* Caller must lock the #db_mutex.
*/
void DeleteSong(Directory &parent, Song *song);
/**
* 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
......@@ -20,7 +20,7 @@
#include "config.h"
#include "InotifyQueue.hxx"
#include "InotifyDomain.hxx"
#include "UpdateGlue.hxx"
#include "Service.hxx"
#include "Log.hxx"
#include <string.h>
......@@ -40,7 +40,7 @@ InotifyQueue::OnTimeout()
while (!queue.empty()) {
const char *uri_utf8 = queue.front().c_str();
id = update_enqueue(uri_utf8, false);
id = update.Enqueue(uri_utf8, false);
if (id == 0) {
/* retry later */
ScheduleSeconds(INOTIFY_UPDATE_DELAY_S);
......
......@@ -26,11 +26,16 @@
#include <list>
#include <string>
class UpdateService;
class InotifyQueue final : private TimeoutMonitor {
UpdateService &update;
std::list<std::string> queue;
public:
InotifyQueue(EventLoop &_loop):TimeoutMonitor(_loop) {}
InotifyQueue(EventLoop &_loop, UpdateService &_update)
:TimeoutMonitor(_loop), update(_update) {}
void Enqueue(const char *uri_utf8);
......
......@@ -285,7 +285,7 @@ mpd_inotify_callback(int wd, unsigned mask,
}
void
mpd_inotify_init(EventLoop &loop, unsigned max_depth)
mpd_inotify_init(EventLoop &loop, UpdateService &update, unsigned max_depth)
{
LogDebug(inotify_domain, "initializing inotify");
......@@ -320,7 +320,7 @@ mpd_inotify_init(EventLoop &loop, unsigned max_depth)
recursive_watch_subdirectories(inotify_root, path, 0);
inotify_queue = new InotifyQueue(loop);
inotify_queue = new InotifyQueue(loop, update);
LogDebug(inotify_domain, "watching music directory");
}
......
......@@ -24,11 +24,12 @@
#include "Compiler.h"
class EventLoop;
class UpdateService;
#ifdef HAVE_INOTIFY_INIT
void
mpd_inotify_init(EventLoop &loop, unsigned max_depth);
mpd_inotify_init(EventLoop &loop, UpdateService &update, unsigned max_depth);
void
mpd_inotify_finish(void);
......@@ -36,7 +37,9 @@ mpd_inotify_finish(void);
#else /* !HAVE_INOTIFY_INIT */
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)
{
}
......
......@@ -18,17 +18,10 @@
*/
#include "config.h"
#include "UpdateQueue.hxx"
#include <queue>
#include <list>
static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32;
static std::queue<UpdateQueueItem, std::list<UpdateQueueItem>> update_queue;
#include "Queue.hxx"
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)
return false;
......@@ -38,7 +31,7 @@ update_queue_push(const char *path, bool discard, unsigned id)
}
UpdateQueueItem
update_queue_shift()
UpdateQueue::Pop()
{
if (update_queue.empty())
return UpdateQueueItem();
......
......@@ -23,6 +23,8 @@
#include "check.h"
#include <string>
#include <queue>
#include <list>
struct UpdateQueueItem {
std::string path_utf8;
......@@ -39,10 +41,15 @@ struct UpdateQueueItem {
}
};
bool
update_queue_push(const char *path, bool discard, unsigned id);
class UpdateQueue {
static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32;
UpdateQueueItem
update_queue_shift();
std::queue<UpdateQueueItem, std::list<UpdateQueueItem>> update_queue;
public:
bool Push(const char *path, bool discard, unsigned id);
UpdateQueueItem Pop();
};
#endif
......@@ -18,7 +18,7 @@
*/
#include "config.h" /* must be first for large file support */
#include "UpdateRemove.hxx"
#include "Remove.hxx"
#include "UpdateDomain.hxx"
#include "GlobalEvents.hxx"
#include "thread/Mutex.hxx"
......@@ -36,17 +36,12 @@
#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
* main task, to be sure that there is no pointer left to it.
*/
static void
song_remove_event(void)
void
UpdateRemoveService::RunDeferred()
{
assert(removed_song != nullptr);
......@@ -74,19 +69,13 @@ song_remove_event(void)
}
void
update_remove_global_init(void)
{
GlobalEvents::Register(GlobalEvents::DELETE, song_remove_event);
}
void
update_remove_song(const Song *song)
UpdateRemoveService::Remove(const Song *song)
{
assert(removed_song == nullptr);
removed_song = song;
GlobalEvents::Emit(GlobalEvents::DELETE);
DeferredMonitor::Schedule();
remove_mutex.lock();
......
......@@ -21,18 +21,37 @@
#define MPD_UPDATE_REMOVE_HXX
#include "check.h"
#include "event/DeferredMonitor.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
struct Song;
void
update_remove_global_init(void);
/**
* 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.
* This class handles #Song removal. It defers the action to the main
* thread to ensure that all references to the #Song are gone.
*/
void
update_remove_song(const Song *song);
class UpdateRemoveService final : DeferredMonitor {
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
......@@ -17,12 +17,5 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_UPDATE_INTERNAL_H
#define MPD_UPDATE_INTERNAL_H
#include "check.h"
extern bool walk_discard;
extern bool modified;
#endif
#include "config.h"
#include "Service.hxx"
......@@ -17,27 +17,73 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_UPDATE_GLUE_HXX
#define MPD_UPDATE_GLUE_HXX
#ifndef MPD_UPDATE_SERVICE_HXX
#define MPD_UPDATE_SERVICE_HXX
#include "Compiler.h"
#include "check.h"
#include "Queue.hxx"
#include "Walk.hxx"
#include "event/DeferredMonitor.hxx"
#include "thread/Thread.hxx"
void update_global_init(void);
/**
* 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
};
void update_global_finish(void);
Progress progress;
unsigned
isUpdatingDB(void);
bool modified;
/**
* 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);
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
/*
* 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
/*
* 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
......@@ -18,15 +18,11 @@
*/
#include "config.h"
#include "UpdateGlue.hxx"
#include "UpdateQueue.hxx"
#include "UpdateWalk.hxx"
#include "UpdateRemove.hxx"
#include "Service.hxx"
#include "UpdateDomain.hxx"
#include "Mapper.hxx"
#include "db/DatabaseSimple.hxx"
#include "Idle.hxx"
#include "GlobalEvents.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
#include "Main.hxx"
......@@ -38,30 +34,8 @@
#include <assert.h>
static enum update_progress {
UPDATE_PROGRESS_IDLE = 0,
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)
inline void
UpdateService::Task()
{
if (!next.path_utf8.empty())
FormatDebug(update_domain, "starting: %s",
......@@ -71,7 +45,7 @@ update_task(gcc_unused void *ctx)
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()) {
Error error;
......@@ -86,11 +60,18 @@ update_task(gcc_unused void *ctx)
LogDebug(update_domain, "finished");
progress = UPDATE_PROGRESS_DONE;
GlobalEvents::Emit(GlobalEvents::UPDATE);
DeferredMonitor::Schedule();
}
static void
spawn_update_task(UpdateQueueItem &&i)
void
UpdateService::Task(void *ctx)
{
UpdateService &service = *(UpdateService *)ctx;
return service.Task();
}
void
UpdateService::StartThread(UpdateQueueItem &&i)
{
assert(main_thread.IsInside());
......@@ -100,15 +81,15 @@ spawn_update_task(UpdateQueueItem &&i)
next = std::move(i);
Error error;
if (!update_thread.Start(update_task, nullptr, error))
if (!update_thread.Start(Task, this, error))
FatalError(error);
FormatDebug(update_domain,
"spawned thread for update job id %i", next.id);
}
static unsigned
generate_update_id()
unsigned
UpdateService::GenerateId()
{
unsigned id = update_task_id + 1;
if (id > update_task_id_max)
......@@ -117,7 +98,7 @@ generate_update_id()
}
unsigned
update_enqueue(const char *path, bool discard)
UpdateService::Enqueue(const char *path, bool discard)
{
assert(main_thread.IsInside());
......@@ -125,16 +106,16 @@ update_enqueue(const char *path, bool discard)
return 0;
if (progress != UPDATE_PROGRESS_IDLE) {
const unsigned id = generate_update_id();
if (!update_queue_push(path, discard, id))
const unsigned id = GenerateId();
if (!queue.Push(path, discard, id))
return 0;
update_task_id = id;
return id;
}
const unsigned id = update_task_id = generate_update_id();
spawn_update_task(UpdateQueueItem(path, discard, id));
const unsigned id = update_task_id = GenerateId();
StartThread(UpdateQueueItem(path, discard, id));
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.
*/
static void update_finished_event(void)
void
UpdateService::RunDeferred()
{
assert(progress == UPDATE_PROGRESS_DONE);
assert(next.IsDefined());
......@@ -158,24 +140,16 @@ static void update_finished_event(void)
/* send "idle" events */
instance->DatabaseModified();
auto i = update_queue_shift();
auto i = queue.Pop();
if (i.IsDefined()) {
/* schedule the next path */
spawn_update_task(std::move(i));
StartThread(std::move(i));
} else {
progress = UPDATE_PROGRESS_IDLE;
}
}
void update_global_init(void)
{
GlobalEvents::Register(GlobalEvents::UPDATE, update_finished_event);
update_remove_global_init();
update_walk_global_init();
}
void update_global_finish(void)
UpdateService::UpdateService(EventLoop &_loop)
:DeferredMonitor(_loop), walk(_loop)
{
update_walk_global_finish();
}
......@@ -18,11 +18,8 @@
*/
#include "config.h" /* must be first for large file support */
#include "UpdateSong.hxx"
#include "UpdateInternal.hxx"
#include "Service.hxx"
#include "UpdateIO.hxx"
#include "UpdateDatabase.hxx"
#include "UpdateContainer.hxx"
#include "UpdateDomain.hxx"
#include "db/DatabaseLock.hxx"
#include "db/Directory.hxx"
......@@ -32,10 +29,10 @@
#include <unistd.h>
static void
update_song_file2(Directory &directory,
const char *name, const struct stat *st,
const char *suffix)
inline void
UpdateWalk::UpdateSongFile2(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
{
db_lock();
Song *song = directory.FindSong(name);
......@@ -47,7 +44,7 @@ update_song_file2(Directory &directory,
directory.GetPath(), name);
if (song != nullptr) {
db_lock();
delete_song(directory, song);
editor.DeleteSong(directory, song);
db_unlock();
}
......@@ -56,10 +53,10 @@ update_song_file2(Directory &directory,
if (!(song != nullptr && st->st_mtime == song->mtime &&
!walk_discard) &&
update_container_file(directory, name, st, suffix)) {
UpdateContainerFile(directory, name, suffix, st)) {
if (song != nullptr) {
db_lock();
delete_song(directory, song);
editor.DeleteSong(directory, song);
db_unlock();
}
......@@ -92,7 +89,7 @@ update_song_file2(Directory &directory,
"deleting unrecognized file %s/%s",
directory.GetPath(), name);
db_lock();
delete_song(directory, song);
editor.DeleteSong(directory, song);
db_unlock();
}
......@@ -101,13 +98,13 @@ update_song_file2(Directory &directory,
}
bool
update_song_file(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
UpdateWalk::UpdateSongFile(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
{
if (!decoder_plugins_supports_suffix(suffix))
return false;
update_song_file2(directory, name, st, suffix);
UpdateSongFile2(directory, name, suffix, st);
return true;
}
/*
* 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
/*
* 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
......@@ -18,11 +18,9 @@
*/
#include "config.h" /* must be first for large file support */
#include "UpdateWalk.hxx"
#include "Walk.hxx"
#include "UpdateIO.hxx"
#include "UpdateDatabase.hxx"
#include "UpdateSong.hxx"
#include "UpdateArchive.hxx"
#include "Editor.hxx"
#include "UpdateDomain.hxx"
#include "db/DatabaseLock.hxx"
#include "db/DatabaseSimple.hxx"
......@@ -49,21 +47,8 @@
#include <stdlib.h>
#include <errno.h>
bool walk_discard;
bool modified;
#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)
UpdateWalk::UpdateWalk(EventLoop &_loop)
:editor(_loop)
{
#ifndef WIN32
follow_inside_symlinks =
......@@ -76,11 +61,6 @@ update_walk_global_init(void)
#endif
}
void
update_walk_global_finish(void)
{
}
static void
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;
}
static void
remove_excluded_from_directory(Directory &directory,
const ExcludeList &exclude_list)
inline void
UpdateWalk::RemoveExcludedFromDirectory(Directory &directory,
const ExcludeList &exclude_list)
{
db_lock();
......@@ -100,7 +80,7 @@ remove_excluded_from_directory(Directory &directory,
const auto name_fs = AllocatedPath::FromUTF8(child->GetName());
if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
delete_directory(child);
editor.DeleteDirectory(child);
modified = true;
}
}
......@@ -111,7 +91,7 @@ remove_excluded_from_directory(Directory &directory,
const auto name_fs = AllocatedPath::FromUTF8(song->uri);
if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
delete_song(directory, song);
editor.DeleteSong(directory, song);
modified = true;
}
}
......@@ -119,8 +99,8 @@ remove_excluded_from_directory(Directory &directory,
db_unlock();
}
static void
purge_deleted_from_directory(Directory &directory)
inline void
UpdateWalk::PurgeDeletedFromDirectory(Directory &directory)
{
Directory *child, *n;
directory_for_each_child_safe(child, n, directory) {
......@@ -128,7 +108,7 @@ purge_deleted_from_directory(Directory &directory)
continue;
db_lock();
delete_directory(child);
editor.DeleteDirectory(child);
db_unlock();
modified = true;
......@@ -139,7 +119,7 @@ purge_deleted_from_directory(Directory &directory)
const auto path = map_song_fs(*song);
if (path.IsNull() || !FileExists(path)) {
db_lock();
delete_song(directory, song);
editor.DeleteSong(directory, song);
db_unlock();
modified = true;
......@@ -195,10 +175,10 @@ find_inode_ancestor(Directory *parent, ino_t inode, dev_t device)
return 0;
}
static bool
update_playlist_file2(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
inline bool
UpdateWalk::UpdatePlaylistFile(Directory &directory,
const char *name, const char *suffix,
const struct stat *st)
{
if (!playlist_suffix_supported(suffix))
return false;
......@@ -212,30 +192,27 @@ update_playlist_file2(Directory &directory,
return true;
}
static bool
update_regular_file(Directory &directory,
const char *name, const struct stat *st)
inline bool
UpdateWalk::UpdateRegularFile(Directory &directory,
const char *name, const struct stat *st)
{
const char *suffix = uri_get_suffix(name);
if (suffix == nullptr)
return false;
return update_song_file(directory, name, suffix, st) ||
update_archive_file(directory, name, suffix, st) ||
update_playlist_file2(directory, name, suffix, st);
return UpdateSongFile(directory, name, suffix, st) ||
UpdateArchiveFile(directory, name, suffix, st) ||
UpdatePlaylistFile(directory, name, suffix, st);
}
static bool
update_directory(Directory &directory, const struct stat *st);
static void
update_directory_child(Directory &directory,
const char *name, const struct stat *st)
void
UpdateWalk::UpdateDirectoryChild(Directory &directory,
const char *name, const struct stat *st)
{
assert(strchr(name, '/') == nullptr);
if (S_ISREG(st->st_mode)) {
update_regular_file(directory, name, st);
UpdateRegularFile(directory, name, st);
} else if (S_ISDIR(st->st_mode)) {
if (find_inode_ancestor(&directory, st->st_ino, st->st_dev))
return;
......@@ -246,9 +223,9 @@ update_directory_child(Directory &directory,
assert(&directory == subdir->parent);
if (!update_directory(*subdir, st)) {
if (!UpdateDirectory(*subdir, st)) {
db_lock();
delete_directory(subdir);
editor.DeleteDirectory(subdir);
db_unlock();
}
} else {
......@@ -268,8 +245,9 @@ static bool skip_path(Path path_fs)
}
gcc_pure
static bool
skip_symlink(const Directory *directory, const char *utf8_name)
bool
UpdateWalk::SkipSymlink(const Directory *directory,
const char *utf8_name) const
{
#ifndef WIN32
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
}
static bool
update_directory(Directory &directory, const struct stat *st)
bool
UpdateWalk::UpdateDirectory(Directory &directory, const struct stat *st)
{
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"));
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()) {
std::string utf8;
......@@ -375,15 +353,15 @@ update_directory(Directory &directory, const struct stat *st)
if (utf8.empty())
continue;
if (skip_symlink(&directory, utf8.c_str())) {
modified |= delete_name_in(directory, utf8.c_str());
if (SkipSymlink(&directory, utf8.c_str())) {
modified |= editor.DeleteNameIn(directory, utf8.c_str());
continue;
}
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
modified |= delete_name_in(directory, utf8.c_str());
modified |= editor.DeleteNameIn(directory, utf8.c_str());
}
directory.mtime = st->st_mtime;
......@@ -391,8 +369,8 @@ update_directory(Directory &directory, const struct stat *st)
return true;
}
static Directory *
directory_make_child_checked(Directory &parent, const char *name_utf8)
inline Directory *
UpdateWalk::DirectoryMakeChildChecked(Directory &parent, const char *name_utf8)
{
db_lock();
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))
return nullptr;
if (skip_symlink(&parent, name_utf8))
if (SkipSymlink(&parent, name_utf8))
return nullptr;
/* 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();
Song *conflicting = parent.FindSong(name_utf8);
if (conflicting)
delete_song(parent, conflicting);
editor.DeleteSong(parent, conflicting);
directory = parent.CreateChild(name_utf8);
db_unlock();
......@@ -423,8 +401,8 @@ directory_make_child_checked(Directory &parent, const char *name_utf8)
return directory;
}
static Directory *
directory_make_uri_parent_checked(const char *uri)
inline Directory *
UpdateWalk::DirectoryMakeUriParentChecked(const char *uri)
{
Directory *directory = db_get_root();
char *duplicated = xstrdup(uri);
......@@ -436,8 +414,8 @@ directory_make_uri_parent_checked(const char *uri)
if (*name_utf8 == 0)
continue;
directory = directory_make_child_checked(*directory,
name_utf8);
directory = DirectoryMakeChildChecked(*directory,
name_utf8);
if (directory == nullptr)
break;
......@@ -448,37 +426,37 @@ directory_make_uri_parent_checked(const char *uri)
return directory;
}
static void
update_uri(const char *uri)
inline void
UpdateWalk::UpdateUri(const char *uri)
{
Directory *parent = directory_make_uri_parent_checked(uri);
Directory *parent = DirectoryMakeUriParentChecked(uri);
if (parent == nullptr)
return;
const char *name = PathTraitsUTF8::GetBase(uri);
struct stat st;
if (!skip_symlink(parent, name) &&
if (!SkipSymlink(parent, name) &&
stat_directory_child(*parent, name, &st) == 0)
update_directory_child(*parent, name, &st);
UpdateDirectoryChild(*parent, name, &st);
else
modified |= delete_name_in(*parent, name);
modified |= editor.DeleteNameIn(*parent, name);
}
bool
update_walk(const char *path, bool discard)
UpdateWalk::Walk(const char *path, bool discard)
{
walk_discard = discard;
modified = false;
if (path != nullptr && !isRootDirectory(path)) {
update_uri(path);
UpdateUri(path);
} else {
Directory *directory = db_get_root();
struct stat st;
if (stat_directory(*directory, &st) == 0)
update_directory(*directory, &st);
UpdateDirectory(*directory, &st);
}
return modified;
......
/*
* 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment