Browse Source

数据迁移工具

fly 1 year ago
parent
commit
c552b72256
34 changed files with 2053 additions and 0 deletions
  1. 73 0
      Tools/Flyinsono.DBCopy.Tool/App.xaml
  2. 27 0
      Tools/Flyinsono.DBCopy.Tool/App.xaml.cs
  3. 134 0
      Tools/Flyinsono.DBCopy.Tool/AppManager.cs
  4. 10 0
      Tools/Flyinsono.DBCopy.Tool/AssemblyInfo.cs
  5. 47 0
      Tools/Flyinsono.DBCopy.Tool/ClientTestManager.cs
  6. 159 0
      Tools/Flyinsono.DBCopy.Tool/Extensions/BoolToVisibilityConverterExtension.cs
  7. 45 0
      Tools/Flyinsono.DBCopy.Tool/Extensions/LogLevelToColorConvertExtension.cs
  8. 45 0
      Tools/Flyinsono.DBCopy.Tool/Flyinsono.DBCopy.Tool.csproj
  9. 135 0
      Tools/Flyinsono.DBCopy.Tool/Log/DefaultLogEngine.cs
  10. 24 0
      Tools/Flyinsono.DBCopy.Tool/Log/LogEngine.cs
  11. 65 0
      Tools/Flyinsono.DBCopy.Tool/Log/LogEngineImplement.cs
  12. 46 0
      Tools/Flyinsono.DBCopy.Tool/Log/LogEntity.cs
  13. 12 0
      Tools/Flyinsono.DBCopy.Tool/Log/LogLevel.cs
  14. 33 0
      Tools/Flyinsono.DBCopy.Tool/Log/LogMessage.cs
  15. 7 0
      Tools/Flyinsono.DBCopy.Tool/Log/LogServiceType.cs
  16. 55 0
      Tools/Flyinsono.DBCopy.Tool/Log/Logger.cs
  17. 54 0
      Tools/Flyinsono.DBCopy.Tool/MainWindow.xaml
  18. 55 0
      Tools/Flyinsono.DBCopy.Tool/MainWindow.xaml.cs
  19. 63 0
      Tools/Flyinsono.DBCopy.Tool/Properties/Resources.Designer.cs
  20. 101 0
      Tools/Flyinsono.DBCopy.Tool/Properties/Resources.resx
  21. 26 0
      Tools/Flyinsono.DBCopy.Tool/Properties/Settings.Designer.cs
  22. 6 0
      Tools/Flyinsono.DBCopy.Tool/Properties/Settings.settings
  23. 8 0
      Tools/Flyinsono.DBCopy.Tool/Properties/launchSettings.json
  24. 43 0
      Tools/Flyinsono.DBCopy.Tool/RpcService/RpcProxy.cs
  25. 35 0
      Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/ExecuteWorker.cs
  26. 57 0
      Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/ExecutingStatus.cs
  27. 203 0
      Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/ExecutorBase.cs
  28. 10 0
      Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/IIdContext.cs
  29. 76 0
      Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/SequenceExecutor.cs
  30. 32 0
      Tools/Flyinsono.DBCopy.Tool/ViewModels/ButtonCommand.cs
  31. 69 0
      Tools/Flyinsono.DBCopy.Tool/ViewModels/Command.cs
  32. 150 0
      Tools/Flyinsono.DBCopy.Tool/ViewModels/MainWindowViewModel.cs
  33. 61 0
      Tools/Flyinsono.DBCopy.Tool/ViewModels/NotificationObject.cs
  34. 87 0
      Tools/Flyinsono.DBCopy.Tool/ViewModels/ViewModel.cs

+ 73 - 0
Tools/Flyinsono.DBCopy.Tool/App.xaml

@@ -0,0 +1,73 @@
+<Application x:Class="Flyinsono.DBCopy.Tool.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:Flyinsono.DBCopy.Tool"
+             StartupUri="MainWindow.xaml">
+    <Application.Resources>
+        <Style TargetType="Button">
+            <Setter Property="Margin" Value="7"/>
+            <Setter Property="Width" Value="80"/>
+        </Style>
+
+        <Style TargetType="TextBox">
+            <Setter Property="Margin" Value="7"/>
+            <Setter Property="MinWidth" Value="120"/>
+        </Style>
+
+        <Style x:Key="CommonButtonStyle" TargetType="Button" >
+            <Setter Property="Margin" Value="7"/>
+            <Setter Property="Width" Value="80"/>
+            <Setter Property="Command" Value="{Binding}"/>
+            <Setter Property="BorderBrush" Value="#4cae4c"/>
+            <Setter Property="Background" Value="#5cb85c"/>
+            <Setter Property="Foreground" Value="#fff"/>
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="Button">
+                        <Border CornerRadius="4" Name="container" Cursor="Hand" Padding="{TemplateBinding Padding}" 
+                            BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"
+                            Background="{TemplateBinding Background}">
+                            <ContentPresenter ContentSource="{TemplateBinding Content}" 
+                            ContentTemplate="{TemplateBinding ContentTemplate}"
+                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
+                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
+                            <VisualStateManager.VisualStateGroups>
+                                <VisualStateGroup Name="CommonStates">
+                                    <VisualState Name="Normal">
+
+                                    </VisualState>
+                                    <VisualState Name="MouseOver">
+                                        <Storyboard>
+                                            <ColorAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" 
+                                                        Duration="0:0:0.02" To="#449d44"></ColorAnimation>
+                                            <ColorAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" 
+                                                        Duration="0:0:0.02" To="#398439"></ColorAnimation>
+                                        </Storyboard>
+                                    </VisualState>
+                                    <VisualState Name="Pressed">
+                                        <Storyboard>
+                                            <ColorAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" 
+                                                        Duration="0:0:0.02" To="#398439"></ColorAnimation>
+                                            <ColorAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" 
+                                                        Duration="0:0:0.02" To="#255625"></ColorAnimation>
+                                        </Storyboard>
+                                    </VisualState>
+                                    <VisualState Name="Disabled">
+                                        <Storyboard>
+                                            <ColorAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" 
+                                                        Duration="0:0:0.02" To="#5cb85c"></ColorAnimation>
+                                            <ColorAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" 
+                                                        Duration="0:0:0.02" To="#4cae4c"></ColorAnimation>
+                                            <DoubleAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="Opacity"
+                                                         Duration="0:0:0.02" To="0.7"></DoubleAnimation>
+                                        </Storyboard>
+                                    </VisualState>
+                                </VisualStateGroup>
+                            </VisualStateManager.VisualStateGroups>
+                        </Border>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+    </Application.Resources>
+</Application>

+ 27 - 0
Tools/Flyinsono.DBCopy.Tool/App.xaml.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Threading;
+using Flyinsono.DBCopy.Tool;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    /// <summary>
+    /// Interaction logic for App.xaml
+    /// </summary>
+    public partial class App : Application
+    {
+        public static Dispatcher MainDispatcher;
+        protected override void OnStartup(StartupEventArgs e)
+        {
+            MainDispatcher = Dispatcher.CurrentDispatcher;
+            DefaultLogEngine.DataFolder = AppDomain.CurrentDomain.BaseDirectory;
+            
+            base.OnStartup(e);
+        }
+    }
+}

+ 134 - 0
Tools/Flyinsono.DBCopy.Tool/AppManager.cs

