From 1b6226131930ea2d6ddf17933f7ec42c745d1469 Mon Sep 17 00:00:00 2001 From: Caleb Depatie Date: Tue, 11 Nov 2025 14:03:04 -0500 Subject: [PATCH 1/3] Added a string splitting util function --- filesystem/src/utilities.cpp | 30 ++++++++++++++++++++++++++++++ filesystem/src/utilities.hpp | 6 +++++- filesystem/tests/utilities.cpp | 26 +++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/filesystem/src/utilities.cpp b/filesystem/src/utilities.cpp index a6f5071..e0cab75 100644 --- a/filesystem/src/utilities.cpp +++ b/filesystem/src/utilities.cpp @@ -72,4 +72,34 @@ std::string extract_query(const char* path) { spdlog::trace("Exiting parse() -> {0}", extracted_query); return extracted_query; +} + +std::vector split(const std::string str, const std::string delim) { + spdlog::trace("Entering split(str={0}, delim={1})", str, delim); + + std::vector parts; + + int part_start = 0; + for (int i = 0; i < str.size(); i++) + { + const std::string delim_match = str.substr(i, delim.size()); + + if (delim_match == delim) + { + if (part_start != i) + { + parts.push_back(str.substr(part_start, i-part_start)); + } + + part_start = i + delim.size(); + } + } + + if (part_start < str.size()) + { + parts.push_back(str.substr(part_start, str.size() - part_start)); + } + + spdlog::trace("Exiting split() -> vec.size()={0}", parts.size()); + return parts; } \ No newline at end of file diff --git a/filesystem/src/utilities.hpp b/filesystem/src/utilities.hpp index 8d386e8..7e9e69e 100644 --- a/filesystem/src/utilities.hpp +++ b/filesystem/src/utilities.hpp @@ -4,9 +4,13 @@ #pragma once #include +#include // Takes a query path that involves a tag query and returns the real FS path std::string reverse_query(const char* path); // Takes a path and returns the query segment -std::string extract_query(const char* path); \ No newline at end of file +std::string extract_query(const char* path); + +// Split a string based on a delimeter +std::vector split(const std::string str, const std::string delim); \ No newline at end of file diff --git a/filesystem/tests/utilities.cpp b/filesystem/tests/utilities.cpp index 490a324..5887636 100644 --- a/filesystem/tests/utilities.cpp +++ b/filesystem/tests/utilities.cpp @@ -43,4 +43,28 @@ TEST_CASE("Query Extraction", "[utilities]") { CHECK(res == "(tag1 & ((!place) | (tag2 & tag3)))"); } -} \ No newline at end of file +} + +TEST_CASE("String Splitting", "[utilities]") { + SECTION("Single char delim") { + const auto haystack = "wow/this/is/a/path/"; + const auto needle = "/"; + + const auto found = split(haystack, needle); + + CHECK(found.size() == 5); + CHECK(found[0] == "wow"); + CHECK(found[4] == "path"); + } + + SECTION("Multi char delim") { + const auto haystack = "> Date: Tue, 11 Nov 2025 15:10:39 -0500 Subject: [PATCH 2/3] Working folder tags; Resolved #51 --- filesystem/src/fs.cpp | 59 +++++------- filesystem/src/utilities.cpp | 169 ++++++++++++++++++++++++++++----- filesystem/src/utilities.hpp | 6 +- integration_tests/adv_query.sh | 2 +- integration_tests/folder.sh | 43 +++++++++ integration_tests/meson.build | 3 +- 6 files changed, 221 insertions(+), 61 deletions(-) create mode 100644 integration_tests/folder.sh diff --git a/filesystem/src/fs.cpp b/filesystem/src/fs.cpp index 8e328e4..8a28623 100644 --- a/filesystem/src/fs.cpp +++ b/filesystem/src/fs.cpp @@ -17,9 +17,8 @@ extern "C" { #include #include -#include "query_lang/parser.hpp" -#include "fs.hpp" #include "db.hpp" +#include "fs.hpp" #include "command_interface.h" #include "utilities.hpp" @@ -41,15 +40,20 @@ int lake_getattr(const char *path, struct stat *stbuf) auto file = reverse_query(path); - if (file.empty()) { + if (!file.has_value()) + { + return file.error(); + } + + if (file.value().empty()) { spdlog::error("No file found!"); return -ENOENT; } - spdlog::debug("stat'ing file at {0}", file); + spdlog::debug("stat'ing file at {0}", file.value()); - if (stat(file.c_str(), stbuf) < 0) + if (stat(file.value().c_str(), stbuf) < 0) { spdlog::error("Error stat'ing: {0}", strerror(errno)); @@ -82,36 +86,14 @@ int lake_readdir( filler(buf, "..", nullptr, 0); #endif - std::vector files; - - // TODO: very nested, smelly - - // Check if path is a query - if (path[strlen(path) - 1] == ')') { - std::string query = extract_query(path); - - try { - const auto query_ast = parse(query); - - if (query_ast.has_value()) { - files = db_run_query(query_ast.value()); - - } else { - spdlog::error("Error while parsing, invalid query"); + const auto files = get_files(path); - return -EINVAL; - } - } catch (std::exception err) { - spdlog::error("Error while parsing: {0}", err.what()); - - return -EINVAL; - } - - } else { - files = db_run_default_query(); + if (!files.has_value()) + { + return files.error(); } - for (const auto& file : files) { + for (const auto& file : files.value()) { const std::string file_name = file.substr(file.find_last_of("/") + 1); spdlog::trace("Will show file {0} as {1}", file, file_name); @@ -136,12 +118,19 @@ int lake_open(const char *path, struct fuse_file_info *fi) { auto file_path = reverse_query(path); - if (file_path.empty()) + if (!file_path.has_value()) + { + return file_path.error(); + } + + if (file_path.value().empty()) + { return -ENOENT; + } - spdlog::trace("Found file {0} to open", file_path); + spdlog::trace("Found file {0} to open", file_path.value()); - fi->fh = open(file_path.c_str(), fi->flags); + fi->fh = open(file_path.value().c_str(), fi->flags); if (fi->fh == -1) { spdlog::error("Could not open file err: {0}", strerror(errno)); diff --git a/filesystem/src/utilities.cpp b/filesystem/src/utilities.cpp index e0cab75..7335582 100644 --- a/filesystem/src/utilities.cpp +++ b/filesystem/src/utilities.cpp @@ -3,50 +3,153 @@ // SPDX-License-Identifier: BSD-3-Clause #include #include +#include #include "query_lang/parser.hpp" #include "db.hpp" #include "utilities.hpp" -std::string reverse_query(const char* path) { - auto path_s = std::string(path); +std::expected, int> get_files(const char* path) { + spdlog::trace("Entering get_files(path={0})", path); - spdlog::trace("Entering reverse_query(path={0})", path_s); + std::vector files; - std::vector query_files; + // Get path segments from query + const auto path_segments = split(path, "/"); - if (path_s.find_first_of('(') == std::string::npos) { - query_files = db_run_default_query(); + const bool path_has_query = path_segments.size() != 0 && path_segments[0].contains('('); - } else { - std::string query = extract_query(path); + if (path_has_query) + { + const auto query = extract_query(path); + const auto query_ast = parse(query); + + if (query_ast.has_value()) { + files = db_run_query(query_ast.value()); + + } else { + spdlog::error("Error while parsing, invalid query"); + + return std::unexpected(-EINVAL); + } + } + else + { + files = db_run_default_query(); + } + + if (path_segments.size() > 1) + { + + // Get the file path by comparing the file name to the query results + std::string file_path; + std::string looking_for_file; + + if (path_has_query) + { + looking_for_file = path_segments[1]; + } + else + { + looking_for_file = path_segments[0]; + } - try { - const auto query_ast = parse(query); - if (query_ast.has_value()) { - query_files = db_run_query(query_ast.value()); + for (const auto& query_file : files) { - } else { - spdlog::error("Error while parsing, no value returned"); + const std::string query_file_name = + query_file.substr(query_file.find_last_of("/") + 1); + + if (query_file_name == looking_for_file) { + file_path = query_file; + break; + } + } + + // Have to drill down arbitry path lengths + // Take current real path, get dir entries + // look for next folder, get that real path + // repeat + for (int i = 1 + path_has_query; i < path_segments.size(); i++) + { + auto finding_dir = path_segments[i]; + + const auto dir_iter = std::filesystem::directory_iterator(file_path); + + for (const auto& dir_entry : dir_iter) + { + if (dir_entry.path().filename() == finding_dir) + { + file_path = dir_entry.path(); + break; + } + } + } + + if (std::filesystem::is_directory(file_path)) + { + // Query is for a folder + spdlog::debug("Found real dir path: {0}", file_path.c_str()); + + // Get files of our actual destination folder + files.clear(); - return ""; + for (auto const& dir_entry : std::filesystem::directory_iterator{file_path}) + { + files.push_back(dir_entry.path()); } + } + } + + spdlog::trace("Exiting get_files() -> vec.size()={0}", files.size()); + return files; +} + +std::expected reverse_query(const char* path) { + auto path_s = std::string(path); + + spdlog::trace("Entering reverse_query(path={0})", path_s); + + // TODO: Refactor & reuse this shared code once working for subdirs + std::vector files; + + // Get path segments from query + const auto path_segments = split(path, "/"); + + const bool path_has_query = path_segments[0].contains('('); + + if (path_has_query) + { + const auto query = extract_query(path); + const auto query_ast = parse(query); + + if (query_ast.has_value()) { + files = db_run_query(query_ast.value()); - } catch (std::exception err) { - spdlog::error("Error while parsing: {0}", err.what()); - - return ""; + } else { + spdlog::error("Error while parsing, invalid query"); + + return std::unexpected(-EINVAL); } + } else { + files = db_run_default_query(); } // Get the file path by comparing the file name to the query results std::string file_path; + std::string looking_for_file; + + if (path_has_query) + { + looking_for_file = path_segments[1]; + } + else + { + looking_for_file = path_segments[0]; + } - const std::string looking_for_file = - std::string(path_s).substr(std::string(path_s).find_last_of("/") + 1); - for (const auto& query_file : query_files) { + for (const auto& query_file : files) { const std::string query_file_name = query_file.substr(query_file.find_last_of("/") + 1); @@ -57,6 +160,26 @@ std::string reverse_query(const char* path) { } } + // Have to drill down arbitry path lengths + // Take current real path, get dir entries + // look for next folder, get that real path + // repeat + for (int i = 1 + path_has_query; i < path_segments.size(); i++) + { + auto finding_dir = path_segments[i]; + + const auto dir_iter = std::filesystem::directory_iterator(file_path); + + for (const auto& dir_entry : dir_iter) + { + if (dir_entry.path().filename() == finding_dir) + { + file_path = dir_entry.path(); + break; + } + } + } + spdlog::trace("Exiting reverse_query() -> {0}", file_path); return file_path; } @@ -70,7 +193,7 @@ std::string extract_query(const char* path) { const auto extracted_query = path_s.substr(path_s.find_first_of('('), path_s.find_last_of(')') - path_s.find_first_of('(') + 1); - spdlog::trace("Exiting parse() -> {0}", extracted_query); + spdlog::trace("Exiting extract_query() -> {0}", extracted_query); return extracted_query; } diff --git a/filesystem/src/utilities.hpp b/filesystem/src/utilities.hpp index 7e9e69e..530f9a9 100644 --- a/filesystem/src/utilities.hpp +++ b/filesystem/src/utilities.hpp @@ -5,9 +5,13 @@ #include #include +#include + +// Runs a query normally. Handles special logic to pull out folders +std::expected, int> get_files(const char* path); // Takes a query path that involves a tag query and returns the real FS path -std::string reverse_query(const char* path); +std::expected reverse_query(const char* path); // Takes a path and returns the query segment std::string extract_query(const char* path); diff --git a/integration_tests/adv_query.sh b/integration_tests/adv_query.sh index 08e9d82..32f4d89 100644 --- a/integration_tests/adv_query.sh +++ b/integration_tests/adv_query.sh @@ -8,7 +8,7 @@ source integration_tests/test_core.sh # Issue #34 :: Exit gracefully with an incorrect query # TODO: Seems to work but I only get 0 in $rc. -# /usr/bin/env ls $lake_dir/'((default | not_default) & )' +# ls $lake_dir/'((default | not_default) & )' && echo 'not ok!!' # rc=$? # if [ $rc != 1 ]; then diff --git a/integration_tests/folder.sh b/integration_tests/folder.sh new file mode 100644 index 0000000..8042e1f --- /dev/null +++ b/integration_tests/folder.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: 2025 Caleb Depatie +# +# SPDX-License-Identifier: 0BSD + +source integration_tests/test_core.sh + +mkdir $test_dir/tagged_dir +touch $test_dir/tagged_dir/makefile +mkdir $test_dir/tagged_dir/src +touch $test_dir/tagged_dir/src/a.c +touch $test_dir/tagged_dir/src/b.c +touch $test_dir/tagged_dir/src/c.c + +$cli add $test_dir/tagged_dir +$cli tag $test_dir/tagged_dir its_a_folder + +# Reading from a folder +results="$(ls -A $lake_dir/'(its_a_folder)'/tagged_dir/ | wc -l)" + +if [ $(echo "$results" | xargs) != "2" ]; then + echo "Error: Tagging a folder not working" + echo "Expected: 2" + echo "Got: $results" + echo "ls: $(ls -A $lake_dir/'(its_a_folder)'/tagged_dir/)" + + cleanup_and_exit 1 +fi + +# Nested folder reading +results="$(ls -A $lake_dir/'(its_a_folder)'/tagged_dir/src/ | wc -l)" + +if [ $(echo "$results" | xargs) != "3" ]; then + echo "Error: Reading a nested folder not working!" + echo "Expected: 3" + echo "Got: $results" + echo "ls: $(ls -A $lake_dir/'(its_a_folder)'/tagged_dir/src/)" + + cleanup_and_exit 1 +fi + +cleanup_and_exit 0 \ No newline at end of file diff --git a/integration_tests/meson.build b/integration_tests/meson.build index 0819fbf..e4f6ac2 100644 --- a/integration_tests/meson.build +++ b/integration_tests/meson.build @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 Caleb Depatie +# SPDX-FileCopyrightText: 2024-2025 Caleb Depatie # # SPDX-License-Identifier: 0BSD @@ -9,6 +9,7 @@ tests_dict = { 'Deleting Files': 'deletion.sh', 'Relinking Files': 'relinking.sh', 'Add multiple tags': 'multiple_tags.sh', + 'Tag Folders': 'folder.sh', 'Advanced Queries': 'adv_query.sh' } From 5301fa7e1bd8bd64c4abc8321fcea2ca1800a560 Mon Sep 17 00:00:00 2001 From: Caleb Depatie Date: Tue, 11 Nov 2025 15:43:56 -0500 Subject: [PATCH 3/3] Refactored utilities.cpp to share code --- filesystem/src/utilities.cpp | 76 +++++++----------------------------- 1 file changed, 15 insertions(+), 61 deletions(-) diff --git a/filesystem/src/utilities.cpp b/filesystem/src/utilities.cpp index 7335582..ccf5862 100644 --- a/filesystem/src/utilities.cpp +++ b/filesystem/src/utilities.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Caleb Depatie // // SPDX-License-Identifier: BSD-3-Clause +#include #include #include #include @@ -110,78 +111,31 @@ std::expected reverse_query(const char* path) { spdlog::trace("Entering reverse_query(path={0})", path_s); - // TODO: Refactor & reuse this shared code once working for subdirs - std::vector files; - - // Get path segments from query const auto path_segments = split(path, "/"); - const bool path_has_query = path_segments[0].contains('('); - - if (path_has_query) - { - const auto query = extract_query(path); - const auto query_ast = parse(query); - - if (query_ast.has_value()) { - files = db_run_query(query_ast.value()); - - } else { - spdlog::error("Error while parsing, invalid query"); - - return std::unexpected(-EINVAL); - } - } else { - files = db_run_default_query(); - } + std::string looking_for_file = path_segments[path_segments.size()-1]; - // Get the file path by comparing the file name to the query results - std::string file_path; - std::string looking_for_file; + std::string reconstructed_path; - if (path_has_query) - { - looking_for_file = path_segments[1]; - } - else + for (int i = 0; i < path_segments.size()-1; i++) { - looking_for_file = path_segments[0]; + reconstructed_path += "/" + path_segments[i]; } + const auto files = get_files(reconstructed_path.c_str()); - for (const auto& query_file : files) { - - const std::string query_file_name = - query_file.substr(query_file.find_last_of("/") + 1); - - if (query_file_name == looking_for_file) { - file_path = query_file; - break; - } - } - - // Have to drill down arbitry path lengths - // Take current real path, get dir entries - // look for next folder, get that real path - // repeat - for (int i = 1 + path_has_query; i < path_segments.size(); i++) + if (!files.has_value()) { - auto finding_dir = path_segments[i]; - - const auto dir_iter = std::filesystem::directory_iterator(file_path); - - for (const auto& dir_entry : dir_iter) - { - if (dir_entry.path().filename() == finding_dir) - { - file_path = dir_entry.path(); - break; - } - } + return std::unexpected(files.error()); } - spdlog::trace("Exiting reverse_query() -> {0}", file_path); - return file_path; + const auto file_path = std::find_if(files->begin(), files->end(), + [looking_for_file] (const std::string& file_name) -> bool { + return std::filesystem::path(file_name).filename() == looking_for_file; + }); + + spdlog::trace("Exiting reverse_query() -> {0}", *file_path); + return *file_path; } std::string extract_query(const char* path) {