ProxyDatabasePlugin.cxx 26.4 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * 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.
 *
 * 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.
 */

#include "ProxyDatabasePlugin.hxx"
21
#include "db/Interface.hxx"
Max Kellermann's avatar
Max Kellermann committed
22 23 24
#include "db/DatabasePlugin.hxx"
#include "db/DatabaseListener.hxx"
#include "db/Selection.hxx"
25
#include "db/VHelper.hxx"
Max Kellermann's avatar
Max Kellermann committed
26
#include "db/DatabaseError.hxx"
27
#include "db/PlaylistInfo.hxx"
Max Kellermann's avatar
Max Kellermann committed
28
#include "db/LightDirectory.hxx"
29
#include "song/LightSong.hxx"
30
#include "db/Stats.hxx"
31
#include "song/Filter.hxx"
32 33 34
#include "song/UriSongFilter.hxx"
#include "song/BaseSongFilter.hxx"
#include "song/TagSongFilter.hxx"
35
#include "util/Compiler.h"
36
#include "config/Block.hxx"
37
#include "tag/Builder.hxx"
38
#include "tag/Tag.hxx"
Max Kellermann's avatar
Max Kellermann committed
39
#include "tag/ParseName.hxx"
40 41
#include "util/ConstBuffer.hxx"
#include "util/RecursiveMap.hxx"
42
#include "util/ScopeExit.hxx"
43
#include "util/RuntimeError.hxx"
44
#include "protocol/Ack.hxx"
45 46 47
#include "event/SocketMonitor.hxx"
#include "event/IdleMonitor.hxx"
#include "Log.hxx"
48 49

#include <mpd/client.h>
50
#include <mpd/async.h>
51 52 53

#include <cassert>
#include <list>
54 55
#include <string>
#include <utility>
56

57
class LibmpdclientError final : public std::runtime_error {
58 59 60 61 62 63
	enum mpd_error code;

public:
	LibmpdclientError(enum mpd_error _code, const char *_msg)
		:std::runtime_error(_msg), code(_code) {}

64
	[[nodiscard]] enum mpd_error GetCode() const {
65 66 67 68
		return code;
	}
};

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
class ProxySong : public LightSong {
	Tag tag2;

public:
	explicit ProxySong(const mpd_song *song);
};

class AllocatedProxySong : public ProxySong {
	mpd_song *const song;

public:
	explicit AllocatedProxySong(mpd_song *_song)
		:ProxySong(_song), song(_song) {}

	~AllocatedProxySong() {
		mpd_song_free(song);
	}
};

88 89 90
class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
	DatabaseListener &listener;

91
	const std::string host;
92
	const std::string password;
93 94
	const unsigned port;
	const bool keepalive;
95 96 97

	struct mpd_connection *connection;

98
	/* this is mutable because GetStats() must be "const" */
99
	mutable std::chrono::system_clock::time_point update_stamp;
100

101 102 103 104 105 106 107 108 109 110 111 112
	/**
	 * The libmpdclient idle mask that was removed from the other
	 * MPD.  This will be handled by the next OnIdle() call.
	 */
	unsigned idle_received;

	/**
	 * Is the #connection currently "idle"?  That is, did we send
	 * the "idle" command to it?
	 */
	bool is_idle;

113
public:
114 115
	ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
		      const ConfigBlock &block);
116

117 118 119 120
	static DatabasePtr Create(EventLoop &main_event_loop,
				  EventLoop &io_event_loop,
				  DatabaseListener &listener,
				  const ConfigBlock &block);
121

122
	void Open() override;
123
	void Close() noexcept override;
124
	const LightSong *GetSong(std::string_view uri_utf8) const override;
125
	void ReturnSong(const LightSong *song) const noexcept override;
126

127 128 129 130
	void Visit(const DatabaseSelection &selection,
		   VisitDirectory visit_directory,
		   VisitSong visit_song,
		   VisitPlaylist visit_playlist) const override;
131

132 133
	RecursiveMap<std::string> CollectUniqueTags(const DatabaseSelection &selection,
						    ConstBuffer<TagType> tag_types) const override;
134

135
	DatabaseStats GetStats(const DatabaseSelection &selection) const override;
136

137
	unsigned Update(const char *uri_utf8, bool discard) override;
138

Max Kellermann's avatar
Max Kellermann committed
139
	std::chrono::system_clock::time_point GetUpdateStamp() const noexcept override {
140
		return update_stamp;
141 142
	}

