Преглед на файлове

Bug 1371 – Synchronization preview in .NET assembly

https://winscp.net/tracker/1371

Source commit: bce225db0a4ed8170296e09aaf659056196e90c2
Martin Prikryl преди 7 години
родител
ревизия
0661aaa75c

+ 53 - 0
dotnet/ComparisonDifference.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using WinSCP;
+
+namespace WinSCP
+{
+    [Guid("B1DAE3A0-5E56-4001-88D8-786F68557E28")]
+    [ComVisible(true)]
+    public enum SynchronizationAction
+    {
+        UploadNew = 1,
+        DownloadNew = 2,
+        UploadUpdate = 3,
+        DownloadUpdate = 4,
+        DeleteRemote = 5,
+        DeleteLocal = 6,
+    };
+
+    [Guid("2D6EFFB5-69BA-47AA-90E8-A92953E8B58A")]
+    [ComVisible(true)]
+    public sealed class ComparisonFileInfo
+    {
+        public string FileName { get; internal set; }
+        public DateTime LastWriteTime { get; internal set; }
+        public long Length { get; internal set; }
+        public int Length32 { get { return GetLength32(); } }
+
+        internal ComparisonFileInfo()
+        {
+        }
+
+        private int GetLength32()
+        {
+            return Tools.LengthTo32Bit(Length);
+        }
+    }
+
+    [Guid("97F5222E-9379-4C24-9E50-E93C7334BBD5")]
+    [ClassInterface(Constants.ClassInterface)]
+    [ComVisible(true)]
+    public sealed class ComparisonDifference
+    {
+        public SynchronizationAction Action { get; internal set; }
+        public bool IsDirectory { get; internal set; }
+        public ComparisonFileInfo Local { get; internal set; }
+        public ComparisonFileInfo Remote { get; internal set; }
+
+        internal ComparisonDifference()
+        {
+        }
+    }
+}

+ 1 - 6
dotnet/RemoteFileInfo.cs

@@ -36,12 +36,7 @@ namespace WinSCP
 
         private int GetLength32()
         {
-            if ((Length < int.MinValue) || (Length > int.MaxValue))
-            {
-                throw new OverflowException(string.Format(CultureInfo.CurrentCulture, "Size {0} cannot be represented using 32-bit value", Length));
-            }
-
-            return (int) Length;
+            return Tools.LengthTo32Bit(Length);
         }
 
         private void SetLength32(int value)

+ 194 - 60
dotnet/Session.cs

@@ -847,75 +847,83 @@ namespace WinSCP
         {
             using (Logger.CreateCallstackAndLock())
             {
-                if (options == null)
-                {
-                    options = new TransferOptions();
-                }
+                DoSynchronizeDirectories(mode, localPath, remotePath, removeFiles, mirror, criteria, options, string.Empty);
 
-                CheckOpened();
+                return ReadSynchronizeDirectories();
+            }
+        }
 
