Added modifications for move detection. Resolves #1999, #1891
authorPanagiotis Kanavos <pkanavos@gmail.com>
Tue, 28 Feb 2012 11:41:41 +0000 (13:41 +0200)
committerPanagiotis Kanavos <pkanavos@gmail.com>
Tue, 28 Feb 2012 11:42:08 +0000 (13:42 +0200)
trunk/Pithos.Core.Test/SnapshotDifferencerTest.cs
trunk/Pithos.Core/Agents/CloudTransferAction.cs
trunk/Pithos.Core/Agents/FileAgent.cs
trunk/Pithos.Core/Agents/NetworkAgent.cs
trunk/Pithos.Core/Agents/PollAgent.cs
trunk/Pithos.Core/Agents/SnapshotDifferencer.cs
trunk/Pithos.Interfaces/ObjectInfo.cs

index 464c502..ba51a20 100644 (file)
@@ -28,34 +28,34 @@ namespace Pithos.Core.Test
         public void Setup()\r
         {\r
             _previous = new []{\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3",Bytes=123,Hash="aa3",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=123,Hash="aa4",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=123,Hash="aa4",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="1"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="2"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="3"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="4"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1,UUID="5"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="6"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="7"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="8"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="9"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="10"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="11"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="12"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="13"},\r
                             };\r
             _current = new []{\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3",Bytes=123,Hash="aa3",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=1234,Hash="aa45",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name5",Bytes=123,Hash="aa5",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=1234,Hash="aa45",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name5",Bytes=123,Hash="aa5",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="2"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="3"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=1234,Hash="aa45",Version=1,UUID="4"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name5",Bytes=123,Hash="aa5",Version=1,UUID="14"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1,UUID="5"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="6"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="7"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="9"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="10"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=1234,Hash="aa45",Version=1,UUID="11"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name5",Bytes=123,Hash="aa5",Version=1,UUID="15"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="12"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="13"},\r
                            };\r
             \r
         }\r
@@ -68,6 +68,7 @@ namespace Pithos.Core.Test
             Assert.That(differencer.Deleted,Is.Empty);\r
             Assert.That(differencer.Created,Is.EquivalentTo(_previous));\r
             Assert.That(differencer.Changed,Is.Empty);\r
+            Assert.That(differencer.Moved, Is.Empty, "should have no moves");\r
             Assert.That(differencer.Unchanged,Is.Empty);\r
         }\r
 \r
@@ -78,8 +79,8 @@ namespace Pithos.Core.Test
             var differencer=d1.Post(_previous).Post(_current);\r
             var deleted=new[]\r
                             {\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="1"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="8"},\r
                             };\r
             Assert.That(differencer.Deleted.ToList(),Is.EquivalentTo(deleted)\r
                 .Using((IEqualityComparer) new ObjectInfoComparer()));\r
@@ -91,8 +92,8 @@ namespace Pithos.Core.Test
             var differencer=d1.Post(_previous).Post(_current);\r
             var created = new[]\r
                               {\r
-                                  new ObjectInfo { Account = "acc1", Container = "Cont1", Name = "Name5", Bytes = 123, Hash = "aa5", Version = 1 },\r
-                                  new ObjectInfo { Account = "acc1", Container = "Cont2", Name = "Name5", Bytes = 123, Hash = "aa5", Version = 1 },\r
+                                  new ObjectInfo { Account = "acc1", Container = "Cont1", Name = "Name5", Bytes = 123, Hash = "aa5", Version = 1,UUID="14" },\r
+                                  new ObjectInfo { Account = "acc1", Container = "Cont2", Name = "Name5", Bytes = 123, Hash = "aa5", Version = 1,UUID="15" },\r
                               };\r
             Assert.That(differencer.Created.ToList(), Is.EquivalentTo(created)\r
                 .Using((IEqualityComparer)new ObjectInfoComparer()));\r
