Added local move to server MOVE only for unchanged files.
authorpkanavos <pkanavos@gmail.com>
Fri, 6 Jul 2012 11:41:40 +0000 (14:41 +0300)
committerpkanavos <pkanavos@gmail.com>
Fri, 6 Jul 2012 11:41:40 +0000 (14:41 +0300)
trunk/Pithos.Core/Agents/CloudTransferAction.cs
trunk/Pithos.Core/Agents/PollAgent.cs
trunk/Pithos.Core/Agents/StateTuple.cs
trunk/Pithos.Interfaces/FileInfoExtensions.cs

index 8de5d7f..7b36f1c 100644 (file)
@@ -130,7 +130,7 @@ namespace Pithos.Core.Agents
             return String.Format("{0}:{1}->{2}", Action, LocalFile.FullName, CloudFile.Name);
         }
 
-        protected static ObjectInfo CreateObjectInfoFor(AccountInfo accountInfo, FileSystemInfo fileInfo)
+        public static ObjectInfo CreateObjectInfoFor(AccountInfo accountInfo, FileSystemInfo fileInfo)
         {
             if(accountInfo==null)
                 throw new ArgumentNullException("accountInfo");
index d29c9d2..a952d23 100644 (file)
@@ -524,16 +524,9 @@ namespace Pithos.Core.Agents
                             //deleteObjectFromLocal()\r
                             using (\r
                                 StatusNotification.GetNotifier("Deleting local {0}", "Deleted local {0}",\r
-                                                               Path.GetFileName(localFilePath)))\r
+                                                               localInfo.Name))\r
                             {\r
-                                StatusKeeper.SetFileState(localFilePath, FileStatus.Deleted,\r
-                                                          FileOverlayStatus.Deleted, "");\r
-                                using (NetworkGate.Acquire(localFilePath, NetworkOperation.Deleting))\r
-                                {\r
-                                    agent.Delete(localFilePath);\r
-                                }\r
-                                //updateRecord(Remove C, L)\r
-                                StatusKeeper.ClearFileStatus(localFilePath);\r
+                                DeleteLocalFile(agent, localFilePath);\r
                             }\r
                         }\r
                         else\r
@@ -543,22 +536,11 @@ namespace Pithos.Core.Agents
                             //If the file has moved on the server, move it locally before downloading\r
                             using (\r
                                 StatusNotification.GetNotifier("Downloading {0}", "Downloaded {0}",\r
-                                                               Path.GetFileName(localFilePath)))\r
+                                                               localInfo.Name))\r
                             {\r
                                 var targetPath = MoveForServerMove(accountInfo, tuple);\r
 \r
-                                StatusKeeper.SetFileState(targetPath, FileStatus.Modified, FileOverlayStatus.Modified,\r
-                                                          "");\r
-\r
-                                await\r
-                                    NetworkAgent.Downloader.DownloadCloudFile(accountInfo, tuple.ObjectInfo, targetPath,\r
-                                                                              token)\r
-                                        .ConfigureAwait(false);\r
-                                //updateRecord( L = S )\r
-                                StatusKeeper.UpdateFileChecksum(targetPath, tuple.ObjectInfo.ETag,\r
-                                                                tuple.ObjectInfo.X_Object_Hash);\r
-\r
-                                StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
+                                await DownloadCloudFile(accountInfo, tuple, token, targetPath).ConfigureAwait(false);\r
 \r
                                 AddOwnFolderToSelectives(accountInfo, tuple, targetPath);\r
                             }\r
@@ -593,15 +575,11 @@ namespace Pithos.Core.Agents
                                 var progress = new Progress<double>(d =>\r
                                     StatusNotification.Notify(new StatusNotification(String.Format("Merkle Hashing for Upload {0:p} of {1}", d, localInfo.Name))));\r
 \r
-                                //Debug.Assert(tuple.FileState !=null);\r
-                                var action = new CloudUploadAction(accountInfo, localInfo, tuple.FileState,\r
-                                                                   accountInfo.BlockSize, accountInfo.BlockHash,\r
-                                                                   "Poll", isUnselectedRootFolder,token,progress);\r
-                                using (\r
-                                    StatusNotification.GetNotifier("Uploading {0}", "Uploaded {0}",\r
-                                                                   Path.GetFileName(localFilePath)))\r
+                                //Is this a result of a FILE move with no modifications? Then try to move it,\r
+                                //to avoid an expensive hash\r
+                                if (!MoveForLocalMove(accountInfo, tuple))\r
                                 {\r
-                                    await NetworkAgent.Uploader.UploadCloudFile(action, token).ConfigureAwait(false);\r
+                                    await UploadLocalFile(accountInfo, tuple, token, isUnselectedRootFolder, localInfo, progress).ConfigureAwait(false);\r
                                 }\r
 \r
                                 //updateRecord( S = C )\r
