Rename folder with folder size

I am about to start coding file system manipulation for the first time & I thought I would make myself a well needed windows program that renames my directories with the size of the folder as reported by Windows Explorer.

I did some preliminary reading & it looks like the way to do it is through recursion, most popularly with either of the two ways:

1) #include <filesystem>
2) Boost filesystem c++ libraries

A) Which is better/faster?
B) Right now doing it on Win 10, but which is more portable (Unix, IOS..etc)
C) Are you a fan of the methods proposed here, or do you have a better way?

https://stackoverflow.com/questions/15495756/how-can-i-find-the-size-of-all-files-located-inside-a-folder/15501719
While I generally prefer portable solutions, if your project is Windows-only, consider using the Win32 API directly, which should be as fast as things can get (because, on the Windows platform, anything else will unavoidable be an additional layer on top of the Win32 API) and give you the most "accurate" information.


Enumerate files and get file info (e.g. size and times) for each:
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew

Get info (e.g. size and times) about a single file, by name:
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesexw

Get size of a single file, by handle:
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfilesizeex

Move or rename a file (or a directory):
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefilew


Be sure to use the "Unicode" (wide string, i.e. wchar_t) versions of these functions, otherwise you'll get garbage for file names containing any characters that can not be represented in the system's "ANSI" codepage :-[

(If you go with a solution other than Win32 API, also make sure that it properly deals with Unicode names!)
Last edited on
That SO question is quite old, before C++17.

C++17 has the <filesystem> library. Iterate at the drive root to find the directories, then iterate each directory, saving the size of all files contained within each directory. Rename each directory with name + total file sizes.

Lather, rinse, repeat. The more portable way than using the WinAPI.

https://en.cppreference.com/w/cpp/filesystem
A) Which is better/faster?
The std filesystem stems from the boost filesystem. If you use the std variant you don't need to add another library...

B) Right now doing it on Win 10, but which is more portable (Unix, IOS..etc)
They are both portable.

C) Are you a fan of the methods proposed here, or do you have a better way?
When the standard offers a good way to solve my problem I would use the standard.

kigar64551 wrote:
f your project is Windows-only, consider using the Win32 API directly
I would discourage from doing so. The Win32 API is very convoluted and hard to manage. The std/boost filesystem is much easier.
Does boost filesystem correctly handle Unicode file names on the Windows platform?

It's always a good idea to test with a file name like:
käsesoßenrührlöffel-ГригорийЕфимовичРаспутин-ΒίοςκαιπολιτείατουΑλέξηΖορμπά-🍺.txt

And yes, Windows Explorer creates such file name without problem; expect similar names in the wild :-D

https://i.imgur.com/J01XEKa.png
Last edited on
> Does boost filesystem correctly handle Unicode file names on the Windows platform?

I guess it would, since we know that std::filesystem::path handles it correctly.
(std::filesystem::path::value_type is wchar_t on Windows implementations.)
I made a post at the link that contains a directory walker. It's in a different language but should give you some ideas. It works fine and is setup to skip over some system folders.

https://www.autoitscript.com/forum/topic/206232-file-explorer-window-prompt-to-store-file-location-to-variable/
I am unsure of the premise: you wish to replace the names of folders with their size?

  • What if two folders have the same size?
  • What happens when a folder’s size changes?

I think this is a bad idea — you are duplicating information by increasing complexity — and I am unsure this isn’t an XY Problem.

What exactly are you trying to accomplish with this?
Rename folder with folder size

Windows Explorer, via a folder's property pages, can report the size 3 different ways:

1. A "friendly" number of the space used, such as 360MB

2. The aggregate of all the files' sizes added together, 377,605,582 bytes

3. The size of the number of clusters needed to store all the files, 378,011,648 bytes

The 3rd size is larger than the 2nd since files may require consuming a full cluster, so there's slack space.

Folders themselves also consume a smidgen of space for file allocation/accounting purposes, another space consideration.

As an intellectual exercise this is a bit weird but doable, but from a practical standpoint adding additional characters to a folder name will consume space, potentially forcing the OS to allocate another cluster.