-                if (removeFiles && (mode == SynchronizationMode.Both))
-                {
-                    throw Logger.WriteException(new ArgumentException("Cannot delete files in synchronization mode Both"));
-                }
+        private void DoSynchronizeDirectories(
+            SynchronizationMode mode, string localPath, string remotePath, bool removeFiles, bool mirror,
+            SynchronizationCriteria criteria, TransferOptions options, string additionalParameters)
+        {
+            if (options == null)
+            {
+                options = new TransferOptions();
+            }
 
-                if (mirror && (mode == SynchronizationMode.Both))
-                {
-                    throw Logger.WriteException(new ArgumentException("Cannot mirror files in synchronization mode Both"));
-                }
+            CheckOpened();
 
-                if ((criteria != SynchronizationCriteria.Time) && (mode == SynchronizationMode.Both))
-                {
-                    throw Logger.WriteException(new ArgumentException("Only Time criteria is allowed in synchronization mode Both"));
-                }
+            if (removeFiles && (mode == SynchronizationMode.Both))
+            {
+                throw Logger.WriteException(new ArgumentException("Cannot delete files in synchronization mode Both"));
+            }
 
-                string modeName;
-                switch (mode)
-                {
-                    case SynchronizationMode.Local:
-                        modeName = "local";
-                        break;
-                    case SynchronizationMode.Remote:
-                        modeName = "remote";
-                        break;
-                    case SynchronizationMode.Both:
-                        modeName = "both";
-                        break;
-                    default:
-                        throw Logger.WriteException(new ArgumentOutOfRangeException("mode"));
-                }
+            if (mirror && (mode == SynchronizationMode.Both))
+            {
+                throw Logger.WriteException(new ArgumentException("Cannot mirror files in synchronization mode Both"));
+            }
 
-                string criteriaName;
-                switch (criteria)
-                {
-                    case SynchronizationCriteria.None:
-                        criteriaName = "none";
-                        break;
-                    case SynchronizationCriteria.Time:
-                        criteriaName = "time";
-                        break;
-                    case SynchronizationCriteria.Size:
-                        criteriaName = "size";
-                        break;
-                    case SynchronizationCriteria.Either:
-                        criteriaName = "either";
-                        break;
-                    default:
-                        throw Logger.WriteException(new ArgumentOutOfRangeException("criteria"));
-                }
+            if ((criteria != SynchronizationCriteria.Time) && (mode == SynchronizationMode.Both))
+            {
+                throw Logger.WriteException(new ArgumentException("Only Time criteria is allowed in synchronization mode Both"));
+            }
 
-                WriteCommand(
-                    string.Format(CultureInfo.InvariantCulture,
-                        "synchronize {0} {1} {2} {3} -criteria=\"{4}\" -- \"{5}\" \"{6}\"",
-                        modeName,
-                        BooleanSwitch(removeFiles, "delete"),
-                        BooleanSwitch(mirror, "mirror"),
-                        options.ToSwitches(),
-                        criteriaName,
-                        Tools.ArgumentEscape(localPath), Tools.ArgumentEscape(remotePath)));
+            string modeName;
+            switch (mode)
+            {
+                case SynchronizationMode.Local:
+                    modeName = "local";
+                    break;
+                case SynchronizationMode.Remote:
+                    modeName = "remote";
+                    break;
+                case SynchronizationMode.Both:
+                    modeName = "both";
+                    break;
+                default:
+                    throw Logger.WriteException(new ArgumentOutOfRangeException("mode"));
+            }
 
-                return ReadSynchronizeDirectories();
+            string criteriaName;
+            switch (criteria)
+            {
+                case SynchronizationCriteria.None:
+                    criteriaName = "none";
+                    break;
+                case SynchronizationCriteria.Time:
+                    criteriaName = "time";
+                    break;
+                case SynchronizationCriteria.Size:
+                    criteriaName = "size";
+                    break;
+                case SynchronizationCriteria.Either:
+                    criteriaName = "either";
+                    break;
+                default:
+                    throw Logger.WriteException(new ArgumentOutOfRangeException("criteria"));
             }
+
+            WriteCommand(
+                string.Format(CultureInfo.InvariantCulture,
+                    "synchronize {0} {1} {2} {3} -criteria=\"{4}\" {5} -- \"{6}\" \"{7}\"",
+                    modeName,
+                    BooleanSwitch(removeFiles, "delete"),
+                    BooleanSwitch(mirror, "mirror"),
+                    options.ToSwitches(),
+                    criteriaName,
+                    additionalParameters,
+                    Tools.ArgumentEscape(localPath), Tools.ArgumentEscape(remotePath)));
         }
 
         private SynchronizationResult ReadSynchronizeDirectories()
@@ -990,6 +998,132 @@ namespace WinSCP
             }
         }
 