@@ -103,8 +104,8 @@ namespace Pithos.Core.Test
             var d1 = new SnapshotDifferencer();            \r
             var differencer=d1.Post(_previous).Post(_current);\r
             var changed = new[] { \r
-                new ObjectInfo { Account = "acc1", Container = "Cont1", Name = "Name4", Bytes = 1234, Hash = "aa45", PreviousHash="aa4", Version = 1 },\r
-                new ObjectInfo { Account = "acc1", Container = "Cont2", Name = "Name4", Bytes = 1234, Hash = "aa45", PreviousHash="aa4", Version = 1 },\r
+                new ObjectInfo { Account = "acc1", Container = "Cont1", Name = "Name4", Bytes = 1234, Hash = "aa45", PreviousHash="aa4", Version = 1,UUID="4" },\r
+                new ObjectInfo { Account = "acc1", Container = "Cont2", Name = "Name4", Bytes = 1234, Hash = "aa45", PreviousHash="aa4", Version = 1,UUID="11" },\r
             };\r
 \r
             Comparison<ObjectInfo> comparer = (x, y) =>\r
@@ -122,21 +123,150 @@ namespace Pithos.Core.Test
         }\r
 \r
         [Test]\r
+        public void when_adding_a_second_snapshot_with_a_rename()\r
+        {\r
+            var d1 = new SnapshotDifferencer();\r
+            var current = new[]{\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="1"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="2"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3a",Bytes=123,Hash="aa3",Version=1,UUID="3"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="4"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1,UUID="5"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="6"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="7"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="8"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="9"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="10"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="11"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="12"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="13"},\r
+                            };\r
+\r
+            var differencer=d1.Post(_previous).Post(current);\r
+            var moved = new[] { \r
+                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3a",Bytes=123,Hash="aa3",PreviousHash="aa3",Version=1,UUID="3"}\r
+            };\r
+\r
+            Comparison<ObjectInfo> comparer = (x, y) =>\r
+                                                  {\r
+                                                      if (x.Account == y.Account\r
+                                                          && x.Container == y.Container\r
+                                                          && x.Name == y.Name\r
+                                                          && x.Hash == y.Hash\r
+                                                          && x.PreviousHash == y.PreviousHash)\r
+                                                          return 0;\r
+                                                      return 1;\r
+                                                  };\r
+            Assert.That(differencer.Moved.ToList(), Is.EquivalentTo(moved).Using(comparer));\r
+            Assert.That(differencer.Deleted, Is.Empty);\r
+            Assert.That(differencer.Created, Is.Empty);\r
+            Assert.That(differencer.Changed, Is.Empty);\r
+            //Assert.That(differencer.Unchanged, Is.Empty);\r
+            Assert.That(differencer.Moved.ToList(), Is.EquivalentTo(moved).Using(comparer));\r
+        }\r
+\r
+        [Test]\r
+        public void when_adding_a_second_snapshot_with_a_changed_rename()\r
+        {\r
+            var d1 = new SnapshotDifferencer();\r
+            var current = new[]{\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="1"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="2"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3a",Bytes=123,Hash="aa35",Version=1,UUID="3"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="4"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1,UUID="5"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="6"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="7"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="8"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="9"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="10"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="11"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="12"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="13"},\r
+                            };\r
+\r
+            var differencer = d1.Post(_previous).Post(current);\r
+            var moved = new[] { \r
+                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3a",Bytes=123,Hash="aa35",PreviousHash="aa3",Version=1,UUID="3"}\r
+            };\r
+\r
+            Comparison<ObjectInfo> comparer = (x, y) =>\r
+            {\r
+                if (x.Account == y.Account\r
+                    && x.Container == y.Container\r
+                    && x.Name == y.Name\r
+                    && x.Hash == y.Hash\r
+                    && x.PreviousHash == y.PreviousHash)\r
+                    return 0;\r
+                return 1;\r
+            };\r
+            Assert.That(differencer.Moved.ToList(), Is.EquivalentTo(moved).Using(comparer));\r
+            Assert.That(differencer.Deleted, Is.Empty);\r
+            Assert.That(differencer.Created, Is.Empty);\r
+            Assert.That(differencer.Changed, Is.Empty);\r
+            //Assert.That(differencer.Unchanged, Is.Empty);\r
+            Assert.That(differencer.Moved.ToList(), Is.EquivalentTo(moved).Using(comparer));\r
+        }\r
+\r
+        [Test]\r
+        public void when_adding_a_second_snapshot_with_a_move()\r
+        {\r
+            var d1 = new SnapshotDifferencer();\r
+            var current = new[]{\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="1"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="2"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name3",Bytes=123,Hash="aa3",Version=1,UUID="3"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="4"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1,UUID="5"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="6"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="7"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="8"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="9"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="10"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="11"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="12"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="13"},\r
+                            };\r
+\r
+            var differencer=d1.Post(_previous).Post(current);\r
+            var moved = new[] { \r
+                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name3",Bytes=123,Hash="aa3",PreviousHash="aa3",Version=1},\r
+            };\r
+\r
+            Comparison<ObjectInfo> comparer = (x, y) =>\r
+                                                  {\r
+                                                      if (x.Account == y.Account\r
+                                                          && x.Container == y.Container\r
+                                                          && x.Name == y.Name\r
+                                                          && x.Hash == y.Hash\r
+                                                          && x.PreviousHash == y.PreviousHash)\r
+                                                          return 0;\r
+                                                      return 1;\r
+                                                  };\r
+\r
+            Assert.That(differencer.Deleted, Is.Empty);\r
+            Assert.That(differencer.Created, Is.Empty);\r
+            Assert.That(differencer.Changed, Is.Empty);\r
+            //Assert.That(differencer.Unchanged, Is.Empty);\r
+            Assert.That(differencer.Moved.ToList(), Is.EquivalentTo(moved).Using(comparer));\r
+        }\r
+\r
+        [Test]\r
         public void when_adding_a_second_snapshot_with_unchanged()\r
         {\r
             var d1 = new SnapshotDifferencer();            \r
             var differencer=d1.Post(_previous).Post(_current);\r
             var unchanged = new[]\r
                                 {\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3",Bytes=123,Hash="aa3",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1},  \r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1},  \r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1},                                    \r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="2"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="3"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="6"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="7"},  \r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1,UUID="5"},  \r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="9"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="10"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="12"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="13"},                                    \r
                                 };            \r
             Assert.That(differencer.Unchanged.ToList(), Is.EquivalentTo(unchanged)\r
                 .Using((IEqualityComparer)new ObjectInfoComparer()));\r
@@ -150,6 +280,7 @@ namespace Pithos.Core.Test
             Assert.That(differencer.Deleted, Is.Empty);\r
             Assert.That(differencer.Created, Is.Empty);\r
             Assert.That(differencer.Changed, Is.Empty);\r
+            Assert.That(differencer.Moved, Is.Empty, "should have no moves");\r
             Assert.That(differencer.Unchanged, Is.Empty);\r
         }\r
 \r
