ProxyDatabasePlugin.cxx 22 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 14 15 16 17 18 19 20 21
 * 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 "config.h"
#include "ProxyDatabasePlugin.hxx"
22
#include "db/Interface.hxx"
Max Kellermann's avatar
Max Kellermann committed
23 24 25 26
#include "db/DatabasePlugin.hxx"
#include "db/DatabaseListener.hxx"
#include "db/Selection.hxx"
#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 "Compiler.h"
36
#include "config/Block.hxx"
37
#include "tag/Builder.hxx"
38
#include "tag/Tag.hxx"
39
#include "tag/Mask.hxx"
Max Kellermann's avatar
Max Kellermann committed
40
#include "tag/ParseName.hxx"
41
#include "util/ScopeExit.hxx"
42
#include "util/RuntimeError.hxx"
43
#include "protocol/Ack.hxx"
44 45 46
#include "event/SocketMonitor.hxx"
#include "event/IdleMonitor.hxx"
#include "Log.hxx"
47 48

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

#include <cassert>
#include <string>
#include <list>

55
class LibmpdclientError final : public std::runtime_error {
56 57 58 59 60 61 62 63 64 65 66
	enum mpd_error code;

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

	enum mpd_error GetCode() const {
		return code;
	}
};

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
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);
	}
};

86 87 88
class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
	DatabaseListener &listener;

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

	struct mpd_connection *connection;

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

99 100 101 102 103 104 105 106 107 108 109 110
	/**
	 * 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;

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

115 116 117
	static Database *Create(EventLoop &main_event_loop,
				EventLoop &io_event_loop,
				DatabaseListener &listener,
118
				const ConfigBlock &block);
119

120 121
	void Open() override;
	void Close() override;
122
	const LightSong *GetSong(const char *uri_utf8) const override;
123
	void ReturnSong(const LightSong *song) const override;
124

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

130
	void VisitUniqueTags(const DatabaseSelection &selection,
131
			     TagType tag_type, TagMask group_mask,
132
			     VisitTag visit_tag) const override;
133

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

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

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

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

	void Disconnect();
148 149

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

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

156
static constexpr struct {
157
	TagType d;
158 159 160 161 162 163 164 165 166 167
	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 },
168 169 170
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	{ TAG_ORIGINAL_DATE, MPD_TAG_ORIGINAL_DATE },
#endif
171 172 173 174 175 176 177 178 179
	{ 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 },
180 181 182
#if LIBMPDCLIENT_CHECK_VERSION(2,10,0)
	{ TAG_MUSICBRAINZ_RELEASETRACKID,
	  MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
183 184 185 186 187 188 189
#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 },
190
#endif
191 192 193
	{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
};

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

	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)
209
	:LightSong(mpd_song_get_uri(song), tag2)
210
{
211
	const auto _mtime = mpd_song_get_last_modified(song);
212 213
	if (_mtime > 0)
		mtime = std::chrono::system_clock::from_time_t(_mtime);
214 215

#if LIBMPDCLIENT_CHECK_VERSION(2,3,0)
216 217
	start_time = SongTime::FromS(mpd_song_get_start(song));
	end_time = SongTime::FromS(mpd_song_get_end(song));
218
#endif
219 220

	TagBuilder tag_builder;
221 222 223 224

	const unsigned duration = mpd_song_get_duration(song);
	if (duration > 0)
		tag_builder.SetDuration(SignedSongTime::FromS(duration));
225 226 227 228 229 230 231

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

232
gcc_const
233
static enum mpd_tag_type
234
Convert(TagType tag_type) noexcept
235
{
236
	for (auto i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
237 238 239 240 241 242
		if (i->d == tag_type)
			return i->s;

	return MPD_TAG_COUNT;
}

243
static void
244
ThrowError(struct mpd_connection *connection)
245
{
246
	const auto code = mpd_connection_get_error(connection);
247

248 249 250 251
	AtScopeExit(connection) {
		mpd_connection_clear_error(connection);
	};

252 253 254 255 256
	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);
257 258
		throw ProtocolError((enum ack)server_error,
				    mpd_connection_get_error_message(connection));
259
	} else {
260 261
		throw LibmpdclientError(code,
					mpd_connection_get_error_message(connection));
262
	}
263
}
264

265 266
static void
CheckError(struct mpd_connection *connection)
267 268
{
	const auto code = mpd_connection_get_error(connection);
269 270
	if (code != MPD_ERROR_SUCCESS)
		ThrowError(connection);
271 272
}

273
static bool
274
SendConstraints(mpd_connection *connection, const ISongFilter &f)
275
{
276 277 278
	if (auto t = dynamic_cast<const TagSongFilter *>(&f)) {
		if (t->IsNegated())
			// TODO implement
279 280
			return true;

281 282 283 284
		if (t->GetTagType() == TAG_NUM_OF_ITEM_TYPES)
			return mpd_search_add_any_tag_constraint(connection,
								 MPD_OPERATOR_DEFAULT,
								 t->GetValue().c_str());
285

286
		const auto tag = Convert(t->GetTagType());
287 288 289 290 291 292
		if (tag == MPD_TAG_COUNT)
			return true;

		return mpd_search_add_tag_constraint(connection,
						     MPD_OPERATOR_DEFAULT,
						     tag,
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
						     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());
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
	} else if (auto b = dynamic_cast<const BaseSongFilter *>(&f)) {
		if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0)
			/* requires MPD 0.18 */
			return true;

		return mpd_search_add_base_constraint(connection,
						      MPD_OPERATOR_DEFAULT,
						      b->GetValue());