+        public ComparisonDifferenceCollection CompareDirectories(
+            SynchronizationMode mode, string localPath, string remotePath,
+            bool removeFiles, bool mirror = false, SynchronizationCriteria criteria = SynchronizationCriteria.Time,
+            TransferOptions options = null)
+        {
+            using (Logger.CreateCallstackAndLock())
+            {
+                DoSynchronizeDirectories(mode, localPath, remotePath, removeFiles, mirror, criteria, options, "-preview");
+
+                return ReadCompareDirectories();
+            }
+        }
+
+        private ComparisonDifferenceCollection ReadCompareDirectories()
+        {
+            using (Logger.CreateCallstack())
+            {
+                ComparisonDifferenceCollection result = new ComparisonDifferenceCollection();
+
+                using (ElementLogReader groupReader = _reader.WaitForGroupAndCreateLogReader())
+                {
+                    while (groupReader.TryWaitForNonEmptyElement("difference", LogReadFlags.ThrowFailures))
+                    {
+                        using (ElementLogReader differenceReader = groupReader.CreateLogReader())
+                        {
+                            ComparisonDifference difference = new ComparisonDifference();
+                            ComparisonFileInfo current = null;
+
+                            while (differenceReader.Read(0))
+                            {
+                                if (differenceReader.GetEmptyElementValue("action", out string actionName))
+                                {
+                                    if ((difference.Local != null) || (difference.Remote != null))
+                                    {
+                                        throw Logger.WriteException(new InvalidOperationException("Tag action after filename"));
+                                    }
+                                    if (actionName.Equals("uploadnew", StringComparison.OrdinalIgnoreCase))
+                                    {
+                                        difference.Action = SynchronizationAction.UploadNew;
+                                        difference.Local = new ComparisonFileInfo();
+                                    }
+                                    else if (actionName.Equals("downloadnew", StringComparison.OrdinalIgnoreCase))
+                                    {
+                                        difference.Action = SynchronizationAction.DownloadNew;
+                                        difference.Remote = new ComparisonFileInfo();
+                                    }
+                                    else if (actionName.Equals("uploadupdate", StringComparison.OrdinalIgnoreCase))
+                                    {
+                                        difference.Action = SynchronizationAction.UploadUpdate;
+                                        difference.Local = new ComparisonFileInfo();
+                                        difference.Remote = new ComparisonFileInfo();
+                                    }
+                                    else if (actionName.Equals("downloadupdate", StringComparison.OrdinalIgnoreCase))
+                                    {
+                                        difference.Action = SynchronizationAction.DownloadUpdate;
+                                        difference.Remote = new ComparisonFileInfo();
+                                        difference.Local = new ComparisonFileInfo();
+                                    }
+                                    else if (actionName.Equals("deleteremote", StringComparison.OrdinalIgnoreCase))
+                                    {
+                                        difference.Action = SynchronizationAction.DeleteRemote;
+                                        difference.Remote = new ComparisonFileInfo();
+                                    }
+                                    else if (actionName.Equals("deletelocal", StringComparison.OrdinalIgnoreCase))
+                                    {
+                                        difference.Action = SynchronizationAction.DeleteLocal;
+                                        difference.Local = new ComparisonFileInfo();
+                                    }
+                                    else
+                                    {
+                                        throw Logger.WriteException(new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Unknown synchronization action \"{0}\"", actionName)));
+                                    }
+                                }
+                                else if (differenceReader.GetEmptyElementValue("filename", out string value))
+                                {
+                                    if (current == null)
+                                    {
+                                        current = difference.Local ?? difference.Remote;
+                                    }
+                                    else if (current == difference.Local)
+                                    {
+                                        current = difference.Remote;
+                                    }
+                                    if (current == null)
+                                    {
+                                        throw Logger.WriteException(new InvalidOperationException("Unexpected filename tag"));
+                                    }
+                                    current.FileName = value;
+                                }
+                                else if (differenceReader.GetEmptyElementValue("modification", out value))
+                                {
+                                    if (current == null)
+                                    {
+                                        throw Logger.WriteException(new InvalidOperationException("Unexpected modification tag"));
+                                    }
+                                    current.LastWriteTime = XmlConvert.ToDateTime(value, XmlDateTimeSerializationMode.Local);
+                                }
+                                else if (differenceReader.GetEmptyElementValue("size", out value))
+                                {
+                                    if (current == null)
+                                    {
+                                        throw Logger.WriteException(new InvalidOperationException("Unexpected size tag"));
+                                    }
+                                    current.Length = long.Parse(value, CultureInfo.InvariantCulture);
+                                }
+                            }
+
+                            if (difference.Action == default(SynchronizationAction))
+                            {
+                                throw Logger.WriteException(new InvalidOperationException("No action tag found"));
+                            }
+                            if (((difference.Local != null) && string.IsNullOrEmpty(difference.Local.FileName)) ||
+                                ((difference.Remote != null) && string.IsNullOrEmpty(difference.Remote.FileName)))
+                            {
+                                throw Logger.WriteException(new InvalidOperationException("Missing file information"));
+                            }
+
+                            result.InternalAdd(difference);
+                        }
+                    }
+                }
+
+                return result;
+            }
+        }
+
         public CommandExecutionResult ExecuteCommand(string command)
         {
             using (Logger.CreateCallstackAndLock())

+ 10 - 0
dotnet/internal/Tools.cs

@@ -56,5 +56,15 @@ namespace WinSCP
                 }
             }
         }
