diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Main.cxx | 2 | ||||
-rw-r--r-- | src/command/CommandError.cxx | 4 | ||||
-rw-r--r-- | src/command/StorageCommands.cxx | 47 | ||||
-rw-r--r-- | src/db/DatabaseError.hxx | 2 | ||||
-rw-r--r-- | src/db/plugins/simple/Directory.cxx | 28 | ||||
-rw-r--r-- | src/db/plugins/simple/Directory.hxx | 11 | ||||
-rw-r--r-- | src/db/plugins/simple/DirectorySave.cxx | 3 | ||||
-rw-r--r-- | src/db/plugins/simple/Mount.cxx | 96 | ||||
-rw-r--r-- | src/db/plugins/simple/Mount.hxx | 36 | ||||
-rw-r--r-- | src/db/plugins/simple/PrefixedLightSong.hxx | 41 | ||||
-rw-r--r-- | src/db/plugins/simple/SimpleDatabasePlugin.cxx | 147 | ||||
-rw-r--r-- | src/db/plugins/simple/SimpleDatabasePlugin.hxx | 30 | ||||
-rw-r--r-- | src/db/update/Container.cxx | 3 | ||||
-rw-r--r-- | src/db/update/Queue.cxx | 29 | ||||
-rw-r--r-- | src/db/update/Queue.hxx | 25 | ||||
-rw-r--r-- | src/db/update/Service.cxx | 101 | ||||
-rw-r--r-- | src/db/update/Service.hxx | 13 | ||||
-rw-r--r-- | src/db/update/Walk.cxx | 6 |
18 files changed, 600 insertions, 24 deletions
diff --git a/src/Main.cxx b/src/Main.cxx index 61e5fb2b6..b26be9135 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -214,7 +214,7 @@ glue_db_init_and_load(void) SimpleDatabase &db = *(SimpleDatabase *)instance->database; instance->update = new UpdateService(*instance->event_loop, db, - *instance->storage, + static_cast<CompositeStorage &>(*instance->storage), *instance); /* run database update after daemonization? */ diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx index c94ffea63..89085fc68 100644 --- a/src/command/CommandError.cxx +++ b/src/command/CommandError.cxx @@ -112,6 +112,10 @@ print_error(Client &client, const Error &error) case DB_NOT_FOUND: command_error(client, ACK_ERROR_NO_EXIST, "Not found"); return CommandResult::ERROR; + + case DB_CONFLICT: + command_error(client, ACK_ERROR_ARG, "Conflict"); + return CommandResult::ERROR; } #endif } else if (error.IsDomain(errno_domain)) { diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx index 92d235f4c..f0698f04b 100644 --- a/src/command/StorageCommands.cxx +++ b/src/command/StorageCommands.cxx @@ -29,6 +29,8 @@ #include "Instance.hxx" #include "storage/Registry.hxx" #include "storage/CompositeStorage.hxx" +#include "db/plugins/simple/SimpleDatabasePlugin.hxx" +#include "db/update/Service.hxx" #include "Idle.hxx" static void @@ -98,6 +100,16 @@ handle_mount(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } + if (strchr(local_uri, '/') != nullptr) { + /* allow only top-level mounts for now */ + /* TODO: eliminate this limitation after ensuring that + UpdateQueue::Erase() really gets called for every + unmount, and no Directory disappears recursively + during database update */ + command_error(client, ACK_ERROR_ARG, "Bad mount point"); + return CommandResult::ERROR; + } + Error error; Storage *storage = CreateStorageURI(remote_uri, error); if (storage == nullptr) { @@ -111,6 +123,23 @@ handle_mount(Client &client, gcc_unused int argc, char *argv[]) composite.Mount(local_uri, storage); idle_add(IDLE_MOUNT); + +#ifdef ENABLE_DATABASE + Database *_db = client.partition.instance.database; + if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) { + SimpleDatabase &db = *(SimpleDatabase *)_db; + + if (!db.Mount(local_uri, remote_uri, error)) { + composite.Unmount(local_uri); + return print_error(client, error); + } + + // TODO: call Instance::OnDatabaseModified()? + // TODO: trigger database update? + idle_add(IDLE_DATABASE); + } +#endif + return CommandResult::OK; } @@ -132,11 +161,29 @@ handle_unmount(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } +#ifdef ENABLE_DATABASE + if (client.partition.instance.update != nullptr) + /* ensure that no database update will attempt to work + with the database/storage instances we're about to + destroy here */ + client.partition.instance.update->CancelMount(local_uri); + + Database *_db = client.partition.instance.database; + if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) { + SimpleDatabase &db = *(SimpleDatabase *)_db; + + if (db.Unmount(local_uri)) + // TODO: call Instance::OnDatabaseModified()? + idle_add(IDLE_DATABASE); + } +#endif + if (!composite.Unmount(local_uri)) { command_error(client, ACK_ERROR_ARG, "Not a mount point"); return CommandResult::ERROR; } idle_add(IDLE_MOUNT); + return CommandResult::OK; } diff --git a/src/db/DatabaseError.hxx b/src/db/DatabaseError.hxx index 1485a21b6..c71bbdfff 100644 --- a/src/db/DatabaseError.hxx +++ b/src/db/DatabaseError.hxx @@ -30,6 +30,8 @@ enum db_error { DB_DISABLED, DB_NOT_FOUND, + + DB_CONFLICT, }; extern const Domain db_domain; diff --git a/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx index b4255b0ac..3ac2f96a2 100644 --- a/src/db/plugins/simple/Directory.cxx +++ b/src/db/plugins/simple/Directory.cxx @@ -21,10 +21,12 @@ #include "Directory.hxx" #include "SongSort.hxx" #include "Song.hxx" +#include "Mount.hxx" #include "db/LightDirectory.hxx" #include "db/LightSong.hxx" #include "db/Uri.hxx" #include "db/DatabaseLock.hxx" +#include "db/Interface.hxx" #include "SongFilter.hxx" #include "lib/icu/Collate.hxx" #include "fs/Traits.hxx" @@ -42,7 +44,8 @@ extern "C" { Directory::Directory(std::string &&_path_utf8, Directory *_parent) :parent(_parent), mtime(0), have_stat(false), - path(std::move(_path_utf8)) + path(std::move(_path_utf8)), + mounted_database(nullptr) { INIT_LIST_HEAD(&children); INIT_LIST_HEAD(&songs); @@ -50,6 +53,8 @@ Directory::Directory(std::string &&_path_utf8, Directory *_parent) Directory::~Directory() { + delete mounted_database; + Song *song, *ns; directory_for_each_song_safe(song, ns, *this) song->Free(); @@ -113,6 +118,11 @@ Directory::PruneEmpty() Directory *child, *n; directory_for_each_child_safe(child, n, *this) { + if (child->IsMount()) + /* never prune mount points; they're always + empty by definition, but that's ok */ + continue; + child->PruneEmpty(); if (child->IsEmpty()) @@ -233,6 +243,22 @@ Directory::Walk(bool recursive, const SongFilter *filter, { assert(!error.IsDefined()); + if (IsMount()) { + assert(IsEmpty()); + + /* TODO: eliminate this unlock/lock; it is necessary + because the child's SimpleDatabasePlugin::Visit() + call will lock it again */ + db_unlock(); + bool result = WalkMount(GetPath(), *mounted_database, + recursive, filter, + visit_directory, visit_song, + visit_playlist, + error); + db_lock(); + return result; + } + if (visit_song) { Song *song; directory_for_each_song(song, *this) { diff --git a/src/db/plugins/simple/Directory.hxx b/src/db/plugins/simple/Directory.hxx index 5b39231e3..029815d95 100644 --- a/src/db/plugins/simple/Directory.hxx +++ b/src/db/plugins/simple/Directory.hxx @@ -57,6 +57,7 @@ struct Song; struct db_visitor; class SongFilter; class Error; +class Database; struct Directory { /** @@ -94,6 +95,12 @@ struct Directory { std::string path; + /** + * If this is not nullptr, then this directory does not really + * exist, but is a mount point for another #Database. + */ + Database *mounted_database; + public: Directory(std::string &&_path_utf8, Directory *_parent); ~Directory(); @@ -106,6 +113,10 @@ public: return new Directory(std::string(), nullptr); } + bool IsMount() const { + return mounted_database != nullptr; + } + /** * Remove this #Directory object from its parent and free it. This * must not be called with the root Directory. diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx index 6cc5df6cb..b5989dd06 100644 --- a/src/db/plugins/simple/DirectorySave.cxx +++ b/src/db/plugins/simple/DirectorySave.cxx @@ -88,7 +88,8 @@ directory_save(FILE *fp, const Directory &directory) directory_for_each_child(cur, directory) { fprintf(fp, DIRECTORY_DIR "%s\n", cur->GetName()); - directory_save(fp, *cur); + if (!cur->IsMount()) + directory_save(fp, *cur); if (ferror(fp)) return; diff --git a/src/db/plugins/simple/Mount.cxx b/src/db/plugins/simple/Mount.cxx new file mode 100644 index 000000000..96c7bbb5c --- /dev/null +++ b/src/db/plugins/simple/Mount.cxx @@ -0,0 +1,96 @@ +/* + * 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. + */ + +#include "config.h" +#include "Mount.hxx" +#include "PrefixedLightSong.hxx" +#include "db/Selection.hxx" +#include "db/LightDirectory.hxx" +#include "db/LightSong.hxx" +#include "db/Interface.hxx" +#include "fs/Traits.hxx" +#include "util/Error.hxx" + +#include <string> + +struct PrefixedLightDirectory : LightDirectory { + std::string buffer; + + PrefixedLightDirectory(const LightDirectory &directory, + const char *base) + :LightDirectory(directory), + buffer(IsRoot() + ? std::string(base) + : PathTraitsUTF8::Build(base, uri)) { + uri = buffer.c_str(); + } +}; + +static bool +PrefixVisitDirectory(const char *base, const VisitDirectory &visit_directory, + const LightDirectory &directory, Error &error) +{ + return visit_directory(PrefixedLightDirectory(directory, base), error); +} + +static bool +PrefixVisitSong(const char *base, const VisitSong &visit_song, + const LightSong &song, Error &error) +{ + return visit_song(PrefixedLightSong(song, base), error); +} + +static bool +PrefixVisitPlaylist(const char *base, const VisitPlaylist &visit_playlist, + const PlaylistInfo &playlist, + const LightDirectory &directory, + Error &error) +{ + return visit_playlist(playlist, + PrefixedLightDirectory(directory, base), + error); +} + +bool +WalkMount(const char *base, const Database &db, + bool recursive, const SongFilter *filter, + const VisitDirectory &visit_directory, const VisitSong &visit_song, + const VisitPlaylist &visit_playlist, + Error &error) +{ + using namespace std::placeholders; + + VisitDirectory vd; + if (visit_directory) + vd = std::bind(PrefixVisitDirectory, + base, std::ref(visit_directory), _1, _2); + + VisitSong vs; + if (visit_song) + vs = std::bind(PrefixVisitSong, + base, std::ref(visit_song), _1, _2); + + VisitPlaylist vp; + if (visit_playlist) + vp = std::bind(PrefixVisitPlaylist, + base, std::ref(visit_playlist), _1, _2, _3); + + return db.Visit(DatabaseSelection("", recursive, filter), + vd, vs, vp, error); +} diff --git a/src/db/plugins/simple/Mount.hxx b/src/db/plugins/simple/Mount.hxx new file mode 100644 index 000000000..a4690114c --- /dev/null +++ b/src/db/plugins/simple/Mount.hxx @@ -0,0 +1,36 @@ +/* + * 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_DB_SIMPLE_MOUNT_HXX +#define MPD_DB_SIMPLE_MOUNT_HXX + +#include "db/Visitor.hxx" + +class Database; +class SongFilter; +class Error; + +bool +WalkMount(const char *base, const Database &db, + bool recursive, const SongFilter *filter, + const VisitDirectory &visit_directory, const VisitSong &visit_song, + const VisitPlaylist &visit_playlist, + Error &error); + +#endif diff --git a/src/db/plugins/simple/PrefixedLightSong.hxx b/src/db/plugins/simple/PrefixedLightSong.hxx new file mode 100644 index 000000000..3664de001 --- /dev/null +++ b/src/db/plugins/simple/PrefixedLightSong.hxx @@ -0,0 +1,41 @@ +/* + * 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_DB_SIMPLE_PREFIXED_LIGHT_SONG_HXX +#define MPD_DB_SIMPLE_PREFIXED_LIGHT_SONG_HXX + +#include "check.h" +#include "db/LightSong.hxx" +#include "fs/Traits.hxx" + +#include <string> + +class PrefixedLightSong : public LightSong { + std::string buffer; + +public: + PrefixedLightSong(const LightSong &song, const char *base) + :LightSong(song), + buffer(PathTraitsUTF8::Build(base, GetURI().c_str())) { + uri = buffer.c_str(); + directory = nullptr; + } +}; + +#endif diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx index e83ef575b..68101f564 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx @@ -19,6 +19,7 @@ #include "config.h" #include "SimpleDatabasePlugin.hxx" +#include "PrefixedLightSong.hxx" #include "db/DatabasePlugin.hxx" #include "db/Selection.hxx" #include "db/Helpers.hxx" @@ -32,6 +33,7 @@ #include "fs/TextFile.hxx" #include "config/ConfigData.hxx" #include "fs/FileSystem.hxx" +#include "util/CharUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -42,7 +44,17 @@ static constexpr Domain simple_db_domain("simple_db"); inline SimpleDatabase::SimpleDatabase() :Database(simple_db_plugin), - path(AllocatedPath::Null()) {} + path(AllocatedPath::Null()), + cache_path(AllocatedPath::Null()), + prefixed_light_song(nullptr) {} + +inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path) + :Database(simple_db_plugin), + path(std::move(_path)), + path_utf8(path.ToUTF8()), + cache_path(AllocatedPath::Null()), + prefixed_light_song(nullptr) { +} Database * SimpleDatabase::Create(gcc_unused EventLoop &loop, @@ -71,6 +83,10 @@ SimpleDatabase::Configure(const config_param ¶m, Error &error) path_utf8 = path.ToUTF8(); + cache_path = param.GetBlockPath("cache_directory", error); + if (path.IsNull() && error.IsDefined()) + return false; + return true; } @@ -169,6 +185,8 @@ SimpleDatabase::Load(Error &error) bool SimpleDatabase::Open(Error &error) { + assert(prefixed_light_song == nullptr); + root = Directory::NewRoot(); mtime = 0; @@ -195,6 +213,7 @@ void SimpleDatabase::Close() { assert(root != nullptr); + assert(prefixed_light_song == nullptr); assert(borrowed_song_count == 0); delete root; @@ -204,11 +223,27 @@ const LightSong * SimpleDatabase::GetSong(const char *uri, Error &error) const { assert(root != nullptr); + assert(prefixed_light_song == nullptr); assert(borrowed_song_count == 0); db_lock(); auto r = root->LookupDirectory(uri); + + if (r.directory->IsMount()) { + /* pass the request to the mounted database */ + db_unlock(); + + const LightSong *song = + r.directory->mounted_database->GetSong(r.uri, error); + if (song == nullptr) + return nullptr; + + prefixed_light_song = + new PrefixedLightSong(*song, r.directory->GetPath()); + return prefixed_light_song; + } + if (r.uri == nullptr) { /* it's a directory */ db_unlock(); @@ -245,11 +280,17 @@ SimpleDatabase::GetSong(const char *uri, Error &error) const void SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const { - assert(song == &light_song); + assert(song != nullptr); + assert(song == &light_song || song == prefixed_light_song); + + delete prefixed_light_song; + prefixed_light_song = nullptr; #ifndef NDEBUG - assert(borrowed_song_count > 0); - --borrowed_song_count; + if (song == &light_song) { + assert(borrowed_song_count > 0); + --borrowed_song_count; + } #endif } @@ -347,6 +388,104 @@ SimpleDatabase::Save(Error &error) return true; } +bool +SimpleDatabase::Mount(const char *uri, Database *db, Error &error) +{ + assert(uri != nullptr); + assert(*uri != 0); + assert(db != nullptr); + + ScopeDatabaseLock protect; + + auto r = root->LookupDirectory(uri); + if (r.uri == nullptr) { + error.Format(db_domain, DB_CONFLICT, + "Already exists: %s", uri); + return nullptr; + } + + if (strchr(r.uri, '/') != nullptr) { + error.Format(db_domain, DB_NOT_FOUND, + "Parent not found: %s", uri); + return nullptr; + } + + Directory *mnt = r.directory->CreateChild(r.uri); + mnt->mounted_database = db; + return true; +} + +static constexpr bool +IsSafeChar(char ch) +{ + return IsAlphaNumericASCII(ch) || ch == '-' || ch == '_' || ch == '%'; +} + +static constexpr bool +IsUnsafeChar(char ch) +{ + return !IsSafeChar(ch); +} + +bool +SimpleDatabase::Mount(const char *local_uri, const char *storage_uri, + Error &error) +{ + if (cache_path.IsNull()) { + error.Format(db_domain, DB_NOT_FOUND, + "No 'cache_directory' configured"); + return nullptr; + } + + std::string name(storage_uri); + std::replace_if(name.begin(), name.end(), IsUnsafeChar, '_'); + + auto db = new SimpleDatabase(AllocatedPath::Build(cache_path, + name.c_str())); + if (!db->Open(error)) { + delete db; + return false; + } + + // TODO: update the new database instance? + + if (!Mount(local_uri, db, error)) { + db->Close(); + delete db; + return false; + } + + return true; +} + +Database * +SimpleDatabase::LockUmountSteal(const char *uri) +{ + ScopeDatabaseLock protect; + + auto r = root->LookupDirectory(uri); + if (r.uri != nullptr || !r.directory->IsMount()) + return nullptr; + + Database *db = r.directory->mounted_database; + r.directory->mounted_database = nullptr; + r.directory->Delete(); + + return db; +} + +bool +SimpleDatabase::Unmount(const char *uri) +{ + Database *db = LockUmountSteal(uri); + if (db == nullptr) + return false; + + db->Close(); + delete db; + return true; +} + const DatabasePlugin simple_db_plugin = { "simple", DatabasePlugin::FLAG_REQUIRE_STORAGE, diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.hxx b/src/db/plugins/simple/SimpleDatabasePlugin.hxx index 9836a6bcf..a03969f92 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.hxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.hxx @@ -32,16 +32,28 @@ struct Directory; struct DatabasePlugin; class EventLoop; class DatabaseListener; +class PrefixedLightSong; class SimpleDatabase : public Database { AllocatedPath path; std::string path_utf8; + /** + * The path where cache files for Mount() are located. + */ + AllocatedPath cache_path; + Directory *root; time_t mtime; /** + * A buffer for GetSong() when prefixing the #LightSong + * instance from a mounted #Database. + */ + mutable PrefixedLightSong *prefixed_light_song; + + /** * A buffer for GetSong(). */ mutable LightSong light_song; @@ -52,6 +64,8 @@ class SimpleDatabase : public Database { SimpleDatabase(); + SimpleDatabase(AllocatedPath &&_path); + public: static Database *Create(EventLoop &loop, DatabaseListener &listener, const config_param ¶m, @@ -73,6 +87,20 @@ public: return mtime > 0; } + /** + * @param db the #Database to be mounted; must be "open"; on + * success, this object gains ownership of the given #Database + */ + gcc_nonnull_all + bool Mount(const char *uri, Database *db, Error &error); + + gcc_nonnull_all + bool Mount(const char *local_uri, const char *storage_uri, + Error &error); + + gcc_nonnull_all + bool Unmount(const char *uri); + /* virtual methods from class Database */ virtual bool Open(Error &error) override; virtual void Close() override; @@ -107,6 +135,8 @@ private: bool Check(Error &error) const; bool Load(Error &error); + + Database *LockUmountSteal(const char *uri); }; extern const DatabasePlugin simple_db_plugin; diff --git a/src/db/update/Container.cxx b/src/db/update/Container.cxx index 9f8d84839..1c420fa99 100644 --- a/src/db/update/Container.cxx +++ b/src/db/update/Container.cxx @@ -42,6 +42,9 @@ UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name, // directory exists already if (directory != nullptr) { + if (directory->IsMount()) + return nullptr; + if (directory->mtime == info.mtime && !walk_discard) { /* not modified */ return nullptr; diff --git a/src/db/update/Queue.cxx b/src/db/update/Queue.cxx index 096a39a8c..6d6d80131 100644 --- a/src/db/update/Queue.cxx +++ b/src/db/update/Queue.cxx @@ -21,12 +21,13 @@ #include "Queue.hxx" bool -UpdateQueue::Push(const char *path, bool discard, unsigned id) +UpdateQueue::Push(SimpleDatabase &db, Storage &storage, + const char *path, bool discard, unsigned id) { if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE) return false; - update_queue.emplace_back(path, discard, id); + update_queue.emplace_back(db, storage, path, discard, id); return true; } @@ -40,3 +41,27 @@ UpdateQueue::Pop() update_queue.pop_front(); return i; } + +void +UpdateQueue::Erase(SimpleDatabase &db) +{ + for (auto i = update_queue.begin(), end = update_queue.end(); + i != end;) { + if (i->db == &db) + i = update_queue.erase(i); + else + ++i; + } +} + +void +UpdateQueue::Erase(Storage &storage) +{ + for (auto i = update_queue.begin(), end = update_queue.end(); + i != end;) { + if (i->storage == &storage) + i = update_queue.erase(i); + else + ++i; + } +} diff --git a/src/db/update/Queue.hxx b/src/db/update/Queue.hxx index 039c62fe3..9064ea481 100644 --- a/src/db/update/Queue.hxx +++ b/src/db/update/Queue.hxx @@ -21,19 +21,30 @@ #define MPD_UPDATE_QUEUE_HXX #include "check.h" +#include "Compiler.h" #include <string> #include <list> +class SimpleDatabase; +class Storage; + struct UpdateQueueItem { + SimpleDatabase *db; + Storage *storage; + std::string path_utf8; unsigned id; bool discard; UpdateQueueItem():id(0) {} - UpdateQueueItem(const char *_path, bool _discard, + + UpdateQueueItem(SimpleDatabase &_db, + Storage &_storage, + const char *_path, bool _discard, unsigned _id) - :path_utf8(_path), id(_id), discard(_discard) {} + :db(&_db), storage(&_storage), path_utf8(_path), + id(_id), discard(_discard) {} bool IsDefined() const { return id != 0; @@ -46,13 +57,21 @@ class UpdateQueue { std::list<UpdateQueueItem> update_queue; public: - bool Push(const char *path, bool discard, unsigned id); + gcc_nonnull_all + bool Push(SimpleDatabase &db, Storage &storage, + const char *path, bool discard, unsigned id); UpdateQueueItem Pop(); void Clear() { update_queue.clear(); } + + gcc_nonnull_all + void Erase(SimpleDatabase &db); + + gcc_nonnull_all + void Erase(Storage &storage); }; #endif diff --git a/src/db/update/Service.cxx b/src/db/update/Service.cxx index 2971998e4..e8a1f6b02 100644 --- a/src/db/update/Service.cxx +++ b/src/db/update/Service.cxx @@ -22,7 +22,10 @@ #include "Walk.hxx" #include "UpdateDomain.hxx" #include "db/DatabaseListener.hxx" +#include "db/DatabaseLock.hxx" #include "db/plugins/simple/SimpleDatabasePlugin.hxx" +#include "db/plugins/simple/Directory.hxx" +#include "storage/CompositeStorage.hxx" #include "Idle.hxx" #include "util/Error.hxx" #include "Log.hxx" @@ -39,7 +42,7 @@ #include <assert.h> UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db, - Storage &_storage, + CompositeStorage &_storage, DatabaseListener &_listener) :DeferredMonitor(_loop), db(_db), storage(_storage), @@ -71,6 +74,42 @@ UpdateService::CancelAllAsync() walk->Cancel(); } +void +UpdateService::CancelMount(const char *uri) +{ + /* determine which (mounted) database will be updated and what + storage will be scanned */ + + db_lock(); + const auto lr = db.GetRoot().LookupDirectory(uri); + db_unlock(); + + if (!lr.directory->IsMount()) + return; + + bool cancel_current = false; + + Storage *storage2 = storage.GetMount(uri); + if (storage2 != nullptr) { + queue.Erase(*storage2); + cancel_current = next.IsDefined() && next.storage == storage2; + } + + Database &_db2 = *lr.directory->mounted_database; + if (_db2.IsPlugin(simple_db_plugin)) { + SimpleDatabase &db2 = static_cast<SimpleDatabase &>(_db2); + queue.Erase(db2); + cancel_current |= next.IsDefined() && next.db == &db2; + } + + if (cancel_current && walk != nullptr) { + walk->Cancel(); + + if (update_thread.IsDefined()) + update_thread.Join(); + } +} + inline void UpdateService::Task() { @@ -84,12 +123,12 @@ UpdateService::Task() SetThreadIdlePriority(); - modified = walk->Walk(db.GetRoot(), next.path_utf8.c_str(), + modified = walk->Walk(next.db->GetRoot(), next.path_utf8.c_str(), next.discard); - if (modified || !db.FileExists()) { + if (modified || !next.db->FileExists()) { Error error; - if (!db.Save(error)) + if (!next.db->Save(error)) LogError(error, "Failed to save database"); } @@ -120,7 +159,7 @@ UpdateService::StartThread(UpdateQueueItem &&i) modified = false; next = std::move(i); - walk = new UpdateWalk(GetEventLoop(), listener, storage); + walk = new UpdateWalk(GetEventLoop(), listener, *next.storage); Error error; if (!update_thread.Start(Task, this, error)) @@ -144,9 +183,52 @@ UpdateService::Enqueue(const char *path, bool discard) { assert(GetEventLoop().IsInsideOrNull()); + /* determine which (mounted) database will be updated and what + storage will be scanned */ + SimpleDatabase *db2; + Storage *storage2; + + db_lock(); + const auto lr = db.GetRoot().LookupDirectory(path); + db_unlock(); + if (lr.directory->IsMount()) { + /* follow the mountpoint, update the mounted + database */ + + Database &_db2 = *lr.directory->mounted_database; + if (!_db2.IsPlugin(simple_db_plugin)) + /* cannot update this type of database */ + return 0; + + db2 = static_cast<SimpleDatabase *>(&_db2); + + if (lr.uri == nullptr) { + storage2 = storage.GetMount(path); + path = ""; + } else { + assert(lr.uri > path); + assert(lr.uri < path + strlen(path)); + assert(lr.uri[-1] == '/'); + + const std::string mountpoint(path, lr.uri - 1); + storage2 = storage.GetMount(mountpoint.c_str()); + path = lr.uri; + } + } else { + /* use the "root" database/storage */ + + db2 = &db; + storage2 = storage.GetMount(""); + } + + if (storage2 == nullptr) + /* no storage found at this mount point - should not + happen */ + return 0; + if (progress != UPDATE_PROGRESS_IDLE) { const unsigned id = GenerateId(); - if (!queue.Push(path, discard, id)) + if (!queue.Push(*db2, *storage2, path, discard, id)) return 0; update_task_id = id; @@ -154,7 +236,7 @@ UpdateService::Enqueue(const char *path, bool discard) } const unsigned id = update_task_id = GenerateId(); - StartThread(UpdateQueueItem(path, discard, id)); + StartThread(UpdateQueueItem(*db2, *storage2, path, discard, id)); idle_add(IDLE_UPDATE); @@ -171,7 +253,10 @@ UpdateService::RunDeferred() assert(next.IsDefined()); assert(walk != nullptr); - update_thread.Join(); + /* wait for thread to finish only if it wasn't cancelled by + CancelMount() */ + if (update_thread.IsDefined()) + update_thread.Join(); delete walk; walk = nullptr; diff --git a/src/db/update/Service.hxx b/src/db/update/Service.hxx index e42a0fa5a..cbb4a3f9d 100644 --- a/src/db/update/Service.hxx +++ b/src/db/update/Service.hxx @@ -28,7 +28,7 @@ class SimpleDatabase; class DatabaseListener; class UpdateWalk; -class Storage; +class CompositeStorage; /** * This class manages the update queue and runs the update thread. @@ -41,7 +41,7 @@ class UpdateService final : DeferredMonitor { }; SimpleDatabase &db; - Storage &storage; + CompositeStorage &storage; DatabaseListener &listener; @@ -63,7 +63,7 @@ class UpdateService final : DeferredMonitor { public: UpdateService(EventLoop &_loop, SimpleDatabase &_db, - Storage &_storage, + CompositeStorage &_storage, DatabaseListener &_listener); ~UpdateService(); @@ -92,6 +92,13 @@ public: */ void CancelAllAsync(); + /** + * Cancel all updates for the given mount point. If an update + * is already running for it, the method will wait for + * cancellation to complete. + */ + void CancelMount(const char *uri); + private: /* virtual methods from class DeferredMonitor */ virtual void RunDeferred() override; diff --git a/src/db/update/Walk.cxx b/src/db/update/Walk.cxx index 201030f25..c329865ff 100644 --- a/src/db/update/Walk.cxx +++ b/src/db/update/Walk.cxx @@ -397,8 +397,12 @@ UpdateWalk::DirectoryMakeChildChecked(Directory &parent, Directory *directory = parent.FindChild(name_utf8); db_unlock(); - if (directory != nullptr) + if (directory != nullptr) { + if (directory->IsMount()) + directory = nullptr; + return directory; + } FileInfo info; if (!GetInfo(storage, uri_utf8, info) || |