F5<|YogrehguSs_{g$HQ>h_LYV4-QYpe_W~1rB^N)zr3%H3
z-Rt@b$FF^`An1>Eq^9#ZxH!;JGG)R*65`^QBFKX4V-}vPW_Z%+sp2wsoRKJzeKvwK
zkR`4xMQ*ePNWgzTI()P!EJ#Uz=<*pKz7_w|48YB8CJ9YDy?O7nfSo|`Ty1J;@nl4W
zKMR=h?w3(yHSMGLseOd94U-4gHjS5|?wchg{E3*?Efab7Tj5oKY(lk}p>zlsioKTL
z*y83^z}O~hE6GwT#bjklmge^yL>EHh0eWz+z2fimDD^e~`h%v%+>dTy)~6qlJu@*9
z+1q|<#na_CMb&j`m9~NSWXiO!Uy_{3O)$AC)VY)j$s3=zXO~yb8qrfWaf_5`3)tM9
zIQ7T;xQvF41c67f(R*l_6qeu~Ek9@v7L4Bn914
z*WvVaCoCs)EV`S{qC0(e>(g;6ugd}Q)tag_VQcgy(!pTEq1O0)&_ZZU^||fFw}z&Y
zb(25?$G6?8BRAY?{snb}$1=dfS$$^7;59mE7;U`evyTysLWm2-K~suzTAi17)pa>C
zVT^3FmvO5PZ~}GJiM_0wsuJsJWJr%`^yj#rDK>_NRwD+-u7*C1W?q{^@P^&
zX4;ueYcl`|kGU%)#JIYyXjb0Y)(Z#Zs&szxG>88vwa8v~Cq5Tru=E~|tkVbVwf){K
z#mcs_wclE@!z}#xB0(TWp&C)jzkd9K%50&3tUAv9Z9utaicEcXUUfSYw5KXh_(8ec
zg?)KsJ0{aK+XNCR%>z3Op9-Bpt4xYB*hU>dyZCL~Ji6Mxk@`tS|5xHAzw0CF-$#u@
zDAC;h({P4|{eKxX;1Jhuh6`>Be0cs0xzezAjem`X@uoCTPvb$TK?AJzyca*QzqC5xaqgxM
zW8a?|M4$55G=DpTHtgws@$8Due|@IAHFQ6@4c5LYNn_c@p1ffCV?f2u&oOFqDQB9a
z!GIpGzGsmza1N43fE5i$gvjv_sJoo7Y0)e*5qeJfF)F5~bC4%H`|x#}zNAysslo}4
zbjKVZ#s~VqVff`#E;?6T-78GGeKdm3B;?u_$hh6w+)B*Ws#Txm6vMy5
zv~#;caTX2R&%)xwSxRU#ItV`+0a%sZwi#?M*d5oJlP3jd0Xng_5ef8flsB89sg3RmybyCN@WpoM!~ApE{nx_gwpzw*Geq0
z?=w}bWMcVZ*JKho2|7&7@A0uE%_lG;QA}(6;N8XG^royNXd9@BNpe}A6Xcr@3Gt#1jm4OWU^(O_13U&X_s^u-Oukd
z6zt{WWDAD!LcreEcGlk3yteLMU>*-IcMq_ax6{)Hei1u9Q6VuA5dj-JK|UdSYg;j4
zYY{#>J28GiAyHv4&;tswv-N<0G~GY)5p^2PKU#TexnijbYX++=#nR6!#8WiAfgJ{$
z1Vs-N3qE!cWR4%YxnFi%*)RX`ti@eZ(*n8bg*ZMlH3nKEA879dzPOQw5dw0##Yv1|
z{k9n08;ARp6Y>L8h=_t7Wz;qIW>9mdli@i#%1R6~Q`cE-iuViza8ky_-`GzN?ce2G
zp7vyhTlWRH(+9o-I9}^J9kfz-fTD3EdQ$u<=2^lK5fT!1j%}rI>_H9nnV^jXiP4yB
z&4s$A9$$48=6(}K4+p+s-DK9)kMrz@%zqTC)}k03{6=b6%Ej5dQ+6FU^BNUrz5k$}
zzuJuh7?PE)V*j;KsQY|qii-xZ9xRO~R}Zu^i5jRc6$
zu#Ow+iJnz93U;zCWGUopQ<}g1MTPIad#S7y!0g^*S~-*zP}C51zcGHo{~7gO+`f&a9?{*01(Dy6^pIR9ga{Vn`IctL-LL!YYfzdA$zg!`x7
z{|UED@ZaE`TKsQt|3MZ0gu{HY`~HJQ{1f1xE%he=F3JBxgXyTFWBmIU7*A{FiGK8d
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.