+
+        public static int LengthTo32Bit(long length)
+        {
+            if (length < int.MinValue || length > int.MaxValue)
+            {
+                throw new OverflowException(string.Format(CultureInfo.CurrentCulture, "Size {0} cannot be represented using 32-bit value", length));
+            }
+
+            return (int)length;
+        }
     }
 }

+ 88 - 0
dotnet/interopcollections/ComparisonDifferenceCollection.cs

@@ -0,0 +1,88 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace WinSCP
+{
+    [Guid("28957CC8-DEBC-48D0-841B-48AD3CB3B49F")]
+    [ClassInterface(Constants.ClassInterface)]
+    [ComVisible(true)]
+    public class ComparisonDifferenceCollection : ICollection<ComparisonDifference>
+    {
+        public ComparisonDifference this[int index]
+        {
+            get
+            {
+                return _helper[index];
+            }
+            set
+            {
+                _helper[index] = value;
+            }
+        }
+
+        #region ICollection<ComparisonDifference> Members
+
+        public void Add(ComparisonDifference item)
+        {
+            _helper.Add(item);
+        }
+
+        public void Clear()
+        {
+            _helper.Clear();
+        }
+
+        public bool Contains(ComparisonDifference item)
+        {
+            return _helper.Contains(item);
+        }
+
+        public void CopyTo(ComparisonDifference[] array, int arrayIndex)
+        {
+            _helper.CopyTo(array, arrayIndex);
+        }
+
+        public int Count
+        {
+            get { return _helper.Count; }
+        }
+
+        public bool IsReadOnly
+        {
+            get { return _helper.IsReadOnly; }
+        }
+
+        public bool Remove(ComparisonDifference item)
+        {
+            return _helper.Remove(item);
+        }
+
+        #endregion
+
+        #region IEnumerable<ComparisonDifference> Members
+
+        IEnumerator<ComparisonDifference> IEnumerable<ComparisonDifference>.GetEnumerator()
+        {
+            return _helper.GetEnumerator();
+        }
+
+        #endregion
+
+        #region IEnumerable Members
+
+        public IEnumerator GetEnumerator()
+        {
+            return _helper.GetEnumerator();
+        }
+
+        #endregion
+
+        internal void InternalAdd(ComparisonDifference item)
+        {
+            _helper.InternalAdd(item);
+        }
+
+        private readonly ReadOnlyInteropCollectionHelper<ComparisonDifference> _helper = new ReadOnlyInteropCollectionHelper<ComparisonDifference>();
+    }
+}

+ 2 - 0
source/core/Script.cpp