What happens when the contents of the folder(s) change? Adding/removing/modifying the files, etc. Your folder size info is now incorrect.
Trying to append folder size to a folder name.
Before: After:
Folder 1 -----> Folder 1 (1.5 Gig)
Folder 2 -----> Folder 2 (400 MB)
Folder 3 -----> Folder 3 (900 KB)
Once things are put into the folder they pretty much won't change for the most part, as I had been doing this manually for a while now over time. On rare occasions they might, but I always do it manually and I guess I could have a check button that rechecks the size & modifies name, but for right now it is not necessary.
_________
Thanks for everyone's input, I could not even get off the ground last time I tried & again today. I try to put in a little time for what I thought would be quick day or two task & here we are.

On Windows 10 Home 64Bit I am using Embaracadero 6.3 install from summer of 2021 & compiling with TDM-GCC 9.2.0 64-bit release. I also tried with 32 bit and the error I get is....
"... [Error] 'filesystem' is not a namespace-name"

I also tried with Microsoft Visual Studio Community 2019 Version 16.10.4 installed this summer....in Debug/x86 & with same compile error " namespace "std" has no member "filesystem" "
How do I find out what compiler I am using in VS?
_________
I tried this today from what I have read from another poster & no go...must have been around the time of C++17 pre-release.

#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;

//Tried below separately too
#include <filesystem>
namespace fs = std::experimental::filesystem;


At this point I am just going to try to get this working & cout some properties from this MS code below to ease into my project. Never seen wcout & wstring, those are wide string & outputs I guess. When I do a simple "wstring myWString("Hello");" it does not work, is this unicode or something?

What is this "L" in this wide output stream being sent?
wos << L"root_name() = " << pathToDisplay.root_name() << endl

I think with a recent install it should be C++20, but I don't know what "// compile by using: /EHsc /W4 /permissive-" that is in comment below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// filesystem_path_example.cpp
// compile by using: /EHsc /W4 /permissive- /std:c++17 (or later)
#include <string>
#include <iostream>
#include <sstream>
#include <filesystem>

using namespace std;
using namespace std::filesystem;

wstring DisplayPathInfo()
{
    // This path may or may not refer to an existing file. We are
    // examining this path string, not file system objects.
    path pathToDisplay(L"C:/FileSystemTest/SubDir3/SubDirLevel2/File2.txt ");

    wostringstream wos;
    int i = 0;
    wos << L"Displaying path info for: " << pathToDisplay << endl;
    for (path::iterator itr = pathToDisplay.begin(); itr != pathToDisplay.end(); ++itr)
    {
        wos << L"path part: " << i++ << L" = " << *itr << endl;
    }

    wos << L"root_name() = " << pathToDisplay.root_name() << endl
        << L"root_path() = " << pathToDisplay.root_path() << endl
        << L"relative_path() = " << pathToDisplay.relative_path() << endl
        << L"parent_path() = " << pathToDisplay.parent_path() << endl
        << L"filename() = " << pathToDisplay.filename() << endl
        << L"stem() = " << pathToDisplay.stem() << endl
        << L"extension() = " << pathToDisplay.extension() << endl;

    return wos.str();
}

int main()
{
    wcout << DisplayPathInfo() << endl;
    // wcout << ComparePaths() << endl; // see following example
    wcout << endl << L"Press Enter to exit" << endl;
    wstring input;
    getline(wcin, input);
}


"The code produces this output:
Output

Displaying path info for: C:\FileSystemTest\SubDir3\SubDirLevel2\File2.txt
path part: 0 = C:
path part: 1 = \
path part: 2 = FileSystemTest
path part: 3 = SubDir3
path part: 4 = SubDirLevel2
path part: 5 = File2.txt
root_name() = C:
root_path() = C:\
relative_path() = FileSystemTest\SubDir3\SubDirLevel2\File2.txt
parent_path() = C:\FileSystemTest\SubDir3\SubDirLevel2
filename() = File2.txt
stem() = File2
extension() = .txt "
I figured it out guys, for anyone else who needs it. Started out as a quick thing for some 4 hours over multiple days.

#include <filesystem> to work in VS requires a change to overcome "E0135 namespace "std" has no member "filesystem"" error and to properly include the pre-processor directive.

Looks like C++ Language Standard is set to C++14 by default, at least in my case.

FIX:
Goto Project/Project properties and on left pane "C/C++"/Language........../C++ Language Standard & choose C++20.

Do you pros leave it on C++20 for new projects mostly, except when going back to legacy code?