@@ -161,6 +292,7 @@ namespace Pithos.Core.Test
             Assert.That(differencer.Deleted, Is.EquivalentTo(_previous));\r
             Assert.That(differencer.Created, Is.Empty);\r
             Assert.That(differencer.Changed, Is.Empty);\r
+            Assert.That(differencer.Moved, Is.Empty, "should have no moves");\r
             Assert.That(differencer.Unchanged, Is.Empty);\r
         }\r
 \r
@@ -181,6 +313,7 @@ namespace Pithos.Core.Test
             Assert.That(differencer.Deleted, Is.Empty,"should have no deletions");\r
             Assert.That(differencer.Created, Is.Empty,"should have no insertions");\r
             Assert.That(differencer.Changed, Is.Empty,"should have no changes");\r
+            Assert.That(differencer.Moved, Is.Empty, "should have no moves");\r
             Assert.That(differencer.Unchanged, Is.EquivalentTo(_previous),"should be equivalent to previous");\r
             \r
         }\r
@@ -199,21 +332,21 @@ namespace Pithos.Core.Test
                                 };\r
             var deleted = new[]\r
                             {\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3",Bytes=123,Hash="aa3",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=123,Hash="aa4",Version=1},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="1"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="2"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="3"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="4"},\r
                             };\r
             var unchanged = new[]{\r
-                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1},\r
-                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=123,Hash="aa4",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1},\r
-                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1},\r
+                                new ObjectInfo{Account="acc1",Container="Cont1",Name="Folder1/Name1",Bytes=123,Hash="aa4",Version=1,UUID="1"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="6"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont1",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="7"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="8"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="9"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name3",Bytes=123,Hash="aa3",Version=1,UUID="10"},\r
+                                new ObjectInfo{Account="acc1",Container="Cont2",Name="Name4",Bytes=123,Hash="aa4",Version=1,UUID="11"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name1",Bytes=123,Hash="aa1",Version=1,UUID="12"},\r
+                                new ObjectInfo{Account="acc2",Container="Cont2",Name="Name2",Bytes=123,Hash="aa2",Version=1,UUID="13"},\r
                             };\r
             var differencer = d1.Post(_previous)\r
                 .Post(noModItems);\r
