PlaylistFile.cxx 10.5 KB
Newer Older
1
/*
2
 * Copyright (C) 2003-2012 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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 "PlaylistFile.hxx"
22
#include "PlaylistSave.hxx"
23 24
#include "PlaylistInfo.hxx"
#include "PlaylistVector.hxx"
25 26
#include "DatabasePlugin.hxx"
#include "DatabaseGlue.hxx"
27
#include "song.h"
28
#include "io_error.h"
Max Kellermann's avatar
Max Kellermann committed
29
#include "Mapper.hxx"
30
#include "TextFile.hxx"
31
#include "conf.h"
32 33

extern "C" {
34
#include "path.h"
35
#include "uri.h"
36
#include "idle.h"
37 38
}

39
#include "glib_compat.h"
Max Kellermann's avatar
Max Kellermann committed
40 41 42 43 44 45 46

#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
47
#include <errno.h>
48

49 50
static const char PLAYLIST_COMMENT = '#';

51 52 53 54 55 56 57 58 59 60 61 62 63 64
static unsigned playlist_max_length;
bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;

void
spl_global_init(void)
{
	playlist_max_length = config_get_positive(CONF_MAX_PLAYLIST_LENGTH,
						  DEFAULT_PLAYLIST_MAX_LENGTH);

	playlist_saveAbsolutePaths =
		config_get_bool(CONF_SAVE_ABSOLUTE_PATHS,
				DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS);
}

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
bool
spl_valid_name(const char *name_utf8)
{
	/*
	 * Not supporting '/' was done out of laziness, and we should
	 * really strive to support it in the future.
	 *
	 * Not supporting '\r' and '\n' is done out of protocol
	 * limitations (and arguably laziness), but bending over head
	 * over heels to modify the protocol (and compatibility with
	 * all clients) to support idiots who put '\r' and '\n' in
	 * filenames isn't going to happen, either.
	 */

	return strchr(name_utf8, '/') == NULL &&
		strchr(name_utf8, '\n') == NULL &&
		strchr(name_utf8, '\r') == NULL;
}

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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
static const char *
spl_map(GError **error_r)
{
	const char *path_fs = map_spl_path();
	if (path_fs == NULL)
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_DISABLED,
				    "Stored playlists are disabled");

	return path_fs;
}

static bool
spl_check_name(const char *name_utf8, GError **error_r)
{
	if (!spl_valid_name(name_utf8)) {
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_BAD_NAME,
				    "Bad playlist name");
		return false;
	}

	return true;
}

static char *
spl_map_to_fs(const char *name_utf8, GError **error_r)
{
	if (spl_map(error_r) == NULL ||
	    !spl_check_name(name_utf8, error_r))
		return NULL;

	char *path_fs = map_spl_utf8_to_fs(name_utf8);
	if (path_fs == NULL)
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_BAD_NAME,
				    "Bad playlist name");

	return path_fs;
}

/**
 * Create a GError for the current errno.
 */
static void
playlist_errno(GError **error_r)
{
	switch (errno) {
	case ENOENT:
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_NO_SUCH_LIST,
				    "No such playlist");
		break;

	default:
139
		set_error_errno(error_r);
140 141 142 143
		break;
	}
}

144
static bool
145
LoadPlaylistFileInfo(PlaylistInfo &info,
146
		     const char *parent_path_fs, const char *name_fs)
147 148 149
{
	size_t name_length = strlen(name_fs);

150
	if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) ||
151
	    memchr(name_fs, '\n', name_length) != NULL)
152
		return false;
153

154
	if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX))
155
		return false;
156

157 158 159
	char *path_fs = g_build_filename(parent_path_fs, name_fs, NULL);
	struct stat st;
	int ret = stat(path_fs, &st);
Max Kellermann's avatar
Max Kellermann committed
160
	g_free(path_fs);
161
	if (ret < 0 || !S_ISREG(st.st_mode))
162
		return false;
163

164 165 166
	char *name = g_strndup(name_fs,
			       name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX));
	char *name_utf8 = fs_charset_to_utf8(name);
167 168
	g_free(name);
	if (name_utf8 == NULL)
169
		return false;
170

171
	info.name = name_utf8;
172
	g_free(name_utf8);
173 174
	info.mtime = st.st_mtime;
	return true;
175 176
}

