From 8cae6cf8ff366a6d6c8c43644c887449ddadf1ad Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 13:02:37 +0200 Subject: [PATCH 01/11] docs: add multiple vault's directories --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7848846..5c697b8 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,6 @@ As a NixOS user, I will likely package it for **nixpkgs** in the future if the p Usage: notewrapper [options] Options: -c, --config Specify the config file. - -d, --directory Specify the vaults' directory. -h, --help Display this message. -e, --editor Specify the editor to open. -j, --jump Jump to the end of the file on opening. @@ -163,7 +162,7 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat ```json { - "directory": "~/Documents/Notes/", + "directory": ["~/Documents/Notes/", "/other/paths/"], "render": true, "jumpToEndOfFileOnLaunch": true, "editor": "neovim", @@ -181,7 +180,7 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat ### Fields -* `directory`: root directory containing all vaults +* `directory`: Array of directories containing the vaults. * `render`: enable/disable Vivify rendering * `jumpToEndOfFileOnLaunch`: move cursor to end of file on open * `editor`: selected editor (must be supported). If not set, it defaults to `$EDITOR`. @@ -207,6 +206,6 @@ It is recommended to use a browser different from your main one for rendering. ## Planned features * [ ] A converter between journal types -* [ ] Support multiple vault directories +* [x] Support multiple vault directories * [ ] Port NoteWrapper to other editors (non-exhaustive list of planned ports: `emacs -nw`, `jed`, `ad`, flow-control, `ee`, `amp`, `dte`, `cano`, `mle`, `zee`, `ptext`, `kibi`, `ox`, `ne`, `dit`, `zile`, `moe`, `joe`, `pico`, `vis`) * [x] Default to $EDITOR From 08fffcb3d18a23178e2db337fc3107ad893bbc24 Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 13:10:43 +0200 Subject: [PATCH 02/11] docs: add instruction changing default configuration --- CONTRIBUTING.md | 9 +++++++++ config.json | 14 -------------- 2 files changed, 9 insertions(+), 14 deletions(-) delete mode 100644 config.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2436368..bbb6fc1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,6 +67,15 @@ To add support for a new editor, you must update both documentation and source c --- +## Changing default configuration + +If you want to change the default configuration or add a new option: +1. Update [README's documentation](./README.md#configuration) +2. In `./src/utils.c`, update the function `initAppFilesAndDirs()` which creates a default configuration if `~/.config/notewrapper/config.json` or if neither `-c` nor `--config` is set. +3. In `./src/main.c` where the JSON is parsed, add the parsing logic. Do not forget the debugging information, errors when wrong type or when an important field is missing and add a default value if a less important field is missing. + +--- + ## Before opening a pull request Before submitting a pull request, ensure that: diff --git a/config.json b/config.json deleted file mode 100644 index c8a885b..0000000 --- a/config.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "directory": "~/Documents/Notes/", - "render": true, - "jumpToEndOfFileOnLaunch": true, - "editor": "neovim", - "journalRegex": ".*journal.*", - "dateEntry": "# %Y %m %d %a", - "newLineOnOpening": true, - "backup": { - "enable": false, - "directory": "/path/to/backup", - "interval": "weekly" - } -} From e2343dd4014b7d6438070c20873dc8dcc8952b39 Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 13:11:30 +0200 Subject: [PATCH 03/11] docs: reworked example in commit convention --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bbb6fc1..28a9279 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ Format: Examples: * `Editor (#1): add support for Microsoft Word` -* `README.md: fix typos` +* `docs: fix typos in README.md` * `Journal: improve entry parsing logic` Keep commit messages short and descriptive. From 107519507fb8b49663f3a111b2283e30b30ff901 Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 20:45:42 +0200 Subject: [PATCH 04/11] feat: allow the usage of multiple vault directories --- src/main.c | 86 +++++++++++++++++++++++++++-------------------------- src/notes.c | 79 ++++++++++++++++++++++++++++++++---------------- src/notes.h | 11 +++++-- src/ui.c | 17 ++++++----- src/ui.h | 6 ++-- src/utils.c | 10 +++++-- src/utils.h | 4 ++- 7 files changed, 131 insertions(+), 82 deletions(-) diff --git a/src/main.c b/src/main.c index f84e55d..383de91 100644 --- a/src/main.c +++ b/src/main.c @@ -1,3 +1,4 @@ +#include "cjson/cJSON.h" #include "ui.h" #include "utils.h" #include "notes.h" @@ -100,20 +101,25 @@ arg_next: if (!json) {free(data);} error(!json, "program", "JSON parse error"); + // Parse all of the directories which will( or do) contain the vaults cJSON *dirJson = cJSON_GetObjectItem(json, "directory"); - char *notesDirectoryString = malloc(PATH_MAX); - if (dirJson && cJSON_IsString(dirJson) && cJSON_GetStringValue(dirJson) != NULL) { // we replaced all the ->valuestr and ->valueint to cJSON_GetStringValue() and cJSON_GetNumberValue() - char *rawPath = cJSON_GetStringValue(dirJson); - if (rawPath[0] == '~') { // expands ~ in the path - snprintf(notesDirectoryString, PATH_MAX, "%s/%s", homedir, rawPath+1); + error(dirJson && !cJSON_IsArray(dirJson), "user", "In %s, \"directory\" is missing or isn't an array", configPath); + int numDirectories = cJSON_GetArraySize(dirJson); + debug("In %s, detected %d paths in \"directory\"", configPath, numDirectories); + error(numDirectories == 0, "user", "In %s, \"directory\" is an empty array.", configPath); + + char **directoriesArray = malloc(numDirectories * sizeof(char*)); + debug("Directories:"); + cJSON *tempEntry = NULL; + int i = 0; + cJSON_ArrayForEach(tempEntry, dirJson) { // iterate over all the elements of the array _i. e._ over all the dirs + if (tempEntry && cJSON_IsString(tempEntry)) { + directoriesArray[i] = strdup(cJSON_GetStringValue(tempEntry)); + altDebug("%s\n",directoriesArray[i]); } else { - notesDirectoryString = rawPath; + error(1, "user", "In %s, in \"directory\", invalid type of one of the entries.", configPath); } - debug("In %s, \"directory\" was set to %s.", configPath, notesDirectoryString); - } else { - // default vault path if it is not set in the config - snprintf(notesDirectoryString, PATH_MAX, "%s/Documents/Notes/", homedir); - debug("In %s, \"directory\" wasn't set or we encountered a abnormal type. Defaulting to %s.", configPath, notesDirectoryString); + i++; } // fetch the render and jumpToEnfOfFileOnLaunch bools @@ -178,7 +184,8 @@ arg_next: int doesBackup = 0; int interval = 0; // this is an int. But some times it will be inputed a string. We must translate it. - char *pathToBackup = malloc(PATH_MAX); + // to handle linking each directory to its backup path. We create an array of char*. If we want to backup the ith directory, the ith pointer will point to the path. Else, we set the ith pointer to NULL. + char **backupDirectoriesArray = malloc(numDirectories*sizeof(char*)); char **rsyncArgs = NULL; int rsyncArgsNumber = 0; cJSON *backupJSON = cJSON_GetObjectItem(json, "backup"); @@ -187,19 +194,22 @@ arg_next: if (doesBackupJSON && cJSON_IsBool(doesBackupJSON)) { doesBackup = cJSON_IsTrue(doesBackupJSON) ? 1 : 0; debug("doesBackup is set to %d", doesBackup); - // handles the path to the backup + if (!doesBackup) { + goto backup_config_end; // easier to just go to rather than do a big if statement + } + + // handles the path to the backup for each directory cJSON *pathToBackupJSON = cJSON_GetObjectItem(backupJSON, "directory"); - if (pathToBackupJSON && cJSON_IsString(pathToBackupJSON)) { - char *temp = cJSON_GetStringValue(pathToBackupJSON); // we name it temp. As we can't directly set pathToBackup because snprintf doesn't like when a pointer is both an arg and the destination // temp should be freed when free(json), because cJSON_GetStringValue returns a pointer. - if (temp[0] == '~') { // expands ~ - temp++; // we shift by one to remove ~ - snprintf(pathToBackup, PATH_MAX, "%s/%s", homedir, temp); - debug("~ was expanded to %s\nThe backup path is %s", homedir, pathToBackup); - } else { - pathToBackup = strndup(temp, PATH_MAX); // temp will be freed later so strndup - debug("Directory for backup is set to %s in %s", pathToBackup, configPath); + error(cJSON_IsObject(pathToBackupJSON), "user", "In %s, incorrect type for \"backup\". It must be a JSON object."); + for (int i = 0; i < numDirectories; i++) { // we iterate over every + cJSON *pathToBackupForIthDirectoryJSON = cJSON_GetObjectItem(pathToBackupJSON, directoriesArray[i]); + if (pathToBackupForIthDirectoryJSON) { + backupDirectoriesArray[i] = strdup(cJSON_GetStringValue(pathToBackupForIthDirectoryJSON)); + } else { // else no path is set. So we won't backup it. + backupDirectoriesArray[i] = NULL; } - } else {error(1, "user", "%s did not contained a directory inside the backup section or the value is from an unexpected type", configPath);} + } + // handles the interval of backup cJSON *intervalJSON = cJSON_GetObjectItem(backupJSON, "interval"); if (intervalJSON && cJSON_IsString(intervalJSON)) { @@ -237,7 +247,7 @@ arg_next: } else { debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to {\"enable\": false}.", configPath); } - +backup_config_end: //cleans up @@ -268,11 +278,6 @@ for (int i = 1; i < argc; i++) { overwriteConfigPath = ++i; debug("--config set to %s", argv[i]); - } else if (strcmp(arg, "--directory") == 0) { - error(i + 1 == argc, "user", "Missing argument for --directory"); - notesDirectoryString = argv[++i]; - debug("--directory set to %s", notesDirectoryString); - } else if (strcmp(arg, "--editor") == 0) { error(i + 1 == argc, "user", "Missing argument for --editor"); editorToOpen = argv[++i]; @@ -311,7 +316,6 @@ for (int i = 1; i < argc; i++) { printf("Usage: notewrapper [options]\n"); printf("Options:\n"); printf(" -c, --config Specify the config file.\n"); - printf(" -d, --directory Specify the vaults' directory.\n"); printf(" -h, --help Display this message.\n"); printf(" -e, --editor Specify the editor to open.\n"); printf(" -j, --jump Jumps to the end of the file on opening.\n"); @@ -369,7 +373,6 @@ for (int i = 1; i < argc; i++) { printf("Usage: notewrapper [options]\n"); printf("Options:\n"); printf(" -c, --config Specify the config file.\n"); - printf(" -d, --directory Specify the vaults' directory.\n"); printf(" -h, --help Display this message.\n"); printf(" -e, --editor Specify the editor to open.\n"); printf(" -j, --jump Jumps to the end of the file on opening.\n"); @@ -383,7 +386,6 @@ for (int i = 1; i < argc; i++) { return 0; // -------- flags with arguments (MUST be last in group) -------- - case 'd': case 'e': case 'n': case 'v': @@ -400,11 +402,6 @@ for (int i = 1; i < argc; i++) { char *value = argv[++i]; switch (opt) { - case 'd': - notesDirectoryString = value; - debug("-d set directory to %s", value); - break; - case 'e': editorToOpen = value; defaultEditor = 0; @@ -451,7 +448,7 @@ next_arg: isEditorValid(editorToOpen, defaultEditor, shouldDebug); // check if editor is supported and if it is installed. If not, it will throw an error. if (doesBackup) { - handleBackups(notesDirectoryString, pathToBackup, homedir, interval, (const char**)rsyncArgs, rsyncArgsNumber, shouldDebug); + handleBackups(directoriesArray, numDirectories, backupDirectoriesArray, homedir, interval, (const char**)rsyncArgs, rsyncArgsNumber, shouldDebug); } initscr(); //initialize ncurses @@ -463,7 +460,8 @@ next_arg: char *vaultSelected = NULL; int vaultsCount = 0; - char **vaultsArray = getVaultsFromDirectory(notesDirectoryString, &vaultsCount, shouldDebug); + int *vaultsCountForEachDirectory = malloc(numDirectories*sizeof(int)); + char **vaultsArray = getVaultsFromDirectories(directoriesArray, numDirectories, vaultsCountForEachDirectory, &vaultsCount, shouldDebug); // when selecting each vault won't show the directory from which they come. We will find this later. // bypass if -v or --vault is set if (bypassSelectionVault) { @@ -505,13 +503,14 @@ next_arg: free(vaultsArray[i]); // we must only free the vaults options and not the extraOptions to avoid segfault } } - free(vaultsArray); debug("Selected vault: %s", vaultSelected); if (strcmp(vaultSelected,"Create a new vault") != 0 && strcmp(vaultSelected,"Settings") != 0 && strcmp(vaultSelected,"Quit (Ctrl+C)") != 0) { note_selection: bypassSelectionVault = 0; // we must reset bypassSelectionVault to not get stuck in a infinite loop of bypassing int shouldChangeVault = 0; + // we must find the directory from which the vault comes again. + char *notesDirectoryString = getDirectoryFromVault(vaultSelected, vaultsArray, vaultsCount, vaultsCountForEachDirectory, directoriesArray, numDirectories, shouldDebug); while (!shouldExit && !shouldChangeVault) { // this loop is the note selector int filesCount = 0; @@ -598,6 +597,8 @@ note_creation: } else if (strcmp(noteSelected,"Back to vault selection") == 0) { shouldChangeVault = 1; } else if (strcmp(noteSelected, "Delete vault") == 0) { + // we must find where does the vault comes from. + char *notesDirectoryString = getDirectoryFromVault(vaultSelected, vaultsArray, vaultsCount, vaultsCountForEachDirectory, directoriesArray, numDirectories, shouldDebug); const char *yesNo[] = {"No, go back to note selection.", "Yes."}; char *answer = ncursesSelect((char **)yesNo, "Are you sure you want to delete the entire vault? This can not be undone (Use arrows or WASD, Enter to select):", 1, 1, " ", "", "", shouldDebug); debug("You answered: %s for deleting the vault %s", answer, vaultSelected); if (strcmp(answer, "Yes.") == 0) { @@ -613,10 +614,10 @@ note_creation: shouldExit = 1; } } - + free(vaultsArray); } else if (strcmp(vaultSelected,"Create a new vault") == 0) { vault_creation: - createNewVault(notesDirectoryString, bypassSelectionVault, bypassSelectionVaultValue, shouldDebug); + createNewVault(directoriesArray, numDirectories, vaultsArray, vaultsCount, bypassSelectionVault, bypassSelectionVaultValue, shouldDebug); bypassSelectionVault = 0; // we need to reset bypassSelectionVault to avoid getting into an infinite loop of bypassing } else if (strcmp(vaultSelected,"Settings") == 0) { openEditor(configPath, editorToOpen, 0, 0, shouldDebug); // as this is not a md file we set render and jumptoEnfOfFile to 0 @@ -626,5 +627,6 @@ vault_creation: } } free(configPath); + free(directoriesArray); return 0; } diff --git a/src/notes.c b/src/notes.c index 44a53fe..37fcd99 100644 --- a/src/notes.c +++ b/src/notes.c @@ -1,5 +1,23 @@ #include "notes.h" -#include "ui.h" +#include "utils.h" + +char *getDirectoryFromVault(char *targetVault, char **vaultsArray, int vaultTotalNumber, int *vaultNumberPerDirectory, char **directoryArray, int directoryNumber, int shouldDebug) { + debug("Searching the vault %s inside all the directories...", targetVault); + int index = 0; + int startIndex = 0; + for (int i = 0; i < directoryNumber; i++) { + while (index - startIndex < vaultNumberPerDirectory[i]) { + if (strcmp(vaultsArray[index], targetVault) == 0) { // we found it + debug("%s found in %s. (This was the %dth step of a maximum of %d)", targetVault, directoryArray[i], index, vaultTotalNumber); + return directoryArray[i]; + } + index++; + } + startIndex += vaultNumberPerDirectory[i]; + } + error(1, "program", "the vault %s was not found", targetVault); + return "this makes the compiler and clangd happy. Who doesn't want GCC and clangd to be happy? Such person would be a terrible monster... One must imagine GCC and clangd happy."; +} char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) { debug("Searching %s for journals", vault); @@ -90,38 +108,49 @@ char** getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int return notesArray; } -char **getVaultsFromDirectory(char *dirString, int *count, int shouldDebug) { - // this function is inputed a path to a directory (which comes usually from the config file) and outpus all the suitable directories (so not the hidden ones) which will serve as separate vaults for notes - debug("Opening %s ", dirString); +char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber, int *vaultsPerDirectoryNumber, int *count, int shouldDebug) { + // to avoid having to work with a tree-dimensional array. We will use a 2d and vaultsPerDirectoryNumber will indicate the width of the directories. + char **vaultsArray = NULL; + int nthVault = 0; // this is only used internally to set the string into the right place in directoryStringArray. + for (int i = 0; i < directoryNumber; i++) { + debug("Opening %s", directoryStringArray[i]); // originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ struct dirent *vaultsDirectoryEntry; - DIR *vaultsDirectory = opendir(dirString); - error(vaultsDirectory==NULL, "program", "Could not open directory %s", dirString); - char **dirsArray = NULL; // will contain all the dirs/vaults - int dirsCount = 0; // we need to count how many dirs there is to always readjust how many memory we alloc + DIR *vaultsDirectory = opendir(directoryStringArray[i]); + error(!vaultsDirectory, "program", "Could not open directory %s", directoryStringArray[i]); + vaultsPerDirectoryNumber[i] = 0; + debug("┌------------------------------\n Detected files and dirs %s:", directoryStringArray[i]); // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html // for readdir() - debug("┌------------------------------\n Detected files and dirs from the directory:"); - while ((vaultsDirectoryEntry = readdir(vaultsDirectory)) != NULL) { - altDebug("%s\n", vaultsDirectoryEntry->d_name); - if (vaultsDirectoryEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files) - char fullPathEntry[PATH_MAX]; // creates a string of size of the maximum path lenght - snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s", dirString, vaultsDirectoryEntry->d_name); // sets the full absolute path to fullPathEntry - - struct stat metadataPathEntry; - if (stat(fullPathEntry, &metadataPathEntry) == 0 && S_ISDIR(metadataPathEntry.st_mode)) { // if this entry is a directory - dirsArray = realloc(dirsArray, (dirsCount + 1)*sizeof(char*)); // resize dirsArray so that - dirsArray[dirsCount] = strdup(vaultsDirectoryEntry->d_name); // copy the dir name into dirsArray - dirsCount++; + while((vaultsDirectoryEntry = readdir(vaultsDirectory)) != NULL) { + char *entryName = vaultsDirectoryEntry->d_name; // gets the entry as a string value + altDebug("%s", entryName); + if (entryName[0] != '.') { // if not hidden file/dir + char tempFullEntryPath[PATH_MAX]; // we recreate the full path to check it's proprieties + snprintf(tempFullEntryPath, PATH_MAX, "%s/%s", directoryStringArray[i], entryName); + //checking the metadata to see if it is a dir + struct stat metadataEntry; + if (stat(tempFullEntryPath, &metadataEntry) == 0 && S_ISDIR(metadataEntry.st_mode)) { // get's the metadata (stat should return 0 if fails) and sees if it is a dir. + altDebug(" is a vault\n"); + vaultsArray = realloc(vaultsArray, sizeof(vaultsArray) + sizeof(char *)); // resize vaultsArray + vaultsPerDirectoryNumber[i]++; // it will be used later to know which vaults goes into which directory + directoryStringArray[nthVault] = strdup(entryName); // we use strdup and not strcpy, because memory used with opendir and readdir will be closed. + nthVault++; // it is used immediatly to set the vault into directoryStringArray + } else { + altDebug(" is not a vault (not a dir.)\n"); } + } else { + altDebug(" is not a vault (hidden file/dir)\n"); } } - altDebug("└------------------------------\n"); - - // free's some used memory closedir(vaultsDirectory); - *count = dirsCount; - return dirsArray; + } + // we calculate once the total number of vaults to avoid recalculation every time we use it + count = 0; + for (int i = 0; i < directoryNumber; i++) { + count += vaultsPerDirectoryNumber[i]; + } + return vaultsArray; } char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, int shouldDebug) { diff --git a/src/notes.h b/src/notes.h index 6502ce7..7b44eca 100644 --- a/src/notes.h +++ b/src/notes.h @@ -2,6 +2,8 @@ #define NOTES_H #include "utils.h" #include "ui.h" +// find which directory contains targetVault +char *getDirectoryFromVault(char *targetVault, char **vaultsArray, int vaultTotalNumber, int *vaultNumberPerDirectory, char **directoryArray, int directoryNumber, int shouldDebug); // gets the journals from the vault. //to distinguish journals from note we use regex. //Note: we must handle them separatly as journals can either be a single file (which will treat specially) or a directory. @@ -9,8 +11,13 @@ char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, // this function is inputed a path to a vault (which was selected before) and outpus all the suitable notes (so not the hidden ones). // journalRegex is the regex code for the journals. If a note matches this code, it is treated as a journal and it is not outputed from this function. char **getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug); -// this function is inputed a path to a directory (which comes usually from the config file) and outpus all the suitable directories (so not the hidden ones) which will serve as separate vaults for notes. -char **getVaultsFromDirectory(char *dirString, int *count, int shouldDebug); +// function gets as input an array of directoryNumber strings. Which are the directories the function will search for vaults. +// it returns an array of vaults. +// the vaults are organized in order per directory. +// vaultsPerDirectoryNumber and count should be initiallized before calling the function and the address be inputed. +// count is the total number of vaults. +// vaultsPerDirectoryNumber gives how many vaults each directory has. +char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber, int *vaultsPerDirectoryNumber, int *count, int shouldDebug); // path is the path to the file. // journal is the name of the file. // journalWasUpdated will be set to 1 if a new entry was created diff --git a/src/ui.c b/src/ui.c index 50b9bd6..caf2907 100644 --- a/src/ui.c +++ b/src/ui.c @@ -1,9 +1,12 @@ #include "ui.h" +#include "utils.h" -void createNewVault(char *dirToVault, int bypass, char *bypassvalue, int shouldDebug) { +void createNewVault(char **directoriesArray, int directoryCount, char **vaultsArray, int vaultCount, int bypass, char *bypassvalue, int shouldDebug) { int duplicateWarning = 0; // set to 1 later if the vault you tried to create already existed int emptyWarning = 0; // set to 1 later if inpted empty name input_screen: + // choose in which directory the vault will be created + char *dirToVault = ncursesSelect(directoriesArray, "Select a directory, in which the vault will remain (Use arrows or WASD, Enter to select)", directoryCount, 0, "", "", " ", shouldDebug); char *vaultName = malloc(PATH_MAX); if (!bypass) { // if won't bypass (if -v or --vault weren't set) echo(); @@ -48,13 +51,13 @@ input_screen: sanitize(vaultName); debug("Sanitized vaultName=%s", vaultName); - struct stat st = {0}; // https://stackoverflow.com/a/7430262 - char vaultFullPath[PATH_MAX]; - sprintf(vaultFullPath, "%s/%s/", dirToVault, vaultName); - if (stat(vaultFullPath, &st) == -1) { + + if (!isStringInArray(vaultName, (const char**)vaultsArray, vaultCount)) { // if vault doesnt already exists. We avoid vaults with the same name even if they are from different directories. + char vaultFullPath[PATH_MAX]; // recreating the full path + sprintf(vaultFullPath, "%s/%s/", dirToVault, vaultName); + mkdir(vaultFullPath, 0744); } else { - // if stat(...) != -1 it means the vault already exist. We will go back to the input screen with a new message. duplicateWarning = 1; goto input_screen; } @@ -260,7 +263,7 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) { return result; } -char* ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int shouldDebug) { +char* ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int shouldDebug) { // TODO we should see how it handles larges strings (like large directories) int highlight = 0; //curently highlighted option int key; diff --git a/src/ui.h b/src/ui.h index 4ffccb6..f9583fa 100644 --- a/src/ui.h +++ b/src/ui.h @@ -22,11 +22,13 @@ returns the path to the note. If you want to bypass the input TUI set bypass to 1 and bypassvalue to the note name */ char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, char *journalRegex, int debug); -/*Uses ncurses to get an input from the user. +/* +Chooses in which directory it want to create the vault. +Uses ncurses to get an input from the user. Creates a new vault with this input. If vault already exists, prints a warning. returns nothing. */ -void createNewVault(char *dirToVault, int bypass, char *bypassvalue, int debug); +void createNewVault(char **directoriesArray, int directoryCount, char **vaultsArray, int vaultCount, int bypass, char *bypassvalue, int debug); // uses fzf and ripgrep to search inside the files to choose between all the options. char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug); /*this function is used multiple times let the user select one options from many with ncurses in a TUI. diff --git a/src/utils.c b/src/utils.c index 2bf8d9b..83fff70 100644 --- a/src/utils.c +++ b/src/utils.c @@ -136,7 +136,7 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) { debug("Creating default config file."); FILE *w = fopen(config_file, "w"); error(!w, "program", "fopen failed opening %s"); - fprintf(w, + fprintf(w, //TODO CHANGE default json "{\n" " \"directory\": \"~/Documents/Notes/\",\n" " \"render\": true,\n" @@ -156,7 +156,7 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) { fclose(w); } -void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber, const int shouldDebug) { +void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber, const int shouldDebug) { int shouldBackup = 0; time_t now = time(NULL); debug("Time since epoch is %ld", (long)now); @@ -202,7 +202,11 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha } if (shouldBackup) { - copyDir(pathOfVaults, pathOfBackup, rsyncArguments, rsyncArgumentsNumber, shouldDebug); + for (int i = 0; i < sourceNumber; i++) { + if (destinationDirectoryArray[i]) { // if we don't want to backup it, it was set to NULL + copyDir(sourceDirectoryArray[i], destinationDirectoryArray[i], rsyncArguments, rsyncArgumentsNumber, shouldDebug); + } + } } } diff --git a/src/utils.h b/src/utils.h index 08f4141..a805024 100644 --- a/src/utils.h +++ b/src/utils.h @@ -82,6 +82,8 @@ int openEditor(char *path, char *editor, int render, int shouldJumpToEndOfFile, char *getFormatedTime(char *format, int shouldDebug); /* Caclulates if we need to do another backup (by reading ~/.cache/NoteWrapper/backupTime.txt. If need launches in the background rsync to do the backuping. +Each pair of source/destination will launch one rsync process. +If a pair don't need to be backed up, the destination should be set to NULL. rsyncArgs is the array of arguments to be passed to rsync. Do not inclue destination or source. It will be added in the function.*/ -void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug); +void handleBackups(char **sourceDirectoryArray, const int sourceDirectoryNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug); #endif From 0d2ec12263b1e1a0ef56b4d64284af4f7159667f Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 20:50:02 +0200 Subject: [PATCH 05/11] config: add correct "directory" field to default configuration --- src/utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.c b/src/utils.c index 83fff70..18a0fb3 100644 --- a/src/utils.c +++ b/src/utils.c @@ -138,7 +138,7 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) { error(!w, "program", "fopen failed opening %s"); fprintf(w, //TODO CHANGE default json "{\n" -" \"directory\": \"~/Documents/Notes/\",\n" +" \"directory\": [\"~/Documents/Notes/\"],\n" " \"render\": true,\n" " \"jumpToEndOfFileOnLaunch\": true,\n" " \"editor\": \"neovim\",\n" From 68991673c0b14f99f857881a4f29a2f303ab993a Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 21:00:20 +0200 Subject: [PATCH 06/11] fix: expands \~ to the home directory inside "directory" in the config file --- src/main.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index 383de91..dd2f127 100644 --- a/src/main.c +++ b/src/main.c @@ -2,6 +2,7 @@ #include "ui.h" #include "utils.h" #include "notes.h" +#include int main(int argc, char *argv[]) { int shouldDebug = 0; @@ -114,8 +115,16 @@ arg_next: int i = 0; cJSON_ArrayForEach(tempEntry, dirJson) { // iterate over all the elements of the array _i. e._ over all the dirs if (tempEntry && cJSON_IsString(tempEntry)) { - directoriesArray[i] = strdup(cJSON_GetStringValue(tempEntry)); - altDebug("%s\n",directoriesArray[i]); + if (cJSON_GetStringValue(tempEntry)[0] == '~') { // we must expand ~ + char *tempUnFixedName = cJSON_GetStringValue(tempEntry); + tempUnFixedName++; // shifts and removes the ~ + directoriesArray[i] = malloc(PATH_MAX); + debug("~ in %s was expanded to %s", tempUnFixedName, homedir); + snprintf(directoriesArray[i], PATH_MAX, "%s/%s", homedir, tempUnFixedName); + } else { + directoriesArray[i] = strdup(cJSON_GetStringValue(tempEntry)); + altDebug("%s\n",directoriesArray[i]); + } } else { error(1, "user", "In %s, in \"directory\", invalid type of one of the entries.", configPath); } From b7ca3eca149361461467308fd91116fe9e45e00b Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 21:02:54 +0200 Subject: [PATCH 07/11] fix: bug available vaults written to wrong array --- src/notes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notes.c b/src/notes.c index 37fcd99..a1b8b8f 100644 --- a/src/notes.c +++ b/src/notes.c @@ -134,7 +134,7 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber altDebug(" is a vault\n"); vaultsArray = realloc(vaultsArray, sizeof(vaultsArray) + sizeof(char *)); // resize vaultsArray vaultsPerDirectoryNumber[i]++; // it will be used later to know which vaults goes into which directory - directoryStringArray[nthVault] = strdup(entryName); // we use strdup and not strcpy, because memory used with opendir and readdir will be closed. + vaultsArray[nthVault] = strdup(entryName); // we use strdup and not strcpy, because memory used with opendir and readdir will be closed. nthVault++; // it is used immediatly to set the vault into directoryStringArray } else { altDebug(" is not a vault (not a dir.)\n"); From 3233b330dbebbadf7e4c632f1087cf02ec27ccf3 Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 21:04:02 +0200 Subject: [PATCH 08/11] fix: bug wrong realloc size --- src/notes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notes.c b/src/notes.c index a1b8b8f..a35e985 100644 --- a/src/notes.c +++ b/src/notes.c @@ -132,7 +132,7 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber struct stat metadataEntry; if (stat(tempFullEntryPath, &metadataEntry) == 0 && S_ISDIR(metadataEntry.st_mode)) { // get's the metadata (stat should return 0 if fails) and sees if it is a dir. altDebug(" is a vault\n"); - vaultsArray = realloc(vaultsArray, sizeof(vaultsArray) + sizeof(char *)); // resize vaultsArray + vaultsArray = realloc(vaultsArray, + sizeof(char *)*(nthVault + 1)); // resize vaultsArray vaultsPerDirectoryNumber[i]++; // it will be used later to know which vaults goes into which directory vaultsArray[nthVault] = strdup(entryName); // we use strdup and not strcpy, because memory used with opendir and readdir will be closed. nthVault++; // it is used immediatly to set the vault into directoryStringArray From 5c87ac0aa2f86749c2cb14c1c91e6f66540a4581 Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 21:14:17 +0200 Subject: [PATCH 09/11] fix: improved memory safety in getVaultsFromDirectories() --- src/notes.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/notes.c b/src/notes.c index a35e985..7b685ed 100644 --- a/src/notes.c +++ b/src/notes.c @@ -127,14 +127,17 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber altDebug("%s", entryName); if (entryName[0] != '.') { // if not hidden file/dir char tempFullEntryPath[PATH_MAX]; // we recreate the full path to check it's proprieties - snprintf(tempFullEntryPath, PATH_MAX, "%s/%s", directoryStringArray[i], entryName); + snprintf(tempFullEntryPath, PATH_MAX, "%s%s", directoryStringArray[i], entryName); + altDebug(" (%s)", tempFullEntryPath); //checking the metadata to see if it is a dir struct stat metadataEntry; if (stat(tempFullEntryPath, &metadataEntry) == 0 && S_ISDIR(metadataEntry.st_mode)) { // get's the metadata (stat should return 0 if fails) and sees if it is a dir. altDebug(" is a vault\n"); - vaultsArray = realloc(vaultsArray, + sizeof(char *)*(nthVault + 1)); // resize vaultsArray + vaultsArray = realloc(vaultsArray, sizeof(char *)*(nthVault + 1)); // resize vaultsArray + error(vaultsArray == NULL, "program", "realloc failed"); vaultsPerDirectoryNumber[i]++; // it will be used later to know which vaults goes into which directory vaultsArray[nthVault] = strdup(entryName); // we use strdup and not strcpy, because memory used with opendir and readdir will be closed. + error(vaultsArray[nthVault] == NULL, "program", "strdup failed"); nthVault++; // it is used immediatly to set the vault into directoryStringArray } else { altDebug(" is not a vault (not a dir.)\n"); @@ -146,9 +149,9 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber closedir(vaultsDirectory); } // we calculate once the total number of vaults to avoid recalculation every time we use it - count = 0; + *count = 0; for (int i = 0; i < directoryNumber; i++) { - count += vaultsPerDirectoryNumber[i]; + *count += vaultsPerDirectoryNumber[i]; } return vaultsArray; } From 3fb2a32b8342b040ff0310cb4f5e69c671dc0369 Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 21:14:54 +0200 Subject: [PATCH 10/11] Fix: prevent cases where // in paths could broke stat() --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index dd2f127..345f897 100644 --- a/src/main.c +++ b/src/main.c @@ -120,7 +120,7 @@ arg_next: tempUnFixedName++; // shifts and removes the ~ directoriesArray[i] = malloc(PATH_MAX); debug("~ in %s was expanded to %s", tempUnFixedName, homedir); - snprintf(directoriesArray[i], PATH_MAX, "%s/%s", homedir, tempUnFixedName); + snprintf(directoriesArray[i], PATH_MAX, "%s%s", homedir, tempUnFixedName); } else { directoriesArray[i] = strdup(cJSON_GetStringValue(tempEntry)); altDebug("%s\n",directoriesArray[i]); From d5b5d07e6415ded71dd574891bf723eb04168298 Mon Sep 17 00:00:00 2001 From: Tomas Rivera Date: Sun, 26 Apr 2026 21:37:20 +0200 Subject: [PATCH 11/11] fix: multiple small bugs from previous commit --- src/main.c | 1 - src/notes.c | 31 +++++++++++++++++++++---------- src/ui.c | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main.c b/src/main.c index 345f897..ce4c807 100644 --- a/src/main.c +++ b/src/main.c @@ -487,7 +487,6 @@ next_arg: } } - qsort(vaultsArray, vaultsCount, sizeof(const char *), compareString); // sorts the vaults alphabetically debug("┌--------------------------------\nAvailable vaults:"); if (shouldDebug) { for (int i = 0; i < vaultsCount; i++) { diff --git a/src/notes.c b/src/notes.c index 7b685ed..a016d76 100644 --- a/src/notes.c +++ b/src/notes.c @@ -3,17 +3,24 @@ char *getDirectoryFromVault(char *targetVault, char **vaultsArray, int vaultTotalNumber, int *vaultNumberPerDirectory, char **directoryArray, int directoryNumber, int shouldDebug) { debug("Searching the vault %s inside all the directories...", targetVault); - int index = 0; - int startIndex = 0; + debug("Here are how many vaults there is per directory:"); for (int i = 0; i < directoryNumber; i++) { - while (index - startIndex < vaultNumberPerDirectory[i]) { - if (strcmp(vaultsArray[index], targetVault) == 0) { // we found it - debug("%s found in %s. (This was the %dth step of a maximum of %d)", targetVault, directoryArray[i], index, vaultTotalNumber); - return directoryArray[i]; + altDebug("%d for %s\n", vaultNumberPerDirectory[i], directoryArray[i]); + } + debug("Here are all the vaults in the order they will be searched:"); + for (int i = 0; i < vaultTotalNumber; i++) { + altDebug("%s\n", vaultsArray[i]); + } + int index = 0; + + for (int i = 0; i < directoryNumber; i++) { + for (int j = 0; j < vaultNumberPerDirectory[i]; j++) { + if (strcmp(vaultsArray[index], targetVault) == 0) { + debug("%s found in %s. (index %d)", targetVault, directoryArray[i], index); + return directoryArray[i]; + } + index++; } - index++; - } - startIndex += vaultNumberPerDirectory[i]; } error(1, "program", "the vault %s was not found", targetVault); return "this makes the compiler and clangd happy. Who doesn't want GCC and clangd to be happy? Such person would be a terrible monster... One must imagine GCC and clangd happy."; @@ -112,6 +119,7 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber // to avoid having to work with a tree-dimensional array. We will use a 2d and vaultsPerDirectoryNumber will indicate the width of the directories. char **vaultsArray = NULL; int nthVault = 0; // this is only used internally to set the string into the right place in directoryStringArray. + int previousStartIndex = 0; for (int i = 0; i < directoryNumber; i++) { debug("Opening %s", directoryStringArray[i]); // originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ @@ -146,7 +154,10 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber altDebug(" is not a vault (hidden file/dir)\n"); } } + // we sort entries for the same vault. We can't sort them all. This would break the order. + qsort(vaultsArray + previousStartIndex, nthVault - previousStartIndex, sizeof(const char *), compareString); // sorts the vaults alphabetically closedir(vaultsDirectory); + previousStartIndex = nthVault; } // we calculate once the total number of vaults to avoid recalculation every time we use it *count = 0; @@ -167,7 +178,7 @@ char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWas sanitize(dateWithExtension); debug("Sanitized date: %s\n(it might be used later for a file name if the journal is divided)", dateWithExtension); struct stat metadata; - error(stat(path, &metadata), "program", "stat() failed to get information about %s", path); + stat(path, &metadata); if (S_ISREG(metadata.st_mode)) { debug("%s is a unified journal.", path); if (!isStringInFile(path, date, shouldDebug)) { // if there is no entry for current date diff --git a/src/ui.c b/src/ui.c index caf2907..acf7c5d 100644 --- a/src/ui.c +++ b/src/ui.c @@ -112,7 +112,7 @@ char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, c snprintf(fileFullPath, PATH_MAX, "%s/%s/%s/", dirToVault, vaultFromDir, fileName); struct stat st = {0}; if (stat(fileFullPath, &st) == -1) { - error(!mkdir(fileFullPath, 0744), "program", "mkdir failed"); + error(mkdir(fileFullPath, 0744), "program", "mkdir failed"); } else { error(1, "program", "%s could not be created", fileFullPath); }