143
private:
144 145 146
	void Connect();
	void CheckConnection();
	void EnsureConnected();
147

148
	void Disconnect() noexcept;
149 150

	/* virtual methods from SocketMonitor */
151
	bool OnSocketReady(unsigned flags) noexcept override;
152 153

	/* virtual methods from IdleMonitor */
154
	void OnIdle() noexcept override;
155 156
};

157
static constexpr struct {
158
	TagType d;
159 160 161 162 163 164 165 166 167 168
	enum mpd_tag_type s;
} tag_table[] = {
	{ TAG_ARTIST, MPD_TAG_ARTIST },
	{ TAG_ALBUM, MPD_TAG_ALBUM },
	{ TAG_ALBUM_ARTIST, MPD_TAG_ALBUM_ARTIST },
	{ TAG_TITLE, MPD_TAG_TITLE },
	{ TAG_TRACK, MPD_TAG_TRACK },
	{ TAG_NAME, MPD_TAG_NAME },
	{ TAG_GENRE, MPD_TAG_GENRE },
	{ TAG_DATE, MPD_TAG_DATE },
169 170 171
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	{ TAG_ORIGINAL_DATE, MPD_TAG_ORIGINAL_DATE },
#endif
172 173 174 175 176 177 178 179 180
	{ TAG_COMPOSER, MPD_TAG_COMPOSER },
	{ TAG_PERFORMER, MPD_TAG_PERFORMER },
	{ TAG_COMMENT, MPD_TAG_COMMENT },
	{ TAG_DISC, MPD_TAG_DISC },
	{ TAG_MUSICBRAINZ_ARTISTID, MPD_TAG_MUSICBRAINZ_ARTISTID },
	{ TAG_MUSICBRAINZ_ALBUMID, MPD_TAG_MUSICBRAINZ_ALBUMID },
	{ TAG_MUSICBRAINZ_ALBUMARTISTID,
	  MPD_TAG_MUSICBRAINZ_ALBUMARTISTID },
	{ TAG_MUSICBRAINZ_TRACKID, MPD_TAG_MUSICBRAINZ_TRACKID },
181 182 183
#if LIBMPDCLIENT_CHECK_VERSION(2,10,0)
	{ TAG_MUSICBRAINZ_RELEASETRACKID,
	  MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
184 185 186 187 188 189 190
#endif
#if LIBMPDCLIENT_CHECK_VERSION(2,11,0)
	{ TAG_ARTIST_SORT, MPD_TAG_ARTIST_SORT },
	{ TAG_ALBUM_ARTIST_SORT, MPD_TAG_ALBUM_ARTIST_SORT },
#endif
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	{ TAG_ALBUM_SORT, MPD_TAG_ALBUM_SORT },
191
#endif
192 193 194
	{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
};

195 196
static void
Copy(TagBuilder &tag, TagType d_tag,
197
     const struct mpd_song *song, enum mpd_tag_type s_tag) noexcept
198 199 200 201 202 203 204 205 206 207 208 209
{

	for (unsigned i = 0;; ++i) {
		const char *value = mpd_song_get_tag(song, s_tag, i);
		if (value == nullptr)
			break;

		tag.AddItem(d_tag, value);
	}
}

ProxySong::ProxySong(const mpd_song *song)
210
	:LightSong(mpd_song_get_uri(song), tag2)
211
{
212
	const auto _mtime = mpd_song_get_last_modified(song);
213 214
	if (_mtime > 0)
		mtime = std::chrono::system_clock::from_time_t(_mtime);
215

216 217
	start_time = SongTime::FromS(mpd_song_get_start(song));
	end_time = SongTime::FromS(mpd_song_get_end(song));
218

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
#if LIBMPDCLIENT_CHECK_VERSION(2,15,0)
	const auto *af = mpd_song_get_audio_format(song);
	if (af != nullptr) {
		if (audio_valid_sample_rate(af->sample_rate))
			audio_format.sample_rate = af->sample_rate;


		switch (af->bits) {
		case MPD_SAMPLE_FORMAT_FLOAT:
			audio_format.format = SampleFormat::FLOAT;
			break;

		case MPD_SAMPLE_FORMAT_DSD:
			audio_format.format = SampleFormat::DSD;
			break;

		case 8:
			audio_format.format = SampleFormat::S8;
			break;

		case 16:
			audio_format.format = SampleFormat::S16;
			break;

		case 24:
			audio_format.format = SampleFormat::S24_P32;
			break;

		case 32:
			audio_format.format = SampleFormat::S32;
			break;
		}

		if (audio_valid_channel_count(af->channels))
			audio_format.channels = af->channels;
	}
#endif

257
	TagBuilder tag_builder;
258 259 260 261

	const unsigned duration = mpd_song_get_duration(song);
	if (duration > 0)
		tag_builder.SetDuration(SignedSongTime::FromS(duration));
262 263 264 265 266 267 268

	for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
		Copy(tag_builder, i->d, song, i->s);

	tag_builder.Commit(tag2);
}

269
gcc_const
270
static enum mpd_tag_type
271
Convert(TagType tag_type) noexcept
272
{
273
	for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
274 275 276 277 278 279
		if (i->d == tag_type)
			return i->s;

	return MPD_TAG_COUNT;
}

280
[[noreturn]]
281
static void
282
ThrowError(struct mpd_connection *connection)
283
{
284
	const auto code = mpd_connection_get_error(connection);
285

286 287 288 289
	AtScopeExit(connection) {
		mpd_connection_clear_error(connection);
	};

290 291 292 293 294
	if (code == MPD_ERROR_SERVER) {
		/* libmpdclient's "enum mpd_server_error" is the same
		   as our "enum ack" */
		const auto server_error =
			mpd_connection_get_server_error(connection);
295 296
		throw ProtocolError((enum ack)server_error,
				    mpd_connection_get_error_message(connection));
297
	} else {
298 299
		throw LibmpdclientError(code,
					mpd_connection_get_error_message(connection));
300
	}
301
}
302

303 304
static void
CheckError(struct mpd_connection *connection)
305 306
{
	const auto code = mpd_connection_get_error(connection);
307 308
	if (code != MPD_ERROR_SUCCESS)
		ThrowError(connection);
309 310
}

311
static bool
312
SendConstraints(mpd_connection *connection, const ISongFilter &f)
313
{
314 315 316
	if (auto t = dynamic_cast<const TagSongFilter *>(&f)) {
		if (t->IsNegated())
			// TODO implement
317 318
			return true;

319 320 321 322
		if (t->GetTagType() == TAG_NUM_OF_ITEM_TYPES)
			return mpd_search_add_any_tag_constraint(connection,
								 MPD_OPERATOR_DEFAULT,
								 t->GetValue().c_str());
323

324
		const auto tag = Convert(t->GetTagType());
325 326 327 328 329 330
		if (tag == MPD_TAG_COUNT)
			return true;

		return mpd_search_add_tag_constraint(connection,
						     MPD_OPERATOR_DEFAULT,
						     tag,
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
						     t->GetValue().c_str());
	} else if (auto u = dynamic_cast<const UriSongFilter *>(&f)) {
		if (u->IsNegated())
			// TODO implement
			return true;

		return mpd_search_add_uri_constraint(connection,
						     MPD_OPERATOR_DEFAULT,
						     u->GetValue().c_str());
	} else if (auto b = dynamic_cast<const BaseSongFilter *>(&f)) {
		return mpd_search_add_base_constraint(connection,
						      MPD_OPERATOR_DEFAULT,
						      b->GetValue());
	} else
		return true;
346 347 348 349 350
}

static bool
SendConstraints(mpd_connection *connection, const SongFilter &filter)
{
351 352 353 354 355 356 357 358
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
	if (mpd_connection_cmp_server_version(connection, 0, 21, 0) >= 0)
		/* with MPD 0.21 (and libmpdclient 2.15), we can pass
		   arbitrary filters as expression */
		return mpd_search_add_expression(connection,
						 filter.ToExpression().c_str());
#endif

359
	for (const auto &i : filter.GetItems())
360
		if (!SendConstraints(connection, *i))
361 362 363 364 365 366 367 368 369
			return false;

	return true;
}

static bool
SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
{
	if (!selection.uri.empty() &&
370 371 372 373
	    !mpd_search_add_base_constraint(connection,
					    MPD_OPERATOR_DEFAULT,
					    selection.uri.c_str()))
		return false;
374 375 376 377 378

	if (selection.filter != nullptr &&
	    !SendConstraints(connection, *selection.filter))
		return false;

379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
#if LIBMPDCLIENT_CHECK_VERSION(2, 11, 0)
	if (selection.sort != TAG_NUM_OF_ITEM_TYPES &&
	    mpd_connection_cmp_server_version(connection, 0, 21, 0) >= 0) {
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
		if (selection.sort == SORT_TAG_LAST_MODIFIED) {
			if (!mpd_search_add_sort_name(connection, "Last-Modified",
						      selection.descending))
				return false;
		} else {
#endif
			const auto sort = Convert(selection.sort);
			/* if this is an unsupported tag, the sort
			   will be done later by class
			   DatabaseVisitorHelper */
			if (sort != MPD_TAG_COUNT &&
			    !mpd_search_add_sort_tag(connection, sort,
						     selection.descending))
				return false;
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
		}
#endif
	}
#endif

#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
	if (selection.window != RangeArg::All() &&
	    mpd_connection_cmp_server_version(connection, 0, 20, 0) >= 0 &&
	    !mpd_search_add_window(connection, selection.window.start,
				   selection.window.end))
		return false;
#endif

411 412 413
	return true;
}

414
static bool
415
SendGroup(mpd_connection *connection, TagType group)
416
{
417
	assert(group != TAG_NUM_OF_ITEM_TYPES);
418

419 420 421 422
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	const auto tag = Convert(group);
	if (tag == MPD_TAG_COUNT)
		throw std::runtime_error("Unsupported tag");
423

424
	return mpd_search_add_group_tag(connection, tag);
425 426 427
#else
	(void)connection;

428
	throw std::runtime_error("Grouping requires libmpdclient 2.12");
429 430 431
#endif
}

432 433 434 435 436 437 438 439 440 441 442 443 444
static bool
SendGroup(mpd_connection *connection, ConstBuffer<TagType> group)
{
	while (!group.empty()) {
		if (!SendGroup(connection, group.back()))
		    return false;

		group.pop_back();
	}

	return true;
}

445 446 447 448 449 450
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
			     const ConfigBlock &block)
	:Database(proxy_db_plugin),
	 SocketMonitor(_loop), IdleMonitor(_loop),
	 listener(_listener),
	 host(block.GetBlockValue("host", "")),
451
	 password(block.GetBlockValue("password", "")),
452
	 port(block.GetBlockValue("port", 0U)),
453
	 keepalive(block.GetBlockValue("keepalive", false))
454 455 456
{
}

457
DatabasePtr
458 459
ProxyDatabase::Create(EventLoop &loop, EventLoop &,
		      DatabaseListener &listener,
460
		      const ConfigBlock &block)
461
{
462
	return std::make_unique<ProxyDatabase>(loop, listener, block);
463 464
}

465 466
void
ProxyDatabase::Open()
467
{
468
	update_stamp = std::chrono::system_clock::time_point::min();
469 470 471

	try {
		Connect();
472
	} catch (...) {
473 474
		/* this error is non-fatal, because this plugin will
		   attempt to reconnect again automatically */
475
		LogError(std::current_exception());
476
	}
477 478 479
}

void
480
ProxyDatabase::Close() noexcept
481 482
{
	if (connection != nullptr)
483
		Disconnect();
484 485
}

486 487
void
ProxyDatabase::Connect()
488 489 490
{
	const char *_host = host.empty() ? nullptr : host.c_str();
	connection = mpd_connection_new(_host, port, 0);
491 492
	if (connection == nullptr)
		throw LibmpdclientError(MPD_ERROR_OOM, "Out of memory");
493

494 495
	try {
		CheckError(connection);
496

497 498 499 500 501 502 503
		if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0) {
			const unsigned *version =
				mpd_connection_get_server_version(connection);
			throw FormatRuntimeError("Connect to MPD %u.%u.%u, but this "
						 "plugin requires at least version 0.19",
						 version[0], version[1], version[2]);
		}
504

505 506 507
		if (!password.empty() &&
		    !mpd_run_password(connection, password.c_str()))
			ThrowError(connection);
508
	} catch (...) {
509 510 511
		mpd_connection_free(connection);
		connection = nullptr;

512 513 514 515
		std::throw_with_nested(host.empty()
				       ? std::runtime_error("Failed to connect to remote MPD")
				       : FormatRuntimeError("Failed to connect to remote MPD '%s'",
							    host.c_str()));
516 517
	}

518 519
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
	mpd_connection_set_keepalive(connection, keepalive);
520 521 522
#else
	// suppress -Wunused-private-field
	(void)keepalive;
523 524
#endif

525
	idle_received = ~0U;
526 527
	is_idle = false;

528
	SocketMonitor::Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection))));
