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 0000000..f32580f
Binary files /dev/null and b/Adv-FolderSize/NuGet/TriggerLib.1.1.1.nupkg differ
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.