I have not looked at how to fix Dev++ 6.3 yet, but on link it says....
"Keystone Features:
C++17/C++20 (partial) support.<<<<<<<<<<<<<<<<<<<
Unicode support.
Parallel Compilation support.
Windows 7/8/10 support."
https://github.com/Embarcadero/Dev-Cpp
Visual Studio does default to C++14, though that default can be changed so it can be set to a later standard, C++17 or C++20 or C++latest, using the IDE's Property Manager. This link -- http://www.cplusplus.com/forum/lounge/271176/ -- is about adding 3rd party library directories as defaults for new projects the same procedure can be used to change what C++ standard is default.

I've set my VS to default to C++latest using the steps in that above link; at least one part of the C++20 standard as implemented by MS requires it. <ranges>.

An example of using <ranges> to do a reverse range-based for loop:
https://www.fluentcpp.com/2020/02/11/reverse-for-loops-in-cpp/

I made a couple of other changes to my VS IDE defaults, adding Boost library directories as well as changing not allowing language extensions.

One thing about Embarcadero's Dev-C++, the IDE doesn't have the option to set a project's options ISO C++ standard to anything other than C++11. Boo!

You can set to a non-standard "standard" using GNU standard. It goes up to GNU C++2a. Maybe editing the project's make file to a later than ISO C++11 would work, but I haven't tested doing that since I very rarely use E's Dev-C++.
going back to legacy code?

Any "legacy code" I deal with will more than likely be updated to a newer C++ standard, or to learn why legacy code is best only as a learning experience of what to not use.

VS doesn't allow for setting a language standard less than C++14. Dev-C++ in all its iterations has problems with setting a language standard via the IDE.

If I really want to muck around with any code that would choke using C++14 or earlier I fire up Code::Blocks. That IDE allows for setting a language standard from C++98 all the way through C++2a. GNU or ISO. Using a newer C++ compiler than the one supplied with C::B is a small bit of work, as is changing the IDE options for what language standard can be chosen.

I very much prefer C::B over Dev-C++. Embarcadero didn't do a good job of updating the IDE from its Bloodshed's roots. It could have been better.

If you are going to spend a lot of time dealing with legacy code consider this book:
https://leanpub.com/legacycode
Last edited on
I use VS2022 set to default to C++latest (so I get the C++23 stuff when implemented).
Thanks for all the help & I am excited to finally have gotten the recursion method to work & it actually reports the folder size in bytes though. I will have to write some code to round off to nearest KB, MB, & GB by the division method I guess. But it would have been nice to just get what Windows Explorer reports when you right click on a folder:
size: 24.6 MB (25,832,052 bytes)

The "24.6 MB" is what I would be looking for.

I did do some more reading & found:
std::filesystem::directory_entry::file_size
https://en.cppreference.com/w/cpp/filesystem/directory_entry/file_size

"If the file size is cached in this directory_entry, returns the cached value. Otherwise, returns std::filesystem::file_size(path()) or std::filesystem::file_size(path(), ec), respectively."

I have no idea if it is cached, but it reports 0 in my code below & so seems not to be cached. I checked my HD & I do have boxed checked for files to have contents indexed.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <string>
#include <iostream>
#include <filesystem>
#include <Windows.h>

using namespace std;
using namespace std::filesystem;


double getFolderSize(string path)
{
    static double r = 0.0;
    try {
        if (!filesystem::is_directory(path))
        {
            r += ((double)filesystem::file_size(path));
            //cout << path << " size = " << filesystem::file_size(path) << endl;      //FOR TEST
            //cout << "Size r+= " << r << "\n\n";                //FOR TEST   
            //Sleep(1000);                                      //FOR TEST, need #include <Windows.h>         
        }
        else
        {
            for (auto entry : filesystem::directory_iterator(path))
                getFolderSize(entry.path().string());
        }
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }
    //cout << "IN FUNCTION r = " << r << endl;                //FOR TEST   
    return r;
}

int main() {
    //Reports folder size & it works (first 2 lines)
    double folderSize = getFolderSize("G:/Z_Temp2");    
    cout << "Size of Folder: " << folderSize << endl;   



    directory_entry entry("G:/Z_Temp2");
    //directory_entry("G:/Z_Temp2");    //This also works
    
    cout << "entry.path()" << entry.path() << endl;
    cout << "entry.file_size() = " << entry.file_size() << endl;    //DOESN'T WORK, reports 0 file size

    //Test to see if directory exists for practice, this works!
    if (entry.is_directory())     
        cout << "entry::path EXISTS = " << entry.path() << endl;
    else
        cout << "entry::path = DOES NOT EXIST!!!" << endl;
    

    return 0;
}
Don't you just want something like this (result in bytes but just recursive / 1024 as required to get in Mb, Gb etc):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