529
	IdleMonitor::Schedule();
530 531
}

532 533
void
ProxyDatabase::CheckConnection()
534 535 536
{
	assert(connection != nullptr);

537
	if (!mpd_connection_clear_error(connection)) {
538
		Disconnect();
539 540
		Connect();
		return;
541 542
	}

543 544
	if (is_idle) {
		unsigned idle = mpd_run_noidle(connection);
545 546 547 548 549 550 551
		if (idle == 0) {
			try {
				CheckError(connection);
			} catch (...) {
				Disconnect();
				throw;
			}
552 553 554 555 556 557
		}

		idle_received |= idle;
		is_idle = false;
		IdleMonitor::Schedule();
	}
558 559
}

560 561
void
ProxyDatabase::EnsureConnected()
562
{
563 564 565 566
	if (connection != nullptr)
		CheckConnection();
	else
		Connect();
567 568
}

569
void
570
ProxyDatabase::Disconnect() noexcept
571 572 573
{
	assert(connection != nullptr);

574 575 576
	IdleMonitor::Cancel();
	SocketMonitor::Steal();

577 578 579 580
	mpd_connection_free(connection);
	connection = nullptr;
}

581
bool
Rosen Penev's avatar
Rosen Penev committed
582
ProxyDatabase::OnSocketReady([[maybe_unused]] unsigned flags) noexcept
583 584 585 586 587 588
{
	assert(connection != nullptr);

	if (!is_idle) {
		// TODO: can this happen?
		IdleMonitor::Schedule();
589 590
		SocketMonitor::Cancel();
		return true;
591 592
	}

Max Kellermann's avatar
Max Kellermann committed
593
	auto idle = (unsigned)mpd_recv_idle(connection, false);
594
	if (idle == 0) {
595 596
		try {
			CheckError(connection);
597 598
		} catch (...) {
			LogError(std::current_exception());
599 600 601 602 603 604 605 606 607
			Disconnect();
			return false;
		}
	}

	/* let OnIdle() handle this */
	idle_received |= idle;
	is_idle = false;
	IdleMonitor::Schedule();
608 609
	SocketMonitor::Cancel();
	return true;
610 611 612
}