177
PlaylistVector
178
ListPlaylistFiles(GError **error_r)
179
{
180
	PlaylistVector list;
181

182
	const char *parent_path_fs = spl_map(error_r);
183
	if (parent_path_fs == NULL)
184
		return list;
185

186
	DIR *dir = opendir(parent_path_fs);
187
	if (dir == NULL) {
188
		set_error_errno(error_r);
189
		return list;
190
	}
191

192
	PlaylistInfo info;
193
	struct dirent *ent;
194
	while ((ent = readdir(dir)) != NULL) {
195 196
		if (LoadPlaylistFileInfo(info, parent_path_fs, ent->d_name))
			list.push_back(std::move(info));
197 198 199 200 201 202
	}

	closedir(dir);
	return list;
}

203
static bool
204 205
SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path,
		 GError **error_r)
206
{
207
	assert(utf8path != NULL);
208

209 210
	if (spl_map(error_r) == NULL)
		return false;
211

212
	char *path_fs = spl_map_to_fs(utf8path, error_r);
213
	if (path_fs == NULL)
214
		return false;
215

216
	FILE *file = fopen(path_fs, "w");
217
	g_free(path_fs);
218 219 220 221
	if (file == NULL) {
		playlist_errno(error_r);
		return false;
	}
222

223 224
	for (const auto &uri_utf8 : contents)
		playlist_print_uri(file, uri_utf8.c_str());
225

226
	fclose(file);
227
	return true;
228 229
}

230 231
PlaylistFileContents
LoadPlaylistFile(const char *utf8path, GError **error_r)
232
{
233 234
	PlaylistFileContents contents;

235
	if (spl_map(error_r) == NULL)
236
		return contents;
237

238
	char *path_fs = spl_map_to_fs(utf8path, error_r);
239
	if (path_fs == NULL)
240
		return contents;
241

242 243
	TextFile file(path_fs);
	if (file.HasFailed()) {
244
		playlist_errno(error_r);
245
		return contents;
246
	}
247

248
	char *s;
249
	while ((s = file.ReadLine()) != NULL) {
250
		if (*s == 0 || *s == PLAYLIST_COMMENT)
251
			continue;
252

253
		if (!uri_has_scheme(s)) {
254
			char *path_utf8;
255

256
			path_utf8 = map_fs_to_utf8(s);
257 258 259
			if (path_utf8 == NULL)
				continue;

260
			s = path_utf8;
261 262
		} else
			s = g_strdup(s);
263

264 265
		contents.emplace_back(s);
		if (contents.size() >= playlist_max_length)
266
			break;
267 268
	}

269
	return contents;
270 271
}

272 273 274
bool
spl_move_index(const char *utf8path, unsigned src, unsigned dest,
	       GError **error_r)
275
{
276 277 278
	if (src == dest)
		/* this doesn't check whether the playlist exists, but
		   what the hell.. */
279
		return true;
280

281 282 283 284
	GError *error = nullptr;
	auto contents = LoadPlaylistFile(utf8path, &error);
	if (contents.empty() && error != nullptr) {
		g_propagate_error(error_r, error);
285
		return false;
286
	}
287

288
	if (src >= contents.size() || dest >= contents.size()) {
289 290 291 292
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_BAD_RANGE,
				    "Bad range");
		return false;
293 294
	}

295 296 297
	const auto src_i = std::next(contents.begin(), src);
	auto value = std::move(*src_i);
	contents.erase(src_i);
298

299 300
	const auto dest_i = std::next(contents.begin(), dest);
	contents.insert(dest_i, std::move(value));
301

302
	bool result = SavePlaylistFile(contents, utf8path, error_r);
303 304

	idle_add(IDLE_STORED_PLAYLIST);
305
	return result;
306 307
}

308 309
bool
spl_clear(const char *utf8path, GError **error_r)
310 311 312
{
	FILE *file;

313 314
	if (spl_map(error_r) == NULL)
		return false;
315

316
	char *path_fs = spl_map_to_fs(utf8path, error_r);
317
	if (path_fs == NULL)
318
		return false;
319

320
	file = fopen(path_fs, "w");
321
	g_free(path_fs);
322 323 324 325
	if (file == NULL) {
		playlist_errno(error_r);
		return false;
	}
326

327
	fclose(file);
328 329

	idle_add(IDLE_STORED_PLAYLIST);
330
	return true;
331 332
}

