Service.cxx 5.89 KB
Newer Older
1
/*
2
 * Copyright 2003-2018 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
#include "Service.hxx"
21
#include "Walk.hxx"
22 23
#include "UpdateDomain.hxx"
#include "db/DatabaseListener.hxx"
Max Kellermann's avatar
Max Kellermann committed
24
#include "db/DatabaseLock.hxx"
25
#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
Max Kellermann's avatar
Max Kellermann committed
26 27
#include "db/plugins/simple/Directory.hxx"
#include "storage/CompositeStorage.hxx"
28
#include "protocol/Ack.hxx"
29 30 31
#include "Idle.hxx"
#include "Log.hxx"
#include "thread/Thread.hxx"
32
#include "thread/Name.hxx"
33 34 35 36 37 38 39 40
#include "thread/Util.hxx"

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

#include <assert.h>

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

53 54 55 56 57 58
UpdateService::~UpdateService()
{
	CancelAllAsync();

	if (update_thread.IsDefined())
		update_thread.Join();
59 60

	delete walk;
61 62 63 64 65
}

void
UpdateService::CancelAllAsync()
{
66
	assert(GetEventLoop().IsInside());
67 68

	queue.Clear();
69 70 71

	if (walk != nullptr)
		walk->Cancel();
72 73
}

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

80 81 82 83 84
	Directory::LookupResult lr;
	{
		const ScopeDatabaseLock protect;
		lr = db.GetRoot().LookupDirectory(uri);
	}
Max Kellermann's avatar
Max Kellermann committed
85 86 87 88 89 90 91 92 93 94 95 96

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

97
	if (auto *db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database.get())) {
98 99
		queue.Erase(*db2);
		cancel_current |= next.IsDefined() && next.db == db2;
Max Kellermann's avatar
Max Kellermann committed
100 101 102 103 104 105 106 107 108 109
	}

	if (cancel_current && walk != nullptr) {
		walk->Cancel();

		if (update_thread.IsDefined())
			update_thread.Join();
	}
}

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

115 116
	SetThreadName("update");

117 118 119 120 121 122 123 124
	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
125
	modified = walk->Walk(next.db->GetRoot(), next.path_utf8.c_str(),
126
			      next.discard);
127

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

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

142
	defer.Schedule();
143 144 145 146 147
}

void
UpdateService::StartThread(UpdateQueueItem &&i)
{
148
	assert(GetEventLoop().IsInside());
149
	assert(walk == nullptr);
150 151 152 153

	modified = false;

	next = std::move(i);
154
	walk = new UpdateWalk(config, GetEventLoop(), listener, *next.storage);
155

156
	update_thread.Start();
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

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

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

181 182 183 184 185 186
	Directory::LookupResult lr;
	{
		const ScopeDatabaseLock protect;
		lr = db.GetRoot().LookupDirectory(path);
	}

Max Kellermann's avatar
Max Kellermann committed
187 188 189 190
	if (lr.directory->IsMount()) {
		/* follow the mountpoint, update the mounted
		   database */

191
		db2 = dynamic_cast<SimpleDatabase *>(lr.directory->mounted_database.get());
192
		if (db2 == nullptr)
193
			throw std::runtime_error("Cannot update this type of database");
Max Kellermann's avatar
Max Kellermann committed
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216

		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 */
217
		throw std::runtime_error("No storage at this path");
Max Kellermann's avatar
Max Kellermann committed
218

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
			throw ProtocolError(ACK_ERROR_UPDATE_ALREADY,
					    "Update queue is full");
224 225 226 227 228 229

		update_task_id = id;
		return id;
	}

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

	idle_add(IDLE_UPDATE);

	return id;
}

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

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

	delete walk;
	walk = nullptr;

254
	next.Clear();
255 256 257 258 259 260 261 262 263 264 265 266 267

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