Service.cxx 5.8 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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
#include "thread/Util.hxx"

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

39
#include <cassert>
40

41 42
UpdateService::UpdateService(const ConfigData &_config,
			     EventLoop &_loop, SimpleDatabase &_db,
Max Kellermann's avatar
Max Kellermann committed
43
			     CompositeStorage &_storage,
44
			     DatabaseListener &_listener) noexcept
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
UpdateService::~UpdateService() noexcept
54 55 56 57 58 59 60 61
{
	CancelAllAsync();

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

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

	queue.Clear();
67 68 69

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

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

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

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

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

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

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

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

113 114
	SetThreadName("update");

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

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

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

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

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

	modified = false;

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

156
	update_thread.Start();
157 158 159 160 161 162

	FormatDebug(update_domain,
		    "spawned thread for update job id %i", next.id);
}

unsigned
163
UpdateService::GenerateId() noexcept
164 165 166 167 168 169 170 171
{
	unsigned id = update_task_id + 1;
	if (id > update_task_id_max)
		id = 1;
	return id;
}

unsigned
172
UpdateService::Enqueue(std::string_view path, bool discard)
173
{
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
		if (lr.rest.data() == nullptr) {
Max Kellermann's avatar
Max Kellermann committed
196 197 198
			storage2 = storage.GetMount(path);
			path = "";
		} else {
199
			storage2 = storage.GetMount(lr.uri);
200
			path = lr.rest;
Max Kellermann's avatar
Max Kellermann committed
201 202 203 204 205 206 207 208 209 210 211
		}
	} else {
		/* use the "root" database/storage */

		db2 = &db;
		storage2 = storage.GetMount("");
	}

	if (storage2 == nullptr)
		/* no storage found at this mount point - should not
		   happen */
212
		throw std::runtime_error("No storage at this path");
Max Kellermann's avatar
Max Kellermann committed
213

214
	if (walk != nullptr) {
215
		const unsigned id = GenerateId();
Max Kellermann's avatar
Max Kellermann committed
216
		if (!queue.Push(*db2, *storage2, path, discard, id))
217 218
			throw ProtocolError(ACK_ERROR_UPDATE_ALREADY,
					    "Update queue is full");
219 220 221 222 223 224

		update_task_id = id;
		return id;
	}

	const unsigned id = update_task_id = GenerateId();
Max Kellermann's avatar
Max Kellermann committed
225
	StartThread(UpdateQueueItem(*db2, *storage2, path, discard, id));
226 227 228 229 230 231 232 233 234 235

	idle_add(IDLE_UPDATE);

	return id;
}

/**
 * Called in the main thread after the database update is finished.
 */
void
236
UpdateService::RunDeferred() noexcept
237 238
{
	assert(next.IsDefined());
239
	assert(walk != nullptr);
240

Max Kellermann's avatar
Max Kellermann committed
241 242 243 244
	/* wait for thread to finish only if it wasn't cancelled by
	   CancelMount() */
	if (update_thread.IsDefined())
		update_thread.Join();
245

246
	walk.reset();
247

248
	next.Clear();
249 250 251 252 253 254 255 256 257 258 259 260 261

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