#endif
	} else
		return true;
314 315 316 317 318 319
}

static bool
SendConstraints(mpd_connection *connection, const SongFilter &filter)
{
	for (const auto &i : filter.GetItems())
320
		if (!SendConstraints(connection, *i))
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
			return false;

	return true;
}

static bool
SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
{
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
	if (!selection.uri.empty() &&
	    mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0) {
		/* requires MPD 0.18 */
		if (!mpd_search_add_base_constraint(connection,
						    MPD_OPERATOR_DEFAULT,
						    selection.uri.c_str()))
			return false;
	}
#endif

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

	return true;
}

347
static bool
Max Kellermann's avatar
Max Kellermann committed
348
SendGroupMask(mpd_connection *connection, TagMask mask)
349 350 351
{
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
	for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
Max Kellermann's avatar
Max Kellermann committed
352 353
		const auto tag_type = TagType(i);
		if (!mask.Test(tag_type))
354 355
			continue;

Max Kellermann's avatar
Max Kellermann committed
356
		const auto tag = Convert(tag_type);
357 358 359 360 361 362 363 364 365 366 367 368
		if (tag == MPD_TAG_COUNT)
			throw std::runtime_error("Unsupported tag");

		if (!mpd_search_add_group_tag(connection, tag))
			return false;
	}

	return true;
#else
	(void)connection;
	(void)mask;

Max Kellermann's avatar
Max Kellermann committed
369
	if (mask.TestAny())
370 371 372 373 374 375
		throw std::runtime_error("Grouping requires libmpdclient 2.12");

	return true;
#endif
}

376 377 378 379 380 381
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
			     const ConfigBlock &block)
	:Database(proxy_db_plugin),
	 SocketMonitor(_loop), IdleMonitor(_loop),
	 listener(_listener),
	 host(block.GetBlockValue("host", "")),
382
	 password(block.GetBlockValue("password", "")),
383 384
	 port(block.GetBlockValue("port", 0u)),
	 keepalive(block.GetBlockValue("keepalive", false))
385 386 387
{
}

388
Database *
389 390
ProxyDatabase::Create(EventLoop &loop, EventLoop &,
		      DatabaseListener &listener,
391
		      const ConfigBlock &block)
392
{
393
	return new ProxyDatabase(loop, listener, block);
394 395
}

396 397
void
ProxyDatabase::Open()
398
{
399
	update_stamp = std::chrono::system_clock::time_point::min();
400 401 402

	try {
		Connect();
403
	} catch (...) {
404 405
		/* this error is non-fatal, because this plugin will
		   attempt to reconnect again automatically */
406
		LogError(std::current_exception());
407
	}
408 409 410 411 412 413
}

void
ProxyDatabase::Close()
{
	if (connection != nullptr)
414
		Disconnect();
415 416
}

417 418
void
ProxyDatabase::Connect()
419 420 421
{
	const char *_host = host.empty() ? nullptr : host.c_str();
	connection = mpd_connection_new(_host, port, 0);
422 423
	if (connection == nullptr)
		throw LibmpdclientError(MPD_ERROR_OOM, "Out of memory");
424

425 426
	try {
		CheckError(connection);
427 428 429 430

		if (!password.empty() &&
		    !mpd_run_password(connection, password.c_str()))
			ThrowError(connection);
431
	} catch (...) {
432 433 434
		mpd_connection_free(connection);
		connection = nullptr;

435 436 437 438
		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()));
439 440
	}

441 442
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
	mpd_connection_set_keepalive(connection, keepalive);
443 444 445
#else
	// suppress -Wunused-private-field
	(void)keepalive;
