From 6b6c7b0920358eb9cceecf52dac919a95f59da7f Mon Sep 17 00:00:00 2001
From: Anthony DeRossi <ajderossi@gmail.com>
Date: Tue, 29 Sep 2015 10:39:07 -0700
Subject: [PATCH] update: apply .mpdignore matches to subdirectories

Wildcard matches are directly applied to all filenames in
subdirectories without any attempt at matching relative paths.

This change is based on the following feature request:

  http://bugs.musicpd.org/view.php?id=3729
---
 NEWS                          |  2 ++
 doc/user.xml                  |  2 ++
 src/db/update/ExcludeList.cxx |  6 ++++++
 src/db/update/ExcludeList.hxx | 10 +++++++++-
 src/db/update/Walk.cxx        | 27 +++++++++++++++++----------
 src/db/update/Walk.hxx        |  2 ++
 6 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/NEWS b/NEWS
index b6a64d4c3..145361c35 100644
--- a/NEWS
+++ b/NEWS
@@ -36,6 +36,8 @@ ver 0.20 (not yet released)
 * support libsystemd (instead of the older libsystemd-daemon)
 * database
   - proxy: add TCP keepalive option
+* update
+  - apply .mpdignore matches to subdirectories
 
 ver 0.19.10 (2015/06/21)
 * input
diff --git a/doc/user.xml b/doc/user.xml
index a08a5aec0..8a37a7cce 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -1077,6 +1077,8 @@ database {
         To exclude a file from the update, create a file called
         <filename>.mpdignore</filename> in its parent directory.  Each
         line of that file may contain a list of shell wildcards.
+        Matching files in the current directory and all subdirectories
+        are excluded.
       </para>
     </section>
 
diff --git a/src/db/update/ExcludeList.cxx b/src/db/update/ExcludeList.cxx
index 3b54d635e..b09f349ac 100644
--- a/src/db/update/ExcludeList.cxx
+++ b/src/db/update/ExcludeList.cxx
@@ -89,6 +89,12 @@ ExcludeList::Check(Path name_fs) const
 	/* XXX include full path name in check */
 
 #ifdef HAVE_CLASS_GLOB
+	if (parent != nullptr) {
+		if (parent->Check(name_fs)) {
+			return true;
+		}
+	}
+
 	for (const auto &i : patterns)
 		if (i.Check(NarrowPath(name_fs).c_str()))
 			return true;
diff --git a/src/db/update/ExcludeList.hxx b/src/db/update/ExcludeList.hxx
index de48bac99..4952d291a 100644
--- a/src/db/update/ExcludeList.hxx
+++ b/src/db/update/ExcludeList.hxx
@@ -36,15 +36,23 @@
 class Path;
 
 class ExcludeList {
+	const ExcludeList *const parent;
+
 #ifdef HAVE_CLASS_GLOB
 	std::forward_list<Glob> patterns;
 #endif
 
 public:
+	ExcludeList()
+		:parent(nullptr) {}
+
+	ExcludeList(const ExcludeList &_parent)
+		:parent(&_parent) {}
+
 	gcc_pure
 	bool IsEmpty() const {
 #ifdef HAVE_CLASS_GLOB
-		return patterns.empty();
+		return ((parent == nullptr) || parent->IsEmpty()) && patterns.empty();
 #else
 		/* not implemented */
 		return true;
diff --git a/src/db/update/Walk.cxx b/src/db/update/Walk.cxx
index af039ee48..3e5654a3c 100644
--- a/src/db/update/Walk.cxx
+++ b/src/db/update/Walk.cxx
@@ -219,6 +219,7 @@ UpdateWalk::UpdateRegularFile(Directory &directory,
 
 void
 UpdateWalk::UpdateDirectoryChild(Directory &directory,
+				 const ExcludeList &exclude_list,
 				 const char *name, const StorageFileInfo &info)
 {
 	assert(strchr(name, '/') == nullptr);
@@ -236,7 +237,7 @@ UpdateWalk::UpdateDirectoryChild(Directory &directory,
 
 		assert(&directory == subdir->parent);
 
-		if (!UpdateDirectory(*subdir, info))
+		if (!UpdateDirectory(*subdir, exclude_list, info))
 			editor.LockDeleteDirectory(subdir);
 	} else {
 		FormatDebug(update_domain,
@@ -327,7 +328,9 @@ UpdateWalk::SkipSymlink(const Directory *directory,
 }
 
 bool
-UpdateWalk::UpdateDirectory(Directory &directory, const StorageFileInfo &info)
+UpdateWalk::UpdateDirectory(Directory &directory,
+			    const ExcludeList &exclude_list,
+			    const StorageFileInfo &info)
 {
 	assert(info.IsDirectory());
 
@@ -340,17 +343,17 @@ UpdateWalk::UpdateDirectory(Directory &directory, const StorageFileInfo &info)
 		return false;
 	}
 
-	ExcludeList exclude_list;
+	ExcludeList child_exclude_list(exclude_list);
 
 	{
 		const auto exclude_path_fs =
 			storage.MapChildFS(directory.GetPath(), ".mpdignore");
 		if (!exclude_path_fs.IsNull())
-			exclude_list.LoadFile(exclude_path_fs);
+			child_exclude_list.LoadFile(exclude_path_fs);
 	}
 
-	if (!exclude_list.IsEmpty())
-		RemoveExcludedFromDirectory(directory, exclude_list);
+	if (!child_exclude_list.IsEmpty())
+		RemoveExcludedFromDirectory(directory, child_exclude_list);
 
 	PurgeDeletedFromDirectory(directory);
 
@@ -361,7 +364,7 @@ UpdateWalk::UpdateDirectory(Directory &directory, const StorageFileInfo &info)
 
 		{
 			const auto name_fs = AllocatedPath::FromUTF8(name_utf8);
-			if (name_fs.IsNull() || exclude_list.Check(name_fs))
+			if (name_fs.IsNull() || child_exclude_list.Check(name_fs))
 				continue;
 		}
 
@@ -376,7 +379,7 @@ UpdateWalk::UpdateDirectory(Directory &directory, const StorageFileInfo &info)
 			continue;
 		}
 
-		UpdateDirectoryChild(directory, name_utf8, info2);
+		UpdateDirectoryChild(directory, child_exclude_list, name_utf8, info2);
 	}
 
 	directory.mtime = info.mtime;
@@ -468,7 +471,9 @@ UpdateWalk::UpdateUri(Directory &root, const char *uri)
 		return;
 	}
 
-	UpdateDirectoryChild(*parent, name, info);
+	ExcludeList exclude_list;
+
+	UpdateDirectoryChild(*parent, exclude_list, name, info);
 }
 
 bool
@@ -484,7 +489,9 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard)
 		if (!GetInfo(storage, "", info))
 			return false;
 
-		UpdateDirectory(root, info);
+		ExcludeList exclude_list;
+
+		UpdateDirectory(root, exclude_list, info);
 	}
 
 	return modified;
diff --git a/src/db/update/Walk.hxx b/src/db/update/Walk.hxx
index d9fe7c84c..99d54ef51 100644
--- a/src/db/update/Walk.hxx
+++ b/src/db/update/Walk.hxx
@@ -129,10 +129,12 @@ private:
 			       const char *name, const StorageFileInfo &info);
 
 	void UpdateDirectoryChild(Directory &directory,
+				  const ExcludeList &exclude_list,
 				  const char *name,
 				  const StorageFileInfo &info);
 
 	bool UpdateDirectory(Directory &directory,
+			     const ExcludeList &exclude_list,
 			     const StorageFileInfo &info);
 
 	/**