From cc7f66822ed4e4f673255f3094bdc123831d8c56 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Mon, 20 Jan 2020 09:10:02 +0100
Subject: [PATCH] command/partition: add command "delpartition"

---
 NEWS                              |  1 +
 doc/protocol.rst                  |  4 +++
 src/Instance.cxx                  | 14 +++++++++++
 src/Instance.hxx                  |  2 ++
 src/command/AllCommands.cxx       |  1 +
 src/command/PartitionCommands.cxx | 42 +++++++++++++++++++++++++++++++
 src/command/PartitionCommands.hxx |  3 +++
 src/output/MultipleOutputs.hxx    | 12 +++++++++
 8 files changed, 79 insertions(+)

diff --git a/NEWS b/NEWS
index 0ddd59453..8e9f1bfdd 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ ver 0.22 (not yet released)
     "window" parameters
   - add command "readpicture" to download embedded pictures
   - command "moveoutput" moves an output between partitions
+  - command "delpartition" deletes a partition
   - show partition name in "status" response
 * tags
   - new tags "Grouping" (for ID3 "TIT1"), "Work" and "Conductor"
diff --git a/doc/protocol.rst b/doc/protocol.rst
index 88742f9d2..a51a3b6b5 100644
--- a/doc/protocol.rst
+++ b/doc/protocol.rst
@@ -1262,6 +1262,10 @@ client is assigned to one partition at a time.
 :command:`newpartition {NAME}`
     Create a new partition.
 
+:command:`delpartition {NAME}`
+    Delete a partition.  The partition must be empty (no connected
+    clients and no outputs).
+
 :command:`moveoutput {OUTPUTNAME}`
     Move an output to the current partition.
 
diff --git a/src/Instance.cxx b/src/Instance.cxx
index bcb8502f9..2d8003f42 100644
--- a/src/Instance.cxx
+++ b/src/Instance.cxx
@@ -87,6 +87,20 @@ Instance::FindPartition(const char *name) noexcept
 	return nullptr;
 }
 
+void
+Instance::DeletePartition(Partition &partition) noexcept
+{
+	// TODO: use boost::intrusive::list to avoid this loop
+	for (auto i = partitions.begin();; ++i) {
+		assert(i != partitions.end());
+
+		if (&*i == &partition) {
+			partitions.erase(i);
+			break;
+		}
+	}
+}
+
 #ifdef ENABLE_DATABASE
 
 const Database &
diff --git a/src/Instance.hxx b/src/Instance.hxx
index 2b72c4521..0baebcc26 100644
--- a/src/Instance.hxx
+++ b/src/Instance.hxx
@@ -171,6 +171,8 @@ struct Instance final
 	gcc_pure
 	Partition *FindPartition(const char *name) noexcept;
 
+	void DeletePartition(Partition &partition) noexcept;
+
 	void BeginShutdownPartitions() noexcept;
 
 #ifdef ENABLE_DATABASE
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index 73ae9b4e7..8943f44e0 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -103,6 +103,7 @@ static constexpr struct command commands[] = {
 	{ "decoders", PERMISSION_READ, 0, 0, handle_decoders },
 	{ "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
 	{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
+	{ "delpartition", PERMISSION_ADMIN, 1, 1, handle_delpartition },
 	{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
 	{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
 #ifdef ENABLE_DATABASE
diff --git a/src/command/PartitionCommands.cxx b/src/command/PartitionCommands.cxx
index bbb0e0143..fbcb8a754 100644
--- a/src/command/PartitionCommands.cxx
+++ b/src/command/PartitionCommands.cxx
@@ -112,6 +112,48 @@ handle_newpartition(Client &client, Request request, Response &response)
 	return CommandResult::OK;
 }
 
+CommandResult
+handle_delpartition(Client &client, Request request, Response &response)
+{
+	const char *name = request.front();
+	if (!IsValidPartitionName(name)) {
+		response.Error(ACK_ERROR_ARG, "bad name");
+		return CommandResult::ERROR;
+	}
+
+	auto &instance = client.GetInstance();
+	auto *partition = instance.FindPartition(name);
+	if (partition == nullptr) {
+		response.Error(ACK_ERROR_NO_EXIST, "no such partition");
+		return CommandResult::ERROR;
+	}
+
+	if (partition == &instance.partitions.front()) {
+		response.Error(ACK_ERROR_UNKNOWN,
+			       "cannot delete the default partition");
+		return CommandResult::ERROR;
+	}
+
+	if (!partition->clients.empty()) {
+		response.Error(ACK_ERROR_UNKNOWN,
+			       "partition still has clients");
+		return CommandResult::ERROR;
+	}
+
+	if (!partition->outputs.IsDummy()) {
+		response.Error(ACK_ERROR_UNKNOWN,
+			       "partition still has outputs");
+		return CommandResult::ERROR;
+	}
+
+	partition->BeginShutdown();
+	instance.DeletePartition(*partition);
+
+	instance.EmitIdle(IDLE_PARTITION);
+
+	return CommandResult::OK;
+}
+
 CommandResult
 handle_moveoutput(Client &client, Request request, Response &response)
 {
diff --git a/src/command/PartitionCommands.hxx b/src/command/PartitionCommands.hxx
index c840c0a82..22ce5df8c 100644
--- a/src/command/PartitionCommands.hxx
+++ b/src/command/PartitionCommands.hxx
@@ -35,6 +35,9 @@ handle_listpartitions(Client &client, Request request, Response &response);
 CommandResult
 handle_newpartition(Client &client, Request request, Response &response);
 
+CommandResult
+handle_delpartition(Client &client, Request request, Response &response);
+
 CommandResult
 handle_moveoutput(Client &client, Request request, Response &response);
 
diff --git a/src/output/MultipleOutputs.hxx b/src/output/MultipleOutputs.hxx
index fc65e8460..885f5f522 100644
--- a/src/output/MultipleOutputs.hxx
+++ b/src/output/MultipleOutputs.hxx
@@ -102,6 +102,18 @@ public:
 		return *outputs[i];
 	}
 
+	/**
+	 * Are all outputs dummy?
+	 */
+	gcc_pure
+	bool IsDummy() const noexcept {
+		for (const auto &i : outputs)
+			if (!i->IsDummy())
+				return false;
+
+		return true;
+	}
+
 	/**
 	 * Returns the audio output device with the specified name.
 	 * Returns nullptr if the name does not exist.