PlaylistFile.cxx 10.3 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"
Max Kellermann's avatar
Max Kellermann committed
32
#include "Idle.hxx"
33
#include "fs/Path.hxx"
34
#include "fs/FileSystem.hxx"
Max Kellermann's avatar
Max Kellermann committed
35
#include "util/UriUtil.hxx"
36

Max Kellermann's avatar
Max Kellermann committed
37 38 39 40 41 42
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
43
#include <errno.h>
44

45 46
static const char PLAYLIST_COMMENT = '#';

47 48 49 50 51 52 53 54 55 56 57 58 59 60
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);
}

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
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;
}

80 81 82
static const char *
spl_map(GError **error_r)
{
83 84
	const Path &path_fs = map_spl_path();
	if (path_fs.IsNull())
85 86 87 88
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_DISABLED,
				    "Stored playlists are disabled");

89
	return path_fs.c_str();
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
}

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

105
static Path
106 107 108 109
spl_map_to_fs(const char *name_utf8, GError **error_r)
{
	if (spl_map(error_r) == NULL ||
	    !spl_check_name(name_utf8, error_r))
110
		return Path::Null();
111

112 113
	Path path_fs = map_spl_utf8_to_fs(name_utf8);
	if (path_fs.IsNull())
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
		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:
135
		set_error_errno(error_r);
136 137 138 139
		break;
	}
}

140
static bool
141
LoadPlaylistFileInfo(PlaylistInfo &info,
142
		     const char *parent_path_fs, const char *name_fs)
143 144 145
{
	size_t name_length = strlen(name_fs);

146
	if (name_length < sizeof(PLAYLIST_FILE_SUFFIX) ||
147
	    memchr(name_fs, '\n', name_length) != NULL)
148
		return false;
149

150
	if (!g_str_has_suffix(name_fs, PLAYLIST_FILE_SUFFIX))
151
		return false;
152

153 154 155
	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
156
	g_free(path_fs);
157
	if (ret < 0 || !S_ISREG(st.st_mode))
158
		return false;
159

160 161
	char *name = g_strndup(name_fs,
			       name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX));
162
	std::string name_utf8 = Path::ToUTF8(name);
163
	g_free(name);
164
	if (name_utf8.empty())
165
		return false;
166

167
	info.name = std::move(name_utf8);
168 169
	info.mtime = st.st_mtime;
	return true;
170 171
}

172
PlaylistVector
173
ListPlaylistFiles(GError **error_r)
174
{
175
	PlaylistVector list;
176

177
	const char *parent_path_fs = spl_map(error_r);
178
	if (parent_path_fs == NULL)
179
		return list;
180

181
	DIR *dir = opendir(parent_path_fs);
182
	if (dir == NULL) {
183
		set_error_errno(error_r);
184
		return list;
185
	}
186

187
	PlaylistInfo info;
188
	struct dirent *ent;
189
	while ((ent = readdir(dir)) != NULL) {
190 191
		if (LoadPlaylistFileInfo(info, parent_path_fs, ent->d_name))
			list.push_back(std::move(info));
192 193 194 195 196 197
	}

	closedir(dir);
	return list;
}

198
static bool
199 200
SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path,
		 GError **error_r)
201
{
202
	assert(utf8path != NULL);
203

204 205
	if (spl_map(error_r) == NULL)
		return false;
206

207 208
	const Path path_fs = spl_map_to_fs(utf8path, error_r);
	if (path_fs.IsNull())
209
		return false;
210

211
	FILE *file = FOpen(path_fs, FOpenMode::WriteText);
212 213 214 215
	if (file == NULL) {
		playlist_errno(error_r);
		return false;
	}
216

217 218
	for (const auto &uri_utf8 : contents)
		playlist_print_uri(file, uri_utf8.c_str());
219

220
	fclose(file);
221
	return true;
222 223
}

224 225
PlaylistFileContents
LoadPlaylistFile(const char *utf8path, GError **error_r)
226
{
227 228
	PlaylistFileContents contents;

229
	if (spl_map(error_r) == NULL)
230
		return contents;
231

232 233
	const Path path_fs = spl_map_to_fs(utf8path, error_r);
	if (path_fs.IsNull())
234
		return contents;
235

236 237
	TextFile file(path_fs);
	if (file.HasFailed()) {
238
		playlist_errno(error_r);
239
		return contents;
240
	}
241

242
	char *s;
243
	while ((s = file.ReadLine()) != NULL) {
244
		if (*s == 0 || *s == PLAYLIST_COMMENT)
245
			continue;
246

247
		if (!uri_has_scheme(s)) {
248
			char *path_utf8;
249

250
			path_utf8 = map_fs_to_utf8(s);
251 252 253
			if (path_utf8 == NULL)
				continue;

254
			s = path_utf8;
255 256
		} else
			s = g_strdup(s);
257

258 259
		contents.emplace_back(s);
		if (contents.size() >= playlist_max_length)
260
			break;
261 262
	}

263
	return contents;
264 265
}

266 267 268
bool
spl_move_index(const char *utf8path, unsigned src, unsigned dest,
	       GError **error_r)
269
{
270 271 272
	if (src == dest)
		/* this doesn't check whether the playlist exists, but
		   what the hell.. */
273
		return true;
274

275 276 277 278
	GError *error = nullptr;
	auto contents = LoadPlaylistFile(utf8path, &error);
	if (contents.empty() && error != nullptr) {
		g_propagate_error(error_r, error);
279
		return false;
280
	}
281

282
	if (src >= contents.size() || dest >= contents.size()) {
283 284 285 286
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_BAD_RANGE,
				    "Bad range");
		return false;
287 288
	}