446 447
#endif

448 449 450
	idle_received = unsigned(-1);
	is_idle = false;

451
	SocketMonitor::Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection))));
452
	IdleMonitor::Schedule();
453 454
}

455 456
void
ProxyDatabase::CheckConnection()
457 458 459
{
	assert(connection != nullptr);

460
	if (!mpd_connection_clear_error(connection)) {
461
		Disconnect();
462 463
		Connect();
		return;
464 465
	}

466 467
	if (is_idle) {
		unsigned idle = mpd_run_noidle(connection);
468 469 470 471 472 473 474
		if (idle == 0) {
			try {
				CheckError(connection);
			} catch (...) {
				Disconnect();
				throw;
			}
475 476 477 478 479 480
		}

		idle_received |= idle;
		is_idle = false;
		IdleMonitor::Schedule();
	}
481 482
}

483 484
void
ProxyDatabase::EnsureConnected()
485
{
486 487 488 489
	if (connection != nullptr)
		CheckConnection();
	else
		Connect();
490 491
}

492 493 494 495 496
void
ProxyDatabase::Disconnect()
{
	assert(connection != nullptr);

497 498 499
	IdleMonitor::Cancel();
	SocketMonitor::Steal();

500 501 502 503
	mpd_connection_free(connection);
	connection = nullptr;
}

504
bool
505
ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
506 507 508 509 510 511 512 513 514 515 516
{
	assert(connection != nullptr);

	if (!is_idle) {
		// TODO: can this happen?
		IdleMonitor::Schedule();
		return false;
	}

	unsigned idle = (unsigned)mpd_recv_idle(connection, false);
	if (idle == 0) {
517 518
		try {
			CheckError(connection);
519 520
		} catch (...) {
			LogError(std::current_exception());
521 522 523 524 525 526 527 528 529 530 531 532 533
			Disconnect();
			return false;
		}
	}

	/* let OnIdle() handle this */
	idle_received |= idle;
	is_idle = false;
	IdleMonitor::Schedule();
	return false;
}

void
534
ProxyDatabase::OnIdle() noexcept
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
{
	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)) {
552 553
		try {
			ThrowError(connection);
554 555
		} catch (...) {
			LogError(std::current_exception());
556
		}
557 558 559 560 561 562 563 564 565 566 567

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

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

568
const LightSong *
569
ProxyDatabase::GetSong(const char *uri) const
570
{
571
	// TODO: eliminate the const_cast
572
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
573

574 575
	if (!mpd_send_list_meta(connection, uri))
		ThrowError(connection);
576 577

	struct mpd_song *song = mpd_recv_song(connection);
578
	if (!mpd_response_finish(connection)) {
579 580
		if (song != nullptr)
			mpd_song_free(song);
581
		ThrowError(connection);
582 583
	}

584 585 586
	if (song == nullptr)
		throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
				    "No such song");
587

588
	return new AllocatedProxySong(song);
589 590
}

591
void
592
ProxyDatabase::ReturnSong(const LightSong *_song) const
593
{
594
	assert(_song != nullptr);
595

596 597 598
	AllocatedProxySong *song = (AllocatedProxySong *)
		const_cast<LightSong *>(_song);
	delete song;
599 600
}

601
static void
602
Visit(struct mpd_connection *connection, const char *uri,
603 604
      bool recursive, const SongFilter *filter,
      VisitDirectory visit_directory, VisitSong visit_song,
605
      VisitPlaylist visit_playlist);
606

607
static void
608
Visit(struct mpd_connection *connection,
609 610
      bool recursive, const SongFilter *filter,
      const struct mpd_directory *directory,
611
      VisitDirectory visit_directory, VisitSong visit_song,
612
      VisitPlaylist visit_playlist)
613
{
614
	const char *path = mpd_directory_get_path(directory);
615 616 617

	std::chrono::system_clock::time_point mtime =
		std::chrono::system_clock::time_point::min();
618
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
619 620 621
	time_t _mtime = mpd_directory_get_last_modified(directory);
	if (_mtime > 0)
		mtime = std::chrono::system_clock::from_time_t(_mtime);
622
#endif
623

624 625
	if (visit_directory)
		visit_directory(LightDirectory(path, mtime));
626

627 628 629
	if (recursive)
		Visit(connection, path, recursive, filter,
		      visit_directory, visit_song, visit_playlist);
630 631
}

632 633
gcc_pure
static bool
634
Match(const SongFilter *filter, const LightSong &song) noexcept
635 636 637 638
{
	return filter == nullptr || filter->Match(song);
}

