From 8047d46ad1cb1d3b731e3c4f8aa3b8e7b5a2493d Mon Sep 17 00:00:00 2001 From: Misaki Bear <51944655+MisakiBear@users.noreply.github.com> Date: Mon, 22 Feb 2021 22:48:55 +0900 Subject: [PATCH] Initial commit --- .gitattributes | 2 + Adv-FolderSize.sln | 31 +++ Adv-FolderSize/Adv-FolderSize.csproj | 17 ++ Adv-FolderSize/Args.cs | 63 +++++++ Adv-FolderSize/ColorConsole.cs | 18 ++ Adv-FolderSize/FolderAnalysis.cs | 175 +++++++++++++++++ Adv-FolderSize/NuGet/TriggerLib.1.1.1.nupkg | Bin 0 -> 5617 bytes Adv-FolderSize/Program.cs | 176 ++++++++++++++++++ .../PublishProfiles/FolderProfile.pubxml | 17 ++ Adv-FolderSize/StringExtension.cs | 19 ++ FolderAnalysisFSLib/ByteMeasure.fs | 27 +++ FolderAnalysisFSLib/FolderAnalysisBase.fs | 59 ++++++ .../FolderAnalysisFSLib.fsproj | 15 ++ FolderAnalysisFSLib/FolderAnalysisTools.fs | 104 +++++++++++ .../NotAnalyzedYetException.fs | 13 ++ README.md | 2 + 16 files changed, 738 insertions(+) create mode 100644 .gitattributes create mode 100644 Adv-FolderSize.sln create mode 100644 Adv-FolderSize/Adv-FolderSize.csproj create mode 100644 Adv-FolderSize/Args.cs create mode 100644 Adv-FolderSize/ColorConsole.cs create mode 100644 Adv-FolderSize/FolderAnalysis.cs create mode 100644 Adv-FolderSize/NuGet/TriggerLib.1.1.1.nupkg create mode 100644 Adv-FolderSize/Program.cs create mode 100644 Adv-FolderSize/Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 Adv-FolderSize/StringExtension.cs create mode 100644 FolderAnalysisFSLib/ByteMeasure.fs create mode 100644 FolderAnalysisFSLib/FolderAnalysisBase.fs create mode 100644 FolderAnalysisFSLib/FolderAnalysisFSLib.fsproj create mode 100644 FolderAnalysisFSLib/FolderAnalysisTools.fs create mode 100644 FolderAnalysisFSLib/NotAnalyzedYetException.fs create mode 100644 README.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/Adv-FolderSize.sln b/Adv-FolderSize.sln new file mode 100644 index 0000000..ff38d3a --- /dev/null +++ b/Adv-FolderSize.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adv-FolderSize", "Adv-FolderSize\Adv-FolderSize.csproj", "{50E3AA47-DCF6-4C5B-969D-9D1744B13A9D}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FolderAnalysisFSLib", "FolderAnalysisFSLib\FolderAnalysisFSLib.fsproj", "{BA6F8578-098B-438E-A2ED-630267396879}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {50E3AA47-DCF6-4C5B-969D-9D1744B13A9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50E3AA47-DCF6-4C5B-969D-9D1744B13A9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50E3AA47-DCF6-4C5B-969D-9D1744B13A9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50E3AA47-DCF6-4C5B-969D-9D1744B13A9D}.Release|Any CPU.Build.0 = Release|Any CPU + {BA6F8578-098B-438E-A2ED-630267396879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA6F8578-098B-438E-A2ED-630267396879}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA6F8578-098B-438E-A2ED-630267396879}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA6F8578-098B-438E-A2ED-630267396879}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {38342B4B-018B-4E83-9286-0458B30F2D79} + EndGlobalSection +EndGlobal diff --git a/Adv-FolderSize/Adv-FolderSize.csproj b/Adv-FolderSize/Adv-FolderSize.csproj new file mode 100644 index 0000000..69aa649 --- /dev/null +++ b/Adv-FolderSize/Adv-FolderSize.csproj @@ -0,0 +1,17 @@ + + + + Exe + net5.0 + Adv_FolderSize + + + + + + + + + + + diff --git a/Adv-FolderSize/Args.cs b/Adv-FolderSize/Args.cs new file mode 100644 index 0000000..ffb1fb4 --- /dev/null +++ b/Adv-FolderSize/Args.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Adv_FolderSize +{ + public record ArgSet + { + // empty -> arg without key + public string Key { get; init; } = ""; + + // null -> value is required + // not null -> value is optional + // empty -> arg without value + public string Default { get; init; } = ""; + } + + public static class Args + { + public static string[] Interprete(ArgSet[] argsets, string args, bool remainder = false) + { + var values = new List(); + var leftargs = new List(); + + if (remainder) + leftargs.Add(args); + else if (args != "") + leftargs.AddRange(args.Split(' ')); + + foreach (var argset in argsets) + { + var key = argset.Key.ToUpper(); + var defaultvalue = argset.Default; + + var matchedarg = leftargs.Find(arg => arg.ToUpper().StartsWith(key)); + + if (matchedarg != null) + { + var matchedvalue = matchedarg[key.Length..]; + if ((defaultvalue == "" && matchedvalue != "") // empty -> arg without value + || (defaultvalue == null && matchedvalue == "")) // null -> value is required + return null; + values.Add(matchedvalue); + leftargs.Remove(matchedarg); + } + else if (defaultvalue == null) // null -> value is required + { + return null; + } + else + { + values.Add(defaultvalue); + } + } + + // Check the unregistered case + if (leftargs.Count == 0) + return values.ToArray(); + else + return null; + } + } +} diff --git a/Adv-FolderSize/ColorConsole.cs b/Adv-FolderSize/ColorConsole.cs new file mode 100644 index 0000000..73c5bb3 --- /dev/null +++ b/Adv-FolderSize/ColorConsole.cs @@ -0,0 +1,18 @@ +using System; + +namespace Adv_FolderSize +{ + public static class ColorConsole + { + public static void Write(string contents, ConsoleColor color, bool asline = true) + { + var curcolor = Console.ForegroundColor; + Console.ForegroundColor = color; + if (asline) + Console.WriteLine(contents); + else + Console.Write(contents); + Console.ForegroundColor = curcolor; + } + } +} diff --git a/Adv-FolderSize/FolderAnalysis.cs b/Adv-FolderSize/FolderAnalysis.cs new file mode 100644 index 0000000..5d46ed5 --- /dev/null +++ b/Adv-FolderSize/FolderAnalysis.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using FolderAnalysisFSLib; + +namespace Adv_FolderSize +{ + public class FolderAnalysis + { + private readonly FolderAnalysisBase _fABase; + private readonly List _selectableTree = new List(); + private readonly List _selectableList = new List(); + + public FolderAnalysis(string path) + => _fABase = new FolderAnalysisBase(path); + + public async Task StartAnalysisAsync() + => await Task.Run(() => _fABase.StartAnalysis()); + + public bool RedirectTo(int idx) + { + if (idx >= 0 && idx < _selectableTree.Count) + return _fABase.RedirectTo(_selectableTree[idx]); + else + return false; + } + + public bool Back() + => _fABase.Back(); + + public string GetListElemPath(int idx) + { + if (idx >= 0 && idx < _selectableList.Count) + return _selectableList[idx]; + else + return null; + } + + public string GetTreeDirPath(int idx) + { + if (idx >= 0 && idx < _selectableTree.Count) + return _selectableTree[idx]; + else + return null; + } + + private static string SizeBar(long stand, long size, int length) + { + if (stand != 0) + return "■".Repeat((int)(size * length / stand)); + else + return ""; + } + + + private static string DirAbbrevName(string path) + => Path.DirectorySeparatorChar + Path.GetFileName(path); + + public void PrintDirTree(string measure, int depthLimt, int dirExpLimt, int fileExpLimt) + { + // Length = 4 + var diridx = "│ "; + var sym = "├── "; + var tabidx = " "; + + var printline = _fABase.GetPrintableTree(depthLimt, dirExpLimt, fileExpLimt); + + _selectableTree.Clear(); + var idxcount = -1; + var dirtopsize = _fABase.GetDirList(top: true, 1); + var filetopsize = _fABase.GetFileList(top: true, 1); + var dirsizestand = dirtopsize.Length != 0 ? dirtopsize[0].Item2 : 0; + var filesizestand = filetopsize.Length != 0 ? filetopsize[0].Item2 : 0; + + foreach (var item in printline) + { + var type = item.Item1; + var name = item.Item2; + var size = item.Item3; + var depth = item.Item4; + + switch (type) + { + case "F": + Console.WriteLine( + $"{SizeBar(filesizestand, size, 16),16} {ByteMeasure.byteToString(measure, size),10} " + + $"{tabidx.Repeat(depth)}{sym}{name}"); + break; + case "D": + ColorConsole.Write( + $"{SizeBar(dirsizestand, size, 16),16} {ByteMeasure.byteToString(measure, size),10} " + , ConsoleColor.Cyan, asline: false); + Console.Write($"{diridx.Repeat(depth)}{sym}"); + + var print = ""; + if (idxcount == -1) + { + print = name; + idxcount++; + } + else if (depth == 1) + { + _selectableTree.Add(name); + print = $"[{idxcount}] {DirAbbrevName(name)}"; + idxcount++; + } + else + { + print = DirAbbrevName(name); + } + ColorConsole.Write(print, ConsoleColor.Cyan); + break; + case "FH": + Console.Write($"{"",16} {"",-10} {tabidx.Repeat(depth)}{sym}"); + ColorConsole.Write($"... {name} files are hided", ConsoleColor.Yellow); + break; + case "DH": + Console.Write($"{"",16} {"",-10} {tabidx.Repeat(depth + 1)}{sym}"); + ColorConsole.Write($"... {name} directories are hided", ConsoleColor.Yellow); + break; + case "DF": + Console.Write($"{"",16} {"",-10} {tabidx.Repeat(depth + 1)}{sym}"); + ColorConsole.Write($"... {name} directories above this directory are folded", ConsoleColor.Yellow); + break; + default: + break; + } + } + } + + public void PrintDirList(string measure = "AUTO", int num = 10) + { + _selectableList.Clear(); + var idxcount = 0; + + var dirlist = _fABase.GetDirList(top: true, num); + var dirsizestand = dirlist.Length != 0 ? dirlist[0].Item2 : 0; + + foreach (var item in dirlist) + { + var name = item.Item1; + var size = item.Item2; + ColorConsole.Write( + $"{SizeBar(dirsizestand, size, 16),16} {ByteMeasure.byteToString(measure, size),10} " + + $"[{idxcount}] {name}" + , ConsoleColor.Cyan); + + _selectableList.Add(name); + idxcount++; + } + } + + public void PrintFileList(string measure = "AUTO", int num = 10) + { + _selectableList.Clear(); + var idxcount = 0; + + var filelist = _fABase.GetFileList(top: true, num); + var filesizestand = filelist.Length != 0 ? filelist[0].Item2 : 0; + + foreach (var item in filelist) + { + var name = item.Item1; + var size = item.Item2; + Console.WriteLine( + $"{SizeBar(filesizestand, size, 16),16} {ByteMeasure.byteToString(measure, size),10} " + + $"[{idxcount}] {name}"); + + _selectableList.Add(name); + idxcount++; + } + } + } +} diff --git a/Adv-FolderSize/NuGet/TriggerLib.1.1.1.nupkg b/Adv-FolderSize/NuGet/TriggerLib.1.1.1.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..f32580fad652bc78d4748f06dca629eeec7c7460 GIT binary patch literal 5617 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 HCm88pItm;g literal 0 HcmV?d00001 diff --git a/Adv-FolderSize/Program.cs b/Adv-FolderSize/Program.cs new file mode 100644 index 0000000..6303fe6 --- /dev/null +++ b/Adv-FolderSize/Program.cs @@ -0,0 +1,176 @@ +using System; +using System.IO; +using System.Diagnostics; +using TriggerLib; +using FolderAnalysisFSLib; + +namespace Adv_FolderSize +{ + class Program + { + private const string _helpMsg = +@"Enter help(h) to get help message +Enter scan(s) [path] to scan all directories and files below the path specified +Enter tree(t) to print the scan\'s result + Option: Measure /m[auto|b|kb|mb|gb] Default is auto + Dir depth limit /dp[number] Default is 3 + Dir expand limit /de[number] Default is 3 + File expand limit /fe[number] Default is 3 +Enter dirlist(d) to print the directories\' paths descending by size + Option: Measure /m[auto|b|kb|mb|gb] Default is auto + Number to display /n[number] Default is 10 +Enter filelist(f) to print the files\' paths descending by size + Option: Measure /m[auto|b|kb|mb|gb] Default is auto + Number to display /n[number] Default is 10 +Enter redirect(r) [index] to move into the specified tree +Enter back(b) to back to the previous tree +Enter open(o) [index] to open the folder or the file specified +Enter exit(e) to exit"; + + static void Main(string[] args) + { + var fa = new FolderAnalysis(""); + var lastdisplayed = 0b0; // 0b0 -> tree 0b1 -> list + + Console.WriteLine("Message: Enter help(h) to get help message"); + ColorConsole.Write("Suggest command: scan(s) [path]", ConsoleColor.DarkGreen); + while (true) + { + try + { + Console.Write("> "); + var line = Console.ReadLine(); + var result = LineInterpreter(line, out var opt); + + switch (result) + { + case "HELP" or "H": + Console.WriteLine(_helpMsg); + break; + case "SCAN" or "S": + if (!Directory.Exists(opt[0])) + throw new DirectoryNotFoundException(); + + var trigger = new TriggerSource(300, () => + Console.WriteLine("... This scaning operation will take several seconds")); + + fa = new FolderAnalysis(opt[0]); + fa.StartAnalysisAsync().Wait(); + + trigger.Cancel(); + Console.WriteLine("Scaning finished."); + + ColorConsole.Write( + "Suggest command: tree(t) [/m /dp /de /fe] | dirlist(d) [/m /n] | filelist(f) [/m /n]", + ConsoleColor.DarkGreen); + break; + case "TREE" or "T": + fa.PrintDirTree(opt[0], int.Parse(opt[1]), int.Parse(opt[2]), int.Parse(opt[3])); + + lastdisplayed = 0b0; + ColorConsole.Write( + "Suggest command: dirlist(d) [/m /n] | filelist(f) [/m /n] | redirect(r) [index]", + ConsoleColor.DarkGreen); + break; + case "DIRLIST" or "D": + fa.PrintDirList(opt[0], int.Parse(opt[1])); + + lastdisplayed = 0b1; + ColorConsole.Write( + "Suggest command: tree(t) [/m /dp /de /fe] | filelist(f) [/m /n]", ConsoleColor.DarkGreen); + break; + case "FILELIST" or "F": + fa.PrintFileList(opt[0], int.Parse(opt[1])); + + lastdisplayed = 0b1; + ColorConsole.Write("Suggest command: tree(t) [/m /dp /de /fe] | dirlist(d) [/m /n]", + ConsoleColor.DarkGreen); + break; + case "REDIRECT" or "R": + if (!fa.RedirectTo(int.Parse(opt[0]))) + throw new Exception("The specified index does not exist."); + + ColorConsole.Write("Suggest command: tree(t) [/m /dp /de /fe] ", ConsoleColor.DarkGreen); + break; + case "BACK" or "B": + if (!fa.Back()) + throw new Exception("Can not go back anymore."); + + ColorConsole.Write("Suggest command: tree(t) [/m /dp /de /fe] ", ConsoleColor.DarkGreen); + break; + case "OPEN" or "O": + var path = ""; + + if (lastdisplayed == 0b0) + path = fa.GetTreeDirPath(int.Parse(opt[0])); + else + path = fa.GetListElemPath(int.Parse(opt[0])); + + if (path == null) + throw new Exception("The specified index does not exist."); + + OpenExplorer(path); + break; + case "EXIT" or "E": + Environment.Exit(0); + break; + default: + throw new Exception("Invalid command."); + } + } + catch (NotAnalyzedYetException ex) + { + Console.WriteLine($"Error: {ex.Message}"); + ColorConsole.Write("Suggest command: scan(s) [path]", ConsoleColor.DarkGreen); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + + Console.WriteLine(); + } + } + + static string LineInterpreter(string line, out string[] options) + { + var splited = line.Trim().Split(' '); + var cmd = splited[0].ToUpper(); + var args = string.Join(' ', splited[1..]); + + options = cmd switch + { + "HELP" or "H" or "BACK" or "B" or "EXIT" or "E" => Args.Interprete(new[] { new ArgSet() }, args), + "SCAN" or "S" => Args.Interprete(new[] { new ArgSet { Default = null } }, args, remainder: true), + "TREE" or "T" => Args.Interprete(new[] { + new ArgSet { Key = "/M" ,Default = "AUTO" }, + new ArgSet { Key="/DP", Default = "3" }, + new ArgSet { Key ="/DE", Default = "3" }, + new ArgSet { Key="/FE", Default = "3" } }, args), + "DIRLIST" or "D" => Args.Interprete(new[] { + new ArgSet { Key = "/M", Default = "AUTO" }, + new ArgSet { Key = "/N", Default = "10" } }, args), + "FILELIST" or "F" => Args.Interprete(new[] { + new ArgSet { Key = "/M", Default = "AUTO" }, + new ArgSet { Key = "/N", Default = "10" } }, args), + "REDIRECT" or "R" => Args.Interprete(new[] { new ArgSet { Default = null } }, args), + "OPEN" or "O" => Args.Interprete(new[] { new ArgSet { Default = null } }, args), + _ => null, + }; + return options != null ? cmd : null; + } + + static void OpenExplorer(string path) + { + var platform = Environment.OSVersion.Platform; + + if (platform == PlatformID.Win32NT) + Process.Start("explorer.exe", "/select," + path); + else if (platform == PlatformID.Unix) + Process.Start("open", $"-R \"{path}\""); + else + throw new Exception("Unknown operating system."); + } + } +} + diff --git a/Adv-FolderSize/Properties/PublishProfiles/FolderProfile.pubxml b/Adv-FolderSize/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..548a293 --- /dev/null +++ b/Adv-FolderSize/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,17 @@ + + + + + Release + Any CPU + bin\Release\net5.0\publish\ + FileSystem + net5.0 + false + win-x86 + True + False + + \ No newline at end of file diff --git a/Adv-FolderSize/StringExtension.cs b/Adv-FolderSize/StringExtension.cs new file mode 100644 index 0000000..ac4deba --- /dev/null +++ b/Adv-FolderSize/StringExtension.cs @@ -0,0 +1,19 @@ +using System; +using System.Linq; + +namespace Adv_FolderSize +{ + public static class StringExtension + { + public static string Repeat(this string str, int num) + => string.Concat(Enumerable.Repeat(str, num)); + + public static string LenLimit(this string str, int len, string abbr) + { + if (str.Length > len) + return str[0..(len - 1)] + abbr; + else + return str; + } + } +} diff --git a/FolderAnalysisFSLib/ByteMeasure.fs b/FolderAnalysisFSLib/ByteMeasure.fs new file mode 100644 index 0000000..45cab67 --- /dev/null +++ b/FolderAnalysisFSLib/ByteMeasure.fs @@ -0,0 +1,27 @@ +module FolderAnalysisFSLib.ByteMeasure + +open System + +let private kilo = 1024.0 +let private bToKB byte = byte / kilo +let private (|ToUpper|)(measure: string) = measure.ToUpper() +let private round(num: float) = Math.Round(num, 2) + +let rec byteToString measure (byte: int64) = + let num = float byte + + match measure with + | ToUpper "B" -> (num |> string) + " B" + | ToUpper "KB" -> (num |> bToKB |> round |> string) + " KB" + | ToUpper "MB" -> (num |> bToKB |> bToKB |> round |> string) + " MB" + | ToUpper "GB" -> (num |> bToKB |> bToKB |> bToKB |> round |> string) + " GB" + | ToUpper "AUTO" -> + if num < kilo then + byteToString "B" byte + elif num < kilo * kilo then + byteToString "KB" byte + elif num < kilo * kilo * kilo then + byteToString "MB" byte + else + byteToString "GB" byte + | _ -> failwith "Failed with matching measure specified." diff --git a/FolderAnalysisFSLib/FolderAnalysisBase.fs b/FolderAnalysisFSLib/FolderAnalysisBase.fs new file mode 100644 index 0000000..be69837 --- /dev/null +++ b/FolderAnalysisFSLib/FolderAnalysisBase.fs @@ -0,0 +1,59 @@ +namespace FolderAnalysisFSLib + +open System.Collections.Generic +open FolderAnalysisTools + +//exception NotAnalyzedYetException of string + +[] +type FolderAnalysisBase(path) = + let _dirTreeHistory = new Stack() + + member private this.CheckIsCreated() = + if _dirTreeHistory.Count = 0 then + raise <| new NotAnalyzedYetException("Not analyzed yet.") + + member this.StartAnalysis() = + if _dirTreeHistory.Count = 0 then + _dirTreeHistory.Push(dirTree path) + + member this.RedirectTo(dirName) = + this.CheckIsCreated() + + let peeked = + _dirTreeHistory.Peek().DirList |> List.tryFind (fun item -> item.Info.Name = dirName) + + match peeked with + | Some(dirTree) -> + _dirTreeHistory.Push(dirTree) + true + | None -> false + + member this.Back() = + this.CheckIsCreated() + + if _dirTreeHistory.Count > 1 then + _dirTreeHistory.Pop() |> ignore + true + else + false + + member this.GetPrintableTree(depthLimt, dirExpLimt, fileExpLimt) = + this.CheckIsCreated() + + printableTree (_dirTreeHistory.Peek()) depthLimt dirExpLimt fileExpLimt + |> List.toArray + + member private this.GetList(list, top, num) = + this.CheckIsCreated() + + if top then + _dirTreeHistory.Peek() |> list |> topSize num |> List.toArray + else + _dirTreeHistory.Peek() |> list |> bottomSize num |> List.toArray + + member this.GetDirList(top, num) = + this.GetList(dirList, top, num) + + member this.GetFileList(top, num) = + this.GetList(fileList, top, num) \ No newline at end of file diff --git a/FolderAnalysisFSLib/FolderAnalysisFSLib.fsproj b/FolderAnalysisFSLib/FolderAnalysisFSLib.fsproj new file mode 100644 index 0000000..6661eaa --- /dev/null +++ b/FolderAnalysisFSLib/FolderAnalysisFSLib.fsproj @@ -0,0 +1,15 @@ + + + + netstandard2.1 + Library + + + + + + + + + + diff --git a/FolderAnalysisFSLib/FolderAnalysisTools.fs b/FolderAnalysisFSLib/FolderAnalysisTools.fs new file mode 100644 index 0000000..30af68d --- /dev/null +++ b/FolderAnalysisFSLib/FolderAnalysisTools.fs @@ -0,0 +1,104 @@ +module internal FolderAnalysisFSLib.FolderAnalysisTools + +open System.IO + +type Info = { Name: string; Size: int64 } +(* + Dir + / | \ + Info FileList DirList + / | \ | \ + Info Info Info Dir Dir +*) +type Dir = { Info: Info; FileList: Info list; DirList: Dir list } + +let dirTree basePath = + let enumerationOption = + new EnumerationOptions(IgnoreInaccessible = true, AttributesToSkip = FileAttributes.System) + + let rec dirTreeSafe path = + let fileInfoList = + Directory.GetFiles(path, "*", enumerationOption) + |> Seq.map + (fun file -> + let fileInfo = FileInfo(file) + { Name = fileInfo.Name; Size = fileInfo.Length }) + |> Seq.toList + + let dirList = + [ for subDir in Directory.GetDirectories(path, "*", enumerationOption) -> + dirTreeSafe subDir ] + + let sumSize = + (fileInfoList |> List.fold (fun acc item -> acc + item.Size) 0L) + + (dirList |> List.fold (fun acc item -> acc + item.Info.Size) 0L) + + { Info = { Name = path; Size = sumSize }; FileList = fileInfoList; DirList = dirList } + + basePath |> dirTreeSafe + +// (Type string, Name string, Size string, Depth int) list +// F = Fold +// D = Dir +// FH = Fold hided +// DH = Dir hided +// DF = Dir folded +let printableTree dirTree depthLimt dirExpLimt fileExpLimt = + let rec printableTreeTR dirTree depthLimt curDepth acc = + let dirInfo = ("D", dirTree.Info.Name, dirTree.Info.Size, curDepth) + + let fileInfoList = + let sortedFL = dirTree.FileList |> List.sortByDescending (fun item -> item.Size) + + let fileInfoRevL list = + list + |> List.map (fun info -> ("F", info.Name, info.Size, curDepth + 1)) + |> List.rev + + if fileExpLimt >= 0 && dirTree.FileList.Length > fileExpLimt then + ("FH", $"%d{dirTree.FileList.Length - fileExpLimt}", 0L, curDepth + 1) + :: (sortedFL.[0..(fileExpLimt - 1)] |> fileInfoRevL) + else + sortedFL |> fileInfoRevL + + let dirInfoRevL list = + List.fold + (fun ac item -> (printableTreeTR item (depthLimt - 1) (curDepth + 1) acc) @ ac) + [] + list + + let infoAcc = fileInfoList @ (dirInfo :: acc) + + if depthLimt <> 0 then + let sortedDL = dirTree.DirList |> List.sortByDescending (fun item -> item.Info.Size) + + if dirExpLimt >= 0 && dirTree.DirList.Length > dirExpLimt then + (("DH", $"%d{dirTree.DirList.Length - dirExpLimt}", 0L, curDepth) + :: (sortedDL.[0..(dirExpLimt - 1)] |> dirInfoRevL)) + @ infoAcc + elif dirTree.DirList.Length <> 0 then + (sortedDL |> dirInfoRevL) @ infoAcc + else + infoAcc + else if dirTree.DirList.Length > 0 then + ("DF", $"%d{dirTree.DirList.Length}", 0L, curDepth) :: infoAcc + else + infoAcc + + printableTreeTR dirTree depthLimt 0 [] |> List.rev + +let rec dirList dirTree = + dirTree.Info :: List.foldBack (fun item acc -> (dirList item) @ acc) dirTree.DirList [] + +let rec fileList dirTree = + (dirTree.FileList + |> List.map (fun item -> { item with Name = Path.Combine(dirTree.Info.Name, item.Name) })) + @ List.foldBack (fun item acc -> (fileList item) @ acc) dirTree.DirList [] + +let topSize number list = + (List.sortByDescending (fun item -> item.Size) list).[0..(number - 1)] + |> List.map (fun item -> (item.Name, item.Size)) + +let bottomSize number list = + (List.sortBy (fun item -> item.Size) list).[0..(number - 1)] + |> List.map (fun item -> (item.Name, item.Size)) \ No newline at end of file diff --git a/FolderAnalysisFSLib/NotAnalyzedYetException.fs b/FolderAnalysisFSLib/NotAnalyzedYetException.fs new file mode 100644 index 0000000..930b538 --- /dev/null +++ b/FolderAnalysisFSLib/NotAnalyzedYetException.fs @@ -0,0 +1,13 @@ +namespace FolderAnalysisFSLib + +open System + +[] +type NotAnalyzedYetException = + inherit Exception + + new() = { inherit Exception() } + + new(message) = { inherit Exception(message) } + + new(message, (innerException: Exception)) = { inherit Exception(message, innerException) } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..64fa289 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Adv-FolderSize + Analyze the folder size quickly(win/macOS). Help to find out the big files and folders.