StickerDatabase.cxx 9.63 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
 * 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
 */

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

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

32 33
#include <assert.h>

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

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

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

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

	//[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>?",
75 76
};

77 78 79 80 81 82 83 84 85 86 87 88
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;
89
static sqlite3_stmt *sticker_stmt[ARRAY_SIZE(sticker_sql)];
90 91

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

	return stmt;
}

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

108
	int ret;
109 110 111

	/* open/create the sqlite database */

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

	/* create the table and index */

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

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

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

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

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

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

		sqlite3_finalize(sticker_stmt[i]);
	}

150 151 152 153
	sqlite3_close(sticker_db);
}

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

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

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

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

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

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

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

	return value;
}

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

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

196
	BindAll(stmt, type, uri);
197

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

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

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

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

	assert(sticker_enabled());

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

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

231
	bool modified = ExecuteModified(stmt);
232

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

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

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

	assert(sticker_enabled());

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

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

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

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

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

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

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

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

289
	BindAll(stmt, type, uri);
290

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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