StickerDatabase.cxx 9.61 KB
Newer Older
1
/*
2
 * Copyright 2003-2018 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * 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.
14 15 16 17
 *
 * 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.
18 19
 */

Max Kellermann's avatar
Max Kellermann committed
20
#include "StickerDatabase.hxx"
21
#include "lib/sqlite/Util.hxx"
22
#include "fs/Path.hxx"
Max Kellermann's avatar
Max Kellermann committed
23
#include "Idle.hxx"
24
#include "util/Macros.hxx"
25
#include "util/StringCompare.hxx"
26
#include "util/ScopeExit.hxx"
27

28 29 30
#include <string>
#include <map>

31 32
#include <assert.h>

33
struct Sticker {
34
	std::map<std::string, std::string> table;
35 36
};

37 38 39 40 41 42
enum sticker_sql {
	STICKER_SQL_GET,
	STICKER_SQL_LIST,
	STICKER_SQL_UPDATE,
	STICKER_SQL_INSERT,
	STICKER_SQL_DELETE,
43
	STICKER_SQL_DELETE_VALUE,
44
	STICKER_SQL_FIND,
45
	STICKER_SQL_FIND_VALUE,
46 47
	STICKER_SQL_FIND_LT,
	STICKER_SQL_FIND_GT,
48 49 50
};

static const char *const sticker_sql[] = {
Max Kellermann's avatar
Max Kellermann committed
51
	//[STICKER_SQL_GET] =
52
	"SELECT value FROM sticker WHERE type=? AND uri=? AND name=?",
Max Kellermann's avatar
Max Kellermann committed
53
	//[STICKER_SQL_LIST] =
54
	"SELECT name,value FROM sticker WHERE type=? AND uri=?",
Max Kellermann's avatar
Max Kellermann committed
55
	//[STICKER_SQL_UPDATE] =
56
	"UPDATE sticker SET value=? WHERE type=? AND uri=? AND name=?",
Max Kellermann's avatar
Max Kellermann committed
57
	//[STICKER_SQL_INSERT] =
58
	"INSERT INTO sticker(type,uri,name,value) VALUES(?, ?, ?, ?)",
Max Kellermann's avatar
Max Kellermann committed
59
	//[STICKER_SQL_DELETE] =
60
	"DELETE FROM sticker WHERE type=? AND uri=?",
Max Kellermann's avatar
Max Kellermann committed
61
	//[STICKER_SQL_DELETE_VALUE] =
62
	"DELETE FROM sticker WHERE type=? AND uri=? AND name=?",
Max Kellermann's avatar
Max Kellermann committed
63
	//[STICKER_SQL_FIND] =
64
	"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?",
65 66 67

	//[STICKER_SQL_FIND_VALUE] =
	"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value=?",
68 69 70 71 72 73

	//[STICKER_SQL_FIND_LT] =
	"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value<?",

	//[STICKER_SQL_FIND_GT] =
	"SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value>?",
74 75
};

76 77 78 79 80 81 82 83 84 85 86 87
static const char sticker_sql_create[] =
	"CREATE TABLE IF NOT EXISTS sticker("
	"  type VARCHAR NOT NULL, "
	"  uri VARCHAR NOT NULL, "
	"  name VARCHAR NOT NULL, "
	"  value VARCHAR NOT NULL"
	");"
	"CREATE UNIQUE INDEX IF NOT EXISTS"
	" sticker_value ON sticker(type, uri, name);"
	"";

static sqlite3 *sticker_db;
88
static sqlite3_stmt *sticker_stmt[ARRAY_SIZE(sticker_sql)];
89 90

static sqlite3_stmt *
91
sticker_prepare(const char *sql)
92 93
{
	sqlite3_stmt *stmt;
94
	int ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr);
95 96 97
	if (ret != SQLITE_OK)
		throw SqliteError(sticker_db, ret,
				  "sqlite3_prepare_v2() failed");
98 99 100 101

	return stmt;
}

