Service.cxx 5.76 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * 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.
 */

20 21
#include "config.h"
#include "Service.hxx"
22
#include "Walk.hxx"
23 24
#include "UpdateDomain.hxx"
#include "db/DatabaseListener.hxx"
Max Kellermann's avatar
Max Kellermann committed
25
#include "db/DatabaseLock.hxx"
26
#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
Max Kellermann's avatar
Max Kellermann committed
27 28
#include "db/plugins/simple/Directory.hxx"
#include "storage/CompositeStorage.hxx"
29 30 31 32 33 34 35 36 37 38 39 40
#include "Idle.hxx"
#include "Log.hxx"
#include "thread/Thread.hxx"
#include "thread/Util.hxx"

#ifndef NDEBUG
#include "event/Loop.hxx"
#endif

#include <assert.h>

UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
Max Kellermann's avatar
Max Kellermann committed
41
			     CompositeStorage &_storage,
42
			     DatabaseListener &_listener)
43
	:defer(_loop, BIND_THIS_METHOD(RunDeferred)),
44 45
	 db(_db), storage(_storage),
	 listener(_listener),
46
	 update_thread(BIND_THIS_METHOD(Task))
47 48 49
{
}

50 51 52 53 54 55
UpdateService::~UpdateService()
{
	CancelAllAsync();

	if (update_thread.IsDefined())
		update_thread.Join();
56 57

	delete walk;
58 59 60 61 62
}

void
UpdateService::CancelAllAsync()
{
63
	assert(GetEventLoop().IsInside());
64 65

	queue.Clear();
66 67 68

	if (walk != nullptr)
		walk->Cancel();
69 70
}

Max Kellermann's avatar
Max Kellermann committed
71 72 73 74 75 76
void
UpdateService::CancelMount(const char *uri)
{
	/* determine which (mounted) database will be updated and what
	   storage will be scanned */

77 78 79 80 81
	Directory::LookupResult lr;
	{
		const ScopeDatabaseLock protect;
		lr = db.GetRoot().LookupDirectory(uri);
	}
Max Kellermann's avatar
Max Kellermann committed
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108

	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();
	}
}

109 110 111
inline void
UpdateService::Task()
{
112 113
	assert(walk != nullptr);

114 115 116 117 118 119 120 121
	if (!next.path_utf8.empty())
		FormatDebug(update_domain, "starting: %s",
			    next.path_utf8.c_str());
	else
		LogDebug(update_domain, "starting");

	SetThreadIdlePriority();

Max Kellermann's avatar
Max Kellermann committed
122
	modified = walk->Walk(next.db->GetRoot(), next.path_utf8.c_str(),
123
			      next.discard);
124

Max Kellermann's avatar
Max Kellermann committed
125
	if (modified || !next.db->FileExists()) {
126
		try {
127
			next.db->Save();
128 129 130
		} catch (const std::exception &e) {
			LogError(e, "Failed to save database");
		}
131 132 133 134 135 136 137 138
	}

	if (!next.path_utf8.empty())
		FormatDebug(update_domain, "finished: %s",
			    next.path_utf8.c_str());
	else
		LogDebug(update_domain, "finished");

139
	defer.Schedule();
140 141 142 143 144
}

void
UpdateService::StartThread(UpdateQueueItem &&i)
{
145
	assert(GetEventLoop().IsInside());
146
	assert(walk == nullptr);
147 148 149 150

	modified = false;

	next = std::move(i);
Max Kellermann's avatar
Max Kellermann committed
151
	walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
152

153
	update_thread.Start();
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170

	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)
{
171
	assert(GetEventLoop().IsInside());
172

Max Kellermann's avatar
Max Kellermann committed
173 174 175 176 177
	/* determine which (mounted) database will be updated and what
	   storage will be scanned */
	SimpleDatabase *db2;
	Storage *storage2;

178 179 180 181 182 183
	Directory::LookupResult lr;
	{
		const ScopeDatabaseLock protect;
		lr = db.GetRoot().LookupDirectory(path);
	}

Max Kellermann's avatar
Max Kellermann committed
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
	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;

219
	if (walk != nullptr) {
220
		const unsigned id = GenerateId();
Max Kellermann's avatar
Max Kellermann committed
221
		if (!queue.Push(*db2, *storage2, path, discard, id))
222 223 224 225 226 227 228
			return 0;

		update_task_id = id;
		return id;
	}

	const unsigned id = update_task_id = GenerateId();
Max Kellermann's avatar
Max Kellermann committed
229
	StartThread(UpdateQueueItem(*db2, *storage2, path, discard, id));
230 231 232 233 234 235 236 237 238 239

	idle_add(IDLE_UPDATE);

	return id;
}

/**
 * Called in the main thread after the database update is finished.
 */
void
240
UpdateService::RunDeferred() noexcept
241 242
{
	assert(next.IsDefined());
243
	assert(walk != nullptr);
244

Max Kellermann's avatar
Max Kellermann committed
245 246 247 248
	/* wait for thread to finish only if it wasn't cancelled by
	   CancelMount() */
	if (update_thread.IsDefined())
		update_thread.Join();
249 250 251 252

	delete walk;
	walk = nullptr;

253 254 255 256 257 258 259 260 261 262 263 264 265 266
	next = UpdateQueueItem();

	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));
	}
}