long long getFolderSize(const fs::path& path) {
	long long sz {};

	if (fs::exists(path) && fs::is_directory(path))
		for (auto const& entry : fs::recursive_directory_iterator(path))
			if (entry.is_regular_file())
				sz += entry.file_size();

	return sz;
}

int main() {
	const auto folderSize {getFolderSize("c:\\MyProgs")};

	std::cout << "Size of Folder: " << folderSize << '\n';
}

@seeplus, yours looks so clean, thanks!

I did get it to work last time & it reports the folder size & format properly. Just need to iterate through the folders within a folder & rename on the next run.

Here is my chicken scratch, but it works. I found double to run faster than unsigned long long, but could have been my computer slowing down. I will test it again on a fresh reboot.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//Get folder size & format ( V2.0)
#include <string>
#include <iostream>
#include <filesystem>
#include <sstream>
//#include <Windows.h>    //for Sleep()

using namespace std;
using namespace std::filesystem;

double getFolderSize(string path)
{
    static double r = 0;
    try {
        if (!filesystem::is_directory(path))
        {
            r += ((double)filesystem::file_size(path));
            //cout << path << " size = " << filesystem::file_size(path) << endl; //FOR TEST
            //cout << "Size r+= " << r << "\n\n";                                //FOR TEST   
            //Sleep(1000);                                                       //FOR TEST, need #include <Windows.h>         
        }
        else
        {
            for (auto entry : filesystem::directory_iterator(path))
                getFolderSize(entry.path().string());
        }
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }
    //cout << "IN FUNCTION r = " << r << endl;                //FOR TEST   
    return r;
}

string FormattedSize(double& rawSize)
{
    int i{};
    double tempSize = rawSize;
    ostringstream os;

    for (; tempSize >= 1024.; ++i)
    {
        tempSize /= 1024.;
    }

    cout << "(int = " << i << ") ";      //TEST ONLY

    tempSize = std::ceil(tempSize * 10.) / 10.;
    os << tempSize << "BKMGTPE"[i];
    i == 0 ? os : os << "B (" << (unsigned long long)rawSize << ")";
    return os.str();
}

int main() {

    //Get raw folder size in bytes
    double folderSize = getFolderSize("G:/Z_Temp");
    cout << "Size of Folder: " << (unsigned long long)folderSize << endl;

    //Format & round off folder size
    directory_entry entry("G:/Z_Temp");
    cout << entry.path() << " size: " << FormattedSize(folderSize) << "\n\n"; //'n';

    //TEST
    cout << "TEST: entry.path()" << entry.path() << endl;
    cout << "TEST: entry.file_size() = " << entry.file_size() << endl;

    return 0;
}
@seeplus
Your snippet is lightning fast, so beautiful.

Just curios, why the need for exists() below, isn't is_directory() enough and means it exists?
"if (fs::exists(path) && fs::is_directory(path))"

Also, no need to try/catch for errors, or Windows Explorer would have done so already before the code & not allowed for error prone files?


Any good books on filesystems so one can study from the beginning & cover extensive ground and not piecemeal it? One with good practice & examples for beginners.

Maybe:
File Structures: An Object-Oriented Approach with C++ 3rd Edition
by Michael J. Folk (Author), Bill Zoellick (Author), Greg Riccardi (Author)
Last edited on
For C++17 I suggest this book which has a chapter on filesystem:

C++17 In Detail
https://leanpub.com/cpp17indetail
https://www.amazon.co.uk/17-Detail-Exciting-Features-Standard/dp/1798834065/ref=sr_1_1

There's also c++17 - The complete guide
https://leanpub.com/cpp17
https://www.amazon.co.uk/C-17-Complete-Guide-First/dp/396730017X/ref=sr_1_2

Re the code line.

If is_directory() doesn't take an error_code() param (as here), then it can throw. Hence the first test.
https://en.cppreference.com/w/cpp/filesystem/is_directory

The test could go into main if you want (might be better there) as 0 is returned for either a) bad path or b) empty recursive directory. Change as required as that is just sample code.

Last edited on
Topic archived. No new replies allowed.