先对比下
以前的
现在的
现在改为接口的方式,为了实现不同云存储的同步,还能先看到有哪些待同步,也可以反向同步。
树Model的Tag赋值的Model定义
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FYJ.Tools.Cloud { public class StoreModel { /// <summary> /// 相对路径 /// </summary> public string Key { get; set; } private string _name; /// <summary> /// 文件名 /// </summary> public string Name { get { if (String.IsNullOrEmpty(_name)) { return System.IO.Path.GetFileName(Key); } return _name; } set { _name = value; } } /// <summary> /// 访问Url /// </summary> public string Url { get; set; } public string MD5 { get; set; } public string SHA1 { get; set; } private string _id; public string ID { get { if (String.IsNullOrEmpty(_id)) { return Key; } return _id; } set { _id = value; } } /// <summary> /// /// </summary> public object Tag { get; set; } /// <summary> /// 大小 /// </summary> public long Size { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime CreateDatetime { get; set; } /// <summary> /// 最后修改时间 /// </summary> public DateTime LastModifiedDatetime { get; set; } public string Bucket { get; set; } public string Icon { get; set; } /// <summary> /// 是否目录 /// </summary> public bool IsDirectory { get; set; } /// <summary> /// 文件大小的字符串展示 /// </summary> public string SizeString { get { if (IsDirectory) { return ""; } if (Size > 0 && Size < 1024) { return Size + "B"; } if (Size >= 1024 && Size < 1048576) { return Math.Round(Size / 1024.0, 2) + "KB"; } if (Size >= 1048576 && Size < 1048576 * 1024) { return Math.Round(Size / 1048576.0, 2) + "MB"; } return Size + ""; } } /// <summary> /// 最后修改时间的字符串展示 /// </summary> public string LastModifiedDatetimeString { get { if (LastModifiedDatetime == DateTime.MinValue) { return ""; } return LastModifiedDatetime.ToString("yyyy-MM-dd hh:mm:ss"); } } } }
同步Model的定义
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FYJ.Tools.Cloud { public class SyncModel { public string LocalPath { get; set; } public string fileName { get; set; } public string FileID { get; set; } public string Bucket { get; set; } /// <summary> /// 文件相对路径 /// </summary> public string Key { get; set; } } }
接口的定义
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FYJ.Tools.Cloud { public interface IStore { /// <summary> /// 列出Bucket /// </summary> /// <returns></returns> IList<string> ListBucket(); /// <summary> /// 列出对象 /// </summary> /// <param name="bucket"></param> /// <param name="key"></param> /// <returns></returns> IList<StoreModel> ListObject(string bucket, string key); /// <summary> /// 上传文件 /// </summary> /// <param name="fileID">数据库对应文件id</ /// <param name="fs"></param>param> /// <param name="bucket"></param> /// <param name="key"></param> /// <returns></returns> string Upload(string fileID, Stream fs, string bucket, string key); /// <summary> /// 获取文件 /// </summary> /// <param name="bucket"></param> /// <param name="key"></param> /// <returns></returns> Stream GetFile(string bucket, string key); /// <summary> /// 创建目录 如果成功返回null /// </summary> /// <param name="folderID"></param> /// <param name="bucket"></param> /// <param name="key"></param> /// <returns></returns> string CreateDir(string folderID, string bucket, string key); /// <summary> /// 删除目录 如果成功返回null /// </summary> /// <param name="folderID"></param> /// <param name="bucket"></param> /// <param name="key"></param> /// <returns></returns> string DeleteDir(string folderID, string bucket, string key); /// <summary> /// 删除文件 如果成功返回null /// </summary> /// <param name="fileID">数据库对应文件id</param> /// <param name="bucket"></param> /// <param name="key"></param> /// <returns></returns> string DeleteFile(string fileID, string bucket, string key); /// <summary> /// 设置配置信息 /// </summary> /// <param name="name"></param> /// <returns></returns> void SetConfigInfo(string name); event MessageHander MessageHappen; } /// <summary> /// 消息 /// </summary> /// <param name="code"></param> /// <param name="message"></param> public delegate void MessageHander(int code, string message); }
阿里云OSS的实现
using Aliyun.OpenServices.OpenStorageService; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; namespace FYJ.Tools.Cloud { public class AliyunStore : IStore { public string DeleteDir(string folderID, string bucket, string key) { key = key.TrimStart('/'); Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); ossClient.DeleteObject(bucket, key); return null; } public string DeleteFile(string fileID, string bucket, string key) { key = key.TrimStart('/'); Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); ossClient.DeleteObject(bucket, key); return null; } string aliyunEndpoint; string aliyunKey; string aliyunSecret; string aliyunBucket; public event MessageHander MessageHappen; public void SetConfigInfo(string name) { XmlDocument doc = new XmlDocument(); doc.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.xml")); XmlNode node = doc.SelectSingleNode("//app[@name='" + name + "']"); if (node == null) { throw new Exception("没有找到名为" + name + "的配置"); } aliyunEndpoint = node.SelectSingleNode("add[@key='aliyunEndpoint']").Attributes["value"].InnerText; aliyunKey = node.SelectSingleNode("add[@key='aliyunKey']").Attributes["value"].InnerText; aliyunSecret = node.SelectSingleNode("add[@key='aliyunSecret']").Attributes["value"].InnerText; aliyunBucket = node.SelectSingleNode("add[@key='aliyunBucket']").Attributes["value"].InnerText; } public IList<string> ListBucket() { return new List<string> { aliyunBucket }; } public IList<StoreModel> ListObject(string bucket, string key) { if (key != null) { key = key.TrimStart('\\').TrimStart('/'); } Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); Aliyun.OpenServices.OpenStorageService.ListObjectsRequest request = new Aliyun.OpenServices.OpenStorageService.ListObjectsRequest(bucket); request.MaxKeys = 1000; request.Prefix = key; if (key != "") { request.Prefix = key.TrimEnd(new char[] { '\\', '/' }) + "/"; } request.Delimiter = "/"; var o = ossClient.ListObjects(request); //文件 var list = o.ObjectSummaries; //目录 var list2 = o.CommonPrefixes; List<StoreModel> result = new List<StoreModel>(); foreach (var item in list2) { result.Add(new StoreModel { ID = item, Name = GetFileName(item), IsDirectory = true, Bucket = bucket, Key = "/" + item.TrimEnd('/').TrimStart('/') }); } foreach (var item in list) { result.Add(new StoreModel { IsDirectory = false, Bucket = item.BucketName, Key = "/" + item.Key.TrimStart('/'), Size = item.Size, LastModifiedDatetime = item.LastModified, MD5 = item.ETag }); } return result; } public string Upload(string fileID, Stream fs, string bucket, string key) { try { key = key.TrimStart('/'); Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); Aliyun.OpenServices.OpenStorageService.ObjectMetadata metadata = new Aliyun.OpenServices.OpenStorageService.ObjectMetadata(); var v = ossClient.PutObject(bucket, key, fs, metadata); return null; } catch (Exception ex) { throw ex; } finally { fs.Close(); } } private string GetFileName(string key) { key = key.TrimEnd('/'); if (key.Contains("/")) { key = key.Substring(key.LastIndexOf("/") + 1); } return key; } private void ClearNullFolder(string bucket, string key) { Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); Aliyun.OpenServices.OpenStorageService.ListObjectsRequest request = new Aliyun.OpenServices.OpenStorageService.ListObjectsRequest(bucket); request.MaxKeys = 1000; request.Prefix = key; request.Delimiter = "/"; var list = ossClient.ListObjects(request); if (list.CommonPrefixes.Count() == 0) { if (list.ObjectSummaries.Count() == 0 || (list.ObjectSummaries.Count() == 1 && list.ObjectSummaries.First().Key == key) ) { try { this.DeleteFile(null, bucket, key); if (MessageHappen != null) { MessageHappen(1, "成功删除" + bucket + "/" + key); } } catch (Exception ex) { if (MessageHappen != null) { MessageHappen(-1, "删除失败" + ex.Message); } } } } foreach (var v in list.CommonPrefixes) { ClearNullFolder(bucket, v); } } public string CreateDir(string folderID, string bucket, string key) { key = key.TrimStart('/'); Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); var v = ossClient.PutObject(bucket, key.TrimEnd('/') + "/", new MemoryStream()); return null; } public Stream GetFile(string bucket, string key) { key = key.TrimStart('/'); Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); GetObjectRequest r = new GetObjectRequest(bucket, key); Stream s = ossClient.GetObject(r).Content; return s; } } }
本地文件系统的实现
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; namespace FYJ.Tools.Cloud { public class LocalStore : IStore { public string DeleteDir(string folderID, string bucket, string key) { if (key != null) { key = key.TrimStart('\\').TrimStart('/'); } string keyPath = System.IO.Path.Combine(bucket, key); if(Directory.Exists(keyPath)) { Directory.Delete(keyPath,true); } return null; } public string DeleteFile(string fileID, string bucket, string key) { if (key != null) { key = key.TrimStart('\\').TrimStart('/'); } string keyPath = System.IO.Path.Combine(bucket, key); if(File.Exists(keyPath)) { File.Delete(keyPath); } return null; } public void SetConfigInfo(string name) { XmlDocument doc = new XmlDocument(); doc.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.xml")); XmlNode node = doc.SelectSingleNode("//app[@name='" + name + "']"); if (node == null) { throw new Exception("没有找到名为" + name + "的配置"); } rootPath = node.SelectSingleNode("add[@key='rootPath']").Attributes["value"].InnerText; } public IList<string> ListBucket() { return new List<string> { rootPath }; } public IList<StoreModel> ListObject(string bucket, string key) { List<StoreModel> result = new List<StoreModel>(); if(key!=null) { key = key.TrimStart('\\').TrimStart('/'); } string keyPath = System.IO.Path.Combine(bucket, key); if(Directory.Exists(keyPath)) { foreach (string s in Directory.GetDirectories(keyPath)) { result.Add(new StoreModel { ID = Guid.NewGuid().ToString("N"), Name = Path.GetFileName(s), IsDirectory = true, Bucket = bucket, Key = s.Replace(bucket, "").Replace("\\", "/") }); } foreach (string s in Directory.GetFiles(keyPath)) { FileStream fs = new FileStream(s, FileMode.Open, FileAccess.Read, FileShare.Read); long lenth = fs.Length; System.Security.Cryptography.HashAlgorithm algorithm = System.Security.Cryptography.MD5.Create(); String md5 = BitConverter.ToString(algorithm.ComputeHash(fs)).Replace("-", ""); long length = fs.Length; fs.Close(); result.Add(new StoreModel { ID = Guid.NewGuid().ToString("N"), Url = "", IsDirectory = false, Bucket = bucket, Key = s.Replace(bucket, "").Replace("\\", "/"), Size = length, LastModifiedDatetime = DateTime.Now, MD5 = md5 }); } } return result; } public string Upload(string fileID, Stream fs, string bucket, string key) { if (key != null) { key = key.TrimStart('\\').TrimStart('/'); } string keyPath = System.IO.Path.Combine(bucket, key); byte[] buffer = new byte[fs.Length]; fs.Read(buffer,0,buffer.Length); fs.Close(); if(!Directory.Exists(Path.GetDirectoryName(keyPath))) { Directory.CreateDirectory(Path.GetDirectoryName(keyPath)); } File.WriteAllBytes(keyPath, buffer); return null; } public string CreateDir(string folderID, string bucket, string key) { if (key != null) { key = key.TrimStart('\\').TrimStart('/'); } string keyPath = System.IO.Path.Combine(bucket, key); if (!Directory.Exists(keyPath)) { Directory.CreateDirectory(keyPath); } return null; } public Stream GetFile(string bucket, string key) { if (key != null) { key = key.TrimStart('\\').TrimStart('/'); } string keyPath = System.IO.Path.Combine(bucket, key); return File.OpenRead(keyPath); } private string rootPath; public event MessageHander MessageHappen; } }
界面
<Window x:Class="FYJ.Tools.Cloud.Sync.SyncWindow" 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:FYJ.Tools.Cloud.Sync" xmlns:c="clr-namespace:FYJ.Windows.Controls;assembly=FYJ.Windows.Controls" mc:Ignorable="d" Title="同步" Height="800" Width="1000"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="500"></RowDefinition> <RowDefinition Height="40"></RowDefinition> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="10"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <StackPanel Grid.Row="0" Grid.Column="0"> <WrapPanel Margin="0,10,0,0"> <TextBlock Text="类型:" VerticalAlignment="Center"></TextBlock> <c:ComboBoxEx Width="200" Margin="5,0,0,0" x:Name="txtTypeLeft"> </c:ComboBoxEx> <Button Content="加载" x:Name="btnLoadLeft" Margin="5,0,0,0" Width="60" Click="btnLoadLeft_Click"></Button> <Button Content="测试" x:Name="btnTest" Click="btnTest_Click"></Button> </WrapPanel> <TextBox Margin="0,10,0,0" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" Height="100" IsReadOnly="True" x:Name="infoLeft"></TextBox> <DockPanel Margin="0,10,0,0"> <TextBlock Text="当前路径:" DockPanel.Dock="Left" Margin="5,0,0,0"></TextBlock> <TextBox x:Name="currentPathLeft" IsReadOnly="True"></TextBox> </DockPanel> <c:CustomTreeView Margin="0,10,0,0" x:Name="treeLeft" CheckboxVisibility="Collapsed" Height="300"/> </StackPanel> <StackPanel Grid.Row="0" Grid.Column="2"> <WrapPanel Margin="0,10,0,0"> <TextBlock Text="类型:" VerticalAlignment="Center"></TextBlock> <c:ComboBoxEx Width="200" Margin="5,0,0,0" x:Name="txtTypeRight"> </c:ComboBoxEx> <Button Content="加载" Margin="5,0,0,0" Width="60" Name="btnLoadRight" Click="btnLoadRight_Click"></Button> </WrapPanel> <TextBox Margin="0,10,0,0" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" Height="100" IsReadOnly="True" x:Name="infoRight"></TextBox> <DockPanel Margin="0,10,0,0"> <TextBlock Text="当前路径:" DockPanel.Dock="Left" Margin="5,0,0,0"></TextBlock> <TextBox x:Name="currentPathRight" IsReadOnly="True"></TextBox> </DockPanel> <c:CustomTreeView Margin="0,10,0,0" CheckboxVisibility="Collapsed" Height="300" x:Name="treeRight"/> </StackPanel> <WrapPanel Grid.Row="1" Grid.ColumnSpan="3"> <c:CheckBoxEx Content="删除目标多余文件" VerticalAlignment="Center" x:Name="chkIsDelete"></c:CheckBoxEx> <Button Content="删除左边空目录" Width="100" Margin="5,0,0,0"></Button> <Button Content="分析==》" Width="80" Margin="5,0,0,0" x:Name="btnAnalyze1" Click="btnAnalyze1_Click"></Button> <Button Content="同步==》" Width="80" Margin="5,0,0,0" x:Name="btnSync1" Click="btnSync1_Click"></Button> <Button Margin="50,0,0,0" Content="《==分析" Width="80" x:Name="btnAnalyze2" Click="btnAnalyze2_Click"></Button> <Button Content="《==同步" Width="80" Margin="5,0,0,0" x:Name="btnSync2" Click="btnSync2_Click"></Button> <Button Content="删除右边空目录" Width="100" Margin="5,0,0,0"></Button> <TextBlock Text="错误数:" VerticalAlignment="Center" Margin="5,0,0,0"></TextBlock> <c:TextBlockEx Text="0" FontSize="16" Foreground="#FFF90202" x:Name="txtErrorCount"></c:TextBlockEx> </WrapPanel> <DockPanel Grid.Row="2" Grid.ColumnSpan="3"> <DockPanel DockPanel.Dock="Bottom"> <c:ProgressBarEx DockPanel.Dock="Right" Width="160" x:Name="progressBar1"></c:ProgressBarEx> <c:TextBlockEx Text="就绪" x:Name="txtStatus"></c:TextBlockEx> </DockPanel> <WrapPanel DockPanel.Dock="Top" Margin="0,5,0,5"> <TextBlock Text="请注意方向" Margin="40,0,0,0" Foreground="#FFF00303"></TextBlock> </WrapPanel> <TabControl> <TabItem Header="日志"> <c:LogListBox Margin="0,10,0,10" x:Name="listBox1" ></c:LogListBox> </TabItem> <TabItem Header="将被新增" x:Name="headerAdd"> <c:LogListBox Margin="0,10,0,10" x:Name="listBox_add" ></c:LogListBox> </TabItem> <TabItem Header="将被覆盖" x:Name="headerChange"> <c:LogListBox Margin="0,10,0,10" x:Name="listBox_change" ></c:LogListBox> </TabItem> <TabItem Header="将被删除" x:Name="headerDelete"> <c:LogListBox Margin="0,10,0,10" x:Name="listBox_delete" ></c:LogListBox> </TabItem> </TabControl> </DockPanel> </Grid> </Window>
后台代码
using FYJ.Windows.Controls; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; 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.Shapes; using System.Xml; namespace FYJ.Tools.Cloud.Sync { /// <summary> /// SyncWindow.xaml 的交互逻辑 /// </summary> public partial class SyncWindow : Window { public SyncWindow() { InitializeComponent(); this.treeLeft.SelectedItemChanged += TreeLeft_SelectedItemChanged; this.treeRight.SelectedItemChanged += TreeRight_SelectedItemChanged; } private void TreeLeft_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { TreeModel currentNode = e.NewValue as TreeModel; if (currentNode != null) { StoreModel model = currentNode.Tag as StoreModel; if (model != null) { this.currentPathLeft.Text = model.Key; } } } private void TreeRight_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { TreeModel currentNode = e.NewValue as TreeModel; if (currentNode != null) { StoreModel model = currentNode.Tag as StoreModel; if (model != null) { this.currentPathRight.Text = model.Key; } } } protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); XmlDocument doc = new XmlDocument(); doc.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.xml")); List<object> list = new List<object>(); foreach (XmlNode node in doc.SelectNodes("/sync/app")) { string name = node.Attributes["name"].InnerText; string type = node.Attributes["type"].InnerText; IStore store = Activator.CreateInstance(Type.GetType(type)) as IStore; store.SetConfigInfo(name); store.MessageHappen += Store_MessageHappen; list.Add(new { name = name, value = store }); } this.txtTypeLeft.ItemsSource = list; this.txtTypeLeft.DisplayMemberPath = "name"; this.txtTypeLeft.SelectedValuePath = "value"; this.txtTypeRight.ItemsSource = list; this.txtTypeRight.DisplayMemberPath = "name"; this.txtTypeRight.SelectedValuePath = "value"; } private void Store_MessageHappen(int code, string message) { this.listBox1.AddErrorItem(message); } private async void LoadChildren(IStore store, TreeModel node, IList<TreeModel> list, TreeModel.GetChildrenHandler getchildren) { string icon = node.Icon; node.Icon = "/FYJ.Windows.Controls;component/Images/16_16/loading.gif"; //await this.Dispatcher.BeginInvoke((Action)(() => //{ txtStatus.Text = "正在加载..."; // })); IList<StoreModel> models = new List<StoreModel>(); await Task.Factory.StartNew(() => { StoreModel model = node.Tag as StoreModel; if (model != null) { models = store.ListObject(model.Bucket, model.Key); } }); //await Task.Factory.StartNew(() => //{ // //测试 // Thread.Sleep(1000); //}); foreach (StoreModel model in models) { TreeModel childnode = new TreeModel(); childnode.Id = model.ID; childnode.Name = model.Name; childnode.Icon = model.Icon; childnode.IsExpanded = false; if (model.IsDirectory) { childnode.GetChildren = getchildren; childnode.Icon = "/FYJ.Windows.Controls;component/Images/16_16/folder.png"; } else { childnode.Icon = "/FYJ.Windows.Controls;component/Images/16_16/file.png"; } childnode.Tag = model; childnode.IsExpanded = false; childnode.IsShowCheckbox = false; list.Add(childnode); } node.Icon = icon; txtStatus.Text = "就绪"; } private async Task LoadTree(IStore store, CustomTreeView tree, TreeModel.GetChildrenHandler getchildren) { txtStatus.Text = "正在加载buckets..."; ObservableCollection<TreeModel> rootTreeList = new ObservableCollection<TreeModel>(); IList<string> buckets = store.ListBucket(); await Task.Factory.StartNew(() => { buckets = store.ListBucket(); }); if (buckets.Count == 0) { txtStatus.Text = "就绪"; return; } foreach (string bucket in buckets) { TreeModel bnode = new TreeModel(); bnode.Id = bucket; bnode.Name = bucket; bnode.IsExpanded = false; bnode.Icon = "/FYJ.Windows.Controls;component/Images/16_16/folder.png"; bnode.Tag = new StoreModel { Key = "/", Bucket = bucket }; IList<StoreModel> models = new List<StoreModel>(); await Task.Factory.StartNew(() => { models = store.ListObject(bucket, ""); }); foreach (StoreModel model in models) { TreeModel node = new TreeModel(); node.Id = model.ID; node.Name = model.Name; node.IsExpanded = false; if (model.IsDirectory) { node.GetChildren = getchildren; node.Icon = "/FYJ.Windows.Controls;component/Images/16_16/folder.png"; } else { node.Icon = "/FYJ.Windows.Controls;component/Images/16_16/file.png"; } node.Tag = model; node.Parent = bnode; bnode.Children.Add(node); } rootTreeList.Add(bnode); } this.txtStatus.Text = "就绪"; tree.ItemsSourceData = rootTreeList; } private IList<TreeModel> GetLeftChildren(TreeModel node) { ObservableCollection<TreeModel> list = new ObservableCollection<TreeModel>(); IStore store = this.txtTypeLeft.SelectedValue as IStore; LoadChildren(store, node, list, GetLeftChildren); return list; } private IList<TreeModel> GetRightChildren(TreeModel node) { ObservableCollection<TreeModel> list = new ObservableCollection<TreeModel>(); IStore store = this.txtTypeRight.SelectedValue as IStore; LoadChildren(store, node, list, GetRightChildren); return list; } private async void btnLoadLeft_Click(object sender, RoutedEventArgs e) { IStore store = this.txtTypeLeft.SelectedValue as IStore; await LoadTree(store, treeLeft, GetLeftChildren); currentPathLeft.Text = "/"; } private async void btnLoadRight_Click(object sender, RoutedEventArgs e) { IStore store = this.txtTypeRight.SelectedValue as IStore; await LoadTree(store, treeRight, GetRightChildren); currentPathRight.Text = "/"; } private async Task Sync(IStore store1, IStore store2, string bucket1, List<SyncMessage> syncList, bool isDelete) { this.txtErrorCount.Text = "0"; this.progressBar1.Value = 0; this.progressBar1.Maximum = syncList.Count; await Task.Factory.StartNew(() => { #region 处理同步 for (int i = 0; i < syncList.Count; i++) { SyncMessage sy = syncList[i]; txtStatus.Text = "正在同步" + sy.Key + "..."; if (sy.IsDirectory == false) { if (sy.SyncType == 2) { if (isDelete) { try { store2.DeleteFile(null, sy.Bucket, sy.Key); this.listBox1.AddItem("成功删除" + sy.Bucket + sy.Key); } catch (Exception ex) { this.listBox1.AddErrorItem("删除失败" + ex.Message); this.txtErrorCount.Text = (Convert.ToInt32(this.txtErrorCount.Text) + 1).ToString(); } } } else { //上传文件 Stream stream = null; try { stream = store1.GetFile(bucket1, sy.Key); store2.Upload(null, stream, sy.Bucket, sy.Key); this.listBox1.AddItem("成功上传" + sy.Bucket + sy.Key); } catch (Exception ex) { this.listBox1.AddErrorItem("上传失败" + ex.Message); this.txtErrorCount.Text = (Convert.ToInt32(this.txtErrorCount.Text) + 1).ToString(); } finally { if (stream != null) { stream.Close(); } } } } this.progressBar1.Value = i + 1; } //创建空目录 for (int i = 0; i < syncList.Count; i++) { SyncMessage sy = syncList[i]; if (sy.IsDirectory == true && sy.SyncType == 0) { //如果该目录是空目录 if (syncList.Where(x => x.Key.StartsWith(sy.Key)).Count() == 1) { txtStatus.Text = "正在创建目录" + sy.Key + "..."; try { store2.CreateDir(null, sy.Bucket, sy.Key); this.listBox1.AddItem("成功创建目录" + sy.Bucket + sy.Key); } catch (Exception ex) { this.listBox1.AddErrorItem("创建目录失败" + ex.Message); this.txtErrorCount.Text = (Convert.ToInt32(this.txtErrorCount.Text) + 1).ToString(); } } } } //最后删除空目录 DeleteDir(syncList, store2); #endregion }); txtStatus.Text = "完成"; } private void DeleteDir(List<SyncMessage> syncList, IStore store) { var v = syncList.OrderByDescending(x => x.Key.Count(c => c == '/')); foreach (SyncMessage sy in v) { if (sy.IsDirectory && sy.SyncType == 2) { txtStatus.Text = "正在删除目录" + sy.Key + "..."; try { store.DeleteDir(null, sy.Bucket, sy.Key); this.listBox1.AddItem("成功删除目录" + sy.Bucket + sy.Key); } catch (Exception ex) { this.listBox1.AddErrorItem("删除目录失败" + ex.Message); this.txtErrorCount.Text = (Convert.ToInt32(this.txtErrorCount.Text) + 1).ToString(); } } } } /// <summary> /// 获取需要同步的信息 store1 同步到 store2 /// </summary> /// <param name="bucket"></param> /// <param name="key"></param> /// <param name="syncList"></param> /// <param name="rootLocalPath"></param> private async Task GetSyncList(string bucket1, string bucket2, string key, List<SyncMessage> syncList, IStore store1, IStore store2) { IList<StoreModel> list1 = null; IList<StoreModel> list2 = null; this.txtStatus.Text = "正在对比" + key + "..."; await Task.Run(() => { list1 = store1.ListObject(bucket1, key); //如果目标不存在该文件夹 则一定会新增该文件夹下的子项目 不用再做对比所以new一个集合为0的 if (syncList.Where(x => x.Key == key && x.SyncType == 0 && x.IsDirectory == true).Count() == 1) { list2 = new List<StoreModel>(); } else { list2 = store2.ListObject(bucket2, key); } }); foreach (StoreModel model in list1) { var v = list2.Where(x => x.Key == model.Key).FirstOrDefault(); //如果store2不存在 if (v == null) { syncList.Add(new SyncMessage { IsDirectory = model.IsDirectory, Key = model.Key, Bucket = bucket2, SyncType = 0, MD5 = model.MD5 }); } else { //如果不是文件夹 if (!model.IsDirectory) { //有拍云只比较大小 因为无法获取到有拍云的md5 if (store2 is UPYun) { if (v.Size == model.Size) { continue; } } if (v.MD5 == model.MD5) { continue; } syncList.Add(new SyncMessage { IsDirectory = model.IsDirectory, Key = model.Key, Bucket = bucket2, SyncType = 1, MD5 = model.MD5 }); } } //如果是文件夹递归 if (model.IsDirectory) { await GetSyncList(bucket1, bucket2, model.Key, syncList, store1, store2); } } foreach (StoreModel model in list2) { //如果store1 不存在store2的文件(夹) 则需要删除store2的文件(夹) if (list1.Where(x => x.Key == model.Key).Count() == 0) { syncList.Add(new SyncMessage { IsDirectory = model.IsDirectory, Key = model.Key, Bucket = bucket2, SyncType = 2, MD5 = model.MD5 }); //再递归 因为如果源目标不存在该文件夹的情况如果不递归则无法查找到该目录下多余的子项目 if (model.IsDirectory) { await GetSyncList(bucket1, bucket2, model.Key, syncList, store1, store2); } } } this.Dispatcher.Invoke(() => { headerAdd.Header = "将被新增(" + syncList.Where(x => x.SyncType == 0).Count() + ")"; headerChange.Header = "将被覆盖(" + syncList.Where(x => x.SyncType == 1).Count() + ")"; headerDelete.Header = "将被删除(" + syncList.Where(x => x.SyncType == 2).Count() + ")"; }); } private async void btnAnalyze1_Click(object sender, RoutedEventArgs e) { if (this.txtTypeLeft.Text == "") { MessageBox.Show("请选择左边"); return; } if (this.txtTypeRight.Text == "") { MessageBox.Show("请选择右边"); return; } if (this.treeLeft.CurrentItem == null) { MessageBox.Show("请选择左边路径"); return; } if (this.treeRight.CurrentItem == null) { MessageBox.Show("请选择右边路径"); return; } if (this.txtTypeLeft.Text == this.txtTypeRight.Text) { MessageBox.Show("左边和右边不能选择相同"); return; } listBox1.Items.Clear(); listBox_add.Items.Clear(); listBox_change.Items.Clear(); listBox_delete.Items.Clear(); List<SyncMessage> syncList = new List<SyncMessage>(); IStore store1 = this.txtTypeLeft.SelectedValue as IStore; IStore store2 = this.txtTypeRight.SelectedValue as IStore; StoreModel model1 = this.treeLeft.CurrentItem.Tag as StoreModel; StoreModel model2 = this.treeRight.CurrentItem.Tag as StoreModel; string key1 = this.currentPathLeft.Text; string key2 = this.currentPathRight.Text; if (key1 != key2) { MessageBox.Show("左边和右边的路径key必须相同"); return; } await GetSyncList(model1.Bucket, model2.Bucket, key1, syncList, store1, store2); foreach (var item in syncList) { string typedes = (item.IsDirectory ? "目录" : "文件"); Color c = (item.IsDirectory ? Color.FromRgb(0, 255, 0) : Color.FromRgb(0, 0, 255)); if (item.SyncType == 0) { this.listBox_add.AddItem("左边比右边多了" + typedes + ":" + item.Key, c); } if (item.SyncType == 1) { this.listBox_change.AddItem("左边比右边变动了" + typedes + ":" + item.Key, c); } if (item.SyncType == 2) { this.listBox_delete.AddItem("左边比右边少了" + typedes + ":" + item.Key, c); } } this.txtStatus.Text = "就绪"; this.btnAnalyze1.Tag = syncList; } private async void btnSync1_Click(object sender, RoutedEventArgs e) { if (btnAnalyze1.Tag == null) { MessageBox.Show("请先分析"); return; } IStore store1 = this.txtTypeLeft.SelectedValue as IStore; IStore store2 = this.txtTypeRight.SelectedValue as IStore; StoreModel model1 = this.treeLeft.CurrentItem.Tag as StoreModel; List<SyncMessage> syncList = btnAnalyze1.Tag as List<SyncMessage>; await Sync(store1, store2, model1.Bucket, syncList, chkIsDelete.IsChecked.Value); } private async void btnAnalyze2_Click(object sender, RoutedEventArgs e) { if (this.txtTypeLeft.Text == "") { MessageBox.Show("请选择左边"); return; } if (this.txtTypeRight.Text == "") { MessageBox.Show("请选择右边"); return; } if (this.treeLeft.CurrentItem == null) { MessageBox.Show("请选择左边路径"); return; } if (this.treeRight.CurrentItem == null) { MessageBox.Show("请选择右边路径"); return; } if (this.txtTypeLeft.Text == this.txtTypeRight.Text) { MessageBox.Show("左边和右边不能选择相同"); return; } listBox1.Items.Clear(); listBox_add.Items.Clear(); listBox_change.Items.Clear(); listBox_delete.Items.Clear(); List<SyncMessage> syncList = new List<SyncMessage>(); IStore store1 = this.txtTypeLeft.SelectedValue as IStore; IStore store2 = this.txtTypeRight.SelectedValue as IStore; StoreModel model1 = this.treeLeft.CurrentItem.Tag as StoreModel; StoreModel model2 = this.treeRight.CurrentItem.Tag as StoreModel; string key1 = this.currentPathLeft.Text; string key2 = this.currentPathRight.Text; if (key1 != key2) { MessageBox.Show("左边和右边的路径key必须相同"); return; } await GetSyncList(model2.Bucket, model1.Bucket, key2, syncList, store2, store1); foreach (var item in syncList) { string typedes = (item.IsDirectory ? "目录" : "文件"); Color c = (item.IsDirectory ? Color.FromRgb(0, 255, 0) : Color.FromRgb(0, 0, 255)); if (item.SyncType == 0) { this.listBox_add.AddItem("右边比左边多了" + typedes + ":" + item.Key, c); } if (item.SyncType == 1) { this.listBox_change.AddItem("右边比左边变动了" + typedes + ":" + item.Key, c); } if (item.SyncType == 2) { this.listBox_delete.AddItem("右边比左边少了" + typedes + ":" + item.Key, c); } } this.txtStatus.Text = "就绪"; this.btnAnalyze2.Tag = syncList; } private async void btnSync2_Click(object sender, RoutedEventArgs e) { if (btnAnalyze2.Tag == null) { MessageBox.Show("请先分析"); return; } IStore store1 = this.txtTypeLeft.SelectedValue as IStore; IStore store2 = this.txtTypeRight.SelectedValue as IStore; StoreModel model2 = this.treeRight.CurrentItem.Tag as StoreModel; List<SyncMessage> syncList = btnAnalyze2.Tag as List<SyncMessage>; await Sync(store2, store1, model2.Bucket, syncList, chkIsDelete.IsChecked.Value); } private void btnTest_Click(object sender, RoutedEventArgs e) { IStore store1 = this.txtTypeLeft.SelectedValue as IStore; store1.CreateDir(null, "images-drive", "/uppp/"); } } public class SyncMessage { public long Size { get; set; } public string MD5 { get; set; } public string LocalPath { get; set; } public string Key { get; set; } public string Bucket { get; set; } /// <summary> /// 是否目录 /// </summary> public bool IsDirectory { get; set; } /// <summary> /// 同步类型 0-将被新增 1-将被覆盖 2-将被删除 /// </summary> public int SyncType { get; set; } } }
关于控件线程的问题,报各种错,比如TextBlock取Text值this.Dispatcher.BeginInvoke 可能为空,后来查询到有个this.Dispatcher.CheckAccess() 判断,不过这个方法VS 没提示!!!