@@ -222,6 +355,7 @@ namespace Pithos.Core.Test
             Assert.That(differencer.Deleted.ToList(), Is.EquivalentTo(deleted).Using(_comparer), "should have deletions only from acc1/cont1");\r
             Assert.That(differencer.Created, Is.Empty,"should have no insertions");\r
             Assert.That(differencer.Changed, Is.Empty,"should have no changes");\r
+            Assert.That(differencer.Moved, Is.Empty, "should have no moves");\r
             Assert.That(differencer.Unchanged, Is.EquivalentTo(unchanged).Using(_comparer),"should be equivalent to previous except the direct leafs of acc1/cont1");\r
             \r
         }\r
index 2eac804..edfd729 100644 (file)
@@ -55,7 +55,8 @@ namespace Pithos.Core.Agents
         DownloadUnconditional,
         DeleteLocal,
         DeleteCloud,
-        RenameCloud
+        RenameCloud,
+        RenameLocal
     }
 
     public class CloudAction
index 5f001e3..b73e14e 100644 (file)
@@ -71,7 +71,7 @@ namespace Pithos.Core.Agents
 
         private AccountInfo AccountInfo { get; set; }
 
-        private string RootPath { get;  set; }
+        internal string RootPath { get;  set; }
 
         private static readonly ILog Log = LogManager.GetLogger("FileAgent");
 
index 85f68a7..d051dbf 100644 (file)
@@ -161,6 +161,9 @@ namespace Pithos.Core.Agents
                                 var moveAction = (CloudMoveAction)action;
                                 RenameCloudFile(accountInfo, moveAction);
                                 break;
+                            case CloudActionType.RenameLocal:
+                                RenameLocalFile(accountInfo, action);
+                                break;
                             case CloudActionType.MustSynch:
                                 if (!File.Exists(downloadPath) && !Directory.Exists(downloadPath))
                                 {
@@ -214,13 +217,70 @@ namespace Pithos.Core.Agents
             }
         }
 
+
         private void UpdateStatus(PithosStatus status)
         {
             StatusKeeper.SetPithosStatus(status);
             StatusNotification.Notify(new Notification());
         }
 