@@ -1842,6 +1842,8 @@ void __fastcall TScript::SynchronizePreview(
     const TSynchronizeChecklist::TItem * Item = Checklist->Item[Index];
     if (Item->Checked)
     {
+      TDifferenceSessionAction Action(FTerminal->ActionLog, Item);
+
       UnicodeString Message;
       UnicodeString LocalRecord = SynchronizeFileRecord(LocalDirectory, Item, true);
       UnicodeString RemoteRecord = SynchronizeFileRecord(RemoteDirectory, Item, false);

+ 80 - 0
source/core/SessionInfo.cpp

@@ -14,6 +14,7 @@
 #include "TextsCore.h"
 #include "CoreMain.h"
 #include "Script.h"
+#include <System.IOUtils.hpp>
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 //---------------------------------------------------------------------------
@@ -309,6 +310,59 @@ public:
     FFile = File->Duplicate(true);
   }
 
+  void __fastcall SynchronizeChecklistItem(const TSynchronizeChecklist::TItem * Item)
+  {
+    UnicodeString Action;
+    bool RecordLocal = false;
+    bool RecordRemote = false;
+    switch (Item->Action)
+    {
+      case TSynchronizeChecklist::saUploadNew:
+        Action = L"uploadnew";
+        RecordLocal = true;
+        break;
+      case TSynchronizeChecklist::saDownloadNew:
+        Action = L"downloadnew";
+        RecordRemote = true;
+        break;
+      case TSynchronizeChecklist::saUploadUpdate:
+        Action = L"uploadupdate";
+        RecordLocal = true;
+        RecordRemote = true;
+        break;
+      case TSynchronizeChecklist::saDownloadUpdate:
+        Action = L"downloadupdate";
+        RecordLocal = true;
+        RecordRemote = true;
+        break;
+      case TSynchronizeChecklist::saDeleteRemote:
+        Action = L"deleteremote";
+        RecordRemote = true;
+        break;
+      case TSynchronizeChecklist::saDeleteLocal:
+        Action = L"deletelocal";
+        RecordLocal = true;
+        break;
+      default:
+        DebugFail();
+        break;
+    }
+
+    Parameter(L"action", Action);
+
+    if (RecordLocal)
+    {
+      UnicodeString FileName = TPath::Combine(Item->Local.Directory, Item->Local.FileName);
+      SynchronizeChecklistItemFileInfo(FileName, Item->IsDirectory, Item->Local);
+    }
+    if (RecordRemote)
+    {
+      UnicodeString FileName = UnixCombinePaths(Item->Remote.Directory, Item->Remote.FileName);
+      SynchronizeChecklistItemFileInfo(FileName, Item->IsDirectory, Item->Remote);
+    }
+  }
+
+
 protected:
   enum TState { Opened, Committed, RolledBack, Cancelled };
 
@@ -336,6 +390,7 @@ protected:
       case laStat: return L"stat";
       case laChecksum: return L"checksum";
       case laCwd: return L"cwd";
+      case laDifference: return L"difference";
       default: DebugFail(); return L"";
     }
   }
@@ -374,6 +429,21 @@ protected:
     FLog->AddIndented(Indent + L"</file>");
   }
 
+  void __fastcall SynchronizeChecklistItemFileInfo(
+    const UnicodeString & AFileName, bool IsDirectory, const TSynchronizeChecklist::TItem::TFileInfo FileInfo)
+  {
+    Parameter(L"type", XmlAttributeEscape(IsDirectory ? L'D' : L'-'));
+    FileName(XmlAttributeEscape(AFileName));
+    if (!IsDirectory)
+    {
+      Parameter(L"size", XmlAttributeEscape(IntToStr(FileInfo.Size)));
+    }
+    if (FileInfo.ModificationFmt != mfNone)
+    {
+      Modification(FileInfo.Modification);
+    }
+  }
+
 private:
   TActionLog * FLog;
   TLogAction FAction;
@@ -658,6 +728,16 @@ __fastcall TCwdSessionAction::TCwdSessionAction(TActionLog * Log, const UnicodeS
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+__fastcall TDifferenceSessionAction::TDifferenceSessionAction(TActionLog * Log, const TSynchronizeChecklist::TItem * Item) :
+  TSessionAction(Log, laDifference)
+{
+  if (FRecord != NULL)
+  {
+    FRecord->SynchronizeChecklistItem(Item);
+  }
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
 TSessionInfo::TSessionInfo()
 {
   LoginTime = Now();

+ 7 - 1
source/core/SessionInfo.h

@@ -82,7 +82,7 @@ enum TLogLineType { llOutput, llInput, llStdError, llMessage, llException };
 enum TLogAction
 {
   laUpload, laDownload, laTouch, laChmod, laMkdir, laRm, laMv, laCp, laCall, laLs,
-  laStat, laChecksum, laCwd
+  laStat, laChecksum, laCwd, laDifference
 };
 //---------------------------------------------------------------------------
 enum TCaptureOutputType { cotOutput, cotError, cotExitCode };
@@ -228,6 +228,12 @@ public:
   __fastcall TCwdSessionAction(TActionLog * Log, const UnicodeString & Path);
 };
 //---------------------------------------------------------------------------
+class TDifferenceSessionAction : public TSessionAction
+{
+public:
+  __fastcall TDifferenceSessionAction(TActionLog * Log, const TSynchronizeChecklist::TItem * Item);
+};
+//---------------------------------------------------------------------------
 class TSessionLog
 {
 public: