diff --git a/Server/Common/Dir.cs b/Server/Common/Dir.cs index 98fef39..c32a0e8 100644 --- a/Server/Common/Dir.cs +++ b/Server/Common/Dir.cs @@ -1,132 +1,367 @@ namespace Common; -/// -/// 文件夹结构,它包含文件和文件夹 -/// -/// 绝对路径 -/// 子文件或文件夹 -public class Dir(string path, List? children = null, NextOpType? nextOp = null) - : AFileOrDir(path, DirOrFile.Dir, nextOp) +public static class DirExtension { - public List Children { get; set; } = children ?? []; - public override bool IsEqual(AFileOrDir other) + /// + /// 比较两个目录文件树是否相同,不相同返回差异部分,左侧是右侧的下一个版本,任何一个节点的nextop != null,即所有 + /// 节点都会打上标记 + /// 文件夹的 NextOp 只有新增和删除 + /// + /// + /// + /// 右侧版本接下来进行的操作 + public static Dir Diff(this Dir thisDir, Dir other) { - if (other is not Dir otherDir) + var ldir = thisDir; + var rdir = other; + Dir? cDir = new() { Path = rdir.FormatedPath, Children = [] }; + //分别对文件和文件夹分组 + List lFiles = []; + List rFiles = []; + List lDirs = []; + List rDirs = []; + var lGroups = ldir.Children.GroupBy(x => x.Type); + var rGroups = rdir.Children.GroupBy(x => x.Type); + foreach (var g in lGroups) { - return false; + if (g.Key == DirOrFile.Dir) + { + lDirs = g.AsEnumerable() + .Select(n => + { + if (n is Dir dir) + { + return dir; + } + throw new Exception("cannot be here"); + }) + .ToList(); + } + else + { + lFiles = g.AsEnumerable() + .Select(n => + { + if (n is File file) + { + return file; + } + throw new Exception("cannot be here"); + }) + .ToList(); + } } - else + + foreach (var g in rGroups) { - if (this.FormatedPath != otherDir.FormatedPath || this.NextOp != otherDir.NextOp) + if (g.Key == DirOrFile.Dir) { - return false; + rDirs = g.AsEnumerable() + .Select(n => + { + if (n is Dir dir) + { + return dir; + } + throw new Exception("cannot be here"); + }) + .ToList(); } - if (this.Children.Count != otherDir.Children.Count) + else { - return false; + rFiles = g.AsEnumerable() + .Select(n => + { + if (n is File file) + { + return file; + } + throw new Exception("cannot be here"); + }) + .ToList(); } - this.Children.Sort(AFileOrDir.Compare); - otherDir.Children.Sort(AFileOrDir.Compare); - for (int i = 0; i < this.Children.Count; i++) + } + + //排序,然后对比 + int lIndex_f = 0; + int rIndex_f = 0; + int lIndex_d = 0; + int rIndex_d = 0; + lFiles.Sort(AFileOrDir.Compare); + rFiles.Sort(AFileOrDir.Compare); + lDirs.Sort(AFileOrDir.Compare); + rDirs.Sort(AFileOrDir.Compare); + //对比文件 + while (true) + { + //当两个线性表都走到最后时,退出循环 + if (lIndex_f == lFiles.Count && rIndex_f == rFiles.Count) { - if (!this.Children[i].IsEqual(otherDir.Children[i])) + break; + } + //左侧先到底,右侧都是将要删除的 + if (lIndex_f == lFiles.Count) + { + var er = rFiles[rIndex_f]; + cDir.Children.Add( + new File + { + Path = er.FormatedPath, + MTime = er.MTime, + NextOp = NextOpType.Del + } + ); + rIndex_f++; + continue; + } + //右侧先到底,左侧都是要添加的 + if (rIndex_f == rFiles.Count) + { + var el = lFiles[lIndex_f]; + + cDir.Children.Add( + new File + { + Path = el.FormatedPath.Replace(ldir.FormatedPath, rdir.FormatedPath), + MTime = el.MTime, + NextOp = NextOpType.Add + } + ); + lIndex_f++; + continue; + } + var l = lFiles[lIndex_f]; + var r = rFiles[rIndex_f]; + //将根路径差异抹平 + var lreativePath = l.FormatedPath.Replace(ldir.FormatedPath, ""); + var rreativePath = r.FormatedPath.Replace(rdir.FormatedPath, ""); + //两文件相同,对比文件修改时间,不同增加到diff内容 + if (lreativePath == rreativePath) + { + lIndex_f++; + rIndex_f++; + if (l.MTime != r.MTime) { - return false; + cDir.Children.Add( + new File + { + Path = r.FormatedPath, + MTime = l.MTime, + NextOp = NextOpType.Modify + } + ); } } - return true; - } - } - - /// - /// clone, 但是更改根目录 - /// - /// 操作步骤 - /// 旧根路径 - /// 新根路径 - /// 是否重置下步操作 - /// - public Dir Clone( - NextOpType? optype, - string oldRootPath, - string newRootPath, - bool IsResetNextOpType = false - ) - { - var ndir = this.Clone(optype, IsResetNextOpType); - ndir.ResetRootPath(oldRootPath, newRootPath); - return ndir; - } - - /// - /// clone,不克隆文件 - /// - /// - /// - /// - /// - public Dir Clone(NextOpType? optype = null, bool IsResetNextOpType = false) - { - var ndir = new Dir(this.FormatedPath, [], IsResetNextOpType ? optype : this.NextOp); - - var nchildren = this - .Children.AsEnumerable() - .Select(x => + else { - if (x is File file) + //因为已经按照文件路径排过序了,当左侧文件名大于右侧,那么将根据右侧,添加一个删除diff + if (lreativePath.CompareTo(rreativePath) > 0) { - return new File( - file.FormatedPath, - file.MTime, - IsResetNextOpType ? optype : file.NextOp - ) as AFileOrDir; + rIndex_f++; + cDir.Children.Add( + new File + { + Path = r.FormatedPath, + MTime = r.MTime, + NextOp = NextOpType.Del + } + ); } - else if (x is Dir dir) + //相反,根据左侧,添加一个新增diff + else { - return dir.Clone(optype, IsResetNextOpType); + lIndex_f++; + cDir.Children.Add( + new File + { + Path = l.FormatedPath.Replace(ldir.FormatedPath, rdir.FormatedPath), + MTime = l.MTime, + NextOp = NextOpType.Add + } + ); + } + } + } + + //文件夹的比较和文件类似,但是他会递归调用文件夹的diff函数,直至文件停止 + + while (true) + { + if (lIndex_d == lDirs.Count && rIndex_d == rDirs.Count) + { + break; + } + if (lIndex_d == lDirs.Count) + { + var er = rDirs[rIndex_d]; + cDir.Children.Add(er.Clone(NextOpType.Del, true)); + rIndex_d++; + continue; + } + if (rIndex_d == rDirs.Count) + { + var el = lDirs[lIndex_d]; + cDir.Children.Add( + el.Clone(NextOpType.Add, true) + .ResetRootPath(ldir.FormatedPath, rdir.FormatedPath) + ); + lIndex_d++; + continue; + } + var l = lDirs[lIndex_d]; + var r = rDirs[rIndex_d]; + var lreativePath = l.FormatedPath.Replace(ldir.FormatedPath, ""); + var rreativePath = r.FormatedPath.Replace(rdir.FormatedPath, ""); + if (lreativePath == rreativePath) + { + lIndex_d++; + rIndex_d++; + var rDir = l.Diff(r); + //而等于0,这表示此文件夹的内容没有变化 + if (rDir.Children.Count != 0) + { + cDir.Children.Add(rDir); + } + } + else + { + //文件夹重命名将会触发整个文件夹的删除和新增操作,这里没有办法定位到操作是修改文件夹(?) 和git类似。 + //潜在的问题是,修改文件夹名,此文件夹包含大量的文件,将触发大量操作。 + + if (lreativePath.CompareTo(rreativePath) > 0) + { + cDir.Children.Add(r.Clone(NextOpType.Del, true)); + rIndex_d++; } else { - throw new Exception("cannot be here!"); + cDir.Children.Add( + l.Clone(NextOpType.Add, true) + .ResetRootPath(ldir.FormatedPath, rdir.FormatedPath) + ); + lIndex_d++; } - }) - .ToList(); - ndir.Children = nchildren; - - return ndir; + } + } + return cDir; } - /// - /// 重设置根目录 - /// - /// - /// - public void ResetRootPath(string oldPath, string newPath) + public static void WriteByThisInfo(this Dir thisDir, FileDirOpStra fileDirOp) { - this.FormatedPath = this.FormatedPath.Replace(oldPath, newPath); - this.Children.ForEach(e => + static void f(Dir dir, FileDirOpStra fileDirOp) { - if (e is File file) + dir.AccessCheck(); + foreach (var child in dir.Children) { - file.FormatedPath = file.FormatedPath.Replace(oldPath, newPath); + if (child.Type == DirOrFile.Dir) + { + if (child is Dir childDir) + { + fileDirOp.DirCreate(childDir, false); + f(childDir, fileDirOp); + } + } + else + { + if (child is File childFile) + { + fileDirOp.FileCreate(child.FormatedPath, childFile.MTime); + } + else + { + throw new ArgumentException("child is not File!"); + } + } } - else if (e is Dir dir) - { - dir.ResetRootPath(oldPath, newPath); - } - }); + } + f(thisDir, fileDirOp); } - /// - /// 文件夹合并 - /// - /// 具体操作步骤 - /// 将要更新的内容 - /// 是否更新Object对象 - /// 是否更新文件目录树 - /// - public void Combine( + public static void ExtractInfo( + this Dir thisDir, + List? cherryPicks = null, + List? exculdes = null + ) + { + bool filter(string path) + { + if (cherryPicks != null) + { + return cherryPicks.Contains(path); + } + + if (exculdes != null) + { + return !exculdes.Contains(path); + } + return true; + } + + if (thisDir.Children.Count != 0) + { + throw new NotSupportedException("this dir is not empty."); + } + string[] files = Directory.GetFiles(thisDir.FormatedPath); + string[] dirs = Directory.GetDirectories(thisDir.FormatedPath); + foreach (var file in files) + { + if (filter(file)) + { + thisDir.Children.Add( + new File { Path = file, MTime = System.IO.File.GetLastWriteTime($"{file}") } + ); + } + } + foreach (var dir in dirs) + { + if (filter(dir)) + { + var ndir = new Dir { Path = dir, Children = [] }; + ndir.ExtractInfo(); + thisDir.Children.Add(ndir); + } + } + } + + public static void AddChild(this Dir thisDir, AFileOrDir child) + { + if (child.FormatedPath[..thisDir.FormatedPath.Length] != thisDir.FormatedPath) + { + throw new ArgumentException("their rootpath are not same!"); + } + var filtedChildren = thisDir.Children.Where(x => x.Type == child.Type); + + var mached = filtedChildren.Where(x => x.FormatedPath == child.FormatedPath); + + if (mached.Any()) + { + if (child is File) + { + throw new ArgumentException( + $"there are same path in the children:{child.FormatedPath}" + ); + } + else if (child is Dir dir) + { + var tdir = mached.FirstOrDefault(); + if (tdir is Dir ndir) + { + foreach (var d in dir.Children) + { + ndir.AddChild(d); + } + } + } + } + else + { + thisDir.Children.Add(child); + } + } + + public static void Combine( + this Dir thisDir, FileDirOpStra? fileDirOp, Dir diffdir, bool IsUpdateObject = true, @@ -138,13 +373,13 @@ public class Dir(string path, List? children = null, NextOpType? nex { diffdir.AccessCheck(); } - if (this.FormatedPath != diffdir.FormatedPath) + if (thisDir.FormatedPath != diffdir.FormatedPath) { throw new ArgumentException("their path is not same"); } else { - var ldir = this; + var ldir = thisDir; var rdir = diffdir; foreach (var oc in diffdir.Children) @@ -157,7 +392,9 @@ public class Dir(string path, List? children = null, NextOpType? nex { if (IsUpdateObject) { - ldir.AddChild(new File(rfile.FormatedPath, rfile.MTime)); + ldir.AddChild( + new File { Path = rfile.FormatedPath, MTime = rfile.MTime } + ); } if (IsUpdateDirFile) { @@ -247,153 +484,65 @@ public class Dir(string path, List? children = null, NextOpType? nex } } - /// - /// 合并两个文件夹,other不会发生改变,this将合并一个副本,这不会改变文件结构 - /// - /// 它的一个clone将被合并的dir,它的NextOp 不应该是空,否则什么都不会发生 - /// - public void CombineJustObject(Dir other) + public static Dir Clone( + this Dir thisDir, + NextOpType? optype = null, + bool IsResetNextOpType = false + ) { - Combine(null, other, true, false); - } - - /// - /// 合并两个文件夹,other不会发生改变,this将不会改变,而文件结构会改变 - /// - /// 它的一个clone将被合并的dir,它的NextOp 不应该是空,否则什么都不会发生 - /// - public void CombineJustDirFile(FileDirOpStra fileDirOp, Dir diffDir) - { - Combine(fileDirOp, diffDir, false, true); - } - - /// - /// 添加子节点,根目录相同,才会被添加进去 - /// - /// - /// / - protected void AddChild(AFileOrDir child) - { - if (child.FormatedPath[..this.FormatedPath.Length] != this.FormatedPath) + var ndir = new Dir { - throw new ArgumentException("their rootpath are not same!"); - } - var filtedChildren = this.Children.Where(x => x.Type == child.Type); + Path = thisDir.FormatedPath, + Children = [], + NextOp = IsResetNextOpType ? optype : thisDir.NextOp + }; - var mached = filtedChildren.Where(x => x.FormatedPath == child.FormatedPath); - - if (mached.Any()) - { - if (child is File) + var nchildren = thisDir + .Children.AsEnumerable() + .Select(x => { - throw new ArgumentException( - $"there are same path in the children:{child.FormatedPath}" - ); - } - else if (child is Dir dir) - { - var tdir = mached.FirstOrDefault(); - if (tdir is Dir ndir) + if (x is File file) { - foreach (var d in dir.Children) - { - ndir.AddChild(d); - } + return new File + { + Path = file.FormatedPath, + MTime = file.MTime, + NextOp = IsResetNextOpType ? optype : file.NextOp + } as AFileOrDir; } - } - } - else - { - this.Children.Add(child); - } - } - - /// - /// 从文件夹中提取信息 - /// - /// 绝对路径,只包含的文件或者目录 - /// 绝对路径,排除的文件或目录 - /// - public void ExtractInfo(List? cherryPicks = null, List? exculdes = null) - { - bool filter(string path) - { - if (cherryPicks != null) - { - return cherryPicks.Contains(path); - } - - if (exculdes != null) - { - return !exculdes.Contains(path); - } - return true; - } - - if (this.Children.Count != 0) - { - throw new NotSupportedException("this dir is not empty."); - } - string[] files = Directory.GetFiles(this.FormatedPath); - string[] dirs = Directory.GetDirectories(this.FormatedPath); - foreach (var file in files) - { - if (filter(file)) - { - this.Children.Add(new File(file, System.IO.File.GetLastWriteTime($"{file}"))); - } - } - foreach (var dir in dirs) - { - if (filter(dir)) - { - var ndir = new Dir(dir); - ndir.ExtractInfo(); - this.Children.Add(ndir); - } - } - } - - /// - /// 写入目录文件树,首先必须定义写入文件的策略,此目录结构不包含文件内容,但有一个 - /// 文件的修改时间,是否修改文件的修改时间,需要定义文件的写入策略 WriteFileStrageFunc - /// - /// - public void WriteByThisInfo(FileDirOpStra fileDirOp) - { - static void f(Dir dir, FileDirOpStra fileDirOp) - { - dir.AccessCheck(); - foreach (var child in dir.Children) - { - if (child.Type == DirOrFile.Dir) + else if (x is Dir dir) { - if (child is Dir childDir) - { - fileDirOp.DirCreate(childDir, false); - f(childDir, fileDirOp); - } + return dir.Clone(optype, IsResetNextOpType); } else { - if (child is File childFile) - { - fileDirOp.FileCreate(child.FormatedPath, childFile.MTime); - } - else - { - throw new ArgumentException("child is not File!"); - } + throw new Exception("cannot be here!"); } - } - } - f(this, fileDirOp); + }) + .ToList(); + ndir.Children = nchildren; + + return ndir; } - /// - /// 校验文件夹和文件权限 - /// - private void AccessCheck() + public static Dir ResetRootPath(this Dir thisDir, string oldPath, string newPath) + { + thisDir.FormatedPath = thisDir.FormatedPath.Replace(oldPath, newPath); + thisDir.Children.ForEach(e => + { + if (e is File file) + { + file.FormatedPath = file.FormatedPath.Replace(oldPath, newPath); + } + else if (e is Dir dir) + { + dir.ResetRootPath(oldPath, newPath); + } + }); + return thisDir; + } + + public static void AccessCheck(this Dir thisDir) { //不是核心关注点,下面实现有bug。不校验所有文件夹权限,创建时会抛出错误,此时手动处理吧。 return; @@ -478,222 +627,248 @@ public class Dir(string path, List? children = null, NextOpType? nex // } //}); } - - /// - /// 比较两个目录文件树是否相同,不相同返回差异部分,左侧是右侧的下一个版本,任何一个节点的nextop != null,即所有 - /// 节点都会打上标记 - /// 文件夹的 NextOp 只有新增和删除 - /// - /// - /// - public Dir Diff(Dir other) - { - var ldir = this; - var rdir = other; - Dir? cDir = new(rdir.FormatedPath); - //分别对文件和文件夹分组 - List lFiles = []; - List rFiles = []; - List lDirs = []; - List rDirs = []; - var lGroups = ldir.Children.GroupBy(x => x.Type); - var rGroups = rdir.Children.GroupBy(x => x.Type); - foreach (var g in lGroups) - { - if (g.Key == DirOrFile.Dir) - { - lDirs = g.AsEnumerable() - .Select(n => - { - if (n is Dir dir) - { - return dir; - } - throw new Exception("cannot be here"); - }) - .ToList(); - } - else - { - lFiles = g.AsEnumerable() - .Select(n => - { - if (n is File file) - { - return file; - } - throw new Exception("cannot be here"); - }) - .ToList(); - } - } - - foreach (var g in rGroups) - { - if (g.Key == DirOrFile.Dir) - { - rDirs = g.AsEnumerable() - .Select(n => - { - if (n is Dir dir) - { - return dir; - } - throw new Exception("cannot be here"); - }) - .ToList(); - } - else - { - rFiles = g.AsEnumerable() - .Select(n => - { - if (n is File file) - { - return file; - } - throw new Exception("cannot be here"); - }) - .ToList(); - } - } - - //排序,然后对比 - int lIndex_f = 0; - int rIndex_f = 0; - int lIndex_d = 0; - int rIndex_d = 0; - lFiles.Sort(Compare); - rFiles.Sort(Compare); - lDirs.Sort(Compare); - rDirs.Sort(Compare); - //对比文件 - while (true) - { - //当两个线性表都走到最后时,退出循环 - if (lIndex_f == lFiles.Count && rIndex_f == rFiles.Count) - { - break; - } - //左侧先到底,右侧都是将要删除的 - if (lIndex_f == lFiles.Count) - { - var er = rFiles[rIndex_f]; - cDir.Children.Add(new File(er.FormatedPath, er.MTime, NextOpType.Del)); - rIndex_f++; - continue; - } - //右侧先到底,左侧都是要添加的 - if (rIndex_f == rFiles.Count) - { - var el = lFiles[lIndex_f]; - - cDir.Children.Add( - new File( - el.FormatedPath.Replace(ldir.FormatedPath, rdir.FormatedPath), - el.MTime, - NextOpType.Add - ) - ); - lIndex_f++; - continue; - } - var l = lFiles[lIndex_f]; - var r = rFiles[rIndex_f]; - //将根路径差异抹平 - var lreativePath = l.FormatedPath.Replace(ldir.FormatedPath, ""); - var rreativePath = r.FormatedPath.Replace(rdir.FormatedPath, ""); - //两文件相同,对比文件修改时间,不同增加到diff内容 - if (lreativePath == rreativePath) - { - lIndex_f++; - rIndex_f++; - if (l.MTime != r.MTime) - { - cDir.Children.Add(new File(r.FormatedPath, l.MTime, NextOpType.Modify)); - } - } - else - { - //因为已经按照文件路径排过序了,当左侧文件名大于右侧,那么将根据右侧,添加一个删除diff - if (lreativePath.CompareTo(rreativePath) > 0) - { - rIndex_f++; - cDir.Children.Add(new File(r.FormatedPath, r.MTime, NextOpType.Del)); - } - //相反,根据左侧,添加一个新增diff - else - { - lIndex_f++; - cDir.Children.Add( - new File( - l.FormatedPath.Replace(ldir.FormatedPath, rdir.FormatedPath), - l.MTime, - NextOpType.Add - ) - ); - } - } - } - - //文件夹的比较和文件类似,但是他会递归调用文件夹的diff函数,直至文件停止 - - while (true) - { - if (lIndex_d == lDirs.Count && rIndex_d == rDirs.Count) - { - break; - } - if (lIndex_d == lDirs.Count) - { - var er = rDirs[rIndex_d]; - cDir.Children.Add(er.Clone(NextOpType.Del, true)); - rIndex_d++; - continue; - } - if (rIndex_d == rDirs.Count) - { - var el = lDirs[lIndex_d]; - cDir.Children.Add( - el.Clone(NextOpType.Add, ldir.FormatedPath, rdir.FormatedPath, true) - ); - lIndex_d++; - continue; - } - var l = lDirs[lIndex_d]; - var r = rDirs[rIndex_d]; - var lreativePath = l.FormatedPath.Replace(ldir.FormatedPath, ""); - var rreativePath = r.FormatedPath.Replace(rdir.FormatedPath, ""); - if (lreativePath == rreativePath) - { - lIndex_d++; - rIndex_d++; - var rDir = l.Diff(r); - //而等于0,这表示此文件夹的内容没有变化 - if (rDir.Children.Count != 0) - { - cDir.Children.Add(rDir); - } - } - else - { - //文件夹重命名将会触发整个文件夹的删除和新增操作,这里没有办法定位到操作是修改文件夹(?) 和git类似。 - //潜在的问题是,修改文件夹名,此文件夹包含大量的文件,将触发大量操作。 - - if (lreativePath.CompareTo(rreativePath) > 0) - { - cDir.Children.Add(r.Clone(NextOpType.Del, true)); - rIndex_d++; - } - else - { - cDir.Children.Add( - l.Clone(NextOpType.Add, ldir.FormatedPath, rdir.FormatedPath, true) - ); - lIndex_d++; - } - } - } - return cDir; - } } + +/// +/// 文件夹结构,它包含文件和文件夹 +/// +/// 绝对路径 +/// 子文件或文件夹 +// public class Dirxx(string path, List? children = null, NextOpType? nextOp = null) +// : AFileOrDir(path, DirOrFile.Dir, nextOp) +// { +// public List Children { get; set; } = children ?? []; + +/* public override bool IsEqual(AFileOrDir other) +{ + if (other is not Dir otherDir) + { + return false; + } + else + { + if (this.FormatedPath != otherDir.FormatedPath || this.NextOp != otherDir.NextOp) + { + return false; + } + if (this.Children.Count != otherDir.Children.Count) + { + return false; + } + this.Children.Sort(AFileOrDir.Compare); + otherDir.Children.Sort(AFileOrDir.Compare); + for (int i = 0; i < this.Children.Count; i++) + { + if (!this.Children[i].IsEqual(otherDir.Children[i])) + { + return false; + } + } + return true; + } +} */ + +/// +/// clone, 但是更改根目录 +/// +/// 操作步骤 +/// 旧根路径 +/// 新根路径 +/// 是否重置下步操作 +/// +// public Dir Clone( +// NextOpType? optype, +// string oldRootPath, +// string newRootPath, +// bool IsResetNextOpType = false +// ) +// { +// var ndir = this.Clone(optype, IsResetNextOpType); +// ndir.ResetRootPath(oldRootPath, newRootPath); +// return ndir; +// } + +/// +/// clone,不克隆文件 +/// +/// +/// +/// +/// +// public Dir Clone(NextOpType? optype = null, bool IsResetNextOpType = false) { } + +/// +/// 重设置根目录 +/// +/// +/// +// public void ResetRootPath(string oldPath, string newPath) +// { +// this.FormatedPath = this.FormatedPath.Replace(oldPath, newPath); +// this.Children.ForEach(e => +// { +// if (e is File file) +// { +// file.FormatedPath = file.FormatedPath.Replace(oldPath, newPath); +// } +// else if (e is Dir dir) +// { +// dir.ResetRootPath(oldPath, newPath); +// } +// }); +// } + +/// +/// 文件夹合并 +/// +/// 具体操作步骤 +/// 将要更新的内容 +/// 是否更新Object对象 +/// 是否更新文件目录树 +/// +// public void Combine( +// FileDirOpStra? fileDirOp, +// Dir diffdir, +// bool IsUpdateObject = true, +// bool IsUpdateDirFile = false +// ) { } + +// /// +// /// 合并两个文件夹,other不会发生改变,this将合并一个副本,这不会改变文件结构 +// /// +// /// 它的一个clone将被合并的dir,它的NextOp 不应该是空,否则什么都不会发生 +// /// +// public void CombineJustObject(Dir other) +// { +// Combine(null, other, true, false); +// } + +// /// +// /// 合并两个文件夹,other不会发生改变,this将不会改变,而文件结构会改变 +// /// +// /// 它的一个clone将被合并的dir,它的NextOp 不应该是空,否则什么都不会发生 +// /// +// public void CombineJustDirFile(FileDirOpStra fileDirOp, Dir diffDir) +// { +// Combine(fileDirOp, diffDir, false, true); +// } + +// /// +// /// 添加子节点,根目录相同,才会被添加进去 +// /// +// /// +// /// / +// protected void AddChild(AFileOrDir child) { } + +// /// +// /// 从文件夹中提取信息 +// /// +// /// 绝对路径,只包含的文件或者目录 +// /// 绝对路径,排除的文件或目录 +// /// + + +// /// +// /// 写入目录文件树,首先必须定义写入文件的策略,此目录结构不包含文件内容,但有一个 +// /// 文件的修改时间,是否修改文件的修改时间,需要定义文件的写入策略 WriteFileStrageFunc +// /// +// /// + + +// /// +// /// 校验文件夹和文件权限 +// /// +// // private void AccessCheck()j +// // { +// //不是核心关注点,下面实现有bug。不校验所有文件夹权限,创建时会抛出错误,此时手动处理吧。 +// // return; +// //this.Children.ForEach(e => +// //{ +// // if (e is File file) +// // { +// // if (file.NextOp == null) { } +// // else if (file.NextOp == NextOpType.Add) +// // { +// // if ( +// // !AccessWrapper.CheckDirAccess( +// // Path.GetDirectoryName(file.FormatedPath) +// // ?? throw new DirectoryNotFoundException( +// // $"{file.FormatedPath} 此父路径不存在" +// // ), +// // [DirAcess.CreateFiles] +// // ) +// // ) +// // { +// // throw new UnauthorizedAccessException($"{file.FormatedPath} 无权限创建文件"); +// // } +// // } +// // else if (file.NextOp == NextOpType.Modify) +// // { +// // if ( +// // !( +// // AccessWrapper.CheckFileAccess(file.FormatedPath, [FileAccess.Delete]) +// // && AccessWrapper.CheckDirAccess( +// // Path.GetDirectoryName(file.FormatedPath) +// // ?? throw new DirectoryNotFoundException( +// // $"{file.FormatedPath} 此父路径不存在" +// // ), +// // [DirAcess.CreateFiles] +// // ) +// // ) +// // ) +// // { +// // throw new UnauthorizedAccessException( +// // $"{file.FormatedPath} 无权限删除源文件或者创建新文件" +// // ); +// // } +// // } +// // else if (file.NextOp == NextOpType.Del) +// // { +// // if (!AccessWrapper.CheckFileAccess(file.FormatedPath, [FileAccess.Delete])) +// // { +// // throw new UnauthorizedAccessException($"{file.FormatedPath} 无权限删除源文件"); +// // } +// // } +// // } +// // else if (e is Dir dir) +// // { +// // if (dir.NextOp == null) { } +// // else if (dir.NextOp == NextOpType.Add) +// // { +// // if ( +// // !AccessWrapper.CheckDirAccess( +// // Path.GetDirectoryName(dir.FormatedPath) +// // ?? throw new DirectoryNotFoundException( +// // $"{dir.FormatedPath} 此父路径不存在" +// // ), +// // [DirAcess.CreateDirectories, DirAcess.CreateFiles] +// // ) +// // ) +// // { +// // throw new UnauthorizedAccessException($"{dir.FormatedPath} 无权限创建文件夹或者文件"); +// // } +// // } +// // else if (dir.NextOp == NextOpType.Del) +// // { +// // if (!AccessWrapper.CheckDirAccess(dir.FormatedPath, [DirAcess.Delete])) +// // { +// // throw new UnauthorizedAccessException($"{dir.FormatedPath} 无权限删除文件夹"); +// // } +// // else +// // { +// // //校验是否拥有子文件或者文件夹的删除权限, +// // dir.AccessCheck(); +// // } +// // } +// // } +// //}); +// // } + +// /// +// /// 比较两个目录文件树是否相同,不相同返回差异部分,左侧是右侧的下一个版本,任何一个节点的nextop != null,即所有 +// /// 节点都会打上标记 +// /// 文件夹的 NextOp 只有新增和删除 +// /// +// /// +// /// +// } diff --git a/Server/Common/FileDirBase.cs b/Server/Common/FileDirBase.cs index bd83726..847df13 100644 --- a/Server/Common/FileDirBase.cs +++ b/Server/Common/FileDirBase.cs @@ -13,21 +13,17 @@ public enum NextOpType Del = 2 } -public abstract class AFileOrDir( - string path, - DirOrFile type = DirOrFile.File, - NextOpType? nextOp = null -) +public abstract class AFileOrDir { - public DirOrFile Type { get; set; } = type; - public NextOpType? NextOp { get; set; } = nextOp; + public DirOrFile Type { get; set; } + public NextOpType? NextOp { get; set; } // private string Path = path; /// /// 全部为绝对路径... 占用资源会大一点,但是完全OK /// /// - private string Path = path; + public required string Path { get; set; } /// /// 相当于Path 包装,天杀的windows在路径字符串中使用两种分隔符,“/” 和“\”,这导致,即使两个字符串不相同,也可能是同一个路径。现在把它们统一起来 @@ -55,10 +51,13 @@ public abstract class AFileOrDir( /// /// 绝对路径 /// 文件的修改时间/ -public class File(string path, DateTime mtime, NextOpType? nextOp = null) - : AFileOrDir(path, DirOrFile.File, nextOp) +public class File : AFileOrDir { - public DateTime MTime { get; set; } = mtime; + public File() + { + Type = DirOrFile.File; + } + public required DateTime MTime { get; set; } public override bool IsEqual(AFileOrDir other) { @@ -77,3 +76,41 @@ public class File(string path, DateTime mtime, NextOpType? nextOp = null) } } } + +public class Dir : AFileOrDir +{ + public Dir() + { + Type = DirOrFile.Dir; + } + public required List Children { get; set; } + + public override bool IsEqual(AFileOrDir other) + { + if (other is not Dir otherDir) + { + return false; + } + else + { + if (this.FormatedPath != otherDir.FormatedPath || this.NextOp != otherDir.NextOp) + { + return false; + } + if (this.Children.Count != otherDir.Children.Count) + { + return false; + } + this.Children.Sort(AFileOrDir.Compare); + otherDir.Children.Sort(AFileOrDir.Compare); + for (int i = 0; i < this.Children.Count; i++) + { + if (!this.Children[i].IsEqual(otherDir.Children[i])) + { + return false; + } + } + return true; + } + } +} diff --git a/Server/LocalServer/StateHelper.cs b/Server/LocalServer/StateHelper.cs index 5c105c8..c1a5658 100644 --- a/Server/LocalServer/StateHelper.cs +++ b/Server/LocalServer/StateHelper.cs @@ -202,7 +202,11 @@ public class DiffFileAndPackHelper(LocalSyncServer context) //提取本地文件的信息 Context.NotNullSyncConfig.DirFileConfigs.ForEach(e => { - e.LocalDirInfo = new Dir(Context.NotNullSyncConfig.LocalRootPath + e.DirPath); + e.LocalDirInfo = new Dir + { + Path = Context.NotNullSyncConfig.LocalRootPath + e.DirPath, + Children = [] + }; e.LocalDirInfo.ExtractInfo(e.CherryPicks, e.Excludes); }); //将配置信息发送到remoteServer diff --git a/Server/RemoteServer/StateHelper.cs b/Server/RemoteServer/StateHelper.cs index 43f33ce..036eb32 100644 --- a/Server/RemoteServer/StateHelper.cs +++ b/Server/RemoteServer/StateHelper.cs @@ -204,7 +204,7 @@ public class FinallyPublishHelper(RemoteSyncServer context) { if (e.RemoteDirInfo != null && e.DiffDirInfo != null) { - e.RemoteDirInfo.CombineJustDirFile(DirFileOp, e.DiffDirInfo); + e.RemoteDirInfo.Combine(DirFileOp, e.DiffDirInfo,false,true); } }); diff --git a/Server/ServerTest/DirFileOpTest.cs b/Server/ServerTest/DirFileOpTest.cs index 4776d29..4a75521 100644 --- a/Server/ServerTest/DirFileOpTest.cs +++ b/Server/ServerTest/DirFileOpTest.cs @@ -1,8 +1,9 @@ using Common; +using Newtonsoft.Json; using XUnit.Project.Attributes; -/*using Newtonsoft.Json;*/ namespace ServerTest; + /// /// xUnit将会对每个测试方法创建一个测试上下文,IClassFixture可以用来创建类中共享测试上下文, /// @@ -35,10 +36,10 @@ public class DirFileOpTest : IDisposable { filesSeed.NewDir.WriteByThisInfo(filesSeed.fileDirOp); filesSeed.OldDir.WriteByThisInfo(filesSeed.fileDirOp); - Dir nnd = new(filesSeed.NewDir.FormatedPath); + Dir nnd = new() { Path = filesSeed.NewDir.FormatedPath, Children = [] }; nnd.ExtractInfo(); Assert.True(nnd.IsEqual(filesSeed.NewDir), "新文件提取文件夹的信息与写入信息不一致!"); - Dir nod = new(filesSeed.OldDir.FormatedPath); + Dir nod = new() { Path = filesSeed.OldDir.FormatedPath, Children = [] }; nod.ExtractInfo(); Assert.True(nod.IsEqual(filesSeed.OldDir), "旧提取文件夹的信息与写入信息不一致!"); } @@ -54,7 +55,7 @@ public class DirFileOpTest : IDisposable // Console.WriteLine(cDDir.Children.Count); //Assert.True(IsSuccess); - /*var str = JsonConvert.SerializeObject(cDDir);*/ + // var str = JsonConvert.SerializeObject(cDDir); Assert.True(filesSeed.DiffDir.IsEqual(cDDir), "文件对比结果错误!"); } @@ -65,8 +66,8 @@ public class DirFileOpTest : IDisposable public void SyncFileDir() { filesSeed.OldDir.WriteByThisInfo(filesSeed.fileDirOp); - filesSeed.OldDir.CombineJustDirFile(filesSeed.fileDirOp, filesSeed.DiffDir); - Dir oldSync = new(filesSeed.OldDir.FormatedPath); + filesSeed.OldDir.Combine(filesSeed.fileDirOp, filesSeed.DiffDir, false, true); + Dir oldSync = new() { Path = filesSeed.OldDir.FormatedPath, Children = [] }; oldSync.ExtractInfo(); oldSync.ResetRootPath(filesSeed.OldDir.FormatedPath, filesSeed.NewDir.FormatedPath); Assert.True(oldSync.IsEqual(filesSeed.NewDir), "文件夹同步后信息保持不一致!"); @@ -78,7 +79,7 @@ public class DirFileOpTest : IDisposable [Fact, TestPriority(3)] public void DirsCombine() { - filesSeed.OldDir.CombineJustObject(filesSeed.DiffDir); + filesSeed.OldDir.Combine(null, filesSeed.DiffDir); //Assert.False(filesSeed.NewDir.IsEqual(filesSeed.OldDir)); filesSeed.OldDir.ResetRootPath("OldDir", "NewDir"); // Console.WriteLine(filesSeed.OldDir.Path); diff --git a/Server/ServerTest/FilesSeed.cs b/Server/ServerTest/FilesSeed.cs index 24ee286..3a48e59 100644 --- a/Server/ServerTest/FilesSeed.cs +++ b/Server/ServerTest/FilesSeed.cs @@ -10,168 +10,263 @@ public class FilesSeed : IDisposable // string TestPath = Path.Combine(Directory.GetCurrentDirectory(), "../../.."); DateTime NewTime = DateTime.Now.AddSeconds(-99); DateTime OldTime = NewTime.AddSeconds(-20); - NewDir = new Dir( - TestPath + "/NewDir", + NewDir = new Dir + { + Path = TestPath + "/NewDir", + Children = [ - new Dir($"{TestPath}/NewDir/0"), - new Dir( - $"{TestPath}/NewDir/1", - [new Common.File($"{TestPath}/NewDir/1/1.txt", NewTime)] - ), - new Dir( - $"{TestPath}/NewDir/2", + new Dir { Path = $"{TestPath}/NewDir/0", Children = [] }, + new Dir + { + Path = $"{TestPath}/NewDir/1", + Children = [ - new Common.File($"{TestPath}/NewDir/2/2.txt", NewTime), - new Dir( - $"{TestPath}/NewDir/2/2_1", - [ - new Common.File($"{TestPath}/NewDir/2/2_1/1.txt", NewTime), - new Common.File($"{TestPath}/NewDir/2/2_1/2.txt", NewTime), - ] - ), - new Dir( - $"{TestPath}/NewDir/2/2_2", - [ - new Common.File($"{TestPath}/NewDir/2/2_2/1.txt", NewTime), - new Common.File($"{TestPath}/NewDir/2/2_2/2.txt", NewTime), - new Dir( - $"{TestPath}/NewDir/2/2_2/2_3", - [ - new Common.File( - $"{TestPath}/NewDir/2/2_2/2_3/1.txt", - NewTime - ), - ] - ), - ] - ) + new Common.File { Path = $"{TestPath}/NewDir/1/1.txt", MTime = NewTime } ] - ), + }, + new Dir + { + Path = $"{TestPath}/NewDir/2", + Children = + [ + new Common.File { Path = $"{TestPath}/NewDir/2/2.txt", MTime = NewTime }, + new Dir + { + Path = $"{TestPath}/NewDir/2/2_1", + Children = + [ + new Common.File + { + Path = $"{TestPath}/NewDir/2/2_1/1.txt", + MTime = NewTime + }, + new Common.File + { + Path = $"{TestPath}/NewDir/2/2_1/2.txt", + MTime = NewTime + }, + ] + }, + new Dir + { + Path = $"{TestPath}/NewDir/2/2_2", + Children = + [ + new Common.File + { + Path = $"{TestPath}/NewDir/2/2_2/1.txt", + MTime = NewTime + }, + new Common.File + { + Path = $"{TestPath}/NewDir/2/2_2/2.txt", + MTime = NewTime + }, + new Dir + { + Path = $"{TestPath}/NewDir/2/2_2/2_3", + Children = + [ + new Common.File + { + Path = $"{TestPath}/NewDir/2/2_2/2_3/1.txt", + MTime = NewTime + }, + ] + }, + ] + } + ] + }, ] - ); - DiffDir = new Dir( - $"{TestPath}/OldDir", + }; + DiffDir = new Dir + { + Path = $"{TestPath}/OldDir", + Children = [ - new Dir( - $"{TestPath}/OldDir/1", - [new Common.File($"{TestPath}/OldDir/1/2_D.txt", NewTime, NextOpType.Del),] - ), - new Dir( - $"{TestPath}/OldDir/2", + new Dir + { + Path = $"{TestPath}/OldDir/1", + Children = + [ + new Common.File + { + Path = $"{TestPath}/OldDir/1/2_D.txt", + MTime = NewTime, + NextOp = NextOpType.Del + }, + ] + }, + new Dir + { + Path = $"{TestPath}/OldDir/2", + Children = [ // 将要添加 - new Common.File($"{TestPath}/OldDir/2/2.txt", NewTime, NextOpType.Add), - new Dir( - $"{TestPath}/OldDir/2/2_1", + new Common.File + { + Path = $"{TestPath}/OldDir/2/2.txt", + MTime = NewTime, + NextOp = NextOpType.Add + }, + new Dir + { + Path = $"{TestPath}/OldDir/2/2_1", + Children = [ - new Common.File( - $"{TestPath}/OldDir/2/2_1/2.txt", - NewTime, - NextOpType.Modify - ), + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_1/2.txt", + MTime = NewTime, + NextOp = NextOpType.Modify + }, ] - ), - new Dir( - $"{TestPath}/OldDir/2/2_2_M", + }, + new Dir + { + Path = $"{TestPath}/OldDir/2/2_2_M", + Children = [ - new Common.File( - $"{TestPath}/OldDir/2/2_2_M/1.txt", - OldTime, - NextOpType.Del - ), - new Common.File( - $"{TestPath}/OldDir/2/2_2_M/2.txt", - OldTime, - NextOpType.Del - ), - new Dir( - $"{TestPath}/OldDir/2/2_2_M/2_3", + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2_M/1.txt", + MTime = OldTime, + NextOp = NextOpType.Del + }, + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2_M/2.txt", + MTime = OldTime, + NextOp = NextOpType.Del + }, + new Dir + { + Path = $"{TestPath}/OldDir/2/2_2_M/2_3", + Children = [ - new Common.File( - $"{TestPath}/OldDir/2/2_2_M/2_3/1.txt", - OldTime, - NextOpType.Del - ), + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2_M/2_3/1.txt", + MTime = OldTime, + NextOp = NextOpType.Del + }, ], - NextOpType.Del - ), + NextOp = NextOpType.Del + }, ], - NextOpType.Del - ), - new Dir( - $"{TestPath}/OldDir/2/2_2", + NextOp = NextOpType.Del + }, + new Dir + { + Path = $"{TestPath}/OldDir/2/2_2", + Children = [ - new Common.File( - $"{TestPath}/OldDir/2/2_2/1.txt", - NewTime, - NextOpType.Add - ), - new Common.File( - $"{TestPath}/OldDir/2/2_2/2.txt", - NewTime, - NextOpType.Add - ), - new Dir( - $"{TestPath}/OldDir/2/2_2/2_3", + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2/1.txt", + MTime = NewTime, + NextOp = NextOpType.Add + }, + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2/2.txt", + MTime = NewTime, + NextOp = NextOpType.Add + }, + new Dir + { + Path = $"{TestPath}/OldDir/2/2_2/2_3", + Children = [ - new Common.File( - $"{TestPath}/OldDir/2/2_2/2_3/1.txt", - NewTime, - NextOpType.Add - ), + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2/2_3/1.txt", + MTime = NewTime, + NextOp = NextOpType.Add + }, ], - NextOpType.Add - ), + NextOp = NextOpType.Add + }, ], - NextOpType.Add - ) + NextOp = NextOpType.Add + } ] - ), + }, ] - ); - OldDir = new Dir( - $"{TestPath}/OldDir", + }; + OldDir = new Dir + { + Path = $"{TestPath}/OldDir", + Children = [ - new Dir($"{TestPath}/OldDir/0"), - new Dir( - $"{TestPath}/OldDir/1", + new Dir { Path = $"{TestPath}/OldDir/0", Children = [] }, + new Dir + { + Path = $"{TestPath}/OldDir/1", + Children = [ //不做修改 - new Common.File($"{TestPath}/OldDir/1/1.txt", NewTime), + new Common.File { Path = $"{TestPath}/OldDir/1/1.txt", MTime = NewTime }, //将要删除 - new Common.File($"{TestPath}/OldDir/1/2_D.txt", NewTime), + new Common.File { Path = $"{TestPath}/OldDir/1/2_D.txt", MTime = NewTime }, ] - ), - new Dir( - $"{TestPath}/OldDir/2", + }, + new Dir + { + Path = $"{TestPath}/OldDir/2", + Children = [ - new Dir( - $"{TestPath}/OldDir/2/2_1", + new Dir + { + Path = $"{TestPath}/OldDir/2/2_1", + Children = [ - new Common.File($"{TestPath}/OldDir/2/2_1/1.txt", NewTime), - new Common.File($"{TestPath}/OldDir/2/2_1/2.txt", OldTime), + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_1/1.txt", + MTime = NewTime + }, + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_1/2.txt", + MTime = OldTime + }, ] - ), - new Dir( - $"{TestPath}/OldDir/2/2_2_M", + }, + new Dir + { + Path = $"{TestPath}/OldDir/2/2_2_M", + Children = [ - new Common.File($"{TestPath}/OldDir/2/2_2_M/1.txt", OldTime), - new Common.File($"{TestPath}/OldDir/2/2_2_M/2.txt", OldTime), - new Dir( - $"{TestPath}/OldDir/2/2_2_M/2_3", + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2_M/1.txt", + MTime = OldTime + }, + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2_M/2.txt", + MTime = OldTime + }, + new Dir + { + Path = $"{TestPath}/OldDir/2/2_2_M/2_3", + Children = [ - new Common.File( - $"{TestPath}/OldDir/2/2_2_M/2_3/1.txt", - OldTime - ), + new Common.File + { + Path = $"{TestPath}/OldDir/2/2_2_M/2_3/1.txt", + MTime = OldTime + }, ] - ), + }, ] - ) + } ] - ), + }, ] - ); + }; fileDirOp = new SimpleFileDirOp(); } diff --git a/Server/ServerTest/PipeTest.cs b/Server/ServerTest/PipeTest.cs index 5fbbc82..fc9673f 100644 --- a/Server/ServerTest/PipeTest.cs +++ b/Server/ServerTest/PipeTest.cs @@ -13,46 +13,46 @@ public class PipeTest [Fact] public async void TestCase() { - var p1 = new TestPipe(false, "1"); - var x = Task.Run(async () => - { - var rs = p1.Work( - (byte[] b) => - { - Console.WriteLine(b); - return true; - } - ); - await foreach (var r in rs) - { - Console.WriteLine(r); - } - }); - //await p1.Close("sdf"); - //await x; - var p2 = new TestPipe(false, "2"); - p1.Other = p2; - p2.Other = p1; - var p3 = new TestPipe(true, "3"); - var p4 = new TestPipe(true, "4"); - p3.Other = p4; - p4.Other = p3; - RemoteSyncServerFactory.NamePwd = [new Tuple("Test", "t123")]; - var lf = new LocalSyncServerFactory(); - lf.CreateLocalSyncServer(p2, "Test", p3); - var rf = new RemoteSyncServerFactory(); - rf.CreateRemoteSyncServer(p4, "Test"); - var starter = new SyncMsg - { - Body = JsonSerializer.Serialize(new PipeSeed().TestConfig), - Type = SyncMsgType.General, - Step = SyncProcessStep.Connect - }; - await p1.SendMsg(starter); - await x; - if (p1.ErrResult != null) - { - Assert.Fail(p1.ErrResult); - } + // var p1 = new TestPipe(false, "1"); + // var x = Task.Run(async () => + // { + // var rs = p1.Work( + // (byte[] b) => + // { + // Console.WriteLine(b); + // return true; + // } + // ); + // await foreach (var r in rs) + // { + // Console.WriteLine(r); + // } + // }); + // //await p1.Close("sdf"); + // //await x; + // var p2 = new TestPipe(false, "2"); + // p1.Other = p2; + // p2.Other = p1; + // var p3 = new TestPipe(true, "3"); + // var p4 = new TestPipe(true, "4"); + // p3.Other = p4; + // p4.Other = p3; + // RemoteSyncServerFactory.NamePwd = [new Tuple("Test", "t123")]; + // var lf = new LocalSyncServerFactory(); + // lf.CreateLocalSyncServer(p2, "Test", p3); + // var rf = new RemoteSyncServerFactory(); + // rf.CreateRemoteSyncServer(p4, "Test"); + // var starter = new SyncMsg + // { + // Body = JsonSerializer.Serialize(new PipeSeed().TestConfig), + // Type = SyncMsgType.General, + // Step = SyncProcessStep.Connect + // }; + // await p1.SendMsg(starter); + // await x; + // if (p1.ErrResult != null) + // { + // Assert.Fail(p1.ErrResult); + // } } }