@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    public interface IClientManager : IDisposable
+    {
+
+    }
+
+    public class AppManager
+    {
+      
+
+        
+        private static bool _initialized;
+        private static AppManager _instance;
+        private readonly Dictionary<Type, IClientManager> _managers = new Dictionary<Type, IClientManager>();
+
+        /// <summary>
+        /// The unique instance of app manager
+        /// </summary>
+        public static AppManager Instance => _instance ?? (_instance = new AppManager());
+
+       
+
+       
+
+        /// <summary>
+		/// Register a manager with type and instance
+		/// </summary>
+		/// <param name="instance">The manager instance</param>
+		public void RegisterManager<T>(T instance) where T : IClientManager
+        {
+            var type = typeof(T);
+            if (_managers.ContainsKey(type))
+            {
+                throw new DuplicateNameException($"Type {type.Name} already exists.");
+            }
+            _managers[type] = instance;
+            Logger.WriteLineInfo($"AppManager Manager {type.Name} registerd");
+        }
+
+        
+
+        /// <summary>
+        /// Unregister a manager from app manager
+        /// </summary>        
+        void UnRegisterManager<T>() where T : IClientManager
+        {
+            var type = typeof(T);
+            if (!_managers.ContainsKey(type))
+            {
+                throw new DuplicateNameException($"Type {type} does not exists.");
+            }
+            _managers[type].Dispose();
+            _managers.Remove(type);
+        }
+
+        /// <summary>
+        /// dispose all managers
+        /// </summary>
+        public void DisposeAllManagers()
+        {
+            foreach (var manager in _managers.Reverse())
+            {
+                try
+                {
+                    Logger.WriteLineInfo($"Disposing - {manager.Key?.Name}");
+                    manager.Value.Dispose();
+                    Logger.WriteLineInfo($"Disposed - {manager.Key?.Name}");
+                }
+                catch (Exception e)
+                {
+                    Logger.WriteLineError($"Dispose Manager {manager.Key} failed, ex: {e}");
+                }
+            }
+        }
+
+        /// <summary>
+        /// Get Manager by specified type
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns></returns>
+        public T GetManager<T>() where T : IClientManager
+        {
+            var type = typeof(T);
+            if (_managers.ContainsKey(type))
+            {
+                return (T)_managers[type];
+            }
+            throw new NullReferenceException($"Can not find instance for type{type}");
+        }
+
+        /// <summary>
+        /// Called at client start
+        /// </summary>
+        public void Initialize()
+        {
+            try
+            {
+                Logger.WriteLineInfo("AppManager Initialize begin");
+                               
+                RegisterCommonManagers();
+
+            }
+            catch (Exception e)
+            {
+                Logger.WriteLineError($"Initialize Error:{e}");
+            }
+            
+            Logger.WriteLineInfo("AppManager Initialize end");
+        }
+
+        
+        private void RegisterCommonManagers()
+        {
+            if (_initialized)
+            {
+                Logger.WriteLineInfo("Has been Initialized");
+                return;
+            }
+                  
+            RegisterManager<IClientTestManager>(new ClientTestManager());
+           
+            Logger.WriteLineInfo("AppManager RegisterPluginCreators Register end");
+            _initialized = true;
+        }        
+        
+    }   
+}

+ 10 - 0
Tools/Flyinsono.DBCopy.Tool/AssemblyInfo.cs

@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+                                     //(used if a resource is not found in the page,
+                                     // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+                                              //(used if a resource is not found in the page,
+                                              // app, or any theme specific resource dictionaries)
+)]

+ 47 - 0
Tools/Flyinsono.DBCopy.Tool/ClientTestManager.cs

@@ -0,0 +1,47 @@
+using Flyinsono.DBCopy.Tool.RpcService;
+using JsonRpcLite.Network;
+using JsonRpcLite.Rpc;
+using System;
+using System.Net;
+using WingInterfaceLibrary.Enum;
+using WingInterfaceLibrary.Interface;
+using WingInterfaceLibrary.Request.User;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    internal interface IClientTestManager : IClientManager
+    {
+        /// <summary>
+        /// Run the test cases
+        /// </summary>
+        void Run(string remoteUrl);
+    }
+    internal class ClientTestManager : IClientTestManager
+    {
+        public async void Run(string remoteUrl)
+        {
+            try {
+                //var clientEngine = new JsonRpcHttpClientEngine(remoteUrl);
+                //var client = new JsonRpcClient();
+                //client.UseEngine(clientEngine);
+                //var proxy = client.CreateProxy<ILoginService>();
+                JsonRpcProxy jrp = new JsonRpcProxy(remoteUrl);
+                var request = new CommonLoginRequest();
+                request.LoginSource = LoginSource.PC;
+                request.AnyAccount = "fly11";
+                request.Password = "7a9da7f9ffc92a1d9e3b9d87137eb3c1";
+                var result = await jrp.Login.CommonLoginAsync(request);
+                Logger.WriteLineInfo($"result:{result.AccountName}- {result.LoginState} - {result.Token}");
+            }
+            catch(Exception ex)
+            {
+                Logger.WriteLineError($"Test run ex:{ex}");
+            }
+            
+        }
+        public void Dispose()
+        {
+            //throw new System.NotImplementedException();
+        }
+    }
+}

+ 159 - 0
Tools/Flyinsono.DBCopy.Tool/Extensions/BoolToVisibilityConverterExtension.cs

@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Markup;
+
+namespace Flyinsono.DBCopy.Tool.Extensions
+{
+    [Flags]
+    public enum BoolToVisibilityEnums
+    {
+        /// <summary>
+        /// True=Visible,False=Collapsed
+        /// </summary>
+        None = 0x0,
+        /// <summary>
+        /// True=invisible,False=visible
+        /// </summary>
+        Revert = 0x2,
+        /// <summary>
+        /// invisible=Visibility.Hidden
+        /// </summary>
+        InvisibleHidden = 0x4,
+
+        RevertHidden = Revert | InvisibleHidden
+    }
+
+    [MarkupExtensionReturnType(typeof(IValueConverter))]
+    public class BoolToVisibilityConverterExtension : MarkupExtension, IValueConverter, IMultiValueConverter
+    {
+        #region Properties 
+
+        [ThreadStatic]
+        private static BoolToVisibilityConverterExtension _converter;
+
+        public static BoolToVisibilityConverterExtension Converter
+        {
+            get { return _converter ?? (_converter = new BoolToVisibilityConverterExtension()); }
+        }
+
+        #endregion Properties 
+
+        #region Methods 
+
+        // Constructors 
+
+        public BoolToVisibilityConverterExtension()
+        {
+
+        }
+        // Methods 
+
+        public override object ProvideValue(IServiceProvider serviceProvider)
+        {
+            return Converter;
+        }
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var boolValue = ToBool(value);
+            bool isRevert;
+            Visibility invisible;
+
+            TryParseParameter(parameter, out invisible, out isRevert);
+
+            if (isRevert)
+            {
+                boolValue = !boolValue;
+            }
+            return boolValue ? Visibility.Visible : invisible;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var visibility = (Visibility)value;
+            return visibility == Visibility.Visible;
+        }
+
+
+        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            bool boolValue = false;
+            if (values != null && values.Length > 0)
+            {
+                bool hasInvisible = false;
+                foreach (var value in values)
+                {
+                    if (!ToBool(value))
+                    {
+                        hasInvisible = true;
+                        break;
+                    }
+                }
+                boolValue = !hasInvisible;
+            }
+            bool isRevert;
+            Visibility invisible;
+
+            TryParseParameter(parameter, out invisible, out isRevert);
+
+            if (isRevert)
+            {
+                boolValue = !boolValue;
+            }
+            return boolValue ? Visibility.Visible : invisible;
+        }
+
+        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+
+        private bool ToBool(object value)
+        {
+            var boolValue = false;
+            if (value != null)
+            {
+                if (value is Visibility)
+                {
+                    boolValue = (Visibility)value == Visibility.Visible;
+                }
+                else
+                {
+                    string valueInString = value.ToString();
+
+                    if (string.IsNullOrEmpty(valueInString) || !bool.TryParse(valueInString, out boolValue))
+                    {
+                        boolValue = false;
+                    }
+                }
+            }
+            return boolValue;
+        }
+
+        private void TryParseParameter(object parameter, out Visibility invisible, out bool isRevert)
+        {
+            isRevert = false;
+            invisible = Visibility.Collapsed;
+            if (parameter is BoolToVisibilityEnums)
+            {
+                BoolToVisibilityEnums options = (BoolToVisibilityEnums)parameter;
+                if ((options & BoolToVisibilityEnums.Revert) != 0)
+                {
+                    isRevert = true;
+                }
+                if ((options & BoolToVisibilityEnums.InvisibleHidden) != 0)
+                {
+                    invisible = Visibility.Hidden;
+                }
+            }
+        }
+
+        #endregion Methods 
+
+
+    }
+}