639
static void
640
Visit(const SongFilter *filter,
641
      const mpd_song *_song,
642
      VisitSong visit_song)
643 644
{
	if (!visit_song)
645
		return;
646

647
	const ProxySong song(_song);
648 649
	if (Match(filter, song))
		visit_song(song);
650 651
}

652
static void
653
Visit(const struct mpd_playlist *playlist,
654
      VisitPlaylist visit_playlist)
655 656
{
	if (!visit_playlist)
657
		return;
658

659 660
	time_t mtime = mpd_playlist_get_last_modified(playlist);

661
	PlaylistInfo p(mpd_playlist_get_path(playlist),
662 663 664
		       mtime > 0
		       ? std::chrono::system_clock::from_time_t(mtime)
		       : std::chrono::system_clock::time_point::min());
665

666
	visit_playlist(p, LightDirectory::Root());
667 668
}

669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
class ProxyEntity {
	struct mpd_entity *entity;

public:
	explicit ProxyEntity(struct mpd_entity *_entity)
		:entity(_entity) {}

	ProxyEntity(const ProxyEntity &other) = delete;

	ProxyEntity(ProxyEntity &&other)
		:entity(other.entity) {
		other.entity = nullptr;
	}

	~ProxyEntity() {
		if (entity != nullptr)
			mpd_entity_free(entity);
	}

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

	operator const struct mpd_entity *() const {
		return entity;
	}
};

static std::list<ProxyEntity>
696 697
ReceiveEntities(struct mpd_connection *connection)
{
698
	std::list<ProxyEntity> entities;
699
	struct mpd_entity *entity;
700
	while ((entity = mpd_recv_entity(connection)) != nullptr)
701
		entities.push_back(ProxyEntity(entity));
702 703 704 705 706

	mpd_response_finish(connection);
	return entities;
}

707
static void
708
Visit(struct mpd_connection *connection, const char *uri,
709 710
      bool recursive, const SongFilter *filter,
      VisitDirectory visit_directory, VisitSong visit_song,
711
      VisitPlaylist visit_playlist)
712
{
713 714
	if (!mpd_send_list_meta(connection, uri))
		ThrowError(connection);
715

716
	std::list<ProxyEntity> entities(ReceiveEntities(connection));
717
	CheckError(connection);
718

719
	for (const auto &entity : entities) {
720 721 722 723 724
		switch (mpd_entity_get_type(entity)) {
		case MPD_ENTITY_TYPE_UNKNOWN:
			break;

		case MPD_ENTITY_TYPE_DIRECTORY:
725 726 727
			Visit(connection, recursive, filter,
			      mpd_entity_get_directory(entity),
			      visit_directory, visit_song, visit_playlist);
728 729 730
			break;

		case MPD_ENTITY_TYPE_SONG:
731
			Visit(filter, mpd_entity_get_song(entity), visit_song);
732 733 734
			break;

		case MPD_ENTITY_TYPE_PLAYLIST:
735 736
			Visit(mpd_entity_get_playlist(entity),
			      visit_playlist);
737 738 739 740 741
			break;
		}
	}
}

742
static void
743 744
SearchSongs(struct mpd_connection *connection,
	    const DatabaseSelection &selection,
745
	    VisitSong visit_song)
746
try {
747 748 749 750 751 752 753 754
	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) ||
755 756
	    !mpd_search_commit(connection))
		ThrowError(connection);
757

758
	while (auto *song = mpd_recv_song(connection)) {
759
		AllocatedProxySong song2(song);
760

761 762 763 764 765 766 767 768
		if (Match(selection.filter, song2)) {
			try {
				visit_song(song2);
			} catch (...) {
				mpd_response_finish(connection);
				throw;
			}
		}
769 770
	}

771
	if (!mpd_response_finish(connection))
772
		ThrowError(connection);
773 774 775 776 777
} catch (...) {
	if (connection != nullptr)
		mpd_search_cancel(connection);

	throw;
778 779
}

780 781 782 783 784 785
/**
 * Check whether we can use the "base" constraint.  Requires
 * libmpdclient 2.9 and MPD 0.18.
 */
gcc_pure
static bool
786
ServerSupportsSearchBase(const struct mpd_connection *connection) noexcept
787 788 789 790 791 792 793 794 795 796
{
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
	return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0;
#else
	(void)connection;

	return false;
#endif
}

797
void
798 799 800
ProxyDatabase::Visit(const DatabaseSelection &selection,
		     VisitDirectory visit_directory,
		     VisitSong visit_song,
801
		     VisitPlaylist visit_playlist) const