-        
+        private void RenameLocalFile(AccountInfo accountInfo, CloudAction action)
+        {
+            if (accountInfo == null)
+                throw new ArgumentNullException("accountInfo");
+            if (action == null)
+                throw new ArgumentNullException("action");
+            if (action.LocalFile == null)
+                throw new ArgumentException("The action's local file is not specified", "action");
+            if (!Path.IsPathRooted(action.LocalFile.FullName))
+                throw new ArgumentException("The action's local file path must be absolute", "action");
+            if (action.CloudFile == null)
+                throw new ArgumentException("The action's cloud file is not specified", "action");
+            Contract.EndContractBlock();
+
+            //We assume that the local file already exists, otherwise the poll agent
+            //would have issued a download request
+
+            var currentInfo = action.CloudFile;
+            var previousInfo = action.CloudFile.Previous;
+            var fileAgent = FileAgent.GetFileAgent(accountInfo);
+
+            var previousRelativepath = previousInfo.RelativeUrlToFilePath(accountInfo.UserName);
+            var previousFile = fileAgent.GetFileSystemInfo(previousRelativepath);
+
+            //In every case we need to move the local file first
+            MoveLocalFile(accountInfo, previousFile, fileAgent, currentInfo);
+
+        }
+
+        private void MoveLocalFile(AccountInfo accountInfo, FileSystemInfo previousFile, FileAgent fileAgent,
+                                   ObjectInfo currentInfo)
+        {
+            var currentRelativepath = currentInfo.RelativeUrlToFilePath(accountInfo.UserName);
+            var newPath = Path.Combine(fileAgent.RootPath, currentRelativepath);
+
+            var isFile= (previousFile is FileInfo);
+            var previousFullPath = isFile? 
+                FileInfoExtensions.GetProperFilePathCapitalization(previousFile.FullName):
+                FileInfoExtensions.GetProperDirectoryCapitalization(previousFile.FullName);                
+            
+            using (var gateOld = NetworkGate.Acquire(previousFullPath, NetworkOperation.Renaming))
+            using (var gateNew = NetworkGate.Acquire(newPath,NetworkOperation.Renaming))
+            using (new SessionScope(FlushAction.Auto))
+            {
+                if (isFile)
+                    (previousFile as FileInfo).MoveTo(newPath);
+                else
+                {
+                    (previousFile as DirectoryInfo).MoveTo(newPath);
+                }
+                var state = StatusKeeper.GetStateByFilePath(previousFullPath);
+                state.FilePath = newPath;
+                state.SaveCopy();
+                StatusKeeper.SetFileState(previousFullPath,FileStatus.Deleted,FileOverlayStatus.Deleted);
+            }            
+        }
+
         private async Task SyncFiles(AccountInfo accountInfo,CloudAction action)
         {
             if (accountInfo == null)
index 33a93be..1a5d764 100644 (file)
@@ -270,7 +270,9 @@ namespace Pithos.Core.Agents
                         \r
 \r
                         //Create a list of actions from the remote files\r
-                        var allActions = ChangesToActions(accountInfo, differencer.Changed.FilterDirectlyBelow(SelectiveUris))\r
+                        var allActions = MovesToActions(accountInfo,differencer.Moved.FilterDirectlyBelow(SelectiveUris))\r
+                                        .Union(\r
+                                        ChangesToActions(accountInfo, differencer.Changed.FilterDirectlyBelow(SelectiveUris)))\r
                                         .Union(\r
                                         CreatesToActions(accountInfo, differencer.Created.FilterDirectlyBelow(SelectiveUris)));\r
 \r
@@ -379,7 +381,12 @@ namespace Pithos.Core.Agents
 \r
         }\r
 \r
-        //Creates an appropriate action for each server file\r
+        /// <summary>\r
+        /// Creates a Sync action for each changed server file\r
+        /// </summary>\r
+        /// <param name="accountInfo"></param>\r
+        /// <param name="changes"></param>\r
+        /// <returns></returns>\r
         private IEnumerable<CloudAction> ChangesToActions(AccountInfo accountInfo, IEnumerable<ObjectInfo> changes)\r
         {\r
             if (changes == null)\r
@@ -392,11 +399,11 @@ namespace Pithos.Core.Agents
             foreach (var objectInfo in changes)\r
             {\r
                 var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);\r
-                //and remove any matching objects from the list, adding them to the commonObjects list\r
+                //If a directory object already exists, we may need to sync it\r
                 if (fileAgent.Exists(relativePath))\r
                 {\r
-                    //If a directory object already exists, we don't need to perform any other action                    \r
                     var localFile = fileAgent.GetFileSystemInfo(relativePath);\r
+                    //We don't need to sync directories\r
                     if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo)\r
                         continue;\r
                     using (new SessionScope(FlushAction.Never))\r
@@ -418,6 +425,58 @@ namespace Pithos.Core.Agents
             }\r
         }\r
 \r
