commit ad5eebba940b28fc0119527dd443aba460a89f5f Author: Your Name Date: Tue Jan 18 23:38:16 2022 +0100 Basic tree building and size measuring. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5cba7b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +DiskUsageInteractive +workspace/* +*.d +build/* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..638b750 --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +CXX ?= g++ + +# path # +SRC_PATH = src +BUILD_PATH = build +BIN_PATH = $(BUILD_PATH)/bin + +# executable # +BIN_NAME = DiskUsageInteractive + +# extensions # +SRC_EXT = cpp + +# code lists # +# Find all source files in the source directory, sorted by +# most recently modified +SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' | sort -k 1nr | cut -f2-) +# Set the object file names, with the source directory stripped +# from the path, and the build path prepended in its place +OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o) +# Set the dependency files that will be used to add header dependencies +DEPS = $(OBJECTS:.o=.d) + +# flags # +COMPILE_FLAGS = -Wall -Wextra -g -std=gnu++0x +INCLUDES = -I include/ -I /usr/local/include +# Space-separated pkg-config libraries used by this project +LIBS = + +.PHONY: default_target +default_target: release + +.PHONY: release +release: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS) +release: dirs + @$(MAKE) all + +.PHONY: dirs +dirs: + @echo "Creating directories" + @mkdir -p $(dir $(OBJECTS)) + @mkdir -p $(BIN_PATH) + +.PHONY: clean +clean: + @echo "Deleting $(BIN_NAME) symlink" + @$(RM) $(BIN_NAME) + @echo "Deleting directories" + @$(RM) -r $(BUILD_PATH) + @$(RM) -r $(BIN_PATH) + @echo "Deleting output files (PPM and CSV)" + @$(RM) *.csv + @$(RM) *.ppm + +# checks the executable and symlinks to the output +.PHONY: all +all: $(BIN_PATH)/$(BIN_NAME) + @echo "Making symlink: $(BIN_NAME) -> $<" + @$(RM) $(BIN_NAME) + @ln -s $(BIN_PATH)/$(BIN_NAME) $(BIN_NAME) + +# Creation of the executable +$(BIN_PATH)/$(BIN_NAME): $(OBJECTS) + @echo "Linking: $@" + $(CXX) $(OBJECTS) -o $@ ${LIBS} + +# Add dependency files, if they exist +-include $(DEPS) + +# Source file rules +# After the first compilation they will be joined with the rules from the +# dependency files to provide header dependencies +$(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT) + @echo "Compiling: $< -> $@" + $(CXX) $(CXXFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@ diff --git a/include/TreeNodeDiskUsage.hpp b/include/TreeNodeDiskUsage.hpp new file mode 100644 index 0000000..8cdc954 --- /dev/null +++ b/include/TreeNodeDiskUsage.hpp @@ -0,0 +1,79 @@ +#ifndef H_TreeNodeDiskUsage +#define H_TreeNodeDiskUsage + +#include +#include +#include +#include +#include + +#define PRINT_VAR(x); std::cout << #x << "\t= " << (x) << "\n"; + +/// \struct PathFilters Stores the filters on paths. +struct PathFilters +{ + std::vector const& pathFiltersInclude; + std::vector const& pathFiltersExclude; +}; + +/// \class TreeNodeDiskUsage Implements a node of a tree of the files and folders on a system. +class TreeNodeDiskUsage +{ + public: + TreeNodeDiskUsage(std::string const& path_, PathFilters const* pathFilters_ = NULL); + ~TreeNodeDiskUsage(); + + /// Returns true if the path of the node matches the filters given to it. If no filters have been defined, all paths are valid. + bool MatchesFilter() const; + + /// Builds the whole tree + void BuildTree(); + + /// Prints the tree to the standard output only including the names of the nodes + void PrintTree(unsigned int maxDepth = 0xFFFFFFFF, unsigned int depth = 0) const; + + /// Returns the number of children of the node. + size_t GetChildrenCount() const; + + /// Returns the total size of the node, including all its children + size_t GetTotalSize() const; + + /// Returns the total size of the node on disk, including all its children + size_t GetTotalSizeOnDisk() const; + + /// Returns the name of the node : /path/to/node.bla -> node.bla + std::string GetNodeName() const; + + /// Returns the path of the node : /path/to/node.bla + std::string GetNodePath() const; + + protected: + /// Cleans the path to remove double slashes, trailing slashes and leading and trailing whitespaces. + void SanitizePath(); + + /// Explores the whole tree to compute every node's size, including all their children. + void ComputeTotalSize(); + + /// Removes the leading character 'c'. + static std::string RemoveLeadingCharacter(std::string const& str, char c); + + /// Removes the trailing character 'c'. + static std::string RemoveTrailingCharacter(std::string const& str, char c); + + /// Removes contiguous occurrences of the character 'c'. + static std::string RemoveDoublesCharacter(std::string const& str, char c); + + /// Replaces all occurrences of c1 by c2 in str. + static std::string ReplaceCharacterInStr(std::string str, char c1, char c2); + + protected: + PathFilters const* pathFilters; // children; // + +TreeNodeDiskUsage::TreeNodeDiskUsage(std::string const& path_, PathFilters const* pathFilters_) +: pathFilters(pathFilters_), + path(path_), + isFolder(false), + totalSize(0), + totalSizeOnDisk(0) +{ + SanitizePath(); +} + +TreeNodeDiskUsage::~TreeNodeDiskUsage() +{ + +} + +bool TreeNodeDiskUsage::MatchesFilter() const +{ + if(pathFilters == NULL) + return true; + + if(!pathFilters->pathFiltersInclude.size() && !pathFilters->pathFiltersExclude.size()) + return true; + /* + bool included = false; + for(unsigned int i = 0 ; i < pathFilters.pathFiltersInclude.size() ; i++) + //*/ + return true;// Temporary +} + +void TreeNodeDiskUsage::BuildTree() +{ + // Build the entire tree starting from the current node + + // Check that the node is included in the current search + if(!MatchesFilter()) + return; + + // Acquire stats on the current path + struct stat s; + + // Check that path exists + if(stat(path.c_str(), &s) != 0) + return; + + std::cout << "BuildTree:path = " << path << "\n";// DEBUG + + isFolder = (s.st_mode & S_IFMT) == S_IFDIR;// True if the path corresponds to a folder + //PRINT_VAR((s.st_mode & S_IFMT) == S_IFLNK);// symbolic link + //PRINT_VAR((s.st_mode & S_IFMT) == S_IFREG);// regular file + + if(!isFolder) + { + totalSize = s.st_size; + totalSizeOnDisk = s.st_blocks*512;// st_blocks is given in number of blocks of 512 bytes + } + else + { + totalSize = 0; + totalSizeOnDisk = 0; + + // List the contents of the folder + DIR *d; + dirent *dir; + d = opendir(path.c_str()); + if(d) + { + while ((dir = readdir(d)) != NULL) + { + std::string elementname = std::string(dir->d_name); + if(elementname != "." && elementname != "..") + { + // Recursively build the sub-tree + TreeNodeDiskUsage node(path + "/" + elementname, pathFilters); + node.BuildTree(); + totalSize += node.totalSize; + totalSizeOnDisk += node.totalSizeOnDisk; + children.push_back(node); + } + //printf("%s\n", dir->d_name); + } + closedir(d); + } + } +} + +void TreeNodeDiskUsage::PrintTree(unsigned int maxDepth, unsigned int depth) const +{ + if(depth > maxDepth) + return; + + for(unsigned int i = 0 ; i < depth ; i++) + std::cout << " "; + //std::cout << path << "\n"; + std::cout << path << "\t" << totalSize << "\t" << totalSizeOnDisk << "\n"; + if(isFolder) + for(unsigned int i = 0 ; i < children.size() ; i++) + children[i].PrintTree(maxDepth, depth+1); +} + +size_t TreeNodeDiskUsage::GetChildrenCount() const { return children.size(); } +size_t TreeNodeDiskUsage::GetTotalSize() const { return totalSize; } +size_t TreeNodeDiskUsage::GetTotalSizeOnDisk() const { return totalSizeOnDisk; } +std::string TreeNodeDiskUsage::GetNodePath() const{ return path; } + +std::string TreeNodeDiskUsage::GetNodeName() const +{ + if(path.size() <= 1) // covers "." and "/" + return path; + + if(path == "..") + return path; + + // Find last "/" in path + unsigned int lastSepPos = 0; + for(int i = (int)path.size() ; i >= 0 ; i--) + if(path[i] == '/') + { + lastSepPos = i; + break; + } + + return path.substr(lastSepPos+1, path.size()-lastSepPos-1); +} + +void TreeNodeDiskUsage::SanitizePath() +{ + // Special cases handling + if(path == "/" || path == "\\" || path == "." || path == "..") + return; + + path = TreeNodeDiskUsage::ReplaceCharacterInStr(path, '\\', '/'); + path = TreeNodeDiskUsage::ReplaceCharacterInStr(path, '\t', ' '); + + path = TreeNodeDiskUsage::RemoveTrailingCharacter(path, ' '); // Remove trailing white spaces + path = TreeNodeDiskUsage::RemoveTrailingCharacter(path, '/'); // Remove trailing slashes + + path = TreeNodeDiskUsage::RemoveLeadingCharacter(path, ' '); // Remove leading white spaces + + // Remove multiple occurrences of slashes + path = TreeNodeDiskUsage::RemoveDoublesCharacter(path, '/'); +} + +std::string TreeNodeDiskUsage::RemoveLeadingCharacter(std::string const& str, char c) +{ + unsigned int slashCounter = 0; + for(unsigned int i = 0 ; i < str.size() ; i++) + { + if(str[i] == c) + slashCounter++; + else + break; + } + if(slashCounter) + return str.substr(slashCounter, str.size()); + return str; +} + +std::string TreeNodeDiskUsage::RemoveTrailingCharacter(std::string const& str, char c) +{ + unsigned int slashCounter = 0; + for(int i = str.size()-1 ; i >= 0 ; i--) + { + if(str[i] == c) + slashCounter++; + else + break; + } + if(slashCounter) + return str.substr(0, str.size()-slashCounter); + return str; +} + +std::string TreeNodeDiskUsage::RemoveDoublesCharacter(std::string const& str, char c) +{ + std::string newStr; + unsigned int slashCounter = 0; + for(unsigned int i = 0 ; i < str.size() ; i++) + { + if(str[i] == c) + slashCounter++; + else + slashCounter = 0; + + if(slashCounter < 2) + newStr += str[i]; + } + return newStr; +} + +std::string TreeNodeDiskUsage::ReplaceCharacterInStr(std::string str, char c1, char c2) +{ + for(unsigned int i = 0 ; i < str.size() ; i++) + if(str[i] == c1) + str[i] = c2; + return str; +} + +void TreeNodeDiskUsage::ComputeTotalSize() +{ + // Recursively add all sizes to the current node's size + //if() +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..231c95c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,65 @@ +#include +#include + +using std::cout; +using std::cerr; +using std::endl; + +int main(int argc, char *argv[]) +{ + if(argc > 1) + { + if(0) + {// stat test + cout << "Reading '" << argv[1] << "'\n"; + struct stat s; + PRINT_VAR(stat(argv[1], &s)); + PRINT_VAR(s.st_mode); + PRINT_VAR(s.st_mode & S_IFMT); + PRINT_VAR((s.st_mode & S_IFMT) == S_IFLNK);// symbolic link + PRINT_VAR((s.st_mode & S_IFMT) == S_IFREG);// regular file + PRINT_VAR((s.st_mode & S_IFMT) == S_IFDIR);// folder + PRINT_VAR(s.st_size); + PRINT_VAR(s.st_blksize); + PRINT_VAR(s.st_blocks); + PRINT_VAR(s.st_blocks*512);// Size on disk, in bytes + } + // if(0) + {// test GetNodeName() + PRINT_VAR(TreeNodeDiskUsage("/some/path/to/victory.tar.gz").GetNodeName()); + PRINT_VAR(TreeNodeDiskUsage("/some/path/to/a/folder").GetNodeName()); + PRINT_VAR(TreeNodeDiskUsage("/some/path/to/a/folder/").GetNodeName()); + PRINT_VAR(TreeNodeDiskUsage("/").GetNodeName()); + PRINT_VAR(TreeNodeDiskUsage(".").GetNodeName()); + PRINT_VAR(TreeNodeDiskUsage("..").GetNodeName()); + PRINT_VAR(TreeNodeDiskUsage("/a").GetNodeName()); + PRINT_VAR(TreeNodeDiskUsage("/a/b/c//////////").GetNodeName()); + PRINT_VAR(TreeNodeDiskUsage("/a///b//c/").GetNodeName()); + } + // if(0) + {// test GetNodePath() + PRINT_VAR(TreeNodeDiskUsage("/a/b/c//////////").GetNodePath()); + PRINT_VAR(TreeNodeDiskUsage("/a///b//c/").GetNodePath()); + PRINT_VAR(TreeNodeDiskUsage("////a///b//c/").GetNodePath()); + PRINT_VAR(TreeNodeDiskUsage(" /a/b/c ").GetNodePath()); + PRINT_VAR(TreeNodeDiskUsage(" /a/b/c/// ").GetNodePath()); + PRINT_VAR(TreeNodeDiskUsage(" \\this\\\\is/sparta/// ").GetNodePath()); + PRINT_VAR(TreeNodeDiskUsage(" \\this\\\\is/sparta/with spaces// ").GetNodePath()); + } + if(0) + { + TreeNodeDiskUsage tree(argv[1]); + tree.BuildTree(); + tree.PrintTree(); + PRINT_VAR(tree.GetTotalSize()); + PRINT_VAR(tree.GetTotalSizeOnDisk()); + tree.PrintTree(0); + tree.PrintTree(1); + tree.PrintTree(2); + } + } + else + cerr << "Error : No path given !\n"; + + return 0; +}