@@ -672,6 +650,76 @@ namespace Pithos.Core.Agents
             }\r
         }\r
 \r
+        private void DeleteLocalFile(FileAgent agent, string localFilePath)\r
+        {\r
+            StatusKeeper.SetFileState(localFilePath, FileStatus.Deleted,\r
+                                      FileOverlayStatus.Deleted, "");\r
+            using (NetworkGate.Acquire(localFilePath, NetworkOperation.Deleting))\r
+            {\r
+                agent.Delete(localFilePath);\r
+            }\r
+            //updateRecord(Remove C, L)\r
+            StatusKeeper.ClearFileStatus(localFilePath);\r
+        }\r
+\r
+        private async Task DownloadCloudFile(AccountInfo accountInfo, StateTuple tuple, CancellationToken token, string targetPath)\r
+        {\r
+            StatusKeeper.SetFileState(targetPath, FileStatus.Modified, FileOverlayStatus.Modified,\r
+                                      "");\r
+\r
+            await\r
+                NetworkAgent.Downloader.DownloadCloudFile(accountInfo, tuple.ObjectInfo, targetPath,\r
+                                                          token)\r
+                    .ConfigureAwait(false);\r
+            //updateRecord( L = S )\r
+            StatusKeeper.UpdateFileChecksum(targetPath, tuple.ObjectInfo.ETag,\r
+                                            tuple.ObjectInfo.X_Object_Hash);\r
+\r
+            StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
+        }\r
+\r
+        private async Task UploadLocalFile(AccountInfo accountInfo, StateTuple tuple, CancellationToken token,\r
+                                     bool isUnselectedRootFolder, FileSystemInfo localInfo, Progress<double> progress)\r
+        {\r
+            var action = new CloudUploadAction(accountInfo, localInfo, tuple.FileState,\r
+                                               accountInfo.BlockSize, accountInfo.BlockHash,\r
+                                               "Poll", isUnselectedRootFolder, token, progress);\r
+            using (StatusNotification.GetNotifier("Uploading {0}", "Uploaded {0}",\r
+                                                  localInfo.Name))\r
+            {\r
+                await NetworkAgent.Uploader.UploadCloudFile(action, token).ConfigureAwait(false);\r
+            }\r
+        }\r
+\r
+        private bool MoveForLocalMove(AccountInfo accountInfo, StateTuple tuple)\r
+        {\r
+            if (!(tuple.FileInfo is FileInfo) || String.IsNullOrWhiteSpace(tuple.OldFullPath) || tuple.OldMD5 != tuple.C)\r
+                return false;\r
+\r
+            try\r
+            {\r
+\r
+                var client = new CloudFilesClient(accountInfo);\r
+                var objectInfo = CloudAction.CreateObjectInfoFor(accountInfo, tuple.FileInfo);\r
+                var containerPath = Path.Combine(accountInfo.AccountPath, objectInfo.Container);\r
+                var oldName = tuple.OldFullPath.AsRelativeTo(containerPath);\r
+                //Then execute a move instead of an upload\r
+                using (StatusNotification.GetNotifier("Moving {0}", "Moved {0}", tuple.FileInfo.Name))\r
+                {\r
+                    client.MoveObject(objectInfo.Account, objectInfo.Container, oldName,\r
+                                                          objectInfo.Container, objectInfo.Name);\r
+                }\r
+                return true;\r
+            }\r
+            catch (Exception exc)\r
+            {\r
+                Log.ErrorFormat("[MOVE] Failed for [{0}],:\r\n{1}", tuple.FilePath, exc);\r
+                //Return false to force an upload of the file\r
+                return false;\r
+            }\r
+\r
+        }\r
+\r
         private void AddOwnFolderToSelectives(AccountInfo accountInfo, StateTuple tuple, string targetPath)\r
         {\r
             //Not for shared folders\r
@@ -811,15 +859,36 @@ namespace Pithos.Core.Agents
                 return results;\r
 \r
          */\r
-        private IEnumerable<StateTuple> MergeSources(IEnumerable<Tuple<string, ObjectInfo>> infos, IEnumerable<FileSystemInfo> files, IEnumerable<FileState> states, ConcurrentDictionary<string, MovedEventArgs> moves)\r
+        private IEnumerable<StateTuple> MergeSources(IEnumerable<Tuple<string, ObjectInfo>> infos, IEnumerable<FileSystemInfo> files, List<FileState> states, ConcurrentDictionary<string, MovedEventArgs> moves)\r
         {\r
-            var tuplesByPath = files.ToDictionary(f => f.FullName, f => new StateTuple {FileInfo = f}); new Dictionary<string, StateTuple>();\r
+            var tuplesByPath = new Dictionary<string, StateTuple>();\r
+            foreach (var info in files)\r
+            {\r
+                var tuple = new StateTuple(info);\r
+                //Is this the target of a move event?\r
+                var moveArg = moves.Values.FirstOrDefault(arg => info.FullName.Equals(arg.FullPath, StringComparison.InvariantCultureIgnoreCase) \r
+                    || info.FullName.IsAtOrBelow(arg.FullPath));\r
+                if (moveArg != null)\r
+                {\r
+                    tuple.NewFullPath = info.FullName;\r
+                    var relativePath = info.AsRelativeTo(moveArg.FullPath);                    \r
+                    tuple.OldFullPath = Path.Combine(moveArg.OldFullPath, relativePath);\r
+                    tuple.OldMD5 = states.FirstOrDefault(st => st.FilePath.Equals(tuple.OldFullPath, StringComparison.InvariantCultureIgnoreCase))\r
+                                .NullSafe(st => st.LastMD5);\r
+                }\r
+\r
+                tuplesByPath[tuple.FilePath] = tuple;\r
+            }\r
+            \r
+\r
+            \r
             \r
             //For files that have state\r
             foreach (var state in states)\r
             {\r
                 StateTuple hashTuple;\r
 \r
+                \r
                 if (tuplesByPath.TryGetValue(state.FilePath, out hashTuple))\r
                 {\r
                     hashTuple.FileState = state;\r
@@ -829,6 +898,20 @@ namespace Pithos.Core.Agents
                 {\r
                     var fsInfo = FileInfoExtensions.FromPath(state.FilePath);\r
                     hashTuple = new StateTuple {FileInfo = fsInfo, FileState = state};\r
+\r
+                    //Is the source of a moved item?\r
+                    var moveArg = moves.Values.FirstOrDefault(arg => state.FilePath.Equals(arg.OldFullPath,StringComparison.InvariantCultureIgnoreCase) \r
+                        || state.FilePath.IsAtOrBelow(arg.OldFullPath));\r
+                    if (moveArg != null)\r
+                    {\r
+                        var relativePath = state.FilePath.AsRelativeTo(moveArg.OldFullPath);\r
+                        hashTuple.NewFullPath = Path.Combine(moveArg.FullPath,relativePath);\r
+                        hashTuple.OldFullPath = state.FilePath;\r
+                        //Do we have the old MD5?\r
+                        hashTuple.OldMD5 = state.LastMD5;\r
+                    }\r
+\r
+\r
                     tuplesByPath[state.FilePath] = hashTuple;\r
                 }\r
             }\r
index 5876fd8..b2158fe 100644 (file)
@@ -70,6 +70,12 @@ namespace Pithos.Core.Agents
 
         public bool Locked { get; set; }
 
+        public string NewFullPath { get; set; }
+
+        public string OldMD5 { get; set; }
+
+        public string OldFullPath { get; set; }
+
         public StateTuple() { }
 
         public StateTuple(FileSystemInfo info)
index 87d00bb..3b4de87 100644 (file)
@@ -60,12 +60,27 @@ namespace Pithos.Interfaces
             Contract.EndContractBlock();
             Contract.Assume(Enum.IsDefined(typeof(StringComparison),StringComparison.InvariantCultureIgnoreCase));
 
+            var filePath = fileInfo.GetProperCapitalization();
+
+            return AsRelativeTo(filePath,path);
+        }
+
+        public static  string AsRelativeTo(this string filePath,string path )
+        {
+            if (String.IsNullOrWhiteSpace(filePath))
+                throw new ArgumentNullException("filePath");            
+            if (String.IsNullOrWhiteSpace(path))
+                throw new ArgumentNullException("path");            
+            Contract.EndContractBlock();
+            Contract.Assume(Enum.IsDefined(typeof(StringComparison),StringComparison.InvariantCultureIgnoreCase));
+
+            if (filePath.Equals(path,StringComparison.InvariantCultureIgnoreCase))
+                return String.Empty;
+
             if (!path.EndsWith("\\"))
                 path=path.ToLower() + "\\";
             int pathLength = path.Length;            
-            
-            var filePath = fileInfo.GetProperCapitalization();
-            
+                        
             if (!filePath.StartsWith(path,StringComparison.InvariantCultureIgnoreCase))
                 throw new ArgumentException(String.Format("The path {0} doesn't contain the file {1}",path,filePath));