diff --git a/BUILD.md b/BUILD.md index e6005ac..cfeb68b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -151,16 +151,32 @@ This target runs the initialization script (`init.sh`). --- -### 12. **global_build** +### 12. **build-global** This target builds and installs the project globally. - **Command**: ```sh - make global_build + make build-global ``` - **Description**: - - This will configure the build with a `BUILD_GLOBAL` flag and install the project using `ninja` after the build completes. - - Useful for installing the project globally for system-wide usage. + - Just runs sudo make install after building for target local release (adds inLimbo.desktop and icon to system wide directories) + +--- + +### 12. **build-global-uninstall** +This target uninstalls every system wide installed file of inLimbo using `install_manifests.txt` in build directory. + +- **Command**: + ```sh + make build-global-uninstall + ``` + + > [!NOTE] + > + > If you remove `build/` directory, the `install_manifests.txt` will not exist so this wont work + > + > Will come with a workaround in the future + > --- @@ -226,8 +242,3 @@ make -j ./run_webassembly.py # assuming the compilation has no errors # This will run in port 8000 ``` - -> [!NOTE] -> -> Debug building using make needs to be setup, will be available soon. (it may not work using make currently) -> diff --git a/CHANGELOG.md b/CHANGELOG.md index ea2b7f5..f51f47a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -883,3 +883,38 @@ Sizeable commit, a lot of bugs resolved which is great, still hoping for better - Centering of the image_view (NOT THAT BIG) --- + +## [ALPHA 2.7] --- 27-01-2025 + +### Added +**NIL** + +### Changed +- Overall version bump to 2.7 + +- Added `build-global`, `build-global-uninstall` to makefile + +- Subsequent makefile, readme and build file changes + +- CMakeLists.txt now posts a very neat verbose output of the build configuration it will undergo (useful for debugging) + +- Moved `PlayingState` struct definition outside of `ui_handler` header + +- Overall code formatting and minor fixes + +### Fixed +- Debug builds for ASan and TSan using makefile + +### Removed +- Removed debounce time for now, seems unnecessary but might add in the future + +Small commit with some neat build changes that makes life pretty easy + +### Known Issues to fix in immediate commits +- Holding the keybind for PlayNextSong() / PlayPrevSong() doesnt break anything, but MiniAudioPlayer class is not as responsive as the UI, so it lags behind (MAJOR ISSUE) + +(The outcome of the above issue would be that if you hold PlayNextSong() func call and it goes to Song A, the MiniAudioPlayer might still be playing Song B, which appears BEFORE Song A) + +- Centering of the image_view (NOT THAT BIG) + +--- diff --git a/CMakeLists.txt b/CMakeLists.txt index 56cca5f..004a6c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,13 +2,19 @@ cmake_minimum_required (VERSION 3.22) project(inLimbo LANGUAGES CXX - VERSION 1.0.0 + VERSION 2.7 ) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CONFIG_TOML_FILE "$ENV{HOME}/.config/inLimbo/config.toml") +set(INLIMBO_CACHE_DIR "$ENV{HOME}/.cache/inLimbo") +set(MINIAUDIO_FILE_RELATIVE "src/music/miniaudio.h") +set(CIMG_FILE_RELATIVE "src/ui/components/libs/CImg.h") +set(TOML_FILE_RELATIVE "src/parser/toml.hpp") +set(INLIMBO_DEBUG_BUILD OFF) enable_testing() @@ -42,6 +48,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug-ASan") elseif(CMAKE_BUILD_TYPE STREQUAL "Debug-TSan") set(EXECUTABLE_NAME "inLimbo-DBG-TSan") else() + set(CMAKE_BUILD_TYPE "Local Release") set(EXECUTABLE_NAME "inLimbo") endif() @@ -61,6 +68,7 @@ target_include_directories(${EXECUTABLE_NAME} PRIVATE src) if(CMAKE_BUILD_TYPE STREQUAL "Debug-ASan") # Enable AddressSanitizer + set(INLIMBO_DEBUG_BUILD ON) message(STATUS "Enabling AddressSanitizer for Debug-AddressSanitizer build") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -g") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") @@ -76,6 +84,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug-ASan") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") elseif(CMAKE_BUILD_TYPE STREQUAL "Debug-TSan") + set(INLIMBO_DEBUG_BUILD ON) # Enable ThreadSanitizer message(STATUS "Enabling ThreadSanitizer for Debug-ThreadSanitizer build") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -g") @@ -115,6 +124,7 @@ endif() # --- Handle Global Build --------------------------------------------------------- if(DEFINED GLOBAL_BUILD AND GLOBAL_BUILD) + set(CMAKE_BUILD_TYPE "Global") set(CMAKE_INSTALL_PREFIX "/usr/") message(STATUS "Starting GLOBAL_BUILD for inLimbo...") @@ -126,7 +136,6 @@ if(DEFINED GLOBAL_BUILD AND GLOBAL_BUILD) else() # Local build (build in ./build directory) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/build") - message(STATUS "Building locally, binaries will be located in ./build/") endif() @@ -176,18 +185,65 @@ target_link_libraries(${EXECUTABLE_NAME} ) # --- Ensure the config.toml file is created in the correct directory --------------------- -if (NOT EXISTS "$ENV{HOME}/.config/inLimbo/config.toml") +if (NOT EXISTS ${CONFIG_TOML_FILE}) + message(STATUS "Creating config.toml...") file(MAKE_DIRECTORY "$ENV{HOME}/.config/inLimbo") - configure_file("${CMAKE_SOURCE_DIR}/src/parser/examples/config.toml" "$ENV{HOME}/.config/inLimbo/config.toml" COPYONLY) + configure_file("${CMAKE_SOURCE_DIR}/src/parser/examples/config.toml" ${CONFIG_TOML_FILE} COPYONLY) endif() -if (NOT EXISTS "$ENV{HOME}/.cache/inLimbo/") - file(MAKE_DIRECTORY "$ENV{HOME}/.cache/inLimbo") +if (NOT EXISTS ${INLIMBO_CACHE_DIR}) + file(MAKE_DIRECTORY ${INLIMBO_CACHE_DIR}) endif() # --- Add Tests Directory Only if INLIMBO_TESTING is Defined ---------------------- if(DEFINED INLIMBO_TESTING AND INLIMBO_TESTING) + set(CMAKE_BUILD_TYPE "Build-Test") message("--> Enabling TESTING for INLIMBO...") add_subdirectory(tests) else() message("--> TESTING is disabled for INLIMBO.") endif() + +# --- Sanity check for required headers ---- +message(STATUS "================== SANITY CHECKS ==========================") +if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${MINIAUDIO_FILE_RELATIVE}") + message(FATAL_ERROR "**Miniaudio header not found. File should exist at the expected location: ${MINIAUDIO_FILE_RELATIVE} (Maybe run init.sh?)**") +else() + message(STATUS "Found Miniaudio header...") +endif() + +if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${CIMG_FILE_RELATIVE}") + message(FATAL_ERROR "**CImg header not found. File should exist at the expected location: ${CIMG_FILE_RELATIVE} (Maybe run init.sh?)**") +else() + message(STATUS "Found CImg header...") +endif() + +if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${TOML_FILE_RELATIVE}") + message(FATAL_ERROR "**TOML++ header not found. File should exist at the expected location: ${TOML_FILE_RELATIVE} (Maybe run init.sh?)**") +else() + message(STATUS "Found TOML++ header...") +endif() +message(STATUS "================== SANITY CHECKS END ========================") + +# --- Print Build Configuration ---------------------------------------------------- +message(STATUS "\n\n-- Building the ${PROJECT_NAME} project v${PROJECT_VERSION}...\n") +message(STATUS "┌─ Build Configuration for inLimbo ────────────────────────") +message(STATUS "│ Operating System : ${CMAKE_SYSTEM_NAME}") +message(STATUS "│ Build Type : ${CMAKE_BUILD_TYPE} (ALPHA)") +message(STATUS "│ CMake Version : ${CMAKE_VERSION}") +message(STATUS "│ Compiler : ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_VERSION}") +message(STATUS "│ Compiler Flags : ${CMAKE_CXX_FLAGS}") +message(STATUS "│ Executable Name : ${EXECUTABLE_NAME}") +message(STATUS "│ C++ Standard : ${CMAKE_CXX_STANDARD}") +message(STATUS "│ Install Prefix (GLOBAL) : ${CMAKE_INSTALL_PREFIX}") +message(STATUS "│ Local Build Directory : ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +message(STATUS "│ Miniaudio header : YES (${MINIAUDIO_FILE_RELATIVE})") +message(STATUS "│ CImg header : YES (${CIMG_FILE_RELATIVE})") +message(STATUS "│ TOML++ header : YES (${TOML_FILE_RELATIVE})") +message(STATUS "│ GLib/Gio Libraries : ${GLIB_LIBRARIES}") +message(STATUS "│ FTXUI Fetched : YES") +message(STATUS "│ Config file location : ${CONFIG_TOML_FILE}") +message(STATUS "│ Cache dir location : ${INLIMBO_CACHE_DIR}") +message(STATUS "│ Build Global Option : ${BUILD_GLOBAL}") +message(STATUS "│ Enable Testing : ${INLIMBO_TESTING}") +message(STATUS "│ Enable Debug Build : ${INLIMBO_DEBUG_BUILD}") +message(STATUS "└──────────────────────────────────────────────────────────\n") diff --git a/Makefile b/Makefile index c8f4641..b96890d 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ BUILD_DIR_DBG_ASAN := build-dbg-asan BUILD_DIR_DBG_TSAN := build-dbg-tsan BUILD_DBG_ASAN_DIR := build-dbg-asan BUILD_DBG_TSAN_DIR := build-dbg-tsan +BUILD_GLOBAL := GLOBAL_BUILD EXECUTABLE := inLimbo CMAKE := cmake CMAKE_BUILD_TYPE := Release @@ -13,9 +14,10 @@ SCRIPT := ./init.sh TEST_SUITE_SCRIPT := ./run_tests.sh VERBOSE_FLAG := VERBOSE=1 TESTING_FLAG := INLIMBO_TESTING +INSTALL_MANIFEST := $(BUILD_DIR)/install_manifest.txt # Targets -.PHONY: all build clean rebuild build-all init asan tsan global_build build-test build-test-all +.PHONY: all build clean rebuild build-all init asan tsan build-global build-test build-test-all build-global-uninstall all: build-all @@ -25,7 +27,7 @@ build-test-all: $(MAKE) build-test build-test: - $(CMAKE) -S . -B $(BUILD_DIR) -D $(TESTING_FLAG)=ON + $(CMAKE) -S . -B $(BUILD_DIR) -D $(TESTING_FLAG)=ON -D $(BUILD_GLOBAL)=OFF $(CMAKE) --build $(BUILD_DIR) build-all: @@ -35,7 +37,7 @@ build-all: build: @echo "==> Fresh Building inLimbo with $(CMAKE_BUILD_TYPE)..." - $(CMAKE) -S . -B build $(BUILD_DIR) -D $(TESTING_FLAG)=OFF + $(CMAKE) -S . -B build $(BUILD_DIR) -D $(TESTING_FLAG)=OFF -D $(BUILD_GLOBAL)=OFF $(CMAKE) --build $(BUILD_DIR) rebuild: @@ -44,6 +46,7 @@ rebuild: asan: @echo "==> Building in AddressSanitizer mode..." + $(MAKE) init mkdir -p $(BUILD_DIR_DBG_ASAN) cmake -S . -B $(BUILD_DIR_DBG_ASAN) -DCMAKE_BUILD_TYPE=Debug-ASan cmake --build $(BUILD_DIR_DBG_ASAN) @@ -54,6 +57,7 @@ asan_run: asan tsan: @echo "==> Building in ThreadSanitizer mode..." + $(MAKE) init mkdir -p $(BUILD_DIR_DBG_TSAN) cmake -S . -B $(BUILD_DIR_DBG_TSAN) -DCMAKE_BUILD_TYPE=Debug-TSan cmake --build $(BUILD_DIR_DBG_TSAN) @@ -70,11 +74,21 @@ init: @echo "==> Running initialization script..." $(SCRIPT) -global_build: - @echo "==> Building globally and installing..." - mkdir -p $(BUILD_DIR) - cd $(BUILD_DIR) && $(CMAKE) -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) -DBUILD_GLOBAL=ON .. && $(NINJA) - cd $(BUILD_DIR) && sudo ninja install +build-global: + @echo "==> Building inLimbo GLOBALLY and installing..." + $(CMAKE) -S . -B build $(BUILD_DIR) -D $(TESTING_FLAG)=OFF -D $(BUILD_GLOBAL)=ON + $(CMAKE) --build $(BUILD_DIR) + cd $(BUILD_DIR) && sudo $(MAKE) install + +build-global-uninstall: + @echo "==> Uninstalling inLimbo from the GLOBAL build..." + @if [ -f "$(INSTALL_MANIFEST)" ]; then \ + xargs -a $(INSTALL_MANIFEST) sudo rm -v; \ + echo "--> Uninstallation complete."; \ + else \ + echo "**Error: No install_manifest.txt found. Please ensure the project was installed.**"; \ + exit 1; \ + fi verbose: @echo "==> Building with verbose output..." diff --git a/README.md b/README.md index 1c1bef9..6a941e3 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,12 @@ A simple command using makefile should build everything you want: make build-all ``` +For a **GLOBAL BUILD**: + +```bash +make build-global +``` + Check out [BUILD.md](https://github.com/nots1dd/inLimbo/blob/main/BUILD.md) for more options to build targets > [!NOTE] @@ -114,6 +120,8 @@ Check out [BUILD.md](https://github.com/nots1dd/inLimbo/blob/main/BUILD.md) for inLimbo is in active development and is prone to having *A LOT* of issues +-> Makefile also allows for building and running of a debug build. + To try out **DEBUG BUILD** to find bugs/issues: 1. Debug build with `AddressSanitizer` (ASan): diff --git a/VERSION b/VERSION index fcd5e60..f4749c5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.3 - ALPHA +2.7 - ALPHA diff --git a/src/arg-handler.hpp b/src/arg-handler.hpp index ccafc11..d465e5e 100644 --- a/src/arg-handler.hpp +++ b/src/arg-handler.hpp @@ -12,14 +12,13 @@ #include "./cmd-line-args.hpp" #include #include -#include #include #include // Constants constexpr const char* DBUS_SERVICE_NAME = "org.mpris.MediaPlayer2.inLimbo"; ///< DBus service name used by inLimbo. -constexpr const char* VERSION = "2.5 (ALPHA)"; ///< Current version of the application. +constexpr const char* VERSION = "2.7 (ALPHA)"; ///< Current version of the application. bool shouldRunApp = false; ///< Indicates if the application should proceed to run after handling arguments. diff --git a/src/cmd-line-args.hpp b/src/cmd-line-args.hpp index a87ed05..a1f601b 100644 --- a/src/cmd-line-args.hpp +++ b/src/cmd-line-args.hpp @@ -79,14 +79,20 @@ class CommandLineArgs * @return `true` if the flag is present, otherwise `false`. */ [[nodiscard]] - auto hasFlag(const std::string& flag) const -> bool { return args.find(flag) != args.end(); } + auto hasFlag(const std::string& flag) const -> bool + { + return args.find(flag) != args.end(); + } /** * @brief Retrieves the list of positional arguments. * * @return A reference to a vector containing the positional arguments. */ - [[nodiscard]] - auto getPositionalArgs() const -> const std::vector& { return positionalArgs; } + [[nodiscard]] + auto getPositionalArgs() const -> const std::vector& + { + return positionalArgs; + } /** * @brief Prints usage information and program details. diff --git a/src/dirsort/inode_mapper.hpp b/src/dirsort/inode_mapper.hpp index bcaafc9..57d573c 100644 --- a/src/dirsort/inode_mapper.hpp +++ b/src/dirsort/inode_mapper.hpp @@ -14,7 +14,7 @@ using namespace std; -#define LIB_BIN_NAME "lib.bin" +#define LIB_BIN_NAME "lib.bin" class InodeFileMapper { diff --git a/src/dirsort/songmap.hpp b/src/dirsort/songmap.hpp index 7bf05ad..1d94584 100644 --- a/src/dirsort/songmap.hpp +++ b/src/dirsort/songmap.hpp @@ -201,10 +201,7 @@ class SongTree * * @return The nested map structure of songs. */ - auto returnSongMap() - { - return tree; - } + auto returnSongMap() { return tree; } /** * @brief Serializes the SongTree object. diff --git a/src/dirsort/taglib_parser.h b/src/dirsort/taglib_parser.h index 8ab0ce3..7cfbfff 100644 --- a/src/dirsort/taglib_parser.h +++ b/src/dirsort/taglib_parser.h @@ -86,7 +86,7 @@ class TagLibParser * @param metadata A reference to a Metadata object where parsed data will be stored. * @return `true` if parsing was successful, `false` otherwise. */ - bool parseFile(const std::string& filePath, Metadata& metadata); + auto parseFile(const std::string& filePath, Metadata& metadata) -> bool; /** * @brief Parse metadata from files in a directory based on inode. @@ -94,8 +94,7 @@ class TagLibParser * @param directory The directory to search in. * @return A map of file paths to corresponding metadata. */ - std::unordered_map parseFromInode(ino_t inode, - const std::string& directory); + auto parseFromInode(ino_t inode, const std::string& directory) -> std::unordered_map; }; /** @@ -135,7 +134,7 @@ void sendErrMsg(std::string debugLogBoolStr, std::string errMsg) #ifndef __EMSCRIPTEN__ // TagLib-specific implementations // Function to parse metadata from a file -bool TagLibParser::parseFile(const std::string& filePath, Metadata& metadata) { +auto TagLibParser::parseFile(const std::string& filePath, Metadata& metadata) -> bool { if (debugLogBool) { std::cout << "-- [TAG PARSE] Parsing file: " << filePath << std::endl; } @@ -230,9 +229,6 @@ bool TagLibParser::parseFile(const std::string& filePath, Metadata& metadata) { if (properties.contains("LYRICS")) { metadata.lyrics = properties["LYRICS"].toString().to8Bit(true); - if (debugLogBool) { - std::cout << "[TAG PARSE] Lyrics found: " << metadata.lyrics << std::endl; - } } else { metadata.lyrics = "No Lyrics"; if (debugLogBool) { @@ -242,12 +238,11 @@ bool TagLibParser::parseFile(const std::string& filePath, Metadata& metadata) { // Populate additional properties if needed if (debugLogBool && !properties.isEmpty()) { - std::cout << "[TAG PARSE] Additional properties found:" << std::endl; + std::cout << "[TAG PARSE] Additional properties found!" << std::endl; for (const auto& prop : properties) { std::string key = prop.first.to8Bit(true); std::string value = prop.second.toString().to8Bit(true); metadata.additionalProperties[key] = value; - std::cout << "[TAG PARSE] " << key << ": " << value << std::endl; } } @@ -255,7 +250,7 @@ bool TagLibParser::parseFile(const std::string& filePath, Metadata& metadata) { } // Function to parse metadata based on inode -std::unordered_map TagLibParser::parseFromInode(ino_t inode, const std::string& directory) { +auto TagLibParser::parseFromInode(ino_t inode, const std::string& directory) -> std::unordered_map { std::unordered_map metadataMap; std::string tempErrMsg; @@ -326,7 +321,7 @@ void printMetadata(const Metadata& metadata) { * @param outputImagePath The path where the extracted album art image will be saved. * @return `true` if the thumbnail was successfully extracted, `false` otherwise. */ -bool extractThumbnail(const std::string& audioFilePath, const std::string& outputImagePath) { +auto extractThumbnail(const std::string& audioFilePath, const std::string& outputImagePath) -> bool { // Determine the file type based on the extension std::string extension = audioFilePath.substr(audioFilePath.find_last_of('.') + 1); diff --git a/src/helpers/levenshtein.hpp b/src/helpers/levenshtein.hpp index 2bbd62a..a97a59c 100644 --- a/src/helpers/levenshtein.hpp +++ b/src/helpers/levenshtein.hpp @@ -2,68 +2,73 @@ #define LEVENSHTEIN_DIST_HPP #include -#include #include +#include /** * @file levenshteinDist.hpp * @brief Contains the implementation of the Levenshtein distance algorithm. - * + * * The Levenshtein distance is a metric for measuring the difference between two strings. - * It calculates the minimum number of single-character edits (insertions, deletions, or substitutions) required to transform one string into another. + * It calculates the minimum number of single-character edits (insertions, deletions, or + * substitutions) required to transform one string into another. */ /** * @brief Computes the Levenshtein distance between two strings. - * - * The Levenshtein distance is calculated by finding the minimum number of operations required to transform one string into another. - * The allowed operations are: + * + * The Levenshtein distance is calculated by finding the minimum number of operations required to + * transform one string into another. The allowed operations are: * - **Insertion**: Insert a character into one string. * - **Deletion**: Delete a character from one string. * - **Substitution**: Replace a character in one string with another. - * + * * @param s1 The first string. * @param s2 The second string. * @return The Levenshtein distance between the two strings. - * + * * @details - * This implementation uses dynamic programming to compute the Levenshtein distance. A 2D table is constructed, where each entry at [i][j] contains the distance - * between the first i characters of s1 and the first j characters of s2. The final distance is found at the bottom-right corner of the table. - * The time complexity of this algorithm is O(m * n), where m and n are the lengths of the two input strings. + * This implementation uses dynamic programming to compute the Levenshtein distance. A 2D table is + * constructed, where each entry at [i][j] contains the distance between the first i characters of + * s1 and the first j characters of s2. The final distance is found at the bottom-right corner of + * the table. The time complexity of this algorithm is O(m * n), where m and n are the lengths of + * the two input strings. */ size_t levenshteinDistance(const std::string& s1, const std::string& s2) { - size_t len1 = s1.size(); ///< Length of the first string. - size_t len2 = s2.size(); ///< Length of the second string. - - // 2D table to store the distances between substrings of s1 and s2. - std::vector> dist(len1 + 1, std::vector(len2 + 1)); + size_t len1 = s1.size(); ///< Length of the first string. + size_t len2 = s2.size(); ///< Length of the second string. + + // 2D table to store the distances between substrings of s1 and s2. + std::vector> dist(len1 + 1, std::vector(len2 + 1)); - // Initialize the first column and row of the table. - for (size_t i = 0; i <= len1; ++i) - dist[i][0] = i; ///< Cost of deleting characters from s1 to match empty s2. + // Initialize the first column and row of the table. + for (size_t i = 0; i <= len1; ++i) + dist[i][0] = i; ///< Cost of deleting characters from s1 to match empty s2. - for (size_t j = 0; j <= len2; ++j) - dist[0][j] = j; ///< Cost of inserting characters into s1 to match s2. + for (size_t j = 0; j <= len2; ++j) + dist[0][j] = j; ///< Cost of inserting characters into s1 to match s2. - // Fill the rest of the table with the calculated distances. - for (size_t i = 1; i <= len1; ++i) + // Fill the rest of the table with the calculated distances. + for (size_t i = 1; i <= len1; ++i) + { + for (size_t j = 1; j <= len2; ++j) { - for (size_t j = 1; j <= len2; ++j) - { - // If the characters at the current positions are the same, no cost to substitute. - size_t cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1; + // If the characters at the current positions are the same, no cost to substitute. + size_t cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1; - // Calculate the minimum of deletion, insertion, or substitution operations. - dist[i][j] = std::min({ dist[i - 1][j] + 1, ///< Deletion - dist[i][j - 1] + 1, ///< Insertion - dist[i - 1][j - 1] + cost ///< Substitution - }); - } + // Calculate the minimum of deletion, insertion, or substitution operations. + dist[i][j] = std::min({ + dist[i - 1][j] + 1, ///< Deletion + dist[i][j - 1] + 1, ///< Insertion + dist[i - 1][j - 1] + cost ///< Substitution + }); } + } - // Return the computed Levenshtein distance, which is the value at the bottom-right corner of the table. - return dist[len1][len2]; + // Return the computed Levenshtein distance, which is the value at the bottom-right corner of the + // table. + return dist[len1][len2]; } #endif diff --git a/src/parser/examples/config.toml b/src/parser/examples/config.toml index 34fcc40..d2cbb4b 100644 --- a/src/parser/examples/config.toml +++ b/src/parser/examples/config.toml @@ -16,7 +16,7 @@ password_hash = "0a4dd737a1f7ab1f0b8f36f7ed75959febad3de996b5d5a8779547a7083fff1 [debug] parser_log = "false" # false to disable it, true to enable it -debounce_time_in_ms = "500" # Adding debounce to events (minimum is 500) +debounce_time_in_ms = "500" # Adding debounce to events (minimum is 500) [# LEGACY] # NOTE: Only `Tab`, `Escape`, `Enter/Return` and `Space` are accepted as special chars aside from: # [a-zA-Z0-9!@#$%\^&*\(\)<>\?/`~:;'"\[\]{}\|,\.-=+] charset diff --git a/src/parser/toml_parser.hpp b/src/parser/toml_parser.hpp index 284ef25..9908413 100644 --- a/src/parser/toml_parser.hpp +++ b/src/parser/toml_parser.hpp @@ -76,10 +76,7 @@ string getBaseConfigPath() * @param fileName The name of the configuration file (e.g., "config.toml"). * @return A string representing the full path to the configuration file. */ -string getConfigPath(string fileName) -{ - return getBaseConfigPath() + fileName; -} +string getConfigPath(string fileName) { return getBaseConfigPath() + fileName; } string getCachePath() { @@ -149,7 +146,8 @@ string_view parseTOMLField(string parent, string field) } /** - * @brief Parses a string field from a custom TOML configuration that is called by the INLIMBO_CONFIG_HOME macro at runtime + * @brief Parses a string field from a custom TOML configuration that is called by the + * INLIMBO_CONFIG_HOME macro at runtime * * This function retrieves the value of a specific field within a parent section of the TOML * configuration. If the field is not found, it returns an empty string view. @@ -158,7 +156,8 @@ string_view parseTOMLField(string parent, string field) * @param field The field name within the parent section (e.g., "name"). * @return A string view representing the value of the field. */ -string_view parseTOMLFieldCustom(const toml::parse_result& custom_config, string parent, string field) +string_view parseTOMLFieldCustom(const toml::parse_result& custom_config, string parent, + string field) { return custom_config[parent][field].value_or( ""sv); /**< If the field is not found, return an empty string view. */ @@ -181,7 +180,8 @@ int64_t parseTOMLFieldInt(string parent, string field) } /** - * @brief Parses an integer field from a custom TOML configuration set by the INLIMBO_CONFIG_HOME macro at runtime. + * @brief Parses an integer field from a custom TOML configuration set by the INLIMBO_CONFIG_HOME + * macro at runtime. * * This function retrieves the value of a specific field as an integer from the TOML configuration. * If the field is not found, it returns -1 as a default value. @@ -190,7 +190,8 @@ int64_t parseTOMLFieldInt(string parent, string field) * @param field The field name within the parent section (e.g., "username"). * @return The integer value of the field, or -1 if the field is not found. */ -int64_t parseTOMLFieldIntCustom(const toml::parse_result& custom_config, string parent, string field) +int64_t parseTOMLFieldIntCustom(const toml::parse_result& custom_config, string parent, + string field) { return custom_config[parent][field].value_or( -1); /**< If the field is not found, return -1 as default. */ diff --git a/src/signal/signalHandler.hpp b/src/signal/signalHandler.hpp index 253130f..3932efd 100644 --- a/src/signal/signalHandler.hpp +++ b/src/signal/signalHandler.hpp @@ -1,140 +1,166 @@ #ifndef SIGNAL_HANDLER_HPP #define SIGNAL_HANDLER_HPP -#include +#include // For PATH_MAX #include -#include #include -#include +#include // For timestamps +#include #include +#include +#include // For thread info #include -#include // For timestamps #include // For resource usage -#include // For system info -#include // For thread info +#include // For system info +#include #include // For getpid, getppid -#include // For PATH_MAX -class SignalHandler { +class SignalHandler +{ public: - // Singleton instance getter - static SignalHandler& getInstance() { - static SignalHandler instance; - return instance; - } + // Singleton instance getter + static SignalHandler& getInstance() + { + static SignalHandler instance; + return instance; + } + + // Initialize the signal handler + void setup() + { + struct sigaction sa; + sa.sa_sigaction = SignalHandler::handleSignal; // Use static member function + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; // Use siginfo_t for extended info + + sigaction(SIGABRT, &sa, nullptr); // Catch SIGABRT + sigaction(SIGSEGV, &sa, nullptr); // Catch SIGSEGV + } - // Initialize the signal handler - void setup() { - struct sigaction sa; - sa.sa_sigaction = SignalHandler::handleSignal; // Use static member function - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; // Use siginfo_t for extended info +private: + SignalHandler() = default; // Private constructor for singleton + ~SignalHandler() = default; + + // Non-copyable and non-movable + SignalHandler(const SignalHandler&) = delete; + SignalHandler& operator=(const SignalHandler&) = delete; + + // Get current timestamp as a string + static std::string getCurrentTimestamp() + { + std::time_t now = std::time(nullptr); + char buffer[100]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); + return std::string(buffer); + } + + static void createCacheDirectory(const std::string& path) + { + struct stat info; + if (stat(path.c_str(), &info) != 0 || !(info.st_mode & S_IFDIR)) + { + mkdir(path.c_str(), 0755); // Create directory with rwx permissions + } + } + + // Get system and environment details + static void logSystemInfo(std::ofstream& logFile) + { + // Process and thread information + logFile << "Process ID: " << getpid() << "\n"; + logFile << "Parent Process ID: " << getppid() << "\n"; + logFile << "Thread ID: " << pthread_self() << "\n"; + + // Current working directory + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd))) + { + logFile << "Current Working Directory: " << cwd << "\n"; + } - sigaction(SIGABRT, &sa, nullptr); // Catch SIGABRT - sigaction(SIGSEGV, &sa, nullptr); // Catch SIGSEGV + // System information + struct utsname sysInfo; + if (uname(&sysInfo) == 0) + { + logFile << "System Name: " << sysInfo.sysname << "\n"; + logFile << "Node Name: " << sysInfo.nodename << "\n"; + logFile << "Release: " << sysInfo.release << "\n"; + logFile << "Version: " << sysInfo.version << "\n"; + logFile << "Machine: " << sysInfo.machine << "\n"; } -private: - SignalHandler() = default; // Private constructor for singleton - ~SignalHandler() = default; - - // Non-copyable and non-movable - SignalHandler(const SignalHandler&) = delete; - SignalHandler& operator=(const SignalHandler&) = delete; - - // Get current timestamp as a string - static std::string getCurrentTimestamp() { - std::time_t now = std::time(nullptr); - char buffer[100]; - std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); - return std::string(buffer); + // Resource usage + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) == 0) + { + logFile << "CPU Time Used (user): " << usage.ru_utime.tv_sec << "s " << usage.ru_utime.tv_usec + << "us\n"; + logFile << "CPU Time Used (system): " << usage.ru_stime.tv_sec << "s " + << usage.ru_stime.tv_usec << "us\n"; + logFile << "Max Resident Set Size: " << usage.ru_maxrss << " KB\n"; } + } + + // Signal handling function + static void handleSignal(int signal, siginfo_t* info, void* context) + { + const char* signalName = nullptr; + switch (signal) + { + case SIGABRT: + signalName = "SIGABRT"; + break; + case SIGSEGV: + signalName = "SIGSEGV"; + break; + default: + signalName = "Unknown"; + break; + } + + std::string logDir = std::string(std::getenv("HOME")) + "/.cache/inLimbo/"; + createCacheDirectory(logDir); // Ensure the directory exists + + std::string logFileName = logDir + "debug-" + std::to_string(signal) + ".log"; + std::ofstream logFile(logFileName, std::ios::out | std::ios::app); - static void createCacheDirectory(const std::string& path) { - struct stat info; - if (stat(path.c_str(), &info) != 0 || !(info.st_mode & S_IFDIR)) { - mkdir(path.c_str(), 0755); // Create directory with rwx permissions + if (logFile.is_open()) + { + logFile << "=== Signal Caught ===\n"; + logFile << "Timestamp: " << getCurrentTimestamp() << "\n"; + logFile << "Signal: " << signalName << " (" << signal << ")\n"; + if (info) + { + logFile << "Address causing signal: " << info->si_addr << "\n"; } - } - // Get system and environment details - static void logSystemInfo(std::ofstream& logFile) { - // Process and thread information - logFile << "Process ID: " << getpid() << "\n"; - logFile << "Parent Process ID: " << getppid() << "\n"; - logFile << "Thread ID: " << pthread_self() << "\n"; - - // Current working directory - char cwd[PATH_MAX]; - if (getcwd(cwd, sizeof(cwd))) { - logFile << "Current Working Directory: " << cwd << "\n"; - } - - // System information - struct utsname sysInfo; - if (uname(&sysInfo) == 0) { - logFile << "System Name: " << sysInfo.sysname << "\n"; - logFile << "Node Name: " << sysInfo.nodename << "\n"; - logFile << "Release: " << sysInfo.release << "\n"; - logFile << "Version: " << sysInfo.version << "\n"; - logFile << "Machine: " << sysInfo.machine << "\n"; - } - - // Resource usage - struct rusage usage; - if (getrusage(RUSAGE_SELF, &usage) == 0) { - logFile << "CPU Time Used (user): " << usage.ru_utime.tv_sec << "s " << usage.ru_utime.tv_usec << "us\n"; - logFile << "CPU Time Used (system): " << usage.ru_stime.tv_sec << "s " << usage.ru_stime.tv_usec << "us\n"; - logFile << "Max Resident Set Size: " << usage.ru_maxrss << " KB\n"; - } - } + // Log system and environment information + logSystemInfo(logFile); + + // Generate a backtrace + void* buffer[128]; + int size = backtrace(buffer, 128); + logFile << "Backtrace (" << size << " frames):\n"; + char** symbols = backtrace_symbols(buffer, size); + for (int i = 0; i < size; ++i) + { + logFile << symbols[i] << '\n'; + } + free(symbols); - // Signal handling function - static void handleSignal(int signal, siginfo_t* info, void* context) { - const char* signalName = nullptr; - switch (signal) { - case SIGABRT: signalName = "SIGABRT"; break; - case SIGSEGV: signalName = "SIGSEGV"; break; - default: signalName = "Unknown"; break; - } - - std::string logDir = std::string(std::getenv("HOME")) + "/.cache/inLimbo/"; - createCacheDirectory(logDir); // Ensure the directory exists - - std::string logFileName = logDir +"debug-" + std::to_string(signal) + ".log"; - std::ofstream logFile(logFileName, std::ios::out | std::ios::app); - - if (logFile.is_open()) { - logFile << "=== Signal Caught ===\n"; - logFile << "Timestamp: " << getCurrentTimestamp() << "\n"; - logFile << "Signal: " << signalName << " (" << signal << ")\n"; - if (info) { - logFile << "Address causing signal: " << info->si_addr << "\n"; - } - - // Log system and environment information - logSystemInfo(logFile); - - // Generate a backtrace - void* buffer[128]; - int size = backtrace(buffer, 128); - logFile << "Backtrace (" << size << " frames):\n"; - char** symbols = backtrace_symbols(buffer, size); - for (int i = 0; i < size; ++i) { - logFile << symbols[i] << '\n'; - } - free(symbols); - - logFile.close(); - - std::cerr << "** Critical error occurred. See " << logFileName << " for details.\n" << "Exiting... **" << std::endl; - } else { - std::cerr << "-- Failed to write to log file: " << logFileName << "\n"; - } - - // Clean termination - _Exit(EXIT_FAILURE); + logFile.close(); + + std::cerr << "** Critical error occurred. See " << logFileName << " for details.\n" + << "Exiting... **" << std::endl; + } + else + { + std::cerr << "-- Failed to write to log file: " << logFileName << "\n"; } + + // Clean termination + _Exit(EXIT_FAILURE); + } }; #endif diff --git a/src/ui/misc.hpp b/src/ui/misc.hpp index b8f12f3..bdf3f8d 100644 --- a/src/ui/misc.hpp +++ b/src/ui/misc.hpp @@ -25,6 +25,25 @@ struct ComponentState Component audioDeviceMenu; }; +struct PlayingState +{ + std::string artist; + std::string title; + std::string genre; + std::string album; + bool has_comment = false; + bool has_lyrics = false; + int duration; + int bitrate; + unsigned int year = 0; + unsigned int track = 0; + unsigned int discNumber = 0; + std::string lyrics; + std::string comment; + std::unordered_map additionalProperties; + std::string filePath; +}; + auto formatLyrics(const std::string& lyrics) { std::vector lines; @@ -172,7 +191,7 @@ auto CreateMenu(const std::vector* vecLines, int* currLine) auto RenderSongMenu(const std::vector& items) { Elements rendered_items; - for (const auto & item : items) + for (const auto& item : items) { rendered_items.push_back(item | frame); } diff --git a/src/ui/ui_handler.hpp b/src/ui/ui_handler.hpp index eaa2ecf..4cac70e 100644 --- a/src/ui/ui_handler.hpp +++ b/src/ui/ui_handler.hpp @@ -47,12 +47,11 @@ class MusicPlayer MusicPlayer( const std::map>>>& - initial_library, Keybinds& keybinds, InLimboColors& colors) + initial_library, + Keybinds& keybinds, InLimboColors& colors) : library(initial_library), INL_Thread_Manager(std::make_unique()), - INL_Thread_State(INL_Thread_Manager->getThreadState()), - global_keybinds(keybinds), - global_colors(colors), - song_queue() // Initialize the queue vector to avoid any abrupt exits + INL_Thread_State(INL_Thread_Manager->getThreadState()), global_keybinds(keybinds), + global_colors(colors), song_queue() // Initialize the queue vector to avoid any abrupt exits { InitializeData(); CreateComponents(); @@ -137,31 +136,12 @@ class MusicPlayer } private: - struct PlayingState - { - std::string artist; - std::string title; - std::string genre; - std::string album; - bool has_comment = false; - bool has_lyrics = false; - int duration; - int bitrate; - unsigned int year = 0; - unsigned int track = 0; - unsigned int discNumber = 0; - std::string lyrics; - std::string comment; - std::unordered_map additionalProperties; - std::string filePath; - }; - PlayingState current_playing_state; std::unique_ptr mprisService; Keybinds global_keybinds; - GlobalProps global_props = parseProps(); + GlobalProps global_props = parseProps(); InLimboColors global_colors; std::shared_ptr audio_player; @@ -210,8 +190,8 @@ class MusicPlayer bool muted = false; int lastVolume = volume; double current_position = 0; - int active_screen = - 0; // 0 -> Main UI ; 1 -> Show help ; 2 -> Show lyrics; 3 -> Songs queue screen; 4 -> Song info screen; 5 -> Audio sinks screen + int active_screen = 0; // 0 -> Main UI ; 1 -> Show help ; 2 -> Show lyrics; 3 -> Songs queue + // screen; 4 -> Song info screen; 5 -> Audio sinks screen bool should_quit = false; bool focus_on_artists = true; ScreenInteractive* screen_ = nullptr; @@ -419,17 +399,43 @@ class MusicPlayer void PlayPreviousSong() { - // Move to the previous song if possible - if (current_song_queue_index > 0) + try { + // Queue state validations + if (song_queue.empty()) + { + SetDialogMessage("Error: Queue is empty."); + INL_Thread_State.is_playing = false; + return; + } + + if (current_song_queue_index + 1 >= song_queue.size()) + { + SetDialogMessage("Error: No more previous songs in the queue."); + return; + } + + // Increment song index current_song_queue_index--; + + // Get current song + Song* current_song = GetCurrentSongFromQueue(); + if (!current_song) + { + INL_Thread_State.is_playing = false; + SetDialogMessage("Error: Invalid song in queue."); + return; + } + current_position = 0; PlayCurrentSong(); UpdatePlayingState(); } - else + catch (std::exception e) { - SetDialogMessage("Error: No previous song available."); + show_dialog = true; + dialog_message = "Error: Invalid!!."; + return; } }