+ 45 - 0
Tools/Flyinsono.DBCopy.Tool/Extensions/LogLevelToColorConvertExtension.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Markup;
+using Brushes = System.Windows.Media.Brushes;
+
+namespace Flyinsono.DBCopy.Tool.Extensions
+{
+    public class LogLevelToColorConvertExtension : MarkupExtension, IValueConverter
+    {
+        [ThreadStatic]
+        private static LogLevelToColorConvertExtension _converter;
+
+        public override object ProvideValue(IServiceProvider serviceProvider)
+        {
+            return _converter ?? (_converter = new LogLevelToColorConvertExtension());
+        }
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value != null)
+            {
+                if (Enum.TryParse(value.ToString(), out LogLevel level))
+                {
+                    switch (level)
+                    {
+                        case LogLevel.Info:
+                            return Brushes.Blue;
+                        case LogLevel.Warn:
+                            return Brushes.Goldenrod;
+                        case LogLevel.Error:
+                            return Brushes.Red;
+                    }
+                }
+            }
+
+            return Brushes.Black;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 45 - 0
Tools/Flyinsono.DBCopy.Tool/Flyinsono.DBCopy.Tool.csproj

@@ -0,0 +1,45 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net6.0-windows</TargetFramework>
+    <Nullable>enable</Nullable>
+    <UseWPF>true</UseWPF>
+    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+    <Platforms>AnyCPU;x64</Platforms>
+  </PropertyGroup>
+
+	<ItemGroup>
+		<PackageReference Include="JsonRpcLite" Version="1.1.0.4" />
+		<PackageReference Include="WingInterfaceLibrary" Version="1.1.4.1" />
+		<PackageReference Include="WingServerCommon" Version="1.1.4.1" />
+	</ItemGroup>
+
+	<ItemGroup>
+	  <Compile Update="Properties\Resources.Designer.cs">
+	    <DesignTime>True</DesignTime>
+	    <AutoGen>True</AutoGen>
+	    <DependentUpon>Resources.resx</DependentUpon>
+	  </Compile>
+	  <Compile Update="Properties\Settings.Designer.cs">
+	    <DesignTimeSharedInput>True</DesignTimeSharedInput>
+	    <AutoGen>True</AutoGen>
+	    <DependentUpon>Settings.settings</DependentUpon>
+	  </Compile>
+	</ItemGroup>
+
+	<ItemGroup>
+	  <EmbeddedResource Update="Properties\Resources.resx">
+	    <Generator>ResXFileCodeGenerator</Generator>
+	    <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+	  </EmbeddedResource>
+	</ItemGroup>
+
+	<ItemGroup>
+	  <None Update="Properties\Settings.settings">
+	    <Generator>SettingsSingleFileGenerator</Generator>
+	    <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+	  </None>
+	</ItemGroup>
+
+</Project>

+ 135 - 0
Tools/Flyinsono.DBCopy.Tool/Log/DefaultLogEngine.cs

@@ -0,0 +1,135 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    public class DefaultLogEngine : LogEngine
+    {
+        private const string InfoTag = "I ; ";
+        private const string DebguTag = "D ; ";
+        private const string WarnTag = "W ; ";
+        private const string ErrorTag = "E ; ";
+        private const string VerboseTag = "V ; ";
+        private const string UserOperationTag = "U ; ";
+
+        private StreamWriter _logWriter;
+
+        private readonly object _mutex = new object();
+        public static string DataFolder;
+        private DateTime _date;
+        private bool _disposed;
+       
+        public DefaultLogEngine()
+        {            
+        }
+
+        ~DefaultLogEngine()
+        {
+            //Dispose the StreamWriter when dispose.
+            DoDispose();
+        }
+
+        private void DoDispose()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+            }
+        }
+
+        protected void InitializeLogger()
+        {
+            var logDirectory = Path.Combine(DataFolder, "Logs");
+            if (!Directory.Exists(logDirectory))
+            {
+                Directory.CreateDirectory(logDirectory);
+            }
+            var now = DateTime.Now;
+            var logTag = $"{now:yyyyMMddHHmmss}_{now.Millisecond:d3}";
+            var logFileName = $"Log_{logTag}.log";
+            var logFilePath = Path.Combine(logDirectory, logFileName);
+            if (!File.Exists(logFilePath))
+            {
+                _logWriter?.Close();
+                var logFileStream = new FileStream(logFilePath,FileMode.Create,FileAccess.ReadWrite,FileShare.ReadWrite);
+                _logWriter = new StreamWriter(logFileStream);
+            }
+            else
+            {
+                _logWriter?.Close();
+                var logFileStream = new FileStream(logFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
+                logFileStream.Position = logFileStream.Length;
+                _logWriter = new StreamWriter(logFileStream);
+            }
+        }
+
+        public override void Write(LogLevel level, string msg)
+        {
+            lock (_mutex)
+            {
+                if (DateTime.Today != _date)
+                {
+                    InitializeLogger();
+                    _date = DateTime.Today;
+                }
+                try
+                {
+                    var message = RecombinateMessage(level, msg);
+                    WriteMessage(message);
+                }
+                catch (Exception)
+                {
+                    // There is exception during writing log, so just ignore
+                }
+            }
+        }
+
+        protected void WriteMessage(string message)
+        {
+            _logWriter.WriteLine(message);
+            _logWriter.Flush();
+        }
+
+        protected string RecombinateMessage(LogLevel level, string msg)
+        {
+            var now = DateTime.Now;
+            StringBuilder sb = new StringBuilder();
+            sb.Append(
+                $"[{now:yyyyMMddHHmmss},{now.Millisecond:d3}]-");
+            string message = msg;
+            if (level == LogLevel.Error)
+            {
+                sb.Append(ErrorTag);
+            }
+            else if (level == LogLevel.Warn)
+            {
+                sb.Append(WarnTag);
+            }
+            else if (level == LogLevel.Info)
+            {
+                sb.Append(InfoTag);
+            }
+            else if (level == LogLevel.Verbose)
+            {
+                sb.Append(VerboseTag);
+            }
+            else if (level == LogLevel.Debug)
+            {
+                sb.Append(DebguTag);
+            }
+            else if (level == LogLevel.UserOperation)
+            {
+                sb.Append(UserOperationTag);
+            }
+            sb.Append(message);
+
+            return sb.ToString();
+        }
+        public override void Dispose()
+        {
+            DoDispose();
+            GC.SuppressFinalize(this);
+        }
+    }
+}

