aboutsummaryrefslogtreecommitdiffstats
path: root/src/db/plugins/simple
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/db/plugins/simple/Directory.cxx28
-rw-r--r--src/db/plugins/simple/Directory.hxx11
-rw-r--r--src/db/plugins/simple/DirectorySave.cxx3
-rw-r--r--src/db/plugins/simple/Mount.cxx96
-rw-r--r--src/db/plugins/simple/Mount.hxx36
-rw-r--r--src/db/plugins/simple/PrefixedLightSong.hxx41
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.cxx147
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.hxx30
8 files changed, 386 insertions, 6 deletions
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 &param, 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 &param,
@@ -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;