333 334
bool
spl_delete(const char *name_utf8, GError **error_r)
335
{
336
	char *path_fs = spl_map_to_fs(name_utf8, error_r);
337
	if (path_fs == NULL)
338
		return false;
339

340
	int ret = unlink(path_fs);
341
	g_free(path_fs);
342 343 344 345
	if (ret < 0) {
		playlist_errno(error_r);
		return false;
	}
346

347
	idle_add(IDLE_STORED_PLAYLIST);
348
	return true;
349 350
}

351 352
bool
spl_remove_index(const char *utf8path, unsigned pos, GError **error_r)
353
{
354 355 356 357
	GError *error = nullptr;
	auto contents = LoadPlaylistFile(utf8path, &error);
	if (contents.empty() && error != nullptr) {
		g_propagate_error(error_r, error);
358
		return false;
359
	}
360

361
	if (pos >= contents.size()) {
362 363 364 365
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_BAD_RANGE,
				    "Bad range");
		return false;
366 367
	}

368
	contents.erase(std::next(contents.begin(), pos));
369

370
	bool result = SavePlaylistFile(contents, utf8path, error_r);
371 372

	idle_add(IDLE_STORED_PLAYLIST);
373
	return result;
374 375
}

376 377
bool
spl_append_song(const char *utf8path, struct song *song, GError **error_r)
378 379 380
{
	FILE *file;

381 382
	if (spl_map(error_r) == NULL)
		return false;
383

384
	char *path_fs = spl_map_to_fs(utf8path, error_r);
385
	if (path_fs == NULL)
386
		return false;
387

388
	file = fopen(path_fs, "a");
389
	g_free(path_fs);
390 391 392 393
	if (file == NULL) {
		playlist_errno(error_r);
		return false;
	}
394

395
	struct stat st;
396
	if (fstat(fileno(file), &st) < 0) {
397
		playlist_errno(error_r);
398
		fclose(file);
399
		return false;
400
	}
401

402
	if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) {
403
		fclose(file);
404 405 406 407
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_TOO_LARGE,
				    "Stored playlist is too large");
		return false;
408
	}
409

410
	playlist_print_song(file, song);
411

412
	fclose(file);
413 414

	idle_add(IDLE_STORED_PLAYLIST);
415
	return true;
416 417
}

418 419
bool
spl_append_uri(const char *url, const char *utf8file, GError **error_r)
420
{
421
	if (uri_has_scheme(url)) {
422
		struct song *song = song_remote_new(url);
423
		bool success = spl_append_song(utf8file, song, error_r);
424
		song_free(song);
425
		return success;
426
	} else {
427 428 429 430 431 432
		const Database *db = GetDatabase(error_r);
		if (db == nullptr)
			return false;

		song *song = db->GetSong(url, error_r);
		if (song == nullptr)
433 434
			return false;

435
		bool success = spl_append_song(utf8file, song, error_r);
436
		db->ReturnSong(song);
437
		return success;
438
	}
439 440
}

441 442 443
static bool
spl_rename_internal(const char *from_path_fs, const char *to_path_fs,
		    GError **error_r)
444
{
445 446 447 448 449 450
	if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) {
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_NO_SUCH_LIST,
				    "No such playlist");
		return false;
	}
451

452 453 454 455 456 457
	if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) {
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_LIST_EXISTS,
				    "Playlist exists already");
		return false;
	}
458

459 460 461 462
	if (rename(from_path_fs, to_path_fs) < 0) {
		playlist_errno(error_r);
		return false;
	}
463

464
	idle_add(IDLE_STORED_PLAYLIST);
465
	return true;
466
}
467

468 469
bool
spl_rename(const char *utf8from, const char *utf8to, GError **error_r)
470
{
471 472
	if (spl_map(error_r) == NULL)
		return false;
473

474 475 476
	char *from_path_fs = spl_map_to_fs(utf8from, error_r);
	if (from_path_fs == NULL)
		return false;
477

478 479 480 481 482
	char *to_path_fs = spl_map_to_fs(utf8to, error_r);
	if (to_path_fs == NULL) {
		g_free(from_path_fs);
		return false;
	}
483

484
	bool success = spl_rename_internal(from_path_fs, to_path_fs, error_r);
485 486 487 488

	g_free(from_path_fs);
	g_free(to_path_fs);

489
	return success;
490
}