+        /// <summary>\r
+        /// Creates a Local Move action for each moved server file\r
+        /// </summary>\r
+        /// <param name="accountInfo"></param>\r
+        /// <param name="moves"></param>\r
+        /// <returns></returns>\r
+        private IEnumerable<CloudAction> MovesToActions(AccountInfo accountInfo, IEnumerable<ObjectInfo> moves)\r
+        {\r
+            if (moves == null)\r
+                throw new ArgumentNullException();\r
+            Contract.EndContractBlock();\r
+            var fileAgent = FileAgent.GetFileAgent(accountInfo);\r
+\r
+            //In order to avoid multiple iterations over the files, we iterate only once\r
+            //over the remote files\r
+            foreach (var objectInfo in moves)\r
+            {\r
+                var previousRelativepath = objectInfo.Previous.RelativeUrlToFilePath(accountInfo.UserName);\r
+                //If the previous file already exists, we can execute a Move operation\r
+                if (fileAgent.Exists(previousRelativepath))\r
+                {\r
+                    var previousFile = fileAgent.GetFileSystemInfo(previousRelativepath);\r
+                    using (new SessionScope(FlushAction.Never))\r
+                    {\r
+                        var state = StatusKeeper.GetStateByFilePath(previousFile.FullName);\r
+                        _lastSeen[previousFile.FullName] = DateTime.Now;\r
+\r
+                        //For each moved object we need to move both the local file and update                                                \r
+                        yield return new CloudAction(accountInfo, CloudActionType.RenameLocal,\r
+                                                     previousFile, objectInfo, state, accountInfo.BlockSize,\r
+                                                     accountInfo.BlockHash);\r
+                        //For modified files, we need to download the changes as well\r
+                        if (objectInfo.Hash!=objectInfo.PreviousHash)\r
+                            yield return new CloudDownloadAction(accountInfo,objectInfo);\r
+                    }\r
+                }\r
+                //If the previous file does not exist, we need to download it in the new location\r
+                else\r
+                {\r
+                    //Remote files should be downloaded\r
+                    yield return new CloudDownloadAction(accountInfo, objectInfo);\r
+                }\r
+            }\r
+        }\r
+\r
+\r
+        /// <summary>\r
+        /// Creates a download action for each new server file\r
+        /// </summary>\r
+        /// <param name="accountInfo"></param>\r
+        /// <param name="creates"></param>\r
+        /// <returns></returns>\r
         private IEnumerable<CloudAction> CreatesToActions(AccountInfo accountInfo, IEnumerable<ObjectInfo> creates)\r
         {\r
             if (creates == null)\r
@@ -430,10 +489,9 @@ namespace Pithos.Core.Agents
             foreach (var objectInfo in creates)\r
             {\r
                 var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);\r
-                //and remove any matching objects from the list, adding them to the commonObjects list\r
+                //If the object already exists, we probably have a conflict\r
                 if (fileAgent.Exists(relativePath))\r
                 {\r
-                    //If the object already exists, we probably have a conflict\r
                     //If a directory object already exists, we don't need to perform any other action                    \r
                     var localFile = fileAgent.GetFileSystemInfo(relativePath);\r
                     StatusKeeper.SetFileState(localFile.FullName, FileStatus.Conflict, FileOverlayStatus.Conflict);\r
index fed03e9..4f46174 100644 (file)
@@ -40,6 +40,7 @@
  */\r
 #endregion\r
 using System.Collections.Concurrent;\r
+using System.Diagnostics.Contracts;\r
 using Pithos.Interfaces;\r
 using Pithos.Network;\r
 using System;\r
@@ -71,6 +72,11 @@ namespace Pithos.Core.Agents
             /// </summary>\r
             public readonly ObjectInfo[] Current;\r
 \r
+            public readonly Dictionary<string, ObjectInfo> CurrentDict;\r
+\r
+            public readonly Dictionary<string, ObjectInfo> PreviousDict;\r
+\r
+\r
             /// <summary>\r
             /// Common objects, lazily evalueated. \r
             /// The common objects are used to calculate both the Changed and Unchanged objects\r
@@ -79,20 +85,30 @@ namespace Pithos.Core.Agents
 \r
             private readonly static ObjectInfo[] Empty = new ObjectInfo[0];\r
 \r
+            [ContractInvariantMethod]\r
+            private void StatInvariant()\r
+            {\r
+// ReSharper disable InvocationIsSkipped\r
+                Contract.Invariant(Current!=null);\r
+                Contract.Invariant(Previous!=null);\r
+                Contract.Invariant(CurrentDict!=null);\r
+                Contract.Invariant(PreviousDict!=null);\r
+                Contract.Invariant(Common!=null);\r
+// ReSharper restore InvocationIsSkipped\r
+            }\r
+\r
             public State(ObjectInfo[] previous, ObjectInfo[] current)\r
             {\r
                 Previous = previous ?? Empty;\r
                 Current = current ?? Empty;\r
+                CurrentDict = Current.ToDictionary(info => info.UUID);\r
+                PreviousDict = Previous.ToDictionary(info => info.UUID);\r
 \r
                 Common=new Lazy<IEnumerable<ObjectInfo>>(() =>\r
                     Current.Join(Previous,\r
                                 outKey => new { outKey.Account, outKey.Container, outKey.Name },\r
                                 inKey => new { inKey.Account, inKey.Container, inKey.Name },\r
-                                (outer, inner) =>\r
-                                {\r
-                                    outer.PreviousHash = inner.Hash;\r
-                                    return outer;\r
-                                }));            \r
+                                (outer, inner) =>outer.SetPrevious(inner)));            \r
             }\r
         }\r
 \r