void
613
ProxyDatabase::OnIdle() noexcept
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
{
	assert(connection != nullptr);

	/* handle previous idle events */

	if (idle_received & MPD_IDLE_DATABASE)
		listener.OnDatabaseModified();

	idle_received = 0;

	/* send a new idle command to the other MPD */

	if (is_idle)
		// TODO: can this happen?
		return;

	if (!mpd_send_idle_mask(connection, MPD_IDLE_DATABASE)) {
631 632
		try {
			ThrowError(connection);
633 634
		} catch (...) {
			LogError(std::current_exception());
635
		}
636 637 638 639 640 641 642 643 644 645 646

		SocketMonitor::Steal();
		mpd_connection_free(connection);
		connection = nullptr;
		return;
	}

	is_idle = true;
	SocketMonitor::ScheduleRead();
}

647
const LightSong *
648
ProxyDatabase::GetSong(std::string_view uri) const
649
{
650
	// TODO: eliminate the const_cast
651
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
652

653
	if (!mpd_send_list_meta(connection, std::string(uri).c_str()))
654
		ThrowError(connection);
655 656

	struct mpd_song *song = mpd_recv_song(connection);
657
	if (!mpd_response_finish(connection)) {
658 659
		if (song != nullptr)
			mpd_song_free(song);
660
		ThrowError(connection);
661 662
	}

663 664 665
	if (song == nullptr)
		throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
				    "No such song");
