13 KiB
Filesystem
Since C++17 we can efficiently work with the file systems on all OS like Mac, Windows, and various Linux distributions.
namespace fs = std::filesystem;
void printStatsForDirectory(const fs::path& dir_path) {
std::cout << "Statistics:\n";
std::cout << ".cpp files: "
<< std::count_if(fs::recursive_directory_iterator(dir_path), {},
[](const auto& entry) {
return entry.is_regular_file() && entry.path().extension() == ".cpp";
}) << '\n';
std::cout << ".md files: "
<< std::count_if(fs::recursive_directory_iterator(dir_path), {},
[](const auto& entry) {
return entry.is_regular_file() && entry.path().extension() == ".md";
}) << '\n';
std::cout << ".h files: "
<< std::count_if(fs::recursive_directory_iterator(dir_path), {},
[](const auto& entry) {
return entry.is_regular_file() && entry.path().extension() == ".h";
}) << '\n';
std::cout << "Total size: "
<< (std::accumulate(fs::recursive_directory_iterator(dir_path), {}, 0,
[](const auto& init, const auto& entry) {
return init + (entry.is_regular_file() ? entry.file_size() : 0);
}) >> 20) << " MB\n";
}
Output
int main() {
const auto path{fs::current_path()};
std::cout << "current path: " << path << '\n';
const auto path2 = path.parent_path().parent_path();
std::cout << "path2: " << path2 << '\n';
printStatsForDirectory(path2);
}
current path: "C:\\Users\\madamski\\Documents\\altcom_academy\\Nokia2022\\Advanced\\Course_part1\\exercises\\streamer"
path2: "C:\\Users\\madamski\\Documents\\altcom_academy\\Nokia2022\\Advanced\\Course_part1"
Statistics:
.cpp files: 16
.md files: 53
.h files: 47
Total size: 103 MB
Info about system space
void printSpaceInfo(auto const& dirs, int width = 10) {
std::cout << std::left;
for (const auto s : {"Capacity", "Free", "Available", "Dir"}) {
std::cout << "| " << std::setw(width) << s << std::string(3, ' ');
}
std::cout << '\n';
for (auto const& dir : dirs) {
const auto info = fs::space(dir);
std::cout
<< "| " << std::setw(width) << (info.capacity >> 30) << " GB"
<< "| " << std::setw(width) << (info.free >> 30) << " GB"
<< "| " << std::setw(width) << (info.available >> 30) << " GB"
<< "| " << dir << '\n';
}
}
int main() {
const auto dirs = {"C:\\", "D:\\", "E:\\"};
printSpaceInfo(dirs);
/*
| Capacity | Free | Available | Dir
| 237 GB| 15 GB| 15 GB| C:\
| 383 GB| 179 GB| 179 GB| D:\
| 463 GB| 220 GB| 220 GB| E:\
*/
}
Permissions
void printPerms(fs::perms p) {
std::cout << ((p & fs::perms::owner_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::owner_write) != fs::perms::none ? "w" : "-")
<< ((p & fs::perms::owner_exec) != fs::perms::none ? "x" : "-")
<< ((p & fs::perms::group_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::group_write) != fs::perms::none ? "w" : "-")
<< ((p & fs::perms::group_exec) != fs::perms::none ? "x" : "-")
<< ((p & fs::perms::others_read) != fs::perms::none ? "r" : "-")
<< ((p & fs::perms::others_write) != fs::perms::none ? "w" : "-")
<< ((p & fs::perms::others_exec) != fs::perms::none ? "x" : "-")
<< '\n';
}
int main() {
for (const auto& entry : fs::directory_iterator(fs::current_path())) {
if (fs::is_regular_file(entry.path())) {
std::cout << "File: "
<< std::left << std::setw(20)
<< entry.path().filename()
<< " | Permissions: ";
printPerms(fs::status(entry.path()).permissions());
}
}
/*
File: "CMakeLists.txt" | Permissions: rw-rw-rw-
File: "example.exe" | Permissions: rwxrwxrwx
File: "README.md" | Permissions: rw-rw-rw-
File: "streamer.cpp" | Permissions: rw-rw-rw-
*/
}
Exercise
Open project files. Write a program which modify files under project directory:
- Change permision for all .exe files to
rwxrw-rw-. - Change permision for all .cpp and .hpp files to
rw-r--r--.
You should get output like this:
File: "fileA.cpp" | Permissions: rw-------
File: "fileA.hpp" | Permissions: rw-------
File: "fileB.cpp" | Permissions: rw-------
File: "fileB.hpp" | Permissions: rw-------
File: "fileC.cpp" | Permissions: rw-------
File: "fileC.hpp" | Permissions: rw-------
File: "project.exe" | Permissions: rw-------
File: "fileA.cpp" | Permissions: rw-r--r--
File: "fileA.hpp" | Permissions: rw-r--r--
File: "fileB.cpp" | Permissions: rw-r--r--
File: "fileB.hpp" | Permissions: rw-r--r--
File: "fileC.cpp" | Permissions: rw-r--r--
File: "fileC.hpp" | Permissions: rw-r--r--
File: "project.exe" | Permissions: rwxrw-rw-
Exercise 2
Open project files. Modify FileB by append any word. Now write a program which:
- return last modify files
- return a vector with 3 files that were longest unmodified.
Expected result (path may by different):
Last modified file is: "/home/runner/SilentSiennaModularity/files/project/fileB.cpp"
File write time is Sat May 14 17:56:18 2022
files that were longest unmodified:
File: "/home/runner/SilentSiennaModularity/files/project/fileA.cpp" | last modify time: Sat May 14 16:43:33 2022
File: "/home/runner/SilentSiennaModularity/files/project/fileA.hpp" | last modify time: Sat May 14 16:43:33 2022
File: "/home/runner/SilentSiennaModularity/files/project/fileB.hpp" | last modify time: Sat May 14 16:43:33 2022
Problems and fixes
-
Remember that
recursive_directory_iteratoranddirectory_iteratorareInputIterator. If you move iterator to the next position you invalidate all reference to previous object. If you need to use algorithm which demandForwardIteratoryou can copy all paths to separate container -
If you want to keep sorted files inside
setormapyou should check whether their modification times are the same. If true you need to compare their names or files with the same modification time will be lost. -
Setup premissions for files works only on Linux/ MacOS system. This is caused by different type of File system. Windows file system mainly NTFS do not support priviliges like in other systems eg. EXT4. There are still properites but handled in different way:
- Full Control: Grants complete access, including the ability to see, read, write, execute and delete files or folders, as well as change permission settings for all subdirectories.
- Modify: The user can see, read, execute, write and delete files. Also allows for the deletion of the folder itself.
- Read & Execute: Can view folder contents and run programs or scripts.
- List folder contents: Allows the user to see files and directories contained within a folder, an important setting for navigating to deeper levels in the folder structure.
- Read: Can see folder contents and also view the files and folders in question.
- Write: Users can add new files and folders and write to existing files.
- Special permissions: Additional permissions available through the Advanced Security Settings in the Windows file system. Includes options such as Read Attributes, Create Files or Traverse Folder.
Temporary files for testing
Sometimes during a test we need to read input data from file. usually we need to create this file and attach to test directory. But since C++17 we can easily use temporary directory to create all files. let's test this class: (The main resposibility is to queued all files and stream them)
class Streamer {
public:
Streamer() = default;
virtual ~Streamer() = default;
Streamer(const Streamer&) = default;
Streamer(Streamer&&) = default;
Streamer& operator=(const Streamer&) = default;
Streamer& operator=(Streamer&&) = default;
virtual void stream() = 0;
virtual void stop() = 0;
};
class MpgegStreamer : public Streamer {
public:
MpgegStreamer(const fs::path& path)
: path_(path) {
for (int i = 0; i < 4; ++i) {
workers_.emplace_back(&MpgegStreamer::work, this, i);
}
}
~MpgegStreamer() override {
for (auto& th : workers_) {
th.join();
}
}
void stream() override {
for (const auto& entry : fs::directory_iterator(path_)) {
std::lock_guard lg(m_);
streamQueue_.push(entry.path());
}
}
virtual void stop() {
finishAction_ = true;
}
private:
void work(int id) {
while (!finishAction_) {
fs::path path;
{
std::lock_guard lg(m_);
// Ofc this will be better with condition variable
if (streamQueue_.empty()) {
continue;
}
path = std::move(streamQueue_.front());
streamQueue_.pop();
}
stream(path, id);
}
}
void stream(const fs::path path, int id) const {
std::fstream fileToStream(path);
std::transform(
std::istream_iterator<std::string>(fileToStream),
{},
std::ostream_iterator<std::string>(std::cout),
[id](const std::string& str) {
std::stringstream ss;
ss << "Streamer: " << id << " | data: " << str << '\n';
return ss.str();
});
}
mutable std::mutex m_;
std::vector<std::thread> workers_;
std::queue<fs::path> streamQueue_;
fs::path path_;
std::atomic<bool> finishAction_{false};
};
We can create temporary direcotories (yes we can create them recursive!) and cleanup at the end. We don't need to be afraid of removing sth neccessary because we will work only with tmoprary directories.
int main() {
const auto tmp_path = fs::temp_directory_path();
const auto mpegDir = tmp_path.string() + "Test/MpgegFiles";
const auto mp4Dir = tmp_path.string() + "Test/MP4Files";
// Create test directory
fs::create_directories(mpegDir);
fs::create_directory(mp4Dir);
// Add files
for (int i = 0; i < 10; ++i) {
std::ofstream file(mpegDir + "/File" + std::to_string(i));
file << "this is some text in the new file\n" << i;
}
for (int i = 0; i < 10; ++i) {
std::ofstream file(mp4Dir + "/File" + std::to_string(i));
file << "this is some text in the new file\n" << i;
}
std::cout << "Start streaming" << std::endl;
std::unique_ptr<Streamer> streamer = std::make_unique<MpgegStreamer>(mpegDir);
streamer->stream();
std::this_thread::sleep_for(std::chrono::seconds(1));
streamer->stop();
fs::remove_all(tmp_path.string() + "Test");
}