From 8f44fd3a5bee9a85d5a33cc288ce2d27228faaab Mon Sep 17 00:00:00 2001 From: pkanavos Date: Thu, 26 Apr 2012 02:13:26 +0300 Subject: [PATCH] Selective Sync fixes FileState persistence fix that correctly stores forbidden files Permission check fix --- .../Preferences/PreferencesViewModel.cs | 4 +-- .../SelectiveSynch/DirectoryRecord.cs | 7 ++++- .../SelectiveSynch/SelectiveSynchViewModel.cs | 32 ++++++++++++-------- trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs | 23 ++++++++------ .../Utils/EnumerableExtensions.cs | 2 +- trunk/Pithos.Core/Agents/CloudTransferAction.cs | 16 +++++++--- trunk/Pithos.Core/Agents/CollectionExtensions.cs | 2 +- trunk/Pithos.Core/Agents/Downloader.cs | 2 +- trunk/Pithos.Core/Agents/StatusAgent.cs | 13 ++++++++ trunk/Pithos.Core/Agents/Uploader.cs | 9 ++++-- trunk/Pithos.Core/Agents/WorkflowAgent.cs | 20 +++++++++++- trunk/Pithos.Core/PithosMonitor.cs | 13 ++++---- trunk/Pithos.Interfaces/ObjectInfo.cs | 21 +++++++++++++ trunk/Pithos.Network/RestClient.cs | 4 +-- 14 files changed, 124 insertions(+), 44 deletions(-) diff --git a/trunk/Pithos.Client.WPF/Preferences/PreferencesViewModel.cs b/trunk/Pithos.Client.WPF/Preferences/PreferencesViewModel.cs index 356557b..5c040d5 100644 --- a/trunk/Pithos.Client.WPF/Preferences/PreferencesViewModel.cs +++ b/trunk/Pithos.Client.WPF/Preferences/PreferencesViewModel.cs @@ -219,10 +219,10 @@ namespace Pithos.Client.WPF.Preferences public void SelectiveSyncFolders() { - var monitor = Shell.Monitors[CurrentAccount.AccountKey]; + //var monitor = Shell.Monitors[CurrentAccount.AccountKey]; - var model = new SelectiveSynchViewModel(monitor,_events,CurrentAccount.Account); + var model = new SelectiveSynchViewModel(/*monitor,*/_events,CurrentAccount.Account,CurrentAccount.ApiKey); if (_windowManager.ShowDialog(model) == true) { diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/DirectoryRecord.cs b/trunk/Pithos.Client.WPF/SelectiveSynch/DirectoryRecord.cs index ce139fe..abd3781 100644 --- a/trunk/Pithos.Client.WPF/SelectiveSynch/DirectoryRecord.cs +++ b/trunk/Pithos.Client.WPF/SelectiveSynch/DirectoryRecord.cs @@ -163,12 +163,17 @@ namespace Pithos.Client.WPF.SelectiveSynch get { if (ObjectInfo != null) - return ObjectInfo.Name; + return ObjectInfo.Name.Split('/').Last(); return _displayName; } set { _displayName = value; } } + public bool IsExplicitlyChecked + { + set { _isChecked=value; } + } + public DirectoryRecord(ObjectInfo info) { ObjectInfo = info; diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs index cae3de2..f7edd53 100644 --- a/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs +++ b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs @@ -49,6 +49,7 @@ using Pithos.Client.WPF.Properties; using Pithos.Client.WPF.Utils; using Pithos.Core; using Pithos.Interfaces; +using Pithos.Network; namespace Pithos.Client.WPF.SelectiveSynch { @@ -66,8 +67,9 @@ namespace Pithos.Client.WPF.SelectiveSynch } private ObservableCollection _checks; - private readonly PithosMonitor _monitor; + //private readonly PithosMonitor _monitor; private bool _isBusy=true; + private string _apiKey; public ObservableCollection Checks { @@ -84,26 +86,30 @@ namespace Pithos.Client.WPF.SelectiveSynch NotifyOfPropertyChange(() => Checks); } - public SelectiveSynchViewModel(PithosMonitor monitor, IEventAggregator events, AccountSettings account) + public SelectiveSynchViewModel(/*PithosMonitor monitor,*/ IEventAggregator events, AccountSettings account, string apiKey) { Account = account; AccountName = account.AccountName; DisplayName = String.Format("Selective folder synchronization for {0}",account.AccountName); - _monitor = monitor; + //_monitor = monitor; _events = events; + _apiKey = apiKey; TaskEx.Run(LoadRootNode); } private void LoadRootNode() - { - var client = _monitor.CloudClient; + { + //TODO: Check this + var client = new CloudFilesClient(AccountName,_apiKey){AuthenticationUrl=Account.ServerUrl,UsePithos=true}; + client.Authenticate(); + - var dirs = from container in client.ListContainers(_monitor.UserName) + var dirs = from container in client.ListContainers(AccountName) select new DirectoryRecord { DisplayName = container.Name, Uri=new Uri(client.StorageUrl,String.Format(@"{0}/{1}",Account.AccountName, container.Name)), - Directories = (from dir in client.ListObjects(_monitor.UserName, container.Name) + Directories = (from dir in client.ListObjects(AccountName, container.Name) where dir.IsDirectory select dir).ToTree() }; @@ -173,21 +179,23 @@ namespace Pithos.Client.WPF.SelectiveSynch from DirectoryRecord record in rootRecord select record).ToList(); + allNodes.Apply(record => record.IsChecked = false); + if (selections.Count == 0) { - allNodes.Apply(record => record.IsChecked = false); + // allNodes.Apply(record => record.IsChecked = false); return; } var selects = (from DirectoryRecord rootRecord in RootNodes from DirectoryRecord record in rootRecord - where record.Uri !=null && !selections.Contains(record.Uri.ToString()) + where record.Uri !=null && selections.Contains(record.Uri.ToString()) select record).ToList(); - var shouldBeChecked = allNodes.Except(selects).ToList(); + //var shouldBeChecked = allNodes.Except(selects).ToList(); - selects.Apply(record=>record.IsChecked=false); + selects.Apply(record=>record.IsExplicitlyChecked=true); - shouldBeChecked.Apply(record => record.IsChecked = true); + //shouldBeChecked.Apply(record => record.IsChecked = true); diff --git a/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs b/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs index f95277d..4aef954 100644 --- a/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs +++ b/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs @@ -957,19 +957,22 @@ namespace Pithos.Client.WPF { #region Event Handlers public void Handle(SelectiveSynchChanges message) - { - PithosMonitor monitor; - if (Monitors.TryGetValue(message.Account.AccountKey, out monitor)) - { - monitor.SetSelectivePaths(message.Uris,message.Added,message.Removed); + { + PithosMonitor monitor; + if (Monitors.TryGetValue(message.Account.AccountKey, out monitor)) + { + monitor.SetSelectivePaths(message.Uris, message.Added, message.Removed); - } + } - var account = Accounts.First(acc => acc.AccountKey == message.Account.AccountKey); - this._pollAgent.SetSelectivePaths(account, message.Added, message.Removed); - + var account = Accounts.FirstOrDefault(acc => acc.AccountKey == message.Account.AccountKey); + if (account!=null) + { + this._pollAgent.SetSelectivePaths(account, message.Added, message.Removed); + } - } + + } private bool _pollStarted; diff --git a/trunk/Pithos.Client.WPF/Utils/EnumerableExtensions.cs b/trunk/Pithos.Client.WPF/Utils/EnumerableExtensions.cs index 9de758a..d76fc09 100644 --- a/trunk/Pithos.Client.WPF/Utils/EnumerableExtensions.cs +++ b/trunk/Pithos.Client.WPF/Utils/EnumerableExtensions.cs @@ -85,7 +85,7 @@ namespace Pithos.Client.WPF.Utils foreach (var item in orderedItems) { var path = item.Uri.ToString(); - var newNode = new DirectoryRecord{ DisplayName=item.Name,ObjectInfo=item}; + var newNode = new DirectoryRecord{ DisplayName=item.Name.Split('/').Last(),ObjectInfo=item}; lookups[path] = newNode; var lastIndex = path.LastIndexOf("/", StringComparison.Ordinal); diff --git a/trunk/Pithos.Core/Agents/CloudTransferAction.cs b/trunk/Pithos.Core/Agents/CloudTransferAction.cs index 7ae68f4..8a81b1b 100644 --- a/trunk/Pithos.Core/Agents/CloudTransferAction.cs +++ b/trunk/Pithos.Core/Agents/CloudTransferAction.cs @@ -139,7 +139,9 @@ namespace Pithos.Core.Agents var capitalizedFileInfo = fileInfo.WithProperCapitalization(); var fullLocalName = capitalizedFileInfo.FullName; var othersPath = Path.Combine(accountInfo.AccountPath, FolderConstants.OthersFolder); - + + ObjectInfo objectInfo; + var isShared = fullLocalName.StartsWith(othersPath, StringComparison.InvariantCultureIgnoreCase); if (isShared) { @@ -147,14 +149,20 @@ namespace Pithos.Core.Agents var otherParts = pathRelativeToOther.Split('\\'); var otherName = otherParts[0]; var otherContainer = otherParts[1]; - return new ObjectInfo + objectInfo=new ObjectInfo { Account = otherName, Container = otherContainer, Name = String.Join("/", otherParts.Splice(2)) }; } - return new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName, fileInfo); + else + objectInfo=new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName, fileInfo); + + objectInfo.Content_Type= (fileInfo is DirectoryInfo) + ? "appication/directory" + : "application/octet-stream"; + return objectInfo; } } @@ -178,7 +186,7 @@ namespace Pithos.Core.Agents public override string ToString() { - return String.Format("{0}: _ <- {1}", this.Action, this.CloudFile.Name); + return String.Format("{0}: _ <- {1}", Action, CloudFile.Name); } } diff --git a/trunk/Pithos.Core/Agents/CollectionExtensions.cs b/trunk/Pithos.Core/Agents/CollectionExtensions.cs index 2a785cc..b4ecd38 100644 --- a/trunk/Pithos.Core/Agents/CollectionExtensions.cs +++ b/trunk/Pithos.Core/Agents/CollectionExtensions.cs @@ -168,7 +168,7 @@ namespace Pithos.Core.Agents //the target is not below the root //DON'T FORGET that Uri segments include the slashes. Must remove them to ensure proper checks var mismatch = rootSegments - .Where((t, i) => !String.Equals(targetSegments[i].TrimEnd('/'), t.TrimEnd('/'))) + .Where((t, i) => !String.Equals(targetSegments[i].TrimEnd('/'), t.TrimEnd('/'),StringComparison.InvariantCultureIgnoreCase)) .Any(); return !mismatch; } diff --git a/trunk/Pithos.Core/Agents/Downloader.cs b/trunk/Pithos.Core/Agents/Downloader.cs index 68b33c6..8e3416c 100644 --- a/trunk/Pithos.Core/Agents/Downloader.cs +++ b/trunk/Pithos.Core/Agents/Downloader.cs @@ -99,7 +99,7 @@ namespace Pithos.Core.Agents DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash); - if (cloudFile.AllowedTo == "read") + if (!cloudFile.IsWritable(accountInfo.UserName)) { var attributes = File.GetAttributes(localPath); File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly); diff --git a/trunk/Pithos.Core/Agents/StatusAgent.cs b/trunk/Pithos.Core/Agents/StatusAgent.cs index 4423f56..9ec5d00 100644 --- a/trunk/Pithos.Core/Agents/StatusAgent.cs +++ b/trunk/Pithos.Core/Agents/StatusAgent.cs @@ -346,6 +346,12 @@ namespace Pithos.Core.Agents command.Parameters.AddWithValue("path", path); var affected = command.ExecuteNonQuery(); + if (affected == 0) + { + var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(path)); + createdState.FileStatus = status; + _persistenceAgent.Post(createdState.Create); + } return affected; } } @@ -379,6 +385,13 @@ namespace Pithos.Core.Agents command.Parameters.AddWithValue("overlayStatus", overlayStatus); var affected = command.ExecuteNonQuery(); + if (affected == 0) + { + var createdState=FileState.CreateFor(FileInfoExtensions.FromPath(absolutePath)); + createdState.FileStatus = fileStatus; + createdState.OverlayStatus = overlayStatus; + _persistenceAgent.Post(createdState.Create); + } return affected; } } diff --git a/trunk/Pithos.Core/Agents/Uploader.cs b/trunk/Pithos.Core/Agents/Uploader.cs index db0735e..67e6db1 100644 --- a/trunk/Pithos.Core/Agents/Uploader.cs +++ b/trunk/Pithos.Core/Agents/Uploader.cs @@ -47,14 +47,16 @@ namespace Pithos.Core.Agents return; } + var latestState = action.FileState; + //Do not upload files in conflict - if (action.FileState.FileStatus == FileStatus.Conflict) + if (latestState.FileStatus == FileStatus.Conflict) { Log.InfoFormat("Skipping file in conflict [{0}]", fileInfo.FullName); return; } //Do not upload files when we have no permission - if (action.FileState.FileStatus == FileStatus.Forbidden) + if (latestState.FileStatus == FileStatus.Forbidden) { Log.InfoFormat("Skipping forbidden file [{0}]", fileInfo.FullName); return; @@ -85,7 +87,7 @@ namespace Pithos.Core.Agents var cloudInfo = client.GetObjectInfo(account, cloudFile.Container, cloudFile.Name); //If this is a read-only file, do not upload changes - if (cloudInfo.AllowedTo == "read") + if (!cloudInfo.IsWritable(action.AccountInfo.UserName)) return; if (fileInfo is DirectoryInfo) @@ -139,6 +141,7 @@ namespace Pithos.Core.Agents if (response.StatusCode == HttpStatusCode.Forbidden) { StatusKeeper.SetFileState(fileInfo.FullName, FileStatus.Forbidden, FileOverlayStatus.Conflict, "Forbidden"); + } else //In any other case, propagate the error diff --git a/trunk/Pithos.Core/Agents/WorkflowAgent.cs b/trunk/Pithos.Core/Agents/WorkflowAgent.cs index 5ff697e..b53691a 100644 --- a/trunk/Pithos.Core/Agents/WorkflowAgent.cs +++ b/trunk/Pithos.Core/Agents/WorkflowAgent.cs @@ -73,6 +73,12 @@ namespace Pithos.Core.Agents [System.ComponentModel.Composition.Import] public IPithosSettings Settings { get; set; } + private List _selectivePaths = new List(); + public List SelectivePaths + { + get { return _selectivePaths; } + set { _selectivePaths = value; } + } public WorkflowAgent() { @@ -245,7 +251,8 @@ namespace Pithos.Core.Agents var pendingStates = pendingEntries .Select(state => new WorkflowState(account, state)) .ToList(); - + + if (Log.IsDebugEnabled) Log.DebugFormat("Found {0} interrupted files", pendingStates.Count); @@ -266,6 +273,17 @@ namespace Pithos.Core.Agents return;*/ //TODO: Need to handle folder renames + //If there are selective sync paths defined + if (SelectivePaths.Count > 0 + //And the target file is not below any of the selective paths + && !SelectivePaths.Any(workflowState.Path.IsAtOrDirectlyBelow)) + //abort the post + { + Log.InfoFormat("File skipped, not under a selected folder [{0}] ",workflowState.Path); + return; + } + + Debug.Assert(workflowState.Path.StartsWith(workflowState.AccountInfo.AccountPath, StringComparison.InvariantCultureIgnoreCase), "File from wrong account posted"); _agent.Post(workflowState); diff --git a/trunk/Pithos.Core/PithosMonitor.cs b/trunk/Pithos.Core/PithosMonitor.cs index 55d6c82..7ca5a30 100644 --- a/trunk/Pithos.Core/PithosMonitor.cs +++ b/trunk/Pithos.Core/PithosMonitor.cs @@ -193,12 +193,12 @@ namespace Pithos.Core } _cancellationSource = new CancellationTokenSource(); - - CloudClient = new CloudFilesClient(UserName, ApiKey) - {UsePithos = true, AuthenticationUrl = AuthenticationUrl}; - - - _accountInfo = CloudClient.Authenticate(); + lock (this) + { + CloudClient = new CloudFilesClient(UserName, ApiKey) + {UsePithos = true, AuthenticationUrl = AuthenticationUrl}; + _accountInfo = CloudClient.Authenticate(); + } _accountInfo.SiteUri = AuthenticationUrl; _accountInfo.AccountPath = RootPath; @@ -432,6 +432,7 @@ namespace Pithos.Core var selectivePaths = UrisToFilePaths(uris); FileAgent.SelectivePaths=selectivePaths; + WorkflowAgent.SelectivePaths = selectivePaths; PollAgent.SetSyncUris(_accountInfo.AccountKey,uris); var removedPaths = UrisToFilePaths(removed); diff --git a/trunk/Pithos.Interfaces/ObjectInfo.cs b/trunk/Pithos.Interfaces/ObjectInfo.cs index 4c43701..484a39a 100644 --- a/trunk/Pithos.Interfaces/ObjectInfo.cs +++ b/trunk/Pithos.Interfaces/ObjectInfo.cs @@ -328,6 +328,25 @@ namespace Pithos.Interfaces && (this.Name == null || other.Name.StartsWith(this.Name)); } + public bool IsWritable(string account) + { + //If the Allowed To header has no value, try to determine the Share permissions + if (AllowedTo == null) + { + //If this file has no permissions defined, we can probably write it + //This case should occur only when the info comes from a listing of the user's own files + if (Permissions == null || Permissions.Count == 0) + return true; + string perms; + + //Do we have an explicit write share to this account? + return Permissions.TryGetValue(account, out perms) + && perms.Equals("write",StringComparison.InvariantCultureIgnoreCase); + + } + //Otherwise return the permissions specified by AllowedTo + return AllowedTo.Equals("write",StringComparison.InvariantCultureIgnoreCase) ; + } public ObjectInfo Previous { get; private set; } @@ -335,6 +354,8 @@ namespace Pithos.Interfaces { get { + if (Content_Type == null) + return false; if (Content_Type.StartsWith(@"application/directory",StringComparison.InvariantCultureIgnoreCase)) return true; if (Content_Type.StartsWith(@"application/folder",StringComparison.InvariantCultureIgnoreCase)) diff --git a/trunk/Pithos.Network/RestClient.cs b/trunk/Pithos.Network/RestClient.cs index 4657d4a..45e0420 100644 --- a/trunk/Pithos.Network/RestClient.cs +++ b/trunk/Pithos.Network/RestClient.cs @@ -107,8 +107,8 @@ namespace Pithos.Network : base() { if (other==null) - Log.ErrorFormat("[ERROR] No parameters provided to the rest client. \n{0}\n", other); - //throw new ArgumentNullException("other"); + //Log.ErrorFormat("[ERROR] No parameters provided to the rest client. \n{0}\n", other); + throw new ArgumentNullException("other"); Contract.EndContractBlock(); //The maximum error response must be large because missing server hashes are return as a Conflivt (409) error response -- 1.7.10.4