+ 24 - 0
Tools/Flyinsono.DBCopy.Tool/Log/LogEngine.cs

@@ -0,0 +1,24 @@
+using System;
+
+namespace Flyinsono.DBCopy.Tool
+{
+
+    /// <summary>
+    /// The log engine which provider the abilaity to Write log into one file or server.
+    /// </summary>
+    public abstract class LogEngine: IDisposable
+    {
+        /// <summary>
+        /// Write the log
+        /// </summary>
+        /// <param name="level">Log's level see <see cref="LogLevel"/></param>
+        /// <param name="msg">The message to write</param>
+        public abstract void Write(LogLevel level, string msg);
+
+        public virtual void Dispose()
+        {
+            //Nothing.
+        }
+    }
+
+}

+ 65 - 0
Tools/Flyinsono.DBCopy.Tool/Log/LogEngineImplement.cs

@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Flyinsono.DBCopy.Tool.Utilities.Executors;
+
+namespace Flyinsono.DBCopy.Tool.Log
+{
+    public class LogItem
+    {
+        /// <summary>
+        /// Log level
+        /// </summary>
+        public LogLevel LogLevel { get; set; }
+
+        /// <summary>
+        /// Log content
+        /// </summary>
+        public string Content { get; set; }
+    }
+
+    public class LogEngineImplement : DefaultLogEngine
+    {
+        private SequenceExecutor<string> _logSequenceExecutor = new SequenceExecutor<string>("LogSequenceExecutor");
+        private DateTime _today;
+
+        Action<LogItem> _actionShowLogger;
+        public LogEngineImplement(Action<LogItem> action)
+        {
+            _actionShowLogger = action;
+        }
+
+        public override void Write(LogLevel level, string msg)
+        {
+            var message = RecombinateMessage(level, msg);
+            _logSequenceExecutor.Add(ExecuteWrite, message);
+            _actionShowLogger?.Invoke(new LogItem() { LogLevel = level, Content = message });
+        }
+
+        private bool ExecuteWrite(string message)
+        {
+            try
+            {
+                if (DateTime.Today != _today)
+                {
+                    InitializeLogger();
+                    _today = DateTime.Today;
+                }
+                WriteMessage(message);
+            }
+            catch
+            {
+            }
+
+            return true;
+        }
+
+        public override void Dispose()
+        {
+            _logSequenceExecutor.Dispose();
+            base.Dispose();
+        }
+    }
+}

+ 46 - 0
Tools/Flyinsono.DBCopy.Tool/Log/LogEntity.cs

@@ -0,0 +1,46 @@
+using System;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    public class LogEntity
+    {
+        /// <summary>
+        /// Gets or sets the default Id for the log.
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets the time of this log.
+        /// </summary>
+        public DateTime Time { get; set; }
+
+        /// <summary>
+        /// Who is log
+        /// </summary>
+        public string Owner { get; set; }
+
+        /// <summary>
+        /// Gets or sets the <see cref="LogLevel"/> of this log.
+        /// </summary>
+        public LogLevel Level { get; set; }
+
+        /// <summary>
+        /// Gets or sets the content of this log.
+        /// </summary>
+        public string Message { get; set; }
+
+
+        public LogEntity()
+        {
+            
+        }
+
+        public LogEntity(DateTime time, LogLevel level, string message)
+        {
+            Id = Guid.NewGuid().ToString();
+            Time = time;
+            Level = level;
+            Message = message;
+        }
+    }
+}

+ 12 - 0
Tools/Flyinsono.DBCopy.Tool/Log/LogLevel.cs

@@ -0,0 +1,12 @@
+namespace Flyinsono.DBCopy.Tool
+{
+    public enum LogLevel
+    {
+        Info,
+        Warn,
+        Error,
+        Verbose,
+        Debug,
+        UserOperation
+    }
+}

+ 33 - 0
Tools/Flyinsono.DBCopy.Tool/Log/LogMessage.cs

@@ -0,0 +1,33 @@
+using System;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    public class LogMessage
+    {
+        //Gets the owner of this log.
+        public string Owner { get; }
+
+        /// <summary>
+        /// Gets the time of this log.
+        /// </summary>
+        public DateTime Time { get; }
+
+        /// <summary>
+        /// Gets the <see cref="LogLevel"/> of this log.
+        /// </summary>
+        public LogLevel Level { get; }
+
+        /// <summary>
+        /// Gets the content of this log.
+        /// </summary>
+        public string Message { get; }
+
+        public LogMessage(string owner, DateTime time, LogLevel level, string message)
+        {
+            Owner = owner;
+            Time = time;
+            Level = level;
+            Message = message;
+        }
+    }
+}

+ 7 - 0
Tools/Flyinsono.DBCopy.Tool/Log/LogServiceType.cs

@@ -0,0 +1,7 @@
+namespace Flyinsono.DBCopy.Tool
+{
+    public enum LogServiceType
+    {
+        Normal
+    }
+}

+ 55 - 0
Tools/Flyinsono.DBCopy.Tool/Log/Logger.cs

@@ -0,0 +1,55 @@
+using System.Threading;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    public class Logger
+    {
+        private static LogEngine _logEngine;
+        private static string _accountName;
+
+        public static bool Debug { get; set; }
+
+        public static void RegisterEngine(LogEngine logEngine)
+        {
+            _logEngine = logEngine;
+        }
+
+        public static void WriteLineInfo(string msg)
+        {
+            _logEngine?.Write(LogLevel.Info, "[Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "]" + msg);
+        }
+
+        public static void WriteLineWarn(string msg)
+        {
+            _logEngine?.Write(LogLevel.Warn, "[Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "]" + msg);
+        }
+
+        public static void WriteLineError(string msg)
+        {
+            _logEngine?.Write(LogLevel.Error, "[Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "]" + msg);
+        }
+
+        public static void WriteUserOperationLog(string msg, string accountName="")
+        {
+            if (string.IsNullOrEmpty(_accountName)&&!string.IsNullOrEmpty(accountName))
+            {
+                _accountName=accountName;
+            }
+            _logEngine?.Write(LogLevel.UserOperation, _accountName+":->"+msg);
+        }
+
+        public static void WriteLineVerbose(string msg)
+        {
+#if DEBUG
+            _logEngine?.Write(LogLevel.Verbose, "[Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "]" + msg);
+#endif
+        }
+
+        public static void WriteLineDebug(string msg)
+        {
+#if DEBUG
+            _logEngine?.Write(LogLevel.Debug, "[Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "]" + msg);
+#endif
+        }
+    }
+}