666

667
	return new AllocatedProxySong(song);
668 669
}

670
void
671
ProxyDatabase::ReturnSong(const LightSong *_song) const noexcept
672
{
673
	assert(_song != nullptr);
674

Max Kellermann's avatar
Max Kellermann committed
675
	auto *song = (AllocatedProxySong *)
676 677
		const_cast<LightSong *>(_song);
	delete song;
678 679
}

680
static void
681
Visit(struct mpd_connection *connection, const char *uri,
682
      bool recursive, const SongFilter *filter,
683 684
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist);
685

686
static void
687
Visit(struct mpd_connection *connection,
688 689
      bool recursive, const SongFilter *filter,
      const struct mpd_directory *directory,
690 691
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist)
692
{
693
	const char *path = mpd_directory_get_path(directory);
694 695 696 697 698 699

	std::chrono::system_clock::time_point mtime =
		std::chrono::system_clock::time_point::min();
	time_t _mtime = mpd_directory_get_last_modified(directory);
	if (_mtime > 0)
		mtime = std::chrono::system_clock::from_time_t(_mtime);
700

701 702
	if (visit_directory)
		visit_directory(LightDirectory(path, mtime));
703

704 705
	if (recursive)
		Visit(connection, path, recursive, filter,
706
		      visit_directory, visit_song, visit_playlist);
707 708
}

709 710
gcc_pure
static bool
711
Match(const SongFilter *filter, const LightSong &song) noexcept
712 713 714 715
{
	return filter == nullptr || filter->Match(song);
}

716
static void
717
Visit(const SongFilter *filter,
718
      const mpd_song *_song,
719
      const VisitSong& visit_song)
720 721
{
	if (!visit_song)
722
		return;
723

724
	const ProxySong song(_song);
725 726
	if (Match(filter, song))
		visit_song(song);
727 728
}

729
static void
730
Visit(const struct mpd_playlist *playlist,
731
      const VisitPlaylist& visit_playlist)
732 733
{
	if (!visit_playlist)
734
		return;
735

736 737
	time_t mtime = mpd_playlist_get_last_modified(playlist);

738
	PlaylistInfo p(mpd_playlist_get_path(playlist),
739 740 741
		       mtime > 0
		       ? std::chrono::system_clock::from_time_t(mtime)
		       : std::chrono::system_clock::time_point::min());
742

743
	visit_playlist(p, LightDirectory::Root());
744 745
}

746 747 748 749
class ProxyEntity {
	struct mpd_entity *entity;

public:
750
	explicit ProxyEntity(struct mpd_entity *_entity) noexcept
751 752 753 754
		:entity(_entity) {}

	ProxyEntity(const ProxyEntity &other) = delete;

755
	ProxyEntity(ProxyEntity &&other) noexcept
756 757 758 759
		:entity(other.entity) {
		other.entity = nullptr;
	}

760
	~ProxyEntity() noexcept {
761 762 763 764 765 766
		if (entity != nullptr)
			mpd_entity_free(entity);
	}

	ProxyEntity &operator=(const ProxyEntity &other) = delete;

767
	operator const struct mpd_entity *() const noexcept {
768 769 770 771 772
		return entity;
	}
};

static std::list<ProxyEntity>
773
ReceiveEntities(struct mpd_connection *connection) noexcept
774
{
775
	std::list<ProxyEntity> entities;
776
	struct mpd_entity *entity;
777
	while ((entity = mpd_recv_entity(connection)) != nullptr)
778
		entities.emplace_back(entity);
779 780 781 782 783

	mpd_response_finish(connection);
	return entities;
}

784
static void
785
Visit(struct mpd_connection *connection, const char *uri,
786
      bool recursive, const SongFilter *filter,
787 788
      const VisitDirectory& visit_directory, const VisitSong& visit_song,
      const VisitPlaylist& visit_playlist)
789
{
790 791
	if (!mpd_send_list_meta(connection, uri))
		ThrowError(connection);
792

793
	std::list<ProxyEntity> entities(ReceiveEntities(connection));
794
	CheckError(connection);
795

796
	for (const auto &entity : entities) {
797 798 799 800 801
		switch (mpd_entity_get_type(entity)) {
		case MPD_ENTITY_TYPE_UNKNOWN:
			break;

		case MPD_ENTITY_TYPE_DIRECTORY:
802 803 804
			Visit(connection, recursive, filter,
			      mpd_entity_get_directory(entity),
			      visit_directory, visit_song, visit_playlist);
805 806 807
			break;

		case MPD_ENTITY_TYPE_SONG:
808
			Visit(filter, mpd_entity_get_song(entity), visit_song);
809 810 811
			break;

		case MPD_ENTITY_TYPE_PLAYLIST:
812 813
			Visit(mpd_entity_get_playlist(entity),
			      visit_playlist);
814 815 816 817 818
			break;
		}
	}
}

819
static void
820 821
SearchSongs(struct mpd_connection *connection,
	    const DatabaseSelection &selection,
822
	    const VisitSong& visit_song)
823
try {
824 825 826 827 828 829 830 831
	assert(selection.recursive);
	assert(visit_song);

	const bool exact = selection.filter == nullptr ||
		!selection.filter->HasFoldCase();

	if (!mpd_search_db_songs(connection, exact) ||
	    !SendConstraints(connection, selection) ||
832 833
	    !mpd_search_commit(connection))
		ThrowError(connection);
834

835
	while (auto *song = mpd_recv_song(connection)) {
836
		AllocatedProxySong song2(song);
837

838 839 840 841 842 843 844 845
		if (Match(selection.filter, song2)) {
			try {
				visit_song(song2);
			} catch (...) {
				mpd_response_finish(connection);
				throw;
			}
		}
846 847
	}

848
	if (!mpd_response_finish(connection))
849
		ThrowError(connection);
850 851 852 853 854
} catch (...) {
	if (connection != nullptr)
		mpd_search_cancel(connection);

	throw;
855 856
}

857 858 859 860 861 862 863 864 865 866 867 868 869 870 871
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)

gcc_pure
static bool
IsFilterSupported(const ISongFilter &f) noexcept
{
	if (auto t = dynamic_cast<const TagSongFilter *>(&f)) {
		if (t->IsNegated())
			// TODO implement
			return false;

		if (t->GetTagType() == TAG_NUM_OF_ITEM_TYPES)
			return true;

		const auto tag = Convert(t->GetTagType());
872
		return tag != MPD_TAG_COUNT;
873 874 875 876 877 878 879 880 881 882 883 884 885 886
	} else if (auto u = dynamic_cast<const UriSongFilter *>(&f)) {
		if (u->IsNegated())
			// TODO implement
			return false;

		return false;
	} else if (dynamic_cast<const BaseSongFilter *>(&f)) {
		return true;
	} else
		return false;
}

gcc_pure
static bool
887 888
IsFilterFullySupported(const SongFilter &filter,
		       const struct mpd_connection *connection) noexcept
889
{
890 891 892 893 894 895 896 897 898
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
	if (mpd_connection_cmp_server_version(connection, 0, 21, 0) >= 0)
		/* with MPD 0.21 (and libmpdclient 2.15), we can pass
		   arbitrary filters as expression */
		return true;
#else
	(void)connection;
#endif

899 900 901 902 903 904 905 906 907
	for (const auto &i : filter.GetItems())
		if (!IsFilterSupported(*i))
			return false;

	return true;
}

gcc_pure
static bool
908 909
IsFilterFullySupported(const SongFilter *filter,
		       const struct mpd_connection *connection) noexcept
910 911
{
	return filter == nullptr ||
912
		IsFilterFullySupported(*filter, connection);
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
}

#endif

#if LIBMPDCLIENT_CHECK_VERSION(2, 11, 0)

gcc_pure
static bool
IsSortSupported(TagType tag_type,
		const struct mpd_connection *connection) noexcept
{
	if (mpd_connection_cmp_server_version(connection, 0, 21, 0) < 0)
		/* sorting requires MPD 0.21 */
		return false;

	if (tag_type == TagType(SORT_TAG_LAST_MODIFIED)) {
		/* sort "Last-Modified" requires libmpdclient 2.15 for
		   mpd_search_add_sort_name() */
#if LIBMPDCLIENT_CHECK_VERSION(2, 15, 0)
		return true;
#else
		return false;
#endif
	}

	return Convert(tag_type) != MPD_TAG_COUNT;
}

#endif

gcc_pure
944
static DatabaseSelection
945 946
CheckSelection(DatabaseSelection selection,
	       struct mpd_connection *connection) noexcept
947 948 949
{
	selection.uri.clear();
	selection.filter = nullptr;
950 951 952 953 954 955 956 957 958 959 960

#if LIBMPDCLIENT_CHECK_VERSION(2, 11, 0)
	if (selection.sort != TAG_NUM_OF_ITEM_TYPES &&
	    IsSortSupported(selection.sort, connection))
		/* we can forward the "sort" parameter to the other
		   MPD */
		selection.sort = TAG_NUM_OF_ITEM_TYPES;
#endif

#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
	if (selection.window != RangeArg::All() &&
961
	    IsFilterFullySupported(selection.filter, connection))
962 963 964 965 966 967 968
		/* we can forward the "window" parameter to the other
		   MPD */
		selection.window = RangeArg::All();
#else
	(void)connection;
#endif

969 970 971
	return selection;
}

972
void
973 974 975
ProxyDatabase::Visit(const DatabaseSelection &selection,
		     VisitDirectory visit_directory,
		     VisitSong visit_song,
976
		     VisitPlaylist visit_playlist) const