289 290 291
	const auto src_i = std::next(contents.begin(), src);
	auto value = std::move(*src_i);
	contents.erase(src_i);
292

293 294
	const auto dest_i = std::next(contents.begin(), dest);
	contents.insert(dest_i, std::move(value));
295

296
	bool result = SavePlaylistFile(contents, utf8path, error_r);
297 298

	idle_add(IDLE_STORED_PLAYLIST);
299
	return result;
300 301
}

302 303
bool
spl_clear(const char *utf8path, GError **error_r)
304
{
305 306
	if (spl_map(error_r) == NULL)
		return false;
307

308 309
	const Path path_fs = spl_map_to_fs(utf8path, error_r);
	if (path_fs.IsNull())
310
		return false;
311

312
	FILE *file = FOpen(path_fs, FOpenMode::WriteText);
313 314 315 316
	if (file == NULL) {
		playlist_errno(error_r);
		return false;
	}
317

318
	fclose(file);
319 320

	idle_add(IDLE_STORED_PLAYLIST);
321
	return true;
322 323
}

324 325
bool
spl_delete(const char *name_utf8, GError **error_r)
326
{
327 328
	const Path path_fs = spl_map_to_fs(name_utf8, error_r);
	if (path_fs.IsNull())
329
		return false;
330

331
	if (!RemoveFile(path_fs)) {
332 333 334
		playlist_errno(error_r);
		return false;
	}
335

336
	idle_add(IDLE_STORED_PLAYLIST);
337
	return true;
338 339
}

340 341
bool
spl_remove_index(const char *utf8path, unsigned pos, GError **error_r)
342
{
343 344 345 346
	GError *error = nullptr;
	auto contents = LoadPlaylistFile(utf8path, &error);
	if (contents.empty() && error != nullptr) {
		g_propagate_error(error_r, error);
347
		return false;
348
	}
349

350
	if (pos >= contents.size()) {
351 352 353 354
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_BAD_RANGE,
				    "Bad range");
		return false;
355 356
	}

357
	contents.erase(std::next(contents.begin(), pos));
358

359
	bool result = SavePlaylistFile(contents, utf8path, error_r);
360 361

	idle_add(IDLE_STORED_PLAYLIST);
362
	return result;
363 364
}

365 366
bool
spl_append_song(const char *utf8path, struct song *song, GError **error_r)
367
{
368 369
	if (spl_map(error_r) == NULL)
		return false;
370

371 372
	const Path path_fs = spl_map_to_fs(utf8path, error_r);
	if (path_fs.IsNull())
373
		return false;
374

375
	FILE *file = FOpen(path_fs, FOpenMode::AppendText);
376 377 378 379
	if (file == NULL) {
		playlist_errno(error_r);
		return false;
	}
380

381
	struct stat st;
382
	if (fstat(fileno(file), &st) < 0) {
383
		playlist_errno(error_r);
384
		fclose(file);
385
		return false;
386
	}
387

388
	if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) {
389
		fclose(file);
390 391 392 393
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_TOO_LARGE,
				    "Stored playlist is too large");
		return false;
394
	}
395

396
	playlist_print_song(file, song);
397

398
	fclose(file);
399 400

	idle_add(IDLE_STORED_PLAYLIST);
401
	return true;
402 403
}

404 405
bool
spl_append_uri(const char *url, const char *utf8file, GError **error_r)
406
{
407
	if (uri_has_scheme(url)) {
408
		struct song *song = song_remote_new(url);
409
		bool success = spl_append_song(utf8file, song, error_r);
410
		song_free(song);
411
		return success;
412
	} else {
413 414 415 416 417 418
		const Database *db = GetDatabase(error_r);
		if (db == nullptr)
			return false;

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

421
		bool success = spl_append_song(utf8file, song, error_r);
422
		db->ReturnSong(song);
423
		return success;
424
	}
425 426
}

427
static bool
428
spl_rename_internal(const Path &from_path_fs, const Path &to_path_fs,
429
		    GError **error_r)
430
{
431
	if (!FileExists(from_path_fs)) {
432 433 434 435 436
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_NO_SUCH_LIST,
				    "No such playlist");
		return false;
	}
437

438
	if (FileExists(to_path_fs)) {
439 440 441 442 443
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_LIST_EXISTS,
				    "Playlist exists already");
		return false;
	}
444

445
	if (!RenameFile(from_path_fs, to_path_fs)) {
446 447 448
		playlist_errno(error_r);
		return false;
	}
449

450
	idle_add(IDLE_STORED_PLAYLIST);
451
	return true;
452
}
453

454 455
bool
spl_rename(const char *utf8from, const char *utf8to, GError **error_r)
456
{
457 458
	if (spl_map(error_r) == NULL)
		return false;
459

460 461
	Path from_path_fs = spl_map_to_fs(utf8from, error_r);
	if (from_path_fs.IsNull())
462
		return false;
463

464 465
	Path to_path_fs = spl_map_to_fs(utf8to, error_r);
	if (to_path_fs.IsNull())
466
		return false;
467

468
	return spl_rename_internal(from_path_fs, to_path_fs, error_r);
469
}