102 103
void
sticker_global_init(Path path)
104
{
105
	assert(!path.IsNull());
106

107
	int ret;
108 109 110

	/* open/create the sqlite database */

111
	ret = sqlite3_open(path.c_str(), &sticker_db);
112
	if (ret != SQLITE_OK) {
113
		const std::string utf8 = path.ToUTF8();
114 115 116
		throw SqliteError(sticker_db, ret,
				  ("Failed to open sqlite database '" +
				   utf8 + "'").c_str());
117
	}
118 119 120

	/* create the table and index */

121 122
	ret = sqlite3_exec(sticker_db, sticker_sql_create,
			   nullptr, nullptr, nullptr);
123 124 125
	if (ret != SQLITE_OK)
		throw SqliteError(sticker_db, ret,
				  "Failed to create sticker table");
126 127 128

	/* prepare the statements we're going to use */

129
	for (unsigned i = 0; i < ARRAY_SIZE(sticker_sql); ++i) {
130
		assert(sticker_sql[i] != nullptr);
131

132
		sticker_stmt[i] = sticker_prepare(sticker_sql[i]);
133
	}
134 135 136
}

void
137
sticker_global_finish()
138
{
139
	if (sticker_db == nullptr)
140 141 142
		/* not configured */
		return;

143
	for (unsigned i = 0; i < ARRAY_SIZE(sticker_stmt); ++i) {
144
		assert(sticker_stmt[i] != nullptr);
145 146 147 148

		sqlite3_finalize(sticker_stmt[i]);
	}

149 150 151 152
	sqlite3_close(sticker_db);
}

bool
153
sticker_enabled() noexcept
154
{
155
	return sticker_db != nullptr;
156 157
}

158
std::string
159
sticker_load_value(const char *type, const char *uri, const char *name)
160
{
161
	sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET];
162 163

	assert(sticker_enabled());
164 165 166
	assert(type != nullptr);
	assert(uri != nullptr);
	assert(name != nullptr);
167

168
	if (StringIsEmpty(name))
169
		return std::string();
170

171 172 173 174 175 176
	BindAll(stmt, type, uri, name);

	AtScopeExit(stmt) {
		sqlite3_reset(stmt);
		sqlite3_clear_bindings(stmt);
	};
177

178
	std::string value;
179
	if (ExecuteRow(stmt))
180
		value = (const char*)sqlite3_column_text(stmt, 0);
181 182 183 184

	return value;
}

185
static void
186
sticker_list_values(std::map<std::string, std::string> &table,
187
		    const char *type, const char *uri)
188
{
189
	sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST];
190

191 192
	assert(type != nullptr);
	assert(uri != nullptr);
193 194
	assert(sticker_enabled());

195
	BindAll(stmt, type, uri);
196

197 198 199 200 201 202
	AtScopeExit(stmt) {
		sqlite3_reset(stmt);
		sqlite3_clear_bindings(stmt);
	};

	ExecuteForEach(stmt, [stmt, &table](){
203 204
			const char *name = (const char *)sqlite3_column_text(stmt, 0);
			const char *value = (const char *)sqlite3_column_text(stmt, 1);
205
			table.insert(std::make_pair(name, value));
206
		});
207 208
}

209 210
static bool
sticker_update_value(const char *type, const char *uri,
211
		     const char *name, const char *value)
212
{
213
	sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE];
214

215 216 217
	assert(type != nullptr);
	assert(uri != nullptr);
	assert(name != nullptr);
218
	assert(*name != 0);
219
	assert(value != nullptr);
220 221 222

	assert(sticker_enabled());

223
	BindAll(stmt, value, type, uri, name);
224

225 226 227 228
	AtScopeExit(stmt) {
		sqlite3_reset(stmt);
		sqlite3_clear_bindings(stmt);
	};
229

230
	bool modified = ExecuteModified(stmt);
231

232 233 234
	if (modified)
		idle_add(IDLE_STICKER);
	return modified;
235 236
}

237
static void
238
sticker_insert_value(const char *type, const char *uri,
239
		     const char *name, const char *value)
240
{
241
	sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT];
242

243 244 245
	assert(type != nullptr);
	assert(uri != nullptr);
	assert(name != nullptr);
246
	assert(*name != 0);
247
	assert(value != nullptr);
248 249 250

	assert(sticker_enabled());

251
	BindAll(stmt, type, uri, name, value);
252

253 254 255 256
	AtScopeExit(stmt) {
		sqlite3_reset(stmt);
		sqlite3_clear_bindings(stmt);
	};
257

258 259
	ExecuteCommand(stmt);
	idle_add(IDLE_STICKER);
260 261
}

262
void
263
sticker_store_value(const char *type, const char *uri,
264
		    const char *name, const char *value)
265 266
{
	assert(sticker_enabled());
267 268 269 270
	assert(type != nullptr);
	assert(uri != nullptr);
	assert(name != nullptr);
	assert(value != nullptr);
271

272
	if (StringIsEmpty(name))
273
		return;
274

275 276
	if (!sticker_update_value(type, uri, name, value))
		sticker_insert_value(type, uri, name, value);
277 278 279
}