977
{
978
	// TODO: eliminate the const_cast
979
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
980

981 982
	DatabaseVisitorHelper helper(CheckSelection(selection, connection),
				     visit_song);
983

984
	if (!visit_directory && !visit_playlist && selection.recursive &&
985
	    !selection.IsEmpty()) {
986 987
		/* this optimized code path can only be used under
		   certain conditions */
988
		::SearchSongs(connection, selection, visit_song);
989
		helper.Commit();
990
		return;
991
	}
992 993

	/* fall back to recursive walk (slow!) */
994 995 996
	::Visit(connection, selection.uri.c_str(),
		selection.recursive, selection.filter,
		visit_directory, visit_song, visit_playlist);
997 998

	helper.Commit();
999 1000
}

1001
RecursiveMap<std::string>
1002
ProxyDatabase::CollectUniqueTags(const DatabaseSelection &selection,
1003
				 ConstBuffer<TagType> tag_types) const
1004
try {
1005
	// TODO: eliminate the const_cast
1006
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
1007

1008
	enum mpd_tag_type tag_type2 = Convert(tag_types.back());
1009 1010
	if (tag_type2 == MPD_TAG_COUNT)
		throw std::runtime_error("Unsupported tag");
1011

1012 1013 1014
	auto group = tag_types;
	group.pop_back();

1015
	if (!mpd_search_db_tags(connection, tag_type2) ||
1016
	    !SendConstraints(connection, selection) ||
1017
	    !SendGroup(connection, group))
1018
		ThrowError(connection);
1019

1020 1021
	if (!mpd_search_commit(connection))
		ThrowError(connection);
1022

1023 1024 1025
	RecursiveMap<std::string> result;
	std::vector<RecursiveMap<std::string> *> position;
	position.emplace_back(&result);
1026

1027 1028 1029 1030
	while (auto *pair = mpd_recv_pair(connection)) {
		AtScopeExit(this, pair) {
			mpd_return_pair(connection, pair);
		};
1031

1032 1033 1034
		const auto current_type = tag_name_parse_i(pair->name);
		if (current_type == TAG_NUM_OF_ITEM_TYPES)
			continue;
1035

1036 1037 1038 1039
		auto it = std::find(tag_types.begin(), tag_types.end(),
				    current_type);
		if (it == tag_types.end())
			continue;
1040

1041 1042 1043
		size_t i = std::distance(tag_types.begin(), it);
		if (i > position.size())
			continue;
1044

1045 1046
		if (i + 1 < position.size())
			position.resize(i + 1);
1047

1048 1049
		auto &parent = *position[i];
		position.emplace_back(&parent[pair->value]);
1050 1051
	}

1052
	if (!mpd_response_finish(connection))
1053
		ThrowError(connection);
1054 1055

	return result;
1056 1057 1058 1059 1060
} catch (...) {
	if (connection != nullptr)
		mpd_search_cancel(connection);

	throw;
1061 1062
}

1063 1064
DatabaseStats
ProxyDatabase::GetStats(const DatabaseSelection &selection) const
1065 1066 1067 1068
{
	// TODO: match
	(void)selection;

1069
	// TODO: eliminate the const_cast
1070
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
1071

1072 1073
	struct mpd_stats *stats2 =
		mpd_run_stats(connection);
1074 1075
	if (stats2 == nullptr)
		ThrowError(connection);
1076

1077
	update_stamp = std::chrono::system_clock::from_time_t(mpd_stats_get_db_update_time(stats2));
1078

1079
	DatabaseStats stats;
1080
	stats.song_count = mpd_stats_get_number_of_songs(stats2);
1081
	stats.total_duration = std::chrono::seconds(mpd_stats_get_db_play_time(stats2));
1082 1083 1084
	stats.artist_count = mpd_stats_get_number_of_artists(stats2);
	stats.album_count = mpd_stats_get_number_of_albums(stats2);
	mpd_stats_free(stats2);
1085
	return stats;
1086 1087
}

1088
unsigned
1089
ProxyDatabase::Update(const char *uri_utf8, bool discard)
1090
{
1091
	EnsureConnected();
1092 1093 1094 1095 1096

	unsigned id = discard
		? mpd_run_rescan(connection, uri_utf8)
		: mpd_run_update(connection, uri_utf8);
	if (id == 0)
1097
		CheckError(connection);
1098 1099 1100 1101

	return id;
}

1102 1103
const DatabasePlugin proxy_db_plugin = {
	"proxy",
1104
	DatabasePlugin::FLAG_REQUIRE_STORAGE,
1105 1106
	ProxyDatabase::Create,
};