+ 54 - 0
Tools/Flyinsono.DBCopy.Tool/MainWindow.xaml

@@ -0,0 +1,54 @@
+<Window x:Class="Flyinsono.DBCopy.Tool.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Flyinsono.DBCopy.Tool"
+        xmlns:extensions="clr-namespace:Flyinsono.DBCopy.Tool.Extensions"
+        mc:Ignorable="d"
+        Title="MainWindow" Height="450" Width="800">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="0.9*"/>
+            <RowDefinition Height="0.1*"/>
+        </Grid.RowDefinitions>
+        <GroupBox Header="Log Info"  Margin="7">
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto"/>
+                    <RowDefinition/>
+                </Grid.RowDefinitions>
+                <Button HorizontalAlignment="Right" Margin="0,7" Content="清理日志" Command="{Binding ClearLogCommand}" Style="{StaticResource CommonButtonStyle}"/>
+                <ListBox Grid.Row="1" Margin="7" x:Name="logList" ItemsSource="{Binding LogItems}"  ScrollViewer.HorizontalScrollBarVisibility="Disabled">
+                    <ListBox.ItemTemplate>
+                        <DataTemplate>
+                            <TextBlock Text="{Binding Content}" TextWrapping="Wrap" Foreground="{Binding LogLevel, Converter={extensions:LogLevelToColorConvertExtension}}" />
+                        </DataTemplate>
+                    </ListBox.ItemTemplate>
+                </ListBox>
+            </Grid>
+        </GroupBox>
+        <Grid Grid.Row="1">
+            <Grid.RowDefinitions>
+                <RowDefinition Height="Auto"></RowDefinition>
+                <RowDefinition Height="Auto"></RowDefinition>
+            </Grid.RowDefinitions>
+            <StackPanel Orientation="Horizontal">
+                <Label VerticalAlignment="Center">Host:</Label>
+                <TextBox Text="{Binding HostUrl}"></TextBox>
+                <Label VerticalAlignment="Center">User Count:</Label>
+                <TextBox Text="{Binding UserCount}"></TextBox>
+               
+            </StackPanel>
+
+            <StackPanel Grid.Row="1" Orientation="Horizontal">
+                <Label VerticalAlignment="Center">Message:</Label>
+                <TextBox Text="{Binding Message}" MinWidth="340"></TextBox>
+                <Button Command="{Binding ConnectCommand}">Run</Button>
+                <Button Command="{Binding ExitCommand}">Close</Button>
+            </StackPanel>
+
+        </Grid>
+        
+    </Grid>
+</Window>

+ 55 - 0
Tools/Flyinsono.DBCopy.Tool/MainWindow.xaml.cs

@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Flyinsono.DBCopy.Tool.Log;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    /// <summary>
+    /// Interaction logic for MainWindow.xaml
+    /// </summary>
+    public partial class MainWindow : Window
+    {
+        private MainWindowViewModel _mainWindowViewModel;
+        public MainWindow()
+        {
+            var logEngine = new LogEngineImplement((s) =>
+            {
+                AddLog(s);
+            });
+            Logger.RegisterEngine(logEngine);
+            AppManager.Instance.Initialize();
+            _mainWindowViewModel = new MainWindowViewModel(RunOnMainDispatcher);
+           
+            InitializeComponent();
+            DataContext = _mainWindowViewModel;
+        }
+
+        private void RunOnMainDispatcher(Action action)
+        {
+            App.MainDispatcher.Invoke(action);
+        }
+
+        private void AddLog(LogItem s)
+        {
+            Dispatcher.BeginInvoke(() =>
+            {
+                if (_mainWindowViewModel != null)
+                {
+                    _mainWindowViewModel.LogItems.Add(s);
+
+                }
+            });
+        }
+    }    
+}

+ 63 - 0
Tools/Flyinsono.DBCopy.Tool/Properties/Resources.Designer.cs

@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本:4.0.30319.42000
+//
+//     对此文件的更改可能会导致不正确的行为,并且如果
+//     重新生成代码,这些更改将会丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Flyinsono.DBCopy.Tool.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   一个强类型的资源类,用于查找本地化的字符串等。
+    /// </summary>
+    // 此类是由 StronglyTypedResourceBuilder
+    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+    // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+    // (以 /str 作为命令选项),或重新生成 VS 项目。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   返回此类使用的缓存的 ResourceManager 实例。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Flyinsono.DBCopy.Tool.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   重写当前线程的 CurrentUICulture 属性,对
+        ///   使用此强类型资源类的所有资源查找执行重写。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 101 - 0
Tools/Flyinsono.DBCopy.Tool/Properties/Resources.resx

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+	<!-- 
+		Microsoft ResX Schema
+
+		Version 1.3
+
+		The primary goals of this format is to allow a simple XML format 
+		that is mostly human readable. The generation and parsing of the 
+		various data types are done through the TypeConverter classes 
+		associated with the data types.
+
+		Example:
+
+		... ado.net/XML headers & schema ...
+		<resheader name="resmimetype">text/microsoft-resx</resheader>
+		<resheader name="version">1.3</resheader>
+		<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+		<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+		<data name="Name1">this is my long string</data>
+		<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+		<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+			[base64 mime encoded serialized .NET Framework object]
+		</data>
+		<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+			[base64 mime encoded string representing a byte array form of the .NET Framework object]
+		</data>
+
+		There are any number of "resheader" rows that contain simple 
+		name/value pairs.
+
+		Each data row contains a name, and value. The row also contains a 
+		type or mimetype. Type corresponds to a .NET class that support 
+		text/value conversion through the TypeConverter architecture. 
+		Classes that don't support this are serialized and stored with the 
+		mimetype set.
+
+		The mimetype is used for serialized objects, and tells the 
+		ResXResourceReader how to depersist the object. This is currently not 
+		extensible. For a given mimetype the value must be set accordingly:
+
+		Note - application/x-microsoft.net.object.binary.base64 is the format 
+		that the ResXResourceWriter will generate, however the reader can 
+		read any of the formats listed below.
+
+		mimetype: application/x-microsoft.net.object.binary.base64
+		value   : The object must be serialized with 
+			: System.Serialization.Formatters.Binary.BinaryFormatter
+			: and then encoded with base64 encoding.
+
+		mimetype: application/x-microsoft.net.object.soap.base64
+		value   : The object must be serialized with 
+			: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+			: and then encoded with base64 encoding.
+
+		mimetype: application/x-microsoft.net.object.bytearray.base64
+		value   : The object must be serialized into a byte array 
+			: using a System.ComponentModel.TypeConverter
+			: and then encoded with base64 encoding.
+	-->
+	
+	<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+		<xsd:element name="root" msdata:IsDataSet="true">
+			<xsd:complexType>
+				<xsd:choice maxOccurs="unbounded">
+					<xsd:element name="data">
+						<xsd:complexType>
+							<xsd:sequence>
+								<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+								<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+							</xsd:sequence>
+							<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+							<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+							<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+						</xsd:complexType>
+					</xsd:element>
+					<xsd:element name="resheader">
+						<xsd:complexType>
+							<xsd:sequence>
+								<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+							</xsd:sequence>
+							<xsd:attribute name="name" type="xsd:string" use="required" />
+						</xsd:complexType>
+					</xsd:element>
+				</xsd:choice>
+			</xsd:complexType>
+		</xsd:element>
+	</xsd:schema>
+	<resheader name="resmimetype">
+		<value>text/microsoft-resx</value>
+	</resheader>
+	<resheader name="version">
+		<value>1.3</value>
+	</resheader>
+	<resheader name="reader">
+		<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+	</resheader>
+	<resheader name="writer">
+		<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+	</resheader>
+</root>

