/* * Copyright 2003-2017 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 "Service.hxx" #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 "Log.hxx" #include "thread/Thread.hxx" #include "thread/Util.hxx" #ifndef NDEBUG #include "event/Loop.hxx" #endif #include UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db, CompositeStorage &_storage, DatabaseListener &_listener) :DeferredMonitor(_loop), db(_db), storage(_storage), listener(_listener), update_thread(BIND_THIS_METHOD(Task)), update_task_id(0), walk(nullptr) { } UpdateService::~UpdateService() { CancelAllAsync(); if (update_thread.IsDefined()) update_thread.Join(); delete walk; } void UpdateService::CancelAllAsync() { assert(GetEventLoop().IsInsideOrNull()); queue.Clear(); if (walk != nullptr) walk->Cancel(); } void UpdateService::CancelMount(const char *uri) { /* determine which (mounted) database will be updated and what storage will be scanned */ Directory::LookupResult lr; { const ScopeDatabaseLock protect; lr = db.GetRoot().LookupDirectory(uri); } 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(_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() { assert(walk != nullptr); if (!next.path_utf8.empty()) FormatDebug(update_domain, "starting: %s", next.path_utf8.c_str()); else LogDebug(update_domain, "starting"); SetThreadIdlePriority(); modified = walk->Walk(next.db->GetRoot(), next.path_utf8.c_str(), next.discard); if (modified || !next.db->FileExists()) { try { next.db->Save(); } catch (const std::exception &e) { LogError(e, "Failed to save database"); } } if (!next.path_utf8.empty()) FormatDebug(update_domain, "finished: %s", next.path_utf8.c_str()); else LogDebug(update_domain, "finished"); DeferredMonitor::Schedule(); } void UpdateService::StartThread(UpdateQueueItem &&i) { assert(GetEventLoop().IsInsideOrNull()); assert(walk == nullptr); modified = false; next = std::move(i); walk = new UpdateWalk(GetEventLoop(), listener, *next.storage); update_thread.Start(); FormatDebug(update_domain, "spawned thread for update job id %i", next.id); } unsigned UpdateService::GenerateId() { unsigned id = update_task_id + 1; if (id > update_task_id_max) id = 1; return id; } unsigned 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; Directory::LookupResult lr; { const ScopeDatabaseLock protect; lr = db.GetRoot().LookupDirectory(path); } 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(&_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 (walk != nullptr) { const unsigned id = GenerateId(); if (!queue.Push(*db2, *storage2, path, discard, id)) return 0; update_task_id = id; return id; } const unsigned id = update_task_id = GenerateId(); StartThread(UpdateQueueItem(*db2, *storage2, path, discard, id)); idle_add(IDLE_UPDATE); return id; } /** * Called in the main thread after the database update is finished. */ void UpdateService::RunDeferred() { assert(next.IsDefined()); assert(walk != nullptr); /* wait for thread to finish only if it wasn't cancelled by CancelMount() */ if (update_thread.IsDefined()) update_thread.Join(); delete walk; walk = nullptr; next.Clear(); idle_add(IDLE_UPDATE); if (modified) /* send "idle" events */ listener.OnDatabaseModified(); auto i = queue.Pop(); if (i.IsDefined()) { /* schedule the next path */ StartThread(std::move(i)); } }