db/simple: mount points
A SimpleDatabase instance can now "mount" other Database instances at certain locations. This is used to use a new SimpleDatabase instance for each storage mount (issued with the "mount" protocol command). Each such instance has its own database file, stored in the directory that is specified with the "cache_directory" option.
This commit is contained in:
parent
2a16fc74fd
commit
e9a85aa4e4
@ -579,6 +579,9 @@ libdb_plugins_a_SOURCES = \
|
|||||||
src/db/plugins/simple/Song.hxx \
|
src/db/plugins/simple/Song.hxx \
|
||||||
src/db/plugins/simple/SongSort.cxx \
|
src/db/plugins/simple/SongSort.cxx \
|
||||||
src/db/plugins/simple/SongSort.hxx \
|
src/db/plugins/simple/SongSort.hxx \
|
||||||
|
src/db/plugins/simple/Mount.cxx \
|
||||||
|
src/db/plugins/simple/Mount.hxx \
|
||||||
|
src/db/plugins/simple/PrefixedLightSong.hxx \
|
||||||
src/db/plugins/simple/SimpleDatabasePlugin.cxx \
|
src/db/plugins/simple/SimpleDatabasePlugin.cxx \
|
||||||
src/db/plugins/simple/SimpleDatabasePlugin.hxx
|
src/db/plugins/simple/SimpleDatabasePlugin.hxx
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ glue_db_init_and_load(void)
|
|||||||
|
|
||||||
SimpleDatabase &db = *(SimpleDatabase *)instance->database;
|
SimpleDatabase &db = *(SimpleDatabase *)instance->database;
|
||||||
instance->update = new UpdateService(*instance->event_loop, db,
|
instance->update = new UpdateService(*instance->event_loop, db,
|
||||||
*instance->storage,
|
static_cast<CompositeStorage &>(*instance->storage),
|
||||||
*instance);
|
*instance);
|
||||||
|
|
||||||
/* run database update after daemonization? */
|
/* run database update after daemonization? */
|
||||||
|
@ -112,6 +112,10 @@ print_error(Client &client, const Error &error)
|
|||||||
case DB_NOT_FOUND:
|
case DB_NOT_FOUND:
|
||||||
command_error(client, ACK_ERROR_NO_EXIST, "Not found");
|
command_error(client, ACK_ERROR_NO_EXIST, "Not found");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
|
|
||||||
|
case DB_CONFLICT:
|
||||||
|
command_error(client, ACK_ERROR_ARG, "Conflict");
|
||||||
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} else if (error.IsDomain(errno_domain)) {
|
} else if (error.IsDomain(errno_domain)) {
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
#include "storage/Registry.hxx"
|
#include "storage/Registry.hxx"
|
||||||
#include "storage/CompositeStorage.hxx"
|
#include "storage/CompositeStorage.hxx"
|
||||||
|
#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
|
||||||
|
#include "db/update/Service.hxx"
|
||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -98,6 +100,16 @@ handle_mount(Client &client, gcc_unused int argc, char *argv[])
|
|||||||
return CommandResult::ERROR;
|
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;
|
Error error;
|
||||||
Storage *storage = CreateStorageURI(remote_uri, error);
|
Storage *storage = CreateStorageURI(remote_uri, error);
|
||||||
if (storage == nullptr) {
|
if (storage == nullptr) {
|
||||||
@ -111,6 +123,23 @@ handle_mount(Client &client, gcc_unused int argc, char *argv[])
|
|||||||
|
|
||||||
composite.Mount(local_uri, storage);
|
composite.Mount(local_uri, storage);
|
||||||
idle_add(IDLE_MOUNT);
|
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;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,11 +161,29 @@ handle_unmount(Client &client, gcc_unused int argc, char *argv[])
|
|||||||
return CommandResult::ERROR;
|
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)) {
|
if (!composite.Unmount(local_uri)) {
|
||||||
command_error(client, ACK_ERROR_ARG, "Not a mount point");
|
command_error(client, ACK_ERROR_ARG, "Not a mount point");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
idle_add(IDLE_MOUNT);
|
idle_add(IDLE_MOUNT);
|
||||||
|
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ enum db_error {
|
|||||||
DB_DISABLED,
|
DB_DISABLED,
|
||||||
|
|
||||||
DB_NOT_FOUND,
|
DB_NOT_FOUND,
|
||||||
|
|
||||||
|
DB_CONFLICT,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const Domain db_domain;
|
extern const Domain db_domain;
|
||||||
|
@ -21,10 +21,12 @@
|
|||||||
#include "Directory.hxx"
|
#include "Directory.hxx"
|
||||||
#include "SongSort.hxx"
|
#include "SongSort.hxx"
|
||||||
#include "Song.hxx"
|
#include "Song.hxx"
|
||||||
|
#include "Mount.hxx"
|
||||||
#include "db/LightDirectory.hxx"
|
#include "db/LightDirectory.hxx"
|
||||||
#include "db/LightSong.hxx"
|
#include "db/LightSong.hxx"
|
||||||
#include "db/Uri.hxx"
|
#include "db/Uri.hxx"
|
||||||
#include "db/DatabaseLock.hxx"
|
#include "db/DatabaseLock.hxx"
|
||||||
|
#include "db/Interface.hxx"
|
||||||
#include "SongFilter.hxx"
|
#include "SongFilter.hxx"
|
||||||
#include "lib/icu/Collate.hxx"
|
#include "lib/icu/Collate.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
@ -42,7 +44,8 @@ extern "C" {
|
|||||||
Directory::Directory(std::string &&_path_utf8, Directory *_parent)
|
Directory::Directory(std::string &&_path_utf8, Directory *_parent)
|
||||||
:parent(_parent),
|
:parent(_parent),
|
||||||
mtime(0), have_stat(false),
|
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(&children);
|
||||||
INIT_LIST_HEAD(&songs);
|
INIT_LIST_HEAD(&songs);
|
||||||
@ -50,6 +53,8 @@ Directory::Directory(std::string &&_path_utf8, Directory *_parent)
|
|||||||
|
|
||||||
Directory::~Directory()
|
Directory::~Directory()
|
||||||
{
|
{
|
||||||
|
delete mounted_database;
|
||||||
|
|
||||||
Song *song, *ns;
|
Song *song, *ns;
|
||||||
directory_for_each_song_safe(song, ns, *this)
|
directory_for_each_song_safe(song, ns, *this)
|
||||||
song->Free();
|
song->Free();
|
||||||
@ -113,6 +118,11 @@ Directory::PruneEmpty()
|
|||||||
|
|
||||||
Directory *child, *n;
|
Directory *child, *n;
|
||||||
directory_for_each_child_safe(child, n, *this) {
|
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();
|
child->PruneEmpty();
|
||||||
|
|
||||||
if (child->IsEmpty())
|
if (child->IsEmpty())
|
||||||
@ -233,6 +243,22 @@ Directory::Walk(bool recursive, const SongFilter *filter,
|
|||||||
{
|
{
|
||||||
assert(!error.IsDefined());
|
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) {
|
if (visit_song) {
|
||||||
Song *song;
|
Song *song;
|
||||||
directory_for_each_song(song, *this) {
|
directory_for_each_song(song, *this) {
|
||||||
|
@ -57,6 +57,7 @@ struct Song;
|
|||||||
struct db_visitor;
|
struct db_visitor;
|
||||||
class SongFilter;
|
class SongFilter;
|
||||||
class Error;
|
class Error;
|
||||||
|
class Database;
|
||||||
|
|
||||||
struct Directory {
|
struct Directory {
|
||||||
/**
|
/**
|
||||||
@ -94,6 +95,12 @@ struct Directory {
|
|||||||
|
|
||||||
std::string path;
|
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:
|
public:
|
||||||
Directory(std::string &&_path_utf8, Directory *_parent);
|
Directory(std::string &&_path_utf8, Directory *_parent);
|
||||||
~Directory();
|
~Directory();
|
||||||
@ -106,6 +113,10 @@ public:
|
|||||||
return new Directory(std::string(), nullptr);
|
return new Directory(std::string(), nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsMount() const {
|
||||||
|
return mounted_database != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove this #Directory object from its parent and free it. This
|
* Remove this #Directory object from its parent and free it. This
|
||||||
* must not be called with the root Directory.
|
* must not be called with the root Directory.
|
||||||
|
@ -88,7 +88,8 @@ directory_save(FILE *fp, const Directory &directory)
|
|||||||
directory_for_each_child(cur, directory) {
|
directory_for_each_child(cur, directory) {
|
||||||
fprintf(fp, DIRECTORY_DIR "%s\n", cur->GetName());
|
fprintf(fp, DIRECTORY_DIR "%s\n", cur->GetName());
|
||||||
|
|
||||||
directory_save(fp, *cur);
|
if (!cur->IsMount())
|
||||||
|
directory_save(fp, *cur);
|
||||||
|
|
||||||
if (ferror(fp))
|
if (ferror(fp))
|
||||||
return;
|
return;
|
||||||
|
96
src/db/plugins/simple/Mount.cxx
Normal file
96
src/db/plugins/simple/Mount.cxx
Normal file
@ -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);
|
||||||
|
}
|
36
src/db/plugins/simple/Mount.hxx
Normal file
36
src/db/plugins/simple/Mount.hxx
Normal file
@ -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
|
41
src/db/plugins/simple/PrefixedLightSong.hxx
Normal file
41
src/db/plugins/simple/PrefixedLightSong.hxx
Normal file
@ -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
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "SimpleDatabasePlugin.hxx"
|
#include "SimpleDatabasePlugin.hxx"
|
||||||
|
#include "PrefixedLightSong.hxx"
|
||||||
#include "db/DatabasePlugin.hxx"
|
#include "db/DatabasePlugin.hxx"
|
||||||
#include "db/Selection.hxx"
|
#include "db/Selection.hxx"
|
||||||
#include "db/Helpers.hxx"
|
#include "db/Helpers.hxx"
|
||||||
@ -32,6 +33,7 @@
|
|||||||
#include "fs/TextFile.hxx"
|
#include "fs/TextFile.hxx"
|
||||||
#include "config/ConfigData.hxx"
|
#include "config/ConfigData.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
|
#include "util/CharUtil.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@ -42,7 +44,17 @@ static constexpr Domain simple_db_domain("simple_db");
|
|||||||
|
|
||||||
inline SimpleDatabase::SimpleDatabase()
|
inline SimpleDatabase::SimpleDatabase()
|
||||||
:Database(simple_db_plugin),
|
: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 *
|
Database *
|
||||||
SimpleDatabase::Create(gcc_unused EventLoop &loop,
|
SimpleDatabase::Create(gcc_unused EventLoop &loop,
|
||||||
@ -71,6 +83,10 @@ SimpleDatabase::Configure(const config_param ¶m, Error &error)
|
|||||||
|
|
||||||
path_utf8 = path.ToUTF8();
|
path_utf8 = path.ToUTF8();
|
||||||
|
|
||||||
|
cache_path = param.GetBlockPath("cache_directory", error);
|
||||||
|
if (path.IsNull() && error.IsDefined())
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +185,8 @@ SimpleDatabase::Load(Error &error)
|
|||||||
bool
|
bool
|
||||||
SimpleDatabase::Open(Error &error)
|
SimpleDatabase::Open(Error &error)
|
||||||
{
|
{
|
||||||
|
assert(prefixed_light_song == nullptr);
|
||||||
|
|
||||||
root = Directory::NewRoot();
|
root = Directory::NewRoot();
|
||||||
mtime = 0;
|
mtime = 0;
|
||||||
|
|
||||||
@ -195,6 +213,7 @@ void
|
|||||||
SimpleDatabase::Close()
|
SimpleDatabase::Close()
|
||||||
{
|
{
|
||||||
assert(root != nullptr);
|
assert(root != nullptr);
|
||||||
|
assert(prefixed_light_song == nullptr);
|
||||||
assert(borrowed_song_count == 0);
|
assert(borrowed_song_count == 0);
|
||||||
|
|
||||||
delete root;
|
delete root;
|
||||||
@ -204,11 +223,27 @@ const LightSong *
|
|||||||
SimpleDatabase::GetSong(const char *uri, Error &error) const
|
SimpleDatabase::GetSong(const char *uri, Error &error) const
|
||||||
{
|
{
|
||||||
assert(root != nullptr);
|
assert(root != nullptr);
|
||||||
|
assert(prefixed_light_song == nullptr);
|
||||||
assert(borrowed_song_count == 0);
|
assert(borrowed_song_count == 0);
|
||||||
|
|
||||||
db_lock();
|
db_lock();
|
||||||
|
|
||||||
auto r = root->LookupDirectory(uri);
|
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) {
|
if (r.uri == nullptr) {
|
||||||
/* it's a directory */
|
/* it's a directory */
|
||||||
db_unlock();
|
db_unlock();
|
||||||
@ -245,11 +280,17 @@ SimpleDatabase::GetSong(const char *uri, Error &error) const
|
|||||||
void
|
void
|
||||||
SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const
|
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
|
#ifndef NDEBUG
|
||||||
assert(borrowed_song_count > 0);
|
if (song == &light_song) {
|
||||||
--borrowed_song_count;
|
assert(borrowed_song_count > 0);
|
||||||
|
--borrowed_song_count;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,6 +388,104 @@ SimpleDatabase::Save(Error &error)
|
|||||||
return true;
|
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 = {
|
const DatabasePlugin simple_db_plugin = {
|
||||||
"simple",
|
"simple",
|
||||||
DatabasePlugin::FLAG_REQUIRE_STORAGE,
|
DatabasePlugin::FLAG_REQUIRE_STORAGE,
|
||||||
|
@ -32,15 +32,27 @@ struct Directory;
|
|||||||
struct DatabasePlugin;
|
struct DatabasePlugin;
|
||||||
class EventLoop;
|
class EventLoop;
|
||||||
class DatabaseListener;
|
class DatabaseListener;
|
||||||
|
class PrefixedLightSong;
|
||||||
|
|
||||||
class SimpleDatabase : public Database {
|
class SimpleDatabase : public Database {
|
||||||
AllocatedPath path;
|
AllocatedPath path;
|
||||||
std::string path_utf8;
|
std::string path_utf8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path where cache files for Mount() are located.
|
||||||
|
*/
|
||||||
|
AllocatedPath cache_path;
|
||||||
|
|
||||||
Directory *root;
|
Directory *root;
|
||||||
|
|
||||||
time_t mtime;
|
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().
|
* A buffer for GetSong().
|
||||||
*/
|
*/
|
||||||
@ -52,6 +64,8 @@ class SimpleDatabase : public Database {
|
|||||||
|
|
||||||
SimpleDatabase();
|
SimpleDatabase();
|
||||||
|
|
||||||
|
SimpleDatabase(AllocatedPath &&_path);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Database *Create(EventLoop &loop, DatabaseListener &listener,
|
static Database *Create(EventLoop &loop, DatabaseListener &listener,
|
||||||
const config_param ¶m,
|
const config_param ¶m,
|
||||||
@ -73,6 +87,20 @@ public:
|
|||||||
return mtime > 0;
|
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 methods from class Database */
|
||||||
virtual bool Open(Error &error) override;
|
virtual bool Open(Error &error) override;
|
||||||
virtual void Close() override;
|
virtual void Close() override;
|
||||||
@ -107,6 +135,8 @@ private:
|
|||||||
bool Check(Error &error) const;
|
bool Check(Error &error) const;
|
||||||
|
|
||||||
bool Load(Error &error);
|
bool Load(Error &error);
|
||||||
|
|
||||||
|
Database *LockUmountSteal(const char *uri);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const DatabasePlugin simple_db_plugin;
|
extern const DatabasePlugin simple_db_plugin;
|
||||||
|
@ -42,6 +42,9 @@ UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name,
|
|||||||
|
|
||||||
// directory exists already
|
// directory exists already
|
||||||
if (directory != nullptr) {
|
if (directory != nullptr) {
|
||||||
|
if (directory->IsMount())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
if (directory->mtime == info.mtime && !walk_discard) {
|
if (directory->mtime == info.mtime && !walk_discard) {
|
||||||
/* not modified */
|
/* not modified */
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -21,12 +21,13 @@
|
|||||||
#include "Queue.hxx"
|
#include "Queue.hxx"
|
||||||
|
|
||||||
bool
|
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)
|
if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
update_queue.emplace_back(path, discard, id);
|
update_queue.emplace_back(db, storage, path, discard, id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,3 +41,27 @@ UpdateQueue::Pop()
|
|||||||
update_queue.pop_front();
|
update_queue.pop_front();
|
||||||
return i;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,19 +21,30 @@
|
|||||||
#define MPD_UPDATE_QUEUE_HXX
|
#define MPD_UPDATE_QUEUE_HXX
|
||||||
|
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
|
#include "Compiler.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
|
class SimpleDatabase;
|
||||||
|
class Storage;
|
||||||
|
|
||||||
struct UpdateQueueItem {
|
struct UpdateQueueItem {
|
||||||
|
SimpleDatabase *db;
|
||||||
|
Storage *storage;
|
||||||
|
|
||||||
std::string path_utf8;
|
std::string path_utf8;
|
||||||
unsigned id;
|
unsigned id;
|
||||||
bool discard;
|
bool discard;
|
||||||
|
|
||||||
UpdateQueueItem():id(0) {}
|
UpdateQueueItem():id(0) {}
|
||||||
UpdateQueueItem(const char *_path, bool _discard,
|
|
||||||
|
UpdateQueueItem(SimpleDatabase &_db,
|
||||||
|
Storage &_storage,
|
||||||
|
const char *_path, bool _discard,
|
||||||
unsigned _id)
|
unsigned _id)
|
||||||
:path_utf8(_path), id(_id), discard(_discard) {}
|
:db(&_db), storage(&_storage), path_utf8(_path),
|
||||||
|
id(_id), discard(_discard) {}
|
||||||
|
|
||||||
bool IsDefined() const {
|
bool IsDefined() const {
|
||||||
return id != 0;
|
return id != 0;
|
||||||
@ -46,13 +57,21 @@ class UpdateQueue {
|
|||||||
std::list<UpdateQueueItem> update_queue;
|
std::list<UpdateQueueItem> update_queue;
|
||||||
|
|
||||||
public:
|
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();
|
UpdateQueueItem Pop();
|
||||||
|
|
||||||
void Clear() {
|
void Clear() {
|
||||||
update_queue.clear();
|
update_queue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_nonnull_all
|
||||||
|
void Erase(SimpleDatabase &db);
|
||||||
|
|
||||||
|
gcc_nonnull_all
|
||||||
|
void Erase(Storage &storage);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -22,7 +22,10 @@
|
|||||||
#include "Walk.hxx"
|
#include "Walk.hxx"
|
||||||
#include "UpdateDomain.hxx"
|
#include "UpdateDomain.hxx"
|
||||||
#include "db/DatabaseListener.hxx"
|
#include "db/DatabaseListener.hxx"
|
||||||
|
#include "db/DatabaseLock.hxx"
|
||||||
#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
|
#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
|
||||||
|
#include "db/plugins/simple/Directory.hxx"
|
||||||
|
#include "storage/CompositeStorage.hxx"
|
||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@ -39,7 +42,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
|
UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
|
||||||
Storage &_storage,
|
CompositeStorage &_storage,
|
||||||
DatabaseListener &_listener)
|
DatabaseListener &_listener)
|
||||||
:DeferredMonitor(_loop),
|
:DeferredMonitor(_loop),
|
||||||
db(_db), storage(_storage),
|
db(_db), storage(_storage),
|
||||||
@ -71,6 +74,42 @@ UpdateService::CancelAllAsync()
|
|||||||
walk->Cancel();
|
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
|
inline void
|
||||||
UpdateService::Task()
|
UpdateService::Task()
|
||||||
{
|
{
|
||||||
@ -84,12 +123,12 @@ UpdateService::Task()
|
|||||||
|
|
||||||
SetThreadIdlePriority();
|
SetThreadIdlePriority();
|
||||||
|
|
||||||
modified = walk->Walk(db.GetRoot(), next.path_utf8.c_str(),
|
modified = walk->Walk(next.db->GetRoot(), next.path_utf8.c_str(),
|
||||||
next.discard);
|
next.discard);
|
||||||
|
|
||||||
if (modified || !db.FileExists()) {
|
if (modified || !next.db->FileExists()) {
|
||||||
Error error;
|
Error error;
|
||||||
if (!db.Save(error))
|
if (!next.db->Save(error))
|
||||||
LogError(error, "Failed to save database");
|
LogError(error, "Failed to save database");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +159,7 @@ UpdateService::StartThread(UpdateQueueItem &&i)
|
|||||||
modified = false;
|
modified = false;
|
||||||
|
|
||||||
next = std::move(i);
|
next = std::move(i);
|
||||||
walk = new UpdateWalk(GetEventLoop(), listener, storage);
|
walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
if (!update_thread.Start(Task, this, error))
|
if (!update_thread.Start(Task, this, error))
|
||||||
@ -144,9 +183,52 @@ UpdateService::Enqueue(const char *path, bool discard)
|
|||||||
{
|
{
|
||||||
assert(GetEventLoop().IsInsideOrNull());
|
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) {
|
if (progress != UPDATE_PROGRESS_IDLE) {
|
||||||
const unsigned id = GenerateId();
|
const unsigned id = GenerateId();
|
||||||
if (!queue.Push(path, discard, id))
|
if (!queue.Push(*db2, *storage2, path, discard, id))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
update_task_id = id;
|
update_task_id = id;
|
||||||
@ -154,7 +236,7 @@ UpdateService::Enqueue(const char *path, bool discard)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const unsigned id = update_task_id = GenerateId();
|
const unsigned id = update_task_id = GenerateId();
|
||||||
StartThread(UpdateQueueItem(path, discard, id));
|
StartThread(UpdateQueueItem(*db2, *storage2, path, discard, id));
|
||||||
|
|
||||||
idle_add(IDLE_UPDATE);
|
idle_add(IDLE_UPDATE);
|
||||||
|
|
||||||
@ -171,7 +253,10 @@ UpdateService::RunDeferred()
|
|||||||
assert(next.IsDefined());
|
assert(next.IsDefined());
|
||||||
assert(walk != nullptr);
|
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;
|
delete walk;
|
||||||
walk = nullptr;
|
walk = nullptr;
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
class SimpleDatabase;
|
class SimpleDatabase;
|
||||||
class DatabaseListener;
|
class DatabaseListener;
|
||||||
class UpdateWalk;
|
class UpdateWalk;
|
||||||
class Storage;
|
class CompositeStorage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class manages the update queue and runs the update thread.
|
* This class manages the update queue and runs the update thread.
|
||||||
@ -41,7 +41,7 @@ class UpdateService final : DeferredMonitor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SimpleDatabase &db;
|
SimpleDatabase &db;
|
||||||
Storage &storage;
|
CompositeStorage &storage;
|
||||||
|
|
||||||
DatabaseListener &listener;
|
DatabaseListener &listener;
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ class UpdateService final : DeferredMonitor {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
UpdateService(EventLoop &_loop, SimpleDatabase &_db,
|
UpdateService(EventLoop &_loop, SimpleDatabase &_db,
|
||||||
Storage &_storage,
|
CompositeStorage &_storage,
|
||||||
DatabaseListener &_listener);
|
DatabaseListener &_listener);
|
||||||
|
|
||||||
~UpdateService();
|
~UpdateService();
|
||||||
@ -92,6 +92,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
void CancelAllAsync();
|
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:
|
private:
|
||||||
/* virtual methods from class DeferredMonitor */
|
/* virtual methods from class DeferredMonitor */
|
||||||
virtual void RunDeferred() override;
|
virtual void RunDeferred() override;
|
||||||
|
@ -397,8 +397,12 @@ UpdateWalk::DirectoryMakeChildChecked(Directory &parent,
|
|||||||
Directory *directory = parent.FindChild(name_utf8);
|
Directory *directory = parent.FindChild(name_utf8);
|
||||||
db_unlock();
|
db_unlock();
|
||||||
|
|
||||||
if (directory != nullptr)
|
if (directory != nullptr) {
|
||||||
|
if (directory->IsMount())
|
||||||
|
directory = nullptr;
|
||||||
|
|
||||||
return directory;
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
FileInfo info;
|
FileInfo info;
|
||||||
if (!GetInfo(storage, uri_utf8, info) ||
|
if (!GetInfo(storage, uri_utf8, info) ||
|
||||||
|
Loading…
Reference in New Issue
Block a user