using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using Caliburn.Micro; using Pithos.Core; using Pithos.Core.Agents; using Pithos.Interfaces; using Pithos.Network; namespace Pithos.Client.WPF.FileProperties { [Export(typeof(IConflictResolver))] public class ConflictResolver : IConflictResolver { [Import] public IStatusKeeper StatusAgent { get; set; } [Import] public ShellViewModel Shell { get; set; } [Import] public IStatusKeeper StatusKeeper { get; set; } [Import] public NetworkAgent NetworkAgent { get; set; } [Import] public IStatusNotification StatusNotification { get; set; } public void Resolve(IEnumerable conflicts) { KeepServer(conflicts.Where(c => c.Action == ConflictAction.KeepServer)); KeepLocal(conflicts.Where(c => c.Action == ConflictAction.KeepLocal)); //KeepBoth(conflicts.Where(c => c.Action == ConflictAction.KeepBoth)); ClearLocal(conflicts.Where(c => c.Action == ConflictAction.ClearLocal)); } //Clearing the local state means that we clear the conflict state of the local file. //This is needed to clear wrong conflicts private void ClearLocal(IEnumerable conflicts) { //This can be done simply by changing the local Filestate status to normal conflicts.Apply(clear => StatusAgent.SetFileState(clear.FilePath, FileStatus.Unchanged, FileOverlayStatus.Normal, "")); } //Keeping the server version means that we need to //download the server's version of the file. private void KeepServer(IEnumerable conflicts) { //This can be done either by scheduling the appropriate action, ignoring the hash //Or directly downloading the file. //This can be done either by scheduling the appropriate action, ignoring the hash //Or directly uploading the file. var monitors = Shell.Monitors.Select(m => m.Value); //Files that were not deleted var filesToDelete= from conflict in conflicts where conflict.State.OverlayStatus == FileOverlayStatus.Deleted let info=FileInfoExtensions.FromPath(conflict.FilePath) where info.Exists select info; filesToDelete.Apply(f=> { f.Delete(); StatusAgent.ClearFileStatus(f.FullName); }); //Files that were not deleted var downloadActions = from conflict in conflicts where conflict.State.OverlayStatus != FileOverlayStatus.Deleted let monitor=monitors.First(m=> conflict.FilePath.StartsWith(m.Account.AccountPath, StringComparison.InvariantCultureIgnoreCase)) let account = monitor.Account let info = TaskEx.Run(async ()=> await monitor.GetObjectInfo(conflict.FilePath).ConfigureAwait(false)).Result select new CloudDownloadAction(account, info, "Resolver"); foreach (var action in downloadActions) { try { action.LocalFile.Delete(); StatusAgent.SetFileState(action.FileState.FilePath, FileStatus.Unchanged, FileOverlayStatus.Normal, "Resolve by downloading"); } catch (Exception ) { MessageBox.Show( String.Format("The file {0} is in use and can't be deleted", action.LocalFile.FullName), "Pithos+. Can't delete file"); } } //downloadActions.Apply(NetworkAgent.Post); } //Keeping the server version means that we need to //upload the local version of the file to the server private void KeepLocal(IEnumerable conflicts) { var accounts = (from monitor in Shell.Monitors select monitor.Value.Account).ToList(); //This can be done either by scheduling the appropriate action, ignoring the hash //Or directly uploading the file. var actions = from conflict in conflicts let account = accounts.First(acc => conflict.FilePath.StartsWith(acc.AccountPath, StringComparison.InvariantCultureIgnoreCase)) let info = FileInfoExtensions.FromPath(conflict.FilePath) select new CloudUploadAction(account, info, conflict.State, account.BlockSize, account.BlockHash,"Resolver",false,CancellationToken.None, new Progress()); foreach (var action in actions) { DeleteCloudFile(action); StatusAgent.SetFileState(action.FileState.FilePath, FileStatus.Modified, FileOverlayStatus.Normal, "Resolve by downloading"); } //If C!=S, C!=L, S!=L, C!=Null, S!=Null and we delete the server file, we will cause an upload of the new file //actions.Apply(NetworkAgent.Post); } private void DeleteCloudFile(CloudUploadAction action) { using (StatusNotification.GetNotifier("Deleting server {0}", "Deleted server {0}", true,Path.GetFileName(action.LocalFile.Name))) { StatusKeeper.SetFileState(action.LocalFile.FullName, FileStatus.Deleted, FileOverlayStatus.Deleted, ""); NetworkAgent.DeleteAgent.DeleteCloudFile(action.AccountInfo, action.CloudFile); StatusKeeper.ClearFileStatus(action.LocalFile.FullName); } } //Keeping both versions means that we need to copy one of them //somewhere and keep the other private void KeepBoth(IEnumerable conflicts) { //We can copy the rename the local file to another name and download the server file //Or rename the server file and upload the local file //Downloading the server file is probably much faster with ADSL connections //We could probably use the local file as a source of blocks to download fewer data, //We create a hashmap of the local file, download the differences from the server //and merge common and changed blocks to create the new file. } } }