@@ -138,13 +154,26 @@ namespace Pithos.Core.Agents
             return this;\r
         }\r
         \r
+        /// <summary>\r
+        /// Deleted objects are those that existed in the Previous listing\r
+        /// but are not found in the Current listing, and their UUIDs do not\r
+        /// appear in the Current listing (ie they were not renamed/moved)\r
+        /// </summary>\r
         public IEnumerable<ObjectInfo> Deleted\r
         {\r
-            get { return _state.Previous.Except(_state.Current,_comparer); }\r
+            get { return _state.Previous.Except(_state.Current,_comparer)\r
+                .Where(info=>!_state.CurrentDict.ContainsKey(info.UUID)); }\r
         }\r
+\r
+        /// <summary>\r
+        /// Created objects are those that exist in the Current listing\r
+        /// but are not found in the Previous listing, and their UUIDs do not\r
+        /// appear in the Previous listing (ie they were not renamed/moved)\r
+        /// </summary>\r
         public IEnumerable<ObjectInfo> Created\r
         {\r
-            get { return _state.Current.Except(_state.Previous,_comparer); }\r
+            get { return _state.Current.Except(_state.Previous,_comparer)\r
+                .Where(info=>!_state.PreviousDict.ContainsKey(info.UUID)); }\r
         }\r
                 \r
         public IEnumerable<ObjectInfo> Common\r
@@ -160,6 +189,19 @@ namespace Pithos.Core.Agents
         {\r
             get{ return Common.Where(i => i.PreviousHash == i.Hash);}\r
         }\r
+\r
+        public IEnumerable<ObjectInfo>  Moved\r
+        {\r
+            get\r
+            {\r
+                                \r
+                return _state.Current.Join(_state.Previous,\r
+                                    outer => outer.UUID,\r
+                                    inner => inner.UUID,\r
+                                    (outer, inner) => (outer.Name == inner.Name ? null : outer.SetPrevious(inner)))\r
+                    .Where(t => t != null);                \r
+            }\r
+        }\r
     }\r
 \r
     public class AccountsDifferencer\r
index 0eaa047..6957189 100644 (file)
@@ -321,5 +321,15 @@ namespace Pithos.Interfaces
                    && other.Container == this.Container
                    && (this.Name == null || other.Name.StartsWith(this.Name));
         }
+
+
+        public ObjectInfo Previous { get; private set; }
+
+        public ObjectInfo SetPrevious(ObjectInfo previous)
+        {            
+            Previous = previous;
+            PreviousHash = previous.Hash;
+            return this;
+        }
     }
 }
\ No newline at end of file