+ 26 - 0
Tools/Flyinsono.DBCopy.Tool/Properties/Settings.Designer.cs

@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     此代码由工具生成。
+//     运行时版本:4.0.30319.42000
+//
+//     对此文件的更改可能会导致不正确的行为,并且如果
+//     重新生成代码,这些更改将会丢失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Flyinsono.DBCopy.Tool.Properties {
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default {
+            get {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 6 - 0
Tools/Flyinsono.DBCopy.Tool/Properties/Settings.settings

@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+</SettingsFile>

+ 8 - 0
Tools/Flyinsono.DBCopy.Tool/Properties/launchSettings.json

@@ -0,0 +1,8 @@
+{
+  "profiles": {
+    "Flyinsono.DBCopy.Tool": {
+      "commandName": "Project",
+      "nativeDebugging": true
+    }
+  }
+}

+ 43 - 0
Tools/Flyinsono.DBCopy.Tool/RpcService/RpcProxy.cs

@@ -0,0 +1,43 @@
+using JsonRpcLite.Network;
+using JsonRpcLite.Rpc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WingInterfaceLibrary.Interface;
+
+namespace Flyinsono.DBCopy.Tool.RpcService
+{
+    public class JsonRpcProxy
+    {
+
+        private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
+        private JsonRpcClient _client = new JsonRpcClient();
+
+        public JsonRpcProxy(string remoteUrl)
+        {
+
+            var clientEngine = new JsonRpcHttpClientEngine(remoteUrl);
+            _client = new JsonRpcClient();
+            _client.UseEngine(clientEngine);
+        }
+
+        public ILoginService Login => GetOrCreateCachedProxy<ILoginService>();
+
+        public IVinnoServerService VinnoServer => GetOrCreateCachedProxy<IVinnoServerService>();
+
+        public void ClearCache() => _serviceCache.Clear();
+
+        private T GetOrCreateCachedProxy<T>()
+        {
+            var serviceType = typeof(T);
+            if (!_serviceCache.ContainsKey(serviceType))
+            {
+                _serviceCache[serviceType] = _client.CreateProxy<T>();
+            }
+            return (T)_serviceCache[serviceType];
+        }
+
+    }
+}

+ 35 - 0
Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/ExecuteWorker.cs

@@ -0,0 +1,35 @@
+using System;
+
+namespace Flyinsono.DBCopy.Tool.Utilities.Executors
+{
+    public class ExecuteWorker<T> where T : class
+    {
+        public T Parameter { get; }
+
+        private readonly Func<T, bool> _executeMethod;
+
+        /// <summary>
+        /// Run the Action of this worker.
+        /// </summary>
+        ///<returns>If False the follow workers will not run, if ture it will be continue run.</returns>
+        public bool Run()
+        {
+            if (_executeMethod != null)
+            {
+                return _executeMethod(Parameter);
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Run the worker.
+        /// </summary>
+        /// <param name="executeMethod">The run func implementation.</param>
+        /// <param name="parameter">The parameter which will pass to the func.</param>
+        public ExecuteWorker(Func<T, bool> executeMethod, T parameter)
+        {
+            Parameter = parameter;
+            _executeMethod = executeMethod;
+        }
+    }
+}

+ 57 - 0
Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/ExecutingStatus.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Threading;
+
+namespace Flyinsono.DBCopy.Tool.Utilities.Executors
+{
+    public class ExecutingStatus
+    {
+        private readonly ManualResetEvent _executingEvent = new ManualResetEvent(true);
+        private volatile bool _executing;
+
+        /// <summary>
+        /// Gets the status if is executing.
+        /// </summary>
+        public bool Executing
+        {
+            get => _executing;
+            private set => _executing = value;
+        }
+
+        public ExecutingStatus()
+        {
+            _executingEvent.Set();
+            Executing = false;
+        }
+
+        /// <summary>
+        /// Reset the status.
+        /// </summary>
+        public void Reset()
+        {
+            _executingEvent.Reset();
+            Executing = true;
+        }
+
+        /// <summary>
+        /// Set the status.
+        /// </summary>
+        public void Set()
+        {
+            Executing = false;
+            _executingEvent.Set();
+        }
+
+        /// <summary>
+        /// Wait for the status to set.
+        /// </summary>
+        public void Wait()
+        {
+            _executingEvent.WaitOne();
+        }
+
+        public void Wait(double secondsTimeout)
+        {
+            _executingEvent.WaitOne(TimeSpan.FromSeconds(secondsTimeout));
+        }
+    }
+}

+ 203 - 0
Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/ExecutorBase.cs

@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Flyinsono.DBCopy.Tool.Utilities.Executors
+{
+    public abstract class ExecutorBase<T> where T : class
+    {
+        private readonly TimeSpan _executeDelayTime;
+        protected readonly object WorkersLocker = new object();
+        private volatile bool _closed;
+        private readonly ExecutingStatus _executingStatus = new ExecutingStatus();
+        private readonly ManualResetEvent _delayManualResetEvent = new ManualResetEvent(false);
+
+        /// <summary>
+        /// Gets the name of this executor.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets all workers count.
+        /// </summary>
+        public int WorkerCount => GetWorkerCount();
+
+        /// <summary>
+        /// Indicate this executor is closed
+        /// </summary>
+        public bool IsClosed => _closed;
+
+        protected ExecutorBase(string name)
+        {
+            _executeDelayTime = TimeSpan.MinValue;
+            Name = name;
+        }
+
+        protected ExecutorBase(string name, TimeSpan executeDelayTime) : this(name)
+        {
+            _executeDelayTime = executeDelayTime;
+        }
+
+        /// <summary>
+        /// Gets current worker count in queue.
+        /// </summary>
+        /// <returns></returns>
+        protected abstract int GetWorkerCount();
+
+        /// <summary>
+        /// Add one worker into the queue.
+        /// </summary>
+        /// <param name="worker"></param>
+        protected abstract void AddWorker(ExecuteWorker<T> worker);
+
+        /// <summary>
+        /// Take one worker from the queue.
+        /// </summary>
+        /// <returns></returns>
+        protected abstract ExecuteWorker<T> TakeWorker();
+
+        /// <summary>
+        /// Gets all workers.
+        /// </summary>
+        /// <returns></returns>
+        protected abstract IEnumerable<ExecuteWorker<T>> GetWorkers();
+
+        /// <summary>
+        /// Clear all workers which not stated.
+        /// </summary>
+        protected abstract void CancelWorkers();
+
+        /// <summary>
+        /// Add one execute func into the queue.
+        /// </summary>
+        /// <param name="executeMethod"></param>
+        /// <param name="parameter"></param>
+        public void Add(Func<T, bool> executeMethod, T parameter)
+        {
+            lock (WorkersLocker)
+            {
+                if (_closed)
+                {
+                    return;
+                }
+
+                var worker = new ExecuteWorker<T>(executeMethod, parameter);
+                AddWorker(worker);
+                if (!_executingStatus.Executing)
+                {
+                    _executingStatus.Reset();
+                    DoExecute();
+                }
+            }
+        }
+
+        /// <summary>
+        /// The final method to run the worker.
+        /// </summary>
+        private void DoExecute()
+        {
+            //Logic mush run in another thread.
+            Task.Run(() =>
+            {
+                ExecuteWorker<T> worker = null;
+                lock (WorkersLocker)
+                {
+                    if (WorkerCount > 0)
+                    {
+                        worker = TakeWorker();
+                    }
+                }
+
+                var executeResult = worker?.Run() ?? true;        
+                
+                if (_closed)
+                {
+                    _executingStatus.Set();
+                }
+
+                lock (WorkersLocker)
+                {
+                    var continueExecute = WorkerCount > 0 && !_closed && executeResult;
+                    if (continueExecute)
+                    {
+                        if (_executeDelayTime != TimeSpan.MinValue)
+                        {
+                            _delayManualResetEvent.WaitOne(_executeDelayTime);
+                        }
+
+                        DoExecute();
+                    }
+                    else
+                    {
+                        _executingStatus.Set();
+                    }
+                }                                    
+            });
+        }
+
+        /// <summary>
+        /// Close the executor and execute all workers.
+        /// </summary>
+        public void Close()
+        {
+            StopExecuting();
+            lock (WorkersLocker)
+            {
+                var workers = GetWorkers();
+                foreach (var worker in workers)
+                {
+                    worker?.Run();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Dispose current executor.
+        /// </summary>
+        public void Dispose(int timeoutSeconds = 10)
+        {
+            _closed = true;
+            lock (WorkersLocker)
+            {
+                CancelWorkers();
+                _executingStatus.Wait(timeoutSeconds);
+            }
+        }
+
+        /// <summary>
+        /// Cancel all workers.
+        /// </summary>
+        public void Cancel()
+        {
+            lock (WorkersLocker)
+            {
+                CancelWorkers();
+            }
+            StopExecuting();
+        }
+
+        /// <summary>
+        /// Stop executing.
+        /// </summary>
+        protected void StopExecuting()
+        {
+            _closed = true;
+            lock (WorkersLocker)
+            {
+                if (WorkerCount == 0)
+                {
+                    _closed = false;
+                    return;
+                }
+                _executingStatus.Wait();
+            }
+            _closed = false;
+        }
+
+        public bool GetExecutingStatus()
+        {
+            return _executingStatus.Executing;
+        }
+    }
+}

+ 10 - 0
Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/IIdContext.cs

@@ -0,0 +1,10 @@
+namespace Flyinsono.DBCopy.Tool.Utilities.Executors
+{
+    public interface IIdContext
+    {
+        /// <summary>
+        /// Gets the Id of this context.
+        /// </summary>
+        string Id { get; }
+    }
+}

+ 76 - 0
Tools/Flyinsono.DBCopy.Tool/Utilities/Executors/SequenceExecutor.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+
+namespace Flyinsono.DBCopy.Tool.Utilities.Executors
+{
+    /// <summary>
+    /// This executor will run workers one by one.
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class SequenceExecutor<T>:ExecutorBase<T> where T: class
+    {
+        private readonly Queue<ExecuteWorker<T>> _workers = new Queue<ExecuteWorker<T>>();
+
+        public SequenceExecutor(string name) : base(name)
+        {
+        }
+
+        public SequenceExecutor(string name, TimeSpan executeDelayTime) : base(name, executeDelayTime)
+        {
+        }
+
+        /// <summary>
+        /// Gets current worker count in queue.
+        /// </summary>
+        /// <returns></returns>
+        protected override int GetWorkerCount()
+        {
+            return _workers.Count;
+        }
+
+        /// <summary>
+        /// Add one worker into the queue.
+        /// </summary>
+        /// <param name="worker"></param>
+        protected override void AddWorker(ExecuteWorker<T> worker)
+        {
+            _workers.Enqueue(worker);
+        }
+
+        /// <summary>
+        /// Take one worker from the queue.
+        /// </summary>
+        /// <returns></returns>
+        protected override ExecuteWorker<T> TakeWorker()
+        {
+            return _workers.Dequeue();
+        }
+
+        /// <summary>
+        /// Gets all workers.
+        /// </summary>
+        /// <returns></returns>
+        protected override IEnumerable<ExecuteWorker<T>> GetWorkers()
+        {
+            return _workers.ToArray();
+        }
+        
+        /// <summary>
+        /// Clear all workers which not stated.
+        /// </summary>
+        protected override void CancelWorkers()
+        {
+            _workers.Clear();
+        }
+
+        /// <summary>
+        /// Get the rest not working workers
+        /// </summary>
+        /// <returns></returns>
+        public IEnumerable<ExecuteWorker<T>> GetRestWorkers()
+        {
+            return GetWorkers();
+        }
+
+    }
+}

+ 32 - 0
Tools/Flyinsono.DBCopy.Tool/ViewModels/ButtonCommand.cs

@@ -0,0 +1,32 @@
+using System;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    public enum ButtonStyle
+    {
+        Default,
+        Cancel
+    }
+    public class ButtonCommand : Command
+    {
+        private ButtonStyle _style = ButtonStyle.Default;
+
+        public ButtonStyle Style
+        {
+            get
+            {
+                return _style;
+            }
+            set
+            {
+                _style = value;
+                OnPropertyChanged(() => Style);
+            }
+        }
+
+        public ButtonCommand(Action<object> executeAction, string description = "", Func<object, bool> canExecuteFunc = null) : base(executeAction, canExecuteFunc)
+        {
+            Description = description;
+        }
+    }
+}

+ 69 - 0
Tools/Flyinsono.DBCopy.Tool/ViewModels/Command.cs

@@ -0,0 +1,69 @@
+using System;
+using System.Windows.Input;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    /// <summary>
+    /// Implementation of ICommand
+    /// </summary>
+    public abstract class Command : ViewModel, ICommand
+    {
+        private readonly Action<object> _executeAction;
+        private readonly Func<object, bool> _canExecuteFunc;
+
+        public event EventHandler CanExecuteChanged;
+
+        protected Command(Action<object> executeAction, Func<object, bool> canExecuteFunc = null)
+        {
+            if (executeAction == null)
+            {
+                throw new NullReferenceException("Execute Action can not be null");
+            }
+            _executeAction = executeAction;
+            _canExecuteFunc = canExecuteFunc;
+        }
+
+        /// <summary>
+        /// If this command can executed
+        /// </summary>
+        /// <param name="parameter"></param>
+        /// <returns></returns>
+        public bool CanExecute(object parameter)
+        {
+            if (!IsEnabled)
+            {
+                return false;
+            }
+
+            if (_canExecuteFunc != null)
+            {
+                return _canExecuteFunc(parameter);
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Execute the command
+        /// </summary>
+        /// <param name="parameter"></param>
+        public void Execute(object parameter)
+        {
+            if (CanExecute(parameter))
+            {
+                Logger.WriteLineInfo($"Execute Command with description {Description}");
+                _executeAction(parameter);
+            }
+        }
+
+        public virtual void OnCanExecuteChanged()
+        {
+            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
+        }
+
+        protected override void OnIsEnabledChanged()
+        {
+            OnCanExecuteChanged();
+            base.OnIsEnabledChanged();
+        }
+    }
+}

+ 150 - 0
Tools/Flyinsono.DBCopy.Tool/ViewModels/MainWindowViewModel.cs

@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Threading;
+using Flyinsono.DBCopy.Tool.Log;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    class MainWindowViewModel : ViewModel
+    {        
+        private string _hostUrl;
+        private uint _userCount;
+        private string _message;
+        
+        private readonly Action<Action> _runOnMainDispatcher;
+
+        public ButtonCommand ConnectCommand { get; set; }
+
+        public ButtonCommand ExitCommand { get; set; }
+        public string HostUrl
+        {
+            get => _hostUrl;
+            set
+            {
+                if (_hostUrl != value)
+                {
+                    _hostUrl = value;
+                    OnPropertyChanged(() => HostUrl);
+                }
+            }
+        }
+
+        public uint UserCount
+        {
+            get => _userCount;
+            set
+            {
+                if (_userCount != value)
+                {
+                    _userCount = value;
+                    OnPropertyChanged(() => UserCount);
+                }
+            }
+        }
+
+        public string Message
+        {
+            get => _message;
+            set
+            {
+                if (_message != value)
+                {
+                    _message = value;
+                    OnPropertyChanged(() => Message);
+                }
+            }
+        }
+
+       
+        //private List<SequenceExecutor<int>> _sequenceExecutors = new List<SequenceExecutor<int>>();
+        /// <summary>
+        /// Request window close
+        /// </summary>
+        public event EventHandler RequestClosed;
+
+        /// <summary>
+        /// Cancel Command
+        /// </summary>
+        public Command CancelCommand { get; }
+
+        /// <summary>
+        /// Finish command
+        /// </summary>
+        public Command CloseCommand { get; }
+
+        /// <summary>
+        /// Open Output Dir Command
+        /// </summary>
+        public Command OpenOutputDirCommand { get; }
+
+        /// <summary>
+        /// Log infoes
+        /// </summary>
+        public ObservableCollection<LogItem> LogItems { get; }
+
+        public Command ClearLogCommand { get; }
+
+
+
+        public MainWindowViewModel(Action<Action> runOnMainDispatcher)
+        {
+            LogItems = new ObservableCollection<LogItem>();
+            Description = "AppName";
+            CancelCommand = new ButtonCommand(OnCancelCommand, "Cancel");
+            CloseCommand = new ButtonCommand(OnCloseCommand, "Finish");
+
+            ClearLogCommand = new ButtonCommand(OnClearLogCommand, "ClearLog");
+
+            HostUrl = "http://192.168.6.80:8303/";
+           
+            //HostUrl = "wss://192.168.6.175:8443/wss/";
+            UserCount = 100;
+            Message = "PC-1";
+            ConnectCommand = new ButtonCommand(OnConnect, "Run");
+            ExitCommand = new ButtonCommand(OnExit, "Exit");
+
+            this._runOnMainDispatcher = runOnMainDispatcher;
+        }
+
+        private void OnExit(object obj)
+        {
+            
+        }
+
+        private async void OnConnect(object obj)
+        {
+            var service = AppManager.Instance.GetManager<IClientTestManager>();
+            service.Run(HostUrl);
+        }
+
+        
+        private void OnClearLogCommand(object obj)
+        {
+            LogItems.Clear();
+        }
+
+
+
+        private void OnCancelCommand(object obj)
+        {
+            OnRequestClosed();
+        }
+
+
+        private void OnCloseCommand(object obj)
+        {           
+            OnRequestClosed();
+        }
+
+
+        void OnRequestClosed()
+        {
+            RequestClosed?.Invoke(this, EventArgs.Empty);
+        }
+    }
+
+}

+ 61 - 0
Tools/Flyinsono.DBCopy.Tool/ViewModels/NotificationObject.cs

@@ -0,0 +1,61 @@
+using System;
+using System.ComponentModel;
+using System.Linq.Expressions;
+using System.Runtime.CompilerServices;
+
+namespace Flyinsono.DBCopy.Tool
+{
+    /// <summary>
+    /// Implementation of INotifyPropertyChanged
+    /// </summary>
+    public abstract class NotificationObject : INotifyPropertyChanged
+    {
+        /// <summary>
+        /// Property change handler
+        /// </summary>
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
+        {
+            PropertyChangedEventHandler handler = PropertyChanged;
+            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
+        }
+
+        protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
+        {
+            var propertyName = GetPropertyName(propertyExpression);
+            OnPropertyChanged(propertyName);
+        }
+
+        protected void OnPropertyChanged(params Expression<Func<object>>[] propertyExpressions)
+        {
+            foreach (var expression in propertyExpressions)
+            {
+                OnPropertyChanged<object>(expression);
+            }
+        }
+
+        private static string GetPropertyName<T>(Expression<Func<T>> propertyExpression)
+        {
+            if (propertyExpression == null)
+            {
+                throw new ArgumentNullException("propertyExpression");
+            }
+
+            var memberExpression = propertyExpression.Body as MemberExpression;
+            if (memberExpression != null)
+            {
+                return memberExpression.Member.Name;
+            }
+
+            var unaryExpression = propertyExpression.Body as UnaryExpression;
+            if (unaryExpression != null)
+            {
+                var memberInfo = (MemberExpression)unaryExpression.Operand;
+                return memberInfo.Member.Name;
+            }
+
+            return string.Empty;
+        }
+    }
+}

+ 87 - 0
Tools/Flyinsono.DBCopy.Tool/ViewModels/ViewModel.cs

@@ -0,0 +1,87 @@
+namespace Flyinsono.DBCopy.Tool
+{
+    /// <summary>
+    /// Base view model
+    /// </summary>
+    public abstract class ViewModel : NotificationObject
+    {
+        private string _description;
+        private bool _isVisible = true;
+        private bool _isEnable = true;
+        private bool _isDisposed;
+
+        /// <summary>
+        /// Description, display on UI
+        /// </summary>
+        public string Description
+        {
+            get { return _description; }
+            set
+            {
+                if (_description != value)
+                {
+                    _description = value;
+                    OnPropertyChanged(() => Description);
+                }
+            }
+        }
+
+        /// <summary>
+        /// A Flag indicates this UI is visible
+        /// </summary>
+        public bool IsVisible
+        {
+            get { return _isVisible; }
+            set
+            {
+                if (_isVisible != value)
+                {
+                    _isVisible = value;
+                    OnIsVisible();
+                    OnPropertyChanged(() => IsVisible);
+                }
+            }
+        }
+
+        /// <summary>
+        /// A Flag indicates this UI is enabled
+        /// </summary>
+        public bool IsEnabled
+        {
+            get { return _isEnable; }
+            set
+            {
+                if (_isEnable != value)
+                {
+                    _isEnable = value;
+                    OnIsEnabledChanged();
+                    OnPropertyChanged(() => IsEnabled);
+                }
+            }
+        }
+
+        protected virtual void OnIsEnabledChanged()
+        {
+            //TODO On IsEnabled changed, child class can override implement it
+        }
+
+        protected virtual void OnIsVisible()
+        {
+            //child class can override implement it
+        }
+
+        public void Dispose()
+        {
+            if (!_isDisposed)
+            {
+                DoDispose();
+                _isDisposed = true;
+            }
+        }
+
+        protected virtual void DoDispose()
+        {
+            //implement it in child class
+        }
+    }
+}