802
{
803
	// TODO: eliminate the const_cast
804
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
805

806 807 808
	if (!visit_directory && !visit_playlist && selection.recursive &&
	    (ServerSupportsSearchBase(connection)
	     ? !selection.IsEmpty()
809
	     : selection.HasOtherThanBase())) {
810 811
		/* this optimized code path can only be used under
		   certain conditions */
812
		::SearchSongs(connection, selection, visit_song);
813
		return;
814
	}
815 816

	/* fall back to recursive walk (slow!) */
817 818 819
	::Visit(connection, selection.uri.c_str(),
		selection.recursive, selection.filter,
		visit_directory, visit_song, visit_playlist);
820 821
}

822
void
823
ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
824
			       TagType tag_type,
Max Kellermann's avatar
Max Kellermann committed
825
			       TagMask group_mask,
826
			       VisitTag visit_tag) const
827
try {
828
	// TODO: eliminate the const_cast
829
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
830

831
	enum mpd_tag_type tag_type2 = Convert(tag_type);
832 833
	if (tag_type2 == MPD_TAG_COUNT)
		throw std::runtime_error("Unsupported tag");
834

835
	if (!mpd_search_db_tags(connection, tag_type2) ||
836 837
	    !SendConstraints(connection, selection) ||
	    !SendGroupMask(connection, group_mask))
838
		ThrowError(connection);
839

840 841
	if (!mpd_search_commit(connection))
		ThrowError(connection);
842

843 844 845
	TagBuilder builder;

	while (auto *pair = mpd_recv_pair(connection)) {
846 847 848 849
		AtScopeExit(this, pair) {
			mpd_return_pair(connection, pair);
		};

850 851 852 853
		const auto current_type = tag_name_parse_i(pair->name);
		if (current_type == TAG_NUM_OF_ITEM_TYPES)
			continue;

Max Kellermann's avatar
Max Kellermann committed
854
		if (current_type == tag_type && !builder.empty()) {
855 856 857 858 859 860 861 862 863
			try {
				visit_tag(builder.Commit());
			} catch (...) {
				mpd_response_finish(connection);
				throw;
			}
		}

		builder.AddItem(current_type, pair->value);
864

865
		if (!builder.HasType(current_type))
866 867 868 869 870
			/* if no tag item has been added, then the
			   given value was not acceptable
			   (e.g. empty); forcefully insert an empty
			   tag in this case, as the caller expects the
			   given tag type to be present */
871 872
			builder.AddEmptyItem(current_type);
	}
873

Max Kellermann's avatar
Max Kellermann committed
874
	if (!builder.empty()) {
875
		try {
876
			visit_tag(builder.Commit());
877 878 879 880
		} catch (...) {
			mpd_response_finish(connection);
			throw;
		}
881 882
	}

883
	if (!mpd_response_finish(connection))
884
		ThrowError(connection);
885 886 887 888 889
} catch (...) {
	if (connection != nullptr)
		mpd_search_cancel(connection);

	throw;
890 891
}

892 893
DatabaseStats
ProxyDatabase::GetStats(const DatabaseSelection &selection) const
894 895 896 897
{
	// TODO: match
	(void)selection;

898
	// TODO: eliminate the const_cast
899
	const_cast<ProxyDatabase *>(this)->EnsureConnected();
900

901 902
	struct mpd_stats *stats2 =
		mpd_run_stats(connection);
903 904
	if (stats2 == nullptr)
		ThrowError(connection);
905

906
	update_stamp = std::chrono::system_clock::from_time_t(mpd_stats_get_db_update_time(stats2));
907

908
	DatabaseStats stats;
909
	stats.song_count = mpd_stats_get_number_of_songs(stats2);
910
	stats.total_duration = std::chrono::seconds(mpd_stats_get_db_play_time(stats2));
911 912 913
	stats.artist_count = mpd_stats_get_number_of_artists(stats2);
	stats.album_count = mpd_stats_get_number_of_albums(stats2);
	mpd_stats_free(stats2);
914
	return stats;
915 916
}

917
unsigned
918
ProxyDatabase::Update(const char *uri_utf8, bool discard)
919
{
920
	EnsureConnected();
921 922 923 924 925

	unsigned id = discard
		? mpd_run_rescan(connection, uri_utf8)
		: mpd_run_update(connection, uri_utf8);
	if (id == 0)
926
		CheckError(connection);
927 928 929 930

	return id;
}

931 932
const DatabasePlugin proxy_db_plugin = {
	"proxy",
933
	DatabasePlugin::FLAG_REQUIRE_STORAGE,
934 935
	ProxyDatabase::Create,
};