bool
280
sticker_delete(const char *type, const char *uri)
281
{
282
	sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE];
283 284

	assert(sticker_enabled());
285 286
	assert(type != nullptr);
	assert(uri != nullptr);
287

288
	BindAll(stmt, type, uri);
289

290 291 292 293
	AtScopeExit(stmt) {
		sqlite3_reset(stmt);
		sqlite3_clear_bindings(stmt);
	};
294

295
	bool modified = ExecuteModified(stmt);
296 297 298
	if (modified)
		idle_add(IDLE_STICKER);
	return modified;
299
}
300

301
bool
302
sticker_delete_value(const char *type, const char *uri, const char *name)
303 304 305 306
{
	sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE];

	assert(sticker_enabled());
307 308
	assert(type != nullptr);
	assert(uri != nullptr);
309

310
	BindAll(stmt, type, uri, name);
311

312 313 314 315
	AtScopeExit(stmt) {
		sqlite3_reset(stmt);
		sqlite3_clear_bindings(stmt);
	};
316

317
	bool modified = ExecuteModified(stmt);
318 319 320
	if (modified)
		idle_add(IDLE_STICKER);
	return modified;
321 322
}

323
void
324
sticker_free(Sticker *sticker)
325
{
326
	delete sticker;
327 328 329
}

const char *
330
sticker_get_value(const Sticker &sticker, const char *name) noexcept
331
{
332 333
	auto i = sticker.table.find(name);
	if (i == sticker.table.end())
334
		return nullptr;
335

336
	return i->second.c_str();
337 338 339
}

void
340
sticker_foreach(const Sticker &sticker,
341
		void (*func)(const char *name, const char *value,
342 343
			     void *user_data),
		void *user_data)
344
{
345
	for (const auto &i : sticker.table)
346
		func(i.first.c_str(), i.second.c_str(), user_data);
347 348
}

349
Sticker *
350
sticker_load(const char *type, const char *uri)
351
{
352
	Sticker s;
353

354
	sticker_list_values(s.table, type, uri);
355

356
	if (s.table.empty())
357
		/* don't return empty sticker objects */
358
		return nullptr;
359

360
	return new Sticker(std::move(s));
361
}
362

363 364
static sqlite3_stmt *
BindFind(const char *type, const char *base_uri, const char *name,
365
	 StickerOperator op, const char *value)
366 367 368 369 370 371 372
{
	assert(type != nullptr);
	assert(name != nullptr);

	if (base_uri == nullptr)
		base_uri = "";

373 374
	switch (op) {
	case StickerOperator::EXISTS:
375 376
		BindAll(sticker_stmt[STICKER_SQL_FIND], type, base_uri, name);
		return sticker_stmt[STICKER_SQL_FIND];
377

378
	case StickerOperator::EQUALS:
379 380 381
		BindAll(sticker_stmt[STICKER_SQL_FIND_VALUE],
			type, base_uri, name, value);
		return sticker_stmt[STICKER_SQL_FIND_VALUE];
382 383

	case StickerOperator::LESS_THAN:
384 385 386
		BindAll(sticker_stmt[STICKER_SQL_FIND_LT],
			type, base_uri, name, value);
		return sticker_stmt[STICKER_SQL_FIND_LT];
387 388

	case StickerOperator::GREATER_THAN:
389 390 391
		BindAll(sticker_stmt[STICKER_SQL_FIND_GT],
			type, base_uri, name, value);
		return sticker_stmt[STICKER_SQL_FIND_GT];
392
	}
393 394 395

	assert(false);
	gcc_unreachable();
396 397
}

398
void
399
sticker_find(const char *type, const char *base_uri, const char *name,
400
	     StickerOperator op, const char *value,
401
	     void (*func)(const char *uri, const char *value,
402
			  void *user_data),
403
	     void *user_data)
404
{
405
	assert(func != nullptr);
406 407
	assert(sticker_enabled());

408 409
	sqlite3_stmt *const stmt = BindFind(type, base_uri, name, op, value);
	assert(stmt != nullptr);
410

411 412 413 414 415 416
	AtScopeExit(stmt) {
		sqlite3_reset(stmt);
		sqlite3_clear_bindings(stmt);
	};

	ExecuteForEach(stmt, [stmt, func, user_data](){
417 418 419
			func((const char*)sqlite3_column_text(stmt, 0),
			     (const char*)sqlite3_column_text(stmt, 1),
			     user_data);
420
		});
421
}