PlaylistFile.cxx 10.4 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"
35
#include "fs/DirectoryReader.hxx"
Max Kellermann's avatar
Max Kellermann committed
36
#include "util/UriUtil.hxx"
37

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

46 47
static const char PLAYLIST_COMMENT = '#';

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

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

81
static const Path &
82 83
spl_map(GError **error_r)
{
84 85
	const Path &path_fs = map_spl_path();
	if (path_fs.IsNull())
86 87 88
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_DISABLED,
				    "Stored playlists are disabled");
89
	return path_fs;
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
spl_map_to_fs(const char *name_utf8, GError **error_r)
{
108
	if (spl_map(error_r).IsNull() || !spl_check_name(name_utf8, error_r))
109
		return Path::Null();
110

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

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

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

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

153
	Path path_fs = Path::Build(parent_path_fs, name_fs);
154
	struct stat st;
155
	if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode))
156
		return false;
157

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

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

170
PlaylistVector
171
ListPlaylistFiles(GError **error_r)
172
{
173
	PlaylistVector list;
174

175 176
	const Path &parent_path_fs = spl_map(error_r);
	if (parent_path_fs.IsNull())
177
		return list;
178

179 180
	DirectoryReader reader(parent_path_fs);
	if (reader.HasFailed()) {
181
		set_error_errno(error_r);
182
		return list;
183
	}
184

185
	PlaylistInfo info;
186 187 188
	while (reader.ReadEntry()) {
		const Path entry = reader.GetEntry();
		if (LoadPlaylistFileInfo(info, parent_path_fs, entry))
189
			list.push_back(std::move(info));
190 191 192 193 194
	}

	return list;
}

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

201
	if (spl_map(error_r).IsNull())
202
		return false;
203

204 205
	const Path path_fs = spl_map_to_fs(utf8path, error_r);
	if (path_fs.IsNull())
206
		return false;
207

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

214 215
	for (const auto &uri_utf8 : contents)
		playlist_print_uri(file, uri_utf8.c_str());
216

217
	fclose(file);
218
	return true;
219 220
}

221 222
PlaylistFileContents
LoadPlaylistFile(const char *utf8path, GError **error_r)
223
{
224 225
	PlaylistFileContents contents;

226
	if (spl_map(error_r).IsNull())
227
		return contents;
228

229 230
	const Path path_fs = spl_map_to_fs(utf8path, error_r);
	if (path_fs.IsNull())
231
		return contents;
232

233 234
	TextFile file(path_fs);
	if (file.HasFailed()) {
235
		playlist_errno(error_r);
236
		return contents;
237
	}
238

239
	char *s;
240
	while ((s = file.ReadLine()) != NULL) {
241
		if (*s == 0 || *s == PLAYLIST_COMMENT)
242
			continue;
243

244
		if (!uri_has_scheme(s)) {
245
			char *path_utf8;
246

247
			path_utf8 = map_fs_to_utf8(s);
248 249 250
			if (path_utf8 == NULL)
				continue;

251
			s = path_utf8;
252 253
		} else
			s = g_strdup(s);
254

255 256
		contents.emplace_back(s);
		if (contents.size() >= playlist_max_length)
257
			break;
258 259
	}

260
	return contents;
261 262
}

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

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

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

286 287 288
	const auto src_i = std::next(contents.begin(), src);
	auto value = std::move(*src_i);
	contents.erase(src_i);
289

290 291
	const auto dest_i = std::next(contents.begin(), dest);
	contents.insert(dest_i, std::move(value));
292

293
	bool result = SavePlaylistFile(contents, utf8path, error_r);
294 295

	idle_add(IDLE_STORED_PLAYLIST);
296
	return result;
297 298
}

299 300
bool
spl_clear(const char *utf8path, GError **error_r)
301
{
302
	if (spl_map(error_r).IsNull())
303
		return false;
304

305 306
	const Path path_fs = spl_map_to_fs(utf8path, error_r);
	if (path_fs.IsNull())
307
		return false;
308

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

315
	fclose(file);
316 317

	idle_add(IDLE_STORED_PLAYLIST);
318
	return true;
319 320
}

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

328
	if (!RemoveFile(path_fs)) {
329 330 331
		playlist_errno(error_r);
		return false;
	}
332

333
	idle_add(IDLE_STORED_PLAYLIST);
334
	return true;
335 336
}

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

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

354
	contents.erase(std::next(contents.begin(), pos));
355

356
	bool result = SavePlaylistFile(contents, utf8path, error_r);
357 358

	idle_add(IDLE_STORED_PLAYLIST);
359
	return result;
360 361
}

362 363
bool
spl_append_song(const char *utf8path, struct song *song, GError **error_r)
364
{
365
	if (spl_map(error_r).IsNull())
366
		return false;
367

368 369
	const Path path_fs = spl_map_to_fs(utf8path, error_r);
	if (path_fs.IsNull())
370
		return false;
371

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

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

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

393
	playlist_print_song(file, song);
394

395
	fclose(file);
396 397

	idle_add(IDLE_STORED_PLAYLIST);
398
	return true;
399 400
}

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

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

418
		bool success = spl_append_song(utf8file, song, error_r);
419
		db->ReturnSong(song);
420
		return success;
421
	}
422 423
}

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

435
	if (FileExists(to_path_fs)) {
436 437 438 439 440
		g_set_error_literal(error_r, playlist_quark(),
				    PLAYLIST_RESULT_LIST_EXISTS,
				    "Playlist exists already");
		return false;
	}
441

442
	if (!RenameFile(from_path_fs, to_path_fs)) {
443 444 445
		playlist_errno(error_r);
		return false;
	}
446

447
	idle_add(IDLE_STORED_PLAYLIST);
448
	return true;
449
}
450

451 452
bool
spl_rename(const char *utf8from, const char *utf8to, GError **error_r)
453
{
454
	if (spl_map(error_r).IsNull())
455
		return false;
456

457 458
	Path from_path_fs = spl_map_to_fs(utf8from, error_r);
	if (from_path_fs.IsNull())
459
		return false;
460

461 462
	Path to_path_fs = spl_map_to_fs(utf8to, error_r);
	if (to_path_fs.IsNull())
463
		return false;
464

465
	return spl_rename_internal(from_path_fs, to_path_fs, error_r);
466
}