Selective Sync fixes
[pithos-ms-client] / trunk / Pithos.Core / Agents / CollectionExtensions.cs
index d433b00..b4ecd38 100644 (file)
@@ -1,57 +1,78 @@
-// -----------------------------------------------------------------------\r
-// <copyright file="CollectionExtensions.cs" company="GRNET">\r
-// Copyright 2011-2012 GRNET S.A. All rights reserved.\r
-// \r
-// Redistribution and use in source and binary forms, with or\r
-// without modification, are permitted provided that the following\r
-// conditions are met:\r
-// \r
-//   1. Redistributions of source code must retain the above\r
-//      copyright notice, this list of conditions and the following\r
-//      disclaimer.\r
-// \r
-//   2. Redistributions in binary form must reproduce the above\r
-//      copyright notice, this list of conditions and the following\r
-//      disclaimer in the documentation and/or other materials\r
-//      provided with the distribution.\r
-// \r
-// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
-// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
-// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
-// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
-// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
-// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
-// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
-// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
-// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
-// POSSIBILITY OF SUCH DAMAGE.\r
-// \r
-// The views and conclusions contained in the software and\r
-// documentation are those of the authors and should not be\r
-// interpreted as representing official policies, either expressed\r
-// or implied, of GRNET S.A.\r
-// </copyright>\r
-// -----------------------------------------------------------------------\r
-\r
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="CollectionExtensions.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer in the documentation and/or other materials\r
+ *      provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
 using System.Collections.Concurrent;\r
