Selective filtering modifications to allow uploading of new root folders
[pithos-ms-client] / trunk / Pithos.Core / Agents / PollAgent.cs
index 5308239..875606e 100644 (file)
@@ -88,6 +88,8 @@ namespace Pithos.Core.Agents
         }\r
 \r
         private FileSystemInfo _fileInfo;\r
+        private TreeHash _merkle;\r
+\r
         public FileSystemInfo FileInfo\r
         {\r
             get { return _fileInfo; }\r
@@ -101,6 +103,18 @@ namespace Pithos.Core.Agents
         public FileState FileState { get; set; }\r
         public ObjectInfo ObjectInfo{ get; set; }\r
 \r
+\r
+        public TreeHash Merkle\r
+        {\r
+            get {\r
+                return _merkle;\r
+            }\r
+            set {\r
+                _merkle = value;\r
+                C = _merkle.TopHash.ToHashString();\r
+            }\r
+        }\r
+\r
         public StateTuple() { }\r
 \r
         public StateTuple(FileSystemInfo info)\r
@@ -190,11 +204,13 @@ namespace Pithos.Core.Agents
         /// <summary>\r
         /// Start a manual synchronization\r
         /// </summary>\r
-        public void SynchNow()\r
+        public void SynchNow(IEnumerable<string> paths=null)\r
         {            \r
+            _batchQueue.Enqueue(paths);\r
             _syncEvent.Set();\r
         }\r
 \r
+        readonly ConcurrentQueue<IEnumerable<string>> _batchQueue=new ConcurrentQueue<IEnumerable<string>>();\r
 \r
         /// <summary>\r
         /// Remote files are polled periodically. Any changes are processed\r
@@ -219,8 +235,23 @@ namespace Pithos.Core.Agents
                     await _unPauseEvent.WaitAsync();\r
                     UpdateStatus(PithosStatus.PollSyncing);\r
 \r
-                    var tasks = from accountInfo in _accounts.Values\r
-                                select ProcessAccountFiles(accountInfo, since);\r
+                    var accountBatches=new Dictionary<Uri, IEnumerable<string>>();\r
+                    IEnumerable<string> batch = null;\r
+                    if (_batchQueue.TryDequeue(out batch) && batch != null)\r
+                        foreach (var account in _accounts.Values)\r
+                        {\r
+                            var accountBatch = batch.Where(path => path.IsAtOrBelow(account.AccountPath));\r
+                            accountBatches[account.AccountKey] = accountBatch;\r
+                        }\r
+\r
+\r
+                    IEnumerable<Task<DateTime?>> tasks = new List<Task<DateTime?>>();\r
+                    foreach(var accountInfo in _accounts.Values)\r
+                    {\r
+                        IEnumerable<string> accountBatch ;\r
+                        accountBatches.TryGetValue(accountInfo.AccountKey,out accountBatch);\r
+                        ProcessAccountFiles (accountInfo, accountBatch, since);\r
+                    }\r
 \r
                     var nextTimes=await TaskEx.WhenAll(tasks.ToList());\r
 \r
@@ -263,7 +294,7 @@ namespace Pithos.Core.Agents
         private async Task<DateTime?> WaitForScheduledOrManualPoll(DateTime? since)\r
         {\r
             var sync = _syncEvent.WaitAsync();\r
-            var wait = TaskEx.Delay(TimeSpan.FromSeconds(Settings.PollingInterval), NetworkAgent.CancellationToken);\r
+            var wait = TaskEx.Delay(TimeSpan.FromSeconds(Settings.PollingInterval));\r
             \r
             var signaledTask = await TaskEx.WhenAny(sync, wait);\r
             \r
@@ -284,7 +315,7 @@ namespace Pithos.Core.Agents
             return since;\r
         }\r
 \r
-        public async Task<DateTime?> ProcessAccountFiles(AccountInfo accountInfo, DateTime? since = null)\r
+        public async Task<DateTime?> ProcessAccountFiles(AccountInfo accountInfo, IEnumerable<string> accountBatch, DateTime? since = null)\r
         {\r
             if (accountInfo == null)\r
                 throw new ArgumentNullException("accountInfo");\r
@@ -379,7 +410,7 @@ namespace Pithos.Core.Agents
                         \r
                         //var differencer = _differencer.PostSnapshot(accountInfo, cleanRemotes);\r
 \r
-                        var filterUris = Selectives.SelectiveUris[accountInfo.AccountKey];\r
+                        //var filterUris = Selectives.SelectiveUris[accountInfo.AccountKey];\r
 \r
 \r
                         //Get the local files here                        \r
@@ -387,7 +418,8 @@ namespace Pithos.Core.Agents
 \r
                         var files = LoadLocalFileTuples(accountInfo);\r
 \r
-                        var states = FileState.Queryable.ToList();\r
+                        var states = FileState.Queryable.ToList();                        \r
+                        \r
 \r
                         var infos = (from remote in cleanRemotes\r
                                     let path = remote.RelativeUrlToFilePath(accountInfo.UserName)\r
@@ -399,8 +431,9 @@ namespace Pithos.Core.Agents
 \r
                         var tuples = MergeSources(infos, files, states).ToList();\r
 \r
-                        \r
-                        foreach (var tuple in tuples)\r
+\r
+                        var stateTuples = accountBatch==null?tuples:tuples.Where(t => accountBatch.Contains(t.FilePath));\r
+                        foreach (var tuple in stateTuples)\r
                         {\r
                             await _unPauseEvent.WaitAsync();\r
 \r
@@ -442,14 +475,14 @@ namespace Pithos.Core.Agents
                 tuple.C = MERKLE_EMPTY;\r
             else if (tuple.FileState != null && tuple.MD5 == tuple.FileState.ShortHash)\r
             {\r
-                //If there is a state whose MD5 matches, load the merkle hash fromthe file state\r
+                //If there is a state whose MD5 matches, load the merkle hash from the file state\r
                 //insteaf of calculating it\r
                 tuple.C = tuple.FileState.Checksum;                              \r
             }\r
             else\r
             {\r
-                tuple.C=Signature.CalculateTreeHash(tuple.FileInfo, accountInfo.BlockSize, accountInfo.BlockHash)\r
-                                   .TopHash.ToHashString();\r
+                tuple.Merkle = Signature.CalculateTreeHash(tuple.FileInfo, accountInfo.BlockSize, accountInfo.BlockHash);\r
+                //tuple.C=tuple.Merkle.TopHash.ToHashString();                \r
             }\r
         }\r
 \r
@@ -511,9 +544,17 @@ namespace Pithos.Core.Agents
             var localInfo = FileInfoExtensions.FromPath(localFilePath);\r
 \r
 \r
+            var isUnselectedRootFolder = agent.IsUnselectedRootFolder(tuple.FilePath);\r
+\r
+            //Unselected root folders that have not yet been uploaded should be uploaded and added to the \r
+            //selective folders\r
+\r
+            if (!Selectives.IsSelected(accountInfo, localFilePath) && !(isUnselectedRootFolder && tuple.ObjectInfo==null) )                \r
+                return;\r
+\r
             // Local file unchanged? If both C and L are null, make sure it's because \r
             //both the file is missing and the state checksum is not missing\r
-            if (tuple.C == tuple.L && (localInfo.Exists || tuple.FileState==null))\r
+            if (tuple.C == tuple.L /*&& (localInfo.Exists || tuple.FileState == null)*/)\r
             {\r
                 //No local changes\r
                 //Server unchanged?\r
@@ -526,44 +567,42 @@ namespace Pithos.Core.Agents
                 else\r
                 {\r
                     //Different from server\r
-                    if (Selectives.IsSelected(accountInfo, localFilePath))\r
+                    //Does the server file exist?\r
+                    if (tuple.S == null)\r
                     {\r
-                        //Does the server file exist?\r
-                        if (tuple.S == null)\r
-                        {\r
-                            //Server file doesn't exist\r
-                            //deleteObjectFromLocal()\r
-                            StatusKeeper.SetFileState(localFilePath, FileStatus.Deleted,\r
-                                                      FileOverlayStatus.Deleted, "");\r
-                            agent.Delete(localFilePath);\r
-                            //updateRecord(Remove C, L)\r
-                            StatusKeeper.ClearFileStatus(localFilePath);\r
-                        }\r
-                        else\r
-                        {\r
-                            //Server file exists\r
-                            //downloadServerObject() // Result: L = S\r
-                            //If the file has moved on the server, move it locally before downloading\r
-                            var targetPath=MoveForServerMove(accountInfo,tuple);\r
-\r
-                            StatusKeeper.SetFileState(targetPath, FileStatus.Modified,\r
-                                                      FileOverlayStatus.Modified, "");\r
-                            NetworkAgent.Downloader.DownloadCloudFile(accountInfo,\r
-                                                                            tuple.ObjectInfo,\r
-                                                                            targetPath, token).Wait(token);\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
+                        //Server file doesn't exist\r
+                        //deleteObjectFromLocal()\r
+                        StatusKeeper.SetFileState(localFilePath, FileStatus.Deleted,\r
+                                                  FileOverlayStatus.Deleted, "");\r
+                        agent.Delete(localFilePath);\r
+                        //updateRecord(Remove C, L)\r
+                        StatusKeeper.ClearFileStatus(localFilePath);\r
+                    }\r
+                    else\r
+                    {\r
+                        //Server file exists\r
+                        //downloadServerObject() // Result: L = S\r
+                        //If the file has moved on the server, move it locally before downloading\r
+                        var targetPath = MoveForServerMove(accountInfo, tuple);\r
+\r
+                        StatusKeeper.SetFileState(targetPath, FileStatus.Modified,\r
+                                                  FileOverlayStatus.Modified, "");\r
+                        NetworkAgent.Downloader.DownloadCloudFile(accountInfo,\r
+                                                                  tuple.ObjectInfo,\r
+                                                                  targetPath, tuple.Merkle, token).Wait(token);\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
-                            StatusKeeper.SetFileState(targetPath, FileStatus.Unchanged,\r
-                                                      FileOverlayStatus.Normal, "");\r
-*/\r
-                        }\r
+                        /*\r
+                                                        StatusKeeper.SetFileState(targetPath, FileStatus.Unchanged,\r
+                                                                                  FileOverlayStatus.Normal, "");\r
+                            */\r
                     }\r
                 }\r
+\r
             }\r
             else\r
             {\r
@@ -584,18 +623,17 @@ namespace Pithos.Core.Agents
                         else\r
                         {\r
                             //uploadLocalObject() // Result: S = C, L = S                        \r
-                            var isUnselected = agent.IsUnselectedRootFolder(tuple.FilePath);\r
 \r
                             //Debug.Assert(tuple.FileState !=null);\r
                             var action = new CloudUploadAction(accountInfo, localInfo, tuple.FileState,\r
                                                                accountInfo.BlockSize, accountInfo.BlockHash,\r
-                                                               "Poll", isUnselected);\r
-                            NetworkAgent.Uploader.UploadCloudFile(action, token).Wait(token);\r
+                                                               "Poll", isUnselectedRootFolder);\r
+                            NetworkAgent.Uploader.UploadCloudFile(action, tuple.Merkle, token).Wait(token);\r
 \r
                             //updateRecord( S = C )\r
                             //State updated by the uploader\r
-                            \r
-                            if (isUnselected)\r
+\r
+                            if (isUnselectedRootFolder)\r
                             {\r
                                 ProcessChildren(accountInfo, tuple, agent, token);\r
                             }\r
@@ -604,29 +642,31 @@ namespace Pithos.Core.Agents
                 }\r
                 else\r
                 {\r
-                    if (Selectives.IsSelected(accountInfo, localFilePath))\r
+                    if (tuple.C == tuple.S)\r
+                    {\r
+                        // (Identical Changes) Result: L = S\r
+                        //doNothing()\r
+                        //Detect server moves\r
+                        var targetPath = MoveForServerMove(accountInfo, tuple);\r
+                        StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
+                    }\r
+                    else\r
                     {\r
-                        if (tuple.C == tuple.S)\r
+                        if ((tuple.C == null || !localInfo.Exists) && tuple.ObjectInfo != null)\r
+                        {\r
+                            //deleteObjectFromServer()\r
+                            DeleteCloudFile(accountInfo, tuple);\r
+                            //updateRecord(Remove L, S)                  \r
+                        }\r
+                            //If both the local and server files are missing, the state is stale\r
+                        else if (!localInfo.Exists && (tuple.S == null || tuple.ObjectInfo == null))\r
                         {\r
-                            // (Identical Changes) Result: L = S\r
-                            //doNothing()\r
-                            //Detect server moves\r
-                            var targetPath=MoveForServerMove(accountInfo, tuple);\r
-                            StatusKeeper.StoreInfo(targetPath,tuple.ObjectInfo);\r
+                            StatusKeeper.ClearFileStatus(localInfo.FullName);\r
                         }\r
                         else\r
                         {\r
-                            if ((tuple.C == null || !localInfo.Exists) && tuple.ObjectInfo != null )\r
-                            {\r
-                                //deleteObjectFromServer()\r
-                                DeleteCloudFile(accountInfo, tuple);\r
-                                //updateRecord(Remove L, S)                  \r
-                            }\r
-                            else\r
-                            {\r
-                                ReportConflictForMismatch(localFilePath);\r
-                                //identifyAsConflict() // Manual action required\r
-                            }\r
+                            ReportConflictForMismatch(localFilePath);\r
+                            //identifyAsConflict() // Manual action required\r
                         }\r
                     }\r
                 }\r
@@ -635,10 +675,13 @@ namespace Pithos.Core.Agents
 \r
         private string MoveForServerMove(AccountInfo accountInfo, StateTuple tuple)\r
         {\r
+            if (tuple.ObjectInfo == null)\r
+                return null;\r
             var relativePath = tuple.ObjectInfo.RelativeUrlToFilePath(accountInfo.UserName);\r
             var serverPath = Path.Combine(accountInfo.AccountPath, relativePath);\r
-\r
-            if (tuple.FilePath == serverPath) return serverPath;\r
+            \r
+            //Compare Case Insensitive\r
+            if (String.Equals(tuple.FilePath ,serverPath,StringComparison.InvariantCultureIgnoreCase)) return serverPath;\r
 \r
             if (tuple.FileInfo.Exists)\r
             {                    \r
@@ -775,7 +818,7 @@ namespace Pithos.Core.Agents
             return threshold;\r
         }\r
 \r
-        //readonly AccountsDifferencer _differencer = new AccountsDifferencer();\r
+        readonly AccountsDifferencer _differencer = new AccountsDifferencer();\r
         private Dictionary<Uri, List<Uri>> _selectiveUris = new Dictionary<Uri, List<Uri>>();\r
         private bool _pause;\r
         private static string MERKLE_EMPTY = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";\r
@@ -1058,18 +1101,18 @@ namespace Pithos.Core.Agents
         {\r
             AccountInfo account;\r
             _accounts.TryRemove(accountInfo.AccountKey, out account);\r
-/*\r
+\r
             SnapshotDifferencer differencer;\r
             _differencer.Differencers.TryRemove(accountInfo.AccountKey, out differencer);\r
-*/\r
         }\r
 \r
         public void SetSelectivePaths(AccountInfo accountInfo,Uri[] added, Uri[] removed)\r
         {\r
             AbortRemovedPaths(accountInfo,removed);\r
-            DownloadNewPaths(accountInfo,added);\r
+            //DownloadNewPaths(accountInfo,added);\r
         }\r
 \r
+/*\r
         private void DownloadNewPaths(AccountInfo accountInfo, Uri[] added)\r
         {\r
             var client = new CloudFilesClient(accountInfo);\r
@@ -1122,8 +1165,9 @@ namespace Pithos.Core.Agents
             //Need to get a listing of each of the URLs, then post them to the NetworkAgent\r
             //CreatesToActions(accountInfo,)\r
 \r
-/*            NetworkAgent.Post();*/\r
+/*            NetworkAgent.Post();#1#\r
         }\r
+*/\r
 \r
         private void AbortRemovedPaths(AccountInfo accountInfo, Uri[] removed)\r
         {\r