+using System.Diagnostics.Contracts;\r
+using Pithos.Interfaces;\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Linq;\r
 \r
 namespace Pithos.Core.Agents\r
 {\r
-    using System;\r
-    using System.Collections.Generic;\r
-    using System.Linq;\r
-    using System.Text;\r
 \r
     /// <summary>\r
     /// Extension methods for collections\r
     /// </summary>\r
     public static class CollectionExtensions\r
     {\r
+        public static IEnumerable<T> Replace<T>(this IEnumerable<T> list,Func<T,bool> match, Func<T,IEnumerable<T>> generate)\r
+        {\r
+            foreach (var item in list)\r
+            {\r
+                if (match(item))\r
+                    foreach (var newItem in generate(item))\r
+                    {\r
+                        yield return newItem;\r
+                    }\r
+                else\r
+                    yield return item;\r
+            }\r
+        }\r
+\r
+\r
         /// <summary>\r
         /// Remove the first message in a queue that matches the predicate\r
         /// </summary>\r
+        /// <param name="queue">The queue</param>\r
         /// <param name="predicate">The condition to match</param>\r
         /// <remarks>Removes the first message that matches the predicate by dequeing all \r
         /// messages and re-enqueing all except the first matching message</remarks>\r
@@ -77,5 +98,170 @@ namespace Pithos.Core.Agents
 \r
             queue.AddFromEnumerable(temp);\r
         }\r
+\r
+\r
+        /// <summary>\r
+        /// Return only the info objects that are below one of the filter Uris\r
+        /// </summary>\r
+        /// <param name="infos">ObjectInfo items to filter</param>\r
+        /// <param name="filterUris">List of filter Uris</param>\r
+        /// <returns>An enumerable of ObjectInfo items. If the list of filter Uris is empty or null, the original list is returned</returns>\r
+        public static IEnumerable<ObjectInfo> FilterBelow(this IEnumerable<ObjectInfo> infos,List<Uri> filterUris  )\r
+        {\r
+            if (filterUris == null)\r
+                return infos;\r
+            if (filterUris.Count == 0)\r
+                return infos;\r
+            //Allow all objects whose Uris start with any of the filters            \r
+            var filteredUris = from info in infos\r
+                                  where filterUris.Any(f => info.Uri.IsAtOrBelow(f)) \r
+                                  select info;            \r
+            return filteredUris;\r
+        }\r
+\r
+        /// <summary>\r
+        /// Return only the info objects that are directly below one of the filter Uris\r
+        /// </summary>\r
+        /// <param name="infos">ObjectInfo items to filter</param>\r
+        /// <param name="filterUris">List of filter Uris</param>\r
+        /// <returns>An enumerable of ObjectInfo items. If the list of filter Uris is empty or null, the original list is returned</returns>\r
+        public static IEnumerable<ObjectInfo> FilterDirectlyBelow(this IEnumerable<ObjectInfo> infos, List<Uri> filterUris)\r
+        {\r
+            if (filterUris == null)\r
+                return infos;\r
+            if (filterUris.Count == 0)\r
+                return infos;\r
+            //Allow all objects whose Uris start with any of the filters            \r
+            var filteredUris = from info in infos\r
+                                  where filterUris.Any(f => info.Uri.IsAtOrDirectlyBelow(f)) \r
+                                  select info;            \r
+            return filteredUris;\r
+        }\r
+\r
+        /// <summary>\r
+        /// Checkes whether a Uri is below a root Uri\r
+        /// </summary>\r
+        /// <param name="target"></param>\r
+        /// <param name="root"></param>\r
+        /// <returns></returns>\r
+        public static bool IsAtOrBelow(this Uri target,Uri root)\r
+        {\r
+            if(root == null)\r
+                throw new ArgumentNullException("root");\r
+            if(target == null)\r
+                throw new ArgumentNullException("target");\r
+            Contract.EndContractBlock();\r
+\r
+            var targetSegments = target.Segments;\r
+            var rootSegments = root.Segments;\r
+\r
+            return InnerAtOrBelow(targetSegments, rootSegments);\r
+        }\r
+\r
+        private static bool InnerAtOrBelow(string[] targetSegments, string[] rootSegments)\r
+        {\r
+            //If the uri is shorter than the root, no point in comparing\r
+            if (targetSegments.Length < rootSegments.Length)\r
+                return false;\r
+            //If the uri is below the root, it should match the root's segments one by one\r
+            //If there is any root segment that doesn't match its corresponding target segment,\r
+            //the target is not below the root\r
+            //DON'T FORGET that Uri segments include the slashes. Must remove them to ensure proper checks\r
+            var mismatch = rootSegments\r
+                .Where((t, i) => !String.Equals(targetSegments[i].TrimEnd('/'), t.TrimEnd('/'),StringComparison.InvariantCultureIgnoreCase))\r
+                .Any();\r
+            return !mismatch;\r
+        }\r
+\r
+\r
+        /// <summary>\r
+        /// Checks whether a Uri is directly below a root Uri\r
+        /// </summary>\r
+        /// <param name="target"></param>\r
+        /// <param name="root"></param>\r
+        /// <returns></returns>\r
+        public static bool IsAtOrDirectlyBelow(this Uri target,Uri root)\r
+        {\r
+            if (root==null)\r
+                throw new ArgumentNullException("root");\r
+            if (target==null)\r
+                throw new ArgumentNullException("target");\r
+            Contract.EndContractBlock();\r
+\r
+            if (target.Equals(root))\r
+                return true;\r
+            return\r
+                //If the target is directly below the root, it will have exactly \r
+                //one segment more than the root            \r
+                target.Segments.Length == root.Segments.Length + 1\r
+                //Ensure that the candidate target is indeed below the root\r
+                && target.IsAtOrBelow(root);\r
+        }\r
+\r
+        /// <summary>\r
+        /// Checkes whether a file path is below a root path\r
+        /// </summary>\r
+        /// <param name="targetPath"></param>\r
+        /// <param name="rootPath"></param>\r
+        /// <returns></returns>\r
+        public static bool IsAtOrBelow(this string targetPath, string rootPath)\r
+        {\r
+            if(String.IsNullOrWhiteSpace(targetPath))\r
+                throw new ArgumentNullException("targetPath");\r
+            if(String.IsNullOrWhiteSpace(rootPath))\r
+                throw new ArgumentNullException("rootPath");\r
+            Contract.EndContractBlock();\r
+\r
+            var targetSegments = targetPath.Split('\\');\r
+            var rootSegments = rootPath.Split('\\');\r
+\r
+            return InnerAtOrBelow(targetSegments, rootSegments);\r
+        }\r
+\r
+        /// <summary>\r
+        /// Checks whether a file path is directly below a root path\r
+        /// </summary>\r
+        /// <param name="targetPath"></param>\r
+        /// <param name="rootPath"></param>\r
+        /// <returns></returns>\r
+        public static bool IsAtOrDirectlyBelow(this string targetPath, string rootPath)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(targetPath))\r
+                throw new ArgumentNullException("targetPath");\r
+            if (String.IsNullOrWhiteSpace(rootPath))\r
+                throw new ArgumentNullException("rootPath");\r
+            Contract.EndContractBlock();\r
+\r
+            if (targetPath==rootPath)\r
+                return true;\r
+            var targetSegments = targetPath.Split('\\');\r
+            var rootSegments = rootPath.Split('\\');\r
+\r
+            return\r
+                //If the target is directly below the root, it will have exactly \r
+                //one segment more than the root            \r
+                targetSegments.Length == rootSegments.Length + 1\r
+                //Ensure that the candidate target is indeed below the root\r
+                && InnerAtOrBelow(targetSegments, rootSegments);\r
+        }\r
+\r
+        /// <summary>\r
+        /// Returns the part of the target string that comes after the prefix string.\r
+        /// The target is not modified if it doesn't start with the prefix string\r
+        /// </summary>\r
+        /// <param name="target"></param>\r
+        /// <param name="prefix"></param>\r
+        /// <returns></returns>\r
+        public static string After(this string target,string prefix)\r
+        {            \r
+            //If the prefix or the target are empty, return the target\r
+            if (String.IsNullOrWhiteSpace(prefix) || String.IsNullOrWhiteSpace(target))\r
+                return target;\r
+            if (target.StartsWith(prefix))\r
+                return target.Remove(0,prefix.Length);\r
+            return target;\r
+        }\r
     }\r
+\r
+\r
 }\r