diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..75a203b --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 100 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c24b0b8..9ad3995 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,10 +10,18 @@ jobs: steps: - uses: actions/checkout@v4 + + - name: Install clang + run: | + sudo apt-get update + sudo apt-get install -y clang-format + - name: Check format + run: | + clang-format --version + find src -name "*.c" -o -name "*.h" | xargs clang-format --dry-run --Werror - name: Install dependencies run: | - sudo apt-get update sudo apt-get install -y \ gcc \ make \ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28a9279..9bf32de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,3 +85,12 @@ Before submitting a pull request, ensure that: * `make`, or * `nix-build` (on NixOS) * There are **no warnings or errors** + +--- + +## Continuous integration + +All PR will need to pass these checks: +1. `clang-format`. Running `find src -name "*.c" -o -name "*.h" | xargs clang-format -i` will automatically apply those rules. +2. The program must build without warning or error. +3. The program must run for five seconds without crashing. diff --git a/makefile b/makefile index 8897536..6c91b0a 100644 --- a/makefile +++ b/makefile @@ -2,7 +2,7 @@ CC := gcc VERSION := $(shell git describe --tags --always --dirty) -CFLAGS := -Wall -Wextra -O2 \ +CFLAGS := -Wall -Wextra -Werror -O2 \ -DVERSION=\"$(VERSION)\" \ $(shell pkg-config --cflags libcjson ncurses) diff --git a/shell.nix b/shell.nix index da68d8c..6af4764 100644 --- a/shell.nix +++ b/shell.nix @@ -8,5 +8,6 @@ pkgs.mkShell { pkgs.gdb pkgs.valgrind pkgs.kdePackages.kcachegrind + pkgs.clang-tools ]; } diff --git a/src/main.c b/src/main.c index b47c59b..35854b4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,41 +1,37 @@ +#include "notes.h" #include "ui.h" #include "utils.h" -#include "notes.h" int main(int argc, char *argv[]) { int shouldDebug = 0; - int overwriteConfigPath = 0; + int overwriteConfigPath = 0; for (int i = 1; i < argc; i++) { - char *arg = argv[i]; - - // Long options - if (strncmp(arg, "--", 2) == 0) { - if (strcmp(arg, "--verbose") == 0) { - shouldDebug = 1; + char *arg = argv[i]; - } else if (strcmp(arg, "--config") == 0) { - error(i + 1 == argc, "user", - "Missing argument. Use --config "); - overwriteConfigPath = ++i; // consume argument + // Long options + if (strncmp(arg, "--", 2) == 0) { + if (strcmp(arg, "--verbose") == 0) { + shouldDebug = 1; - } + } else if (strcmp(arg, "--config") == 0) { + error(i + 1 == argc, "user", "Missing argument. Use --config "); + overwriteConfigPath = ++i; // consume argument + } - // Short options (allow grouping like -V) - } else if (arg[0] == '-' && arg[1] != '\0') { - for (int j = 1; arg[j] != '\0'; j++) { - char opt = arg[j]; + // Short options (allow grouping like -V) + } else if (arg[0] == '-' && arg[1] != '\0') { + for (int j = 1; arg[j] != '\0'; j++) { + char opt = arg[j]; - switch (opt) { + switch (opt) { case 'V': shouldDebug = 1; break; case 'c': // must NOT be combined (needs argument) - error(arg[j+1] != '\0', "user", - "-c cannot be combined (use -c )"); - error(i + 1 == argc, "user", - "Missing argument for -c"); + error(arg[j + 1] != '\0', "user", "-c cannot be combined (use -c )"); + error(i + 1 == argc, "user", "Missing argument for -c"); overwriteConfigPath = ++i; // consume argument goto arg_next; @@ -43,53 +39,58 @@ int main(int argc, char *argv[]) { default: // ignore unknown here OR handle error break; + } } } - } -arg_next: - ; -} + arg_next:; + } // gets the home directory struct passwd *pw = getpwuid(getuid()); const char *homedir = pw->pw_dir; - + initAppFilesAndDirs(homedir, shouldDebug); //--------------------------------------------------------------------------------------------- - //--------------------------------------------------------------------------------------------- + //--------------------------------------------------------------------------------------------- // this part handles the config.json file // gets the home directory char *configPath = malloc(PATH_MAX); if (overwriteConfigPath) { - configPath = argv[overwriteConfigPath]; - debug("-c or --config specified the config file %s", configPath); + configPath = argv[overwriteConfigPath]; + debug("-c or --config specified the config file %s", configPath); } else { - snprintf(configPath, PATH_MAX, "%s/.config/notewrapper/config.json", homedir); - debug("Path to the config file is %s", configPath); + snprintf(configPath, PATH_MAX, "%s/.config/notewrapper/config.json", homedir); + debug("Path to the config file is %s", configPath); } // check if the config file exists struct stat st = {0}; - error(stat(configPath, &st) == -1, "user", "The config file %s does not exist.\nMaybe try the default path to the config ~/.config/notewrapper/config.json\nOr if you used the flag -c or --config, verifiy that you point to the correct file.", configPath); // if the config directory does not exist + error(stat(configPath, &st) == -1, "user", + "The config file %s does not exist.\nMaybe try the default path to the config " + "~/.config/notewrapper/config.json\nOr if you used the flag -c or --config, verifiy that " + "you point to the correct file.", + configPath); // if the config directory does not exist // opens config.json FILE *f = fopen(configPath, "r"); error(!f, "program", "The config file does exist, but can not be open."); // loads and read the config file - //gets the size + // gets the size fseek(f, 0, SEEK_END); size_t size = ftell(f); rewind(f); - //gets the data - char *data = malloc(size+1); + // gets the data + char *data = malloc(size + 1); error(!data, "program", "malloc failed allocating memory for the variable data."); size_t readBytes = fread(data, 1, size, f); // 1 --> size of each item - - if (readBytes!=size) { - free(data); - fclose(f); + + if (readBytes != size) { + free(data); + fclose(f); } - error(readBytes!=size, "program", "Failed to read config file (%s) (%zu bytes read, expected %ld)", configPath, readBytes, size); + error(readBytes != size, "program", + "Failed to read config file (%s) (%zu bytes read, expected %ld)", configPath, readBytes, + size); data[size] = '\0'; fclose(f); @@ -97,36 +98,42 @@ int main(int argc, char *argv[]) { debug("Parsing the JSON config"); cJSON *json = cJSON_Parse(data); - if (!json) {free(data);} + 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"); - error(dirJson && !cJSON_IsArray(dirJson), "user", "In %s, \"directory\" is missing or isn't an array", configPath); + 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*)); + 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)) { - 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); + cJSON_ArrayForEach( + tempEntry, + dirJson) { // iterate over all the elements of the array _i. e._ over all the dirs + if (tempEntry && cJSON_IsString(tempEntry)) { + 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 { - directoriesArray[i] = strdup(cJSON_GetStringValue(tempEntry)); - altDebug("%s\n",directoriesArray[i]); + error(1, "user", "In %s, in \"directory\", invalid type of one of the entries.", + configPath); } - } else { - error(1, "user", "In %s, in \"directory\", invalid type of one of the entries.", configPath); - } - i++; + i++; } // fetch the render and jumpToEnfOfFileOnLaunch bools @@ -138,237 +145,299 @@ int main(int argc, char *argv[]) { } else { debug("In %s, \"render\" wasn't set. Defaulting to true.", configPath); } - + int shouldJumpToEnd = 1; cJSON *shouldJumpToEndJSON = cJSON_GetObjectItem(json, "jumpToEndOfFileOnLaunch"); if (shouldJumpToEndJSON && cJSON_IsBool(shouldJumpToEndJSON)) { - shouldJumpToEnd = cJSON_IsTrue(shouldJumpToEndJSON) ? 1 : 0; - debug("In %s, \"jumpToEndOfFileOnLaunch\" was set to %d.", configPath, shouldJumpToEnd); + shouldJumpToEnd = cJSON_IsTrue(shouldJumpToEndJSON) ? 1 : 0; + debug("In %s, \"jumpToEndOfFileOnLaunch\" was set to %d.", configPath, shouldJumpToEnd); } else { - debug("In %s, \"jumpToEndOfFileOnLaunch\" wasn't set or we encountered a abnormal type. Defaulting to true.", configPath); + debug("In %s, \"jumpToEndOfFileOnLaunch\" wasn't set or we encountered a abnormal type. " + "Defaulting to true.", + configPath); } - + char *editorToOpen = getenv("EDITOR"); // default to $EDITOR - int defaultEditor = 1; // this will be used in the warning if the editor does not exists or is unsupported. + int defaultEditor = + 1; // this will be used in the warning if the editor does not exists or is unsupported. cJSON *editorToOpenJSON = cJSON_GetObjectItem(json, "editor"); if (editorToOpenJSON && cJSON_IsString(editorToOpenJSON)) { - editorToOpen = strdup(cJSON_GetStringValue(editorToOpenJSON)); // we must strdup and not just = as we will free all the json after (before parsing args) - defaultEditor = 0; - debug("In %s, \"editor\" was set to %s.", configPath, editorToOpen); - //error(!isStringInArray(editorToOpen, supportedEditor, numEditors), "user", "%s (fetched from config.json) is not a supported editor.", editorToOpen); // we check if editor is supported at the end + editorToOpen = strdup(cJSON_GetStringValue( + editorToOpenJSON)); // we must strdup and not just = as we will free all the json after + // (before parsing args) + defaultEditor = 0; + debug("In %s, \"editor\" was set to %s.", configPath, editorToOpen); + // error(!isStringInArray(editorToOpen, supportedEditor, numEditors), "user", "%s (fetched + // from config.json) is not a supported editor.", editorToOpen); // we check if editor is + // supported at the end } else { - debug("In %s, \"editor\" wasn't set or we encountered a abnormal type. Defaulting to $EDITOR (%s).\n P.S. this might still be overwritten by -e or --editor.", configPath, editorToOpen); + debug("In %s, \"editor\" wasn't set or we encountered a abnormal type. Defaulting to " + "$EDITOR (%s).\n P.S. this might still be overwritten by -e or --editor.", + configPath, editorToOpen); } - + cJSON *journalRegexJSON = cJSON_GetObjectItem(json, "journalRegex"); char *journalRegex = ".*journal.*"; // default regex pattern for the journal if (journalRegexJSON && cJSON_IsString(journalRegexJSON)) { - journalRegex = strdup(cJSON_GetStringValue(journalRegexJSON)); - debug("In %s, \"journalRegex\" was set to %s.", configPath, journalRegex); + journalRegex = strdup(cJSON_GetStringValue(journalRegexJSON)); + debug("In %s, \"journalRegex\" was set to %s.", configPath, journalRegex); } else { - debug("In %s, \"journalRegex\" wasn't set or we encountered a abnormal type. Defaulting to %s.", configPath, journalRegex); + debug("In %s, \"journalRegex\" wasn't set or we encountered a abnormal type. Defaulting to " + "%s.", + configPath, journalRegex); } - char *timeFormat = "# \%Y \%m \%d \%a";// default + char *timeFormat = "# \%Y \%m \%d \%a"; // default cJSON *timeFormatJSON = cJSON_GetObjectItem(json, "dateEntry"); if (timeFormatJSON && cJSON_IsString(timeFormatJSON)) { - timeFormat = strdup(cJSON_GetStringValue(timeFormatJSON)); - debug("In %s, \"dateEntry\" was set to %s.", configPath, timeFormat); + timeFormat = strdup(cJSON_GetStringValue(timeFormatJSON)); + debug("In %s, \"dateEntry\" was set to %s.", configPath, timeFormat); } else { - debug("In %s, \"dateEntry\" wasn't set or we encountered a abnormal type. Defaulting to %s.", configPath, timeFormat); + debug( + "In %s, \"dateEntry\" wasn't set or we encountered a abnormal type. Defaulting to %s.", + configPath, timeFormat); } int newLineOnOpening = 1; cJSON *newLineOnOpeningJSON = cJSON_GetObjectItem(json, "newLineOnOpening"); if (newLineOnOpeningJSON && cJSON_IsBool(newLineOnOpeningJSON)) { - debug("The value for newLineOnOpening in config.json is %d", cJSON_IsTrue(newLineOnOpeningJSON)); + debug("The value for newLineOnOpening in config.json is %d", + cJSON_IsTrue(newLineOnOpeningJSON)); - newLineOnOpening = cJSON_IsTrue(newLineOnOpeningJSON) ? 1 : 0; - debug("In %s, \"newLineOnOpening\" was set to %d.", configPath, newLineOnOpening); + newLineOnOpening = cJSON_IsTrue(newLineOnOpeningJSON) ? 1 : 0; + debug("In %s, \"newLineOnOpening\" was set to %d.", configPath, newLineOnOpening); } else { - debug("In %s, \"newLineOnOpening\" wasn't set or we encountered a abnormal type. Defaulting to true.", configPath); + debug("In %s, \"newLineOnOpening\" wasn't set or we encountered a abnormal type. " + "Defaulting to true.", + configPath); } int doesBackup = 0; - int interval = 0; // this is an int. But some times it will be inputed a string. We must translate it. - // 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*)); + int interval = + 0; // this is an int. But some times it will be inputed a string. We must translate it. + // 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"); if (backupJSON && cJSON_IsObject(backupJSON)) { - cJSON *doesBackupJSON = cJSON_GetObjectItem(backupJSON, "enable"); - if (doesBackupJSON && cJSON_IsBool(doesBackupJSON)) { - doesBackup = cJSON_IsTrue(doesBackupJSON) ? 1 : 0; - debug("doesBackup is set to %d", doesBackup); - if (!doesBackup) { - debug("a backup is not needed. Skipping parsing the rest of the json"); - goto backup_config_end; // easier to just go to rather than do a big if statement - } + cJSON *doesBackupJSON = cJSON_GetObjectItem(backupJSON, "enable"); + if (doesBackupJSON && cJSON_IsBool(doesBackupJSON)) { + doesBackup = cJSON_IsTrue(doesBackupJSON) ? 1 : 0; + debug("doesBackup is set to %d", doesBackup); + if (!doesBackup) { + debug("a backup is not needed. Skipping parsing the rest of the json"); + 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"); - debug("%s", cJSON_Print(pathToBackupJSON)); - error(pathToBackupJSON == NULL && !(cJSON_IsObject(pathToBackupJSON) || cJSON_IsArray(pathToBackupJSON) || cJSON_IsString(pathToBackupJSON) || cJSON_IsNumber(pathToBackupJSON) || cJSON_IsBool(pathToBackupJSON)), "user", "In %s, incorrect type for \"directory\". It must be a JSON object.", configPath); // for some reason cJSON_IsObject does not work. So we must do it like this. - - // we can't just iterate over directoriesArray as we replaced ~ to $HOME - // we must iterate over the json entries - cJSON *directoryEntry = NULL; - int i = 0; - cJSON_ArrayForEach(directoryEntry, dirJson) { // iterate over all the elements of the array _i. e._ over all the dirs - // we don't need to type error handle as we did it before - char *directoryEntryPath = cJSON_GetStringValue(directoryEntry); - cJSON *pathToBackupForIthDirectoryJSON = cJSON_GetObjectItem(pathToBackupJSON, directoryEntryPath); - if (pathToBackupForIthDirectoryJSON) { // if the entry exist - char *textPath = cJSON_GetStringValue(pathToBackupForIthDirectoryJSON); - if (textPath[0] == '~') { - textPath++; //cuts the ~ - backupDirectoriesArray[i] = malloc(PATH_MAX); - error(backupDirectoriesArray[i] == NULL, "program", "malloc failed"); - snprintf(backupDirectoriesArray[i], PATH_MAX, "%s%s", homedir, textPath); + // handles the path to the backup for each directory + cJSON *pathToBackupJSON = cJSON_GetObjectItem(backupJSON, "directory"); + debug("%s", cJSON_Print(pathToBackupJSON)); + error(pathToBackupJSON == NULL && + !(cJSON_IsObject(pathToBackupJSON) || cJSON_IsArray(pathToBackupJSON) || + cJSON_IsString(pathToBackupJSON) || cJSON_IsNumber(pathToBackupJSON) || + cJSON_IsBool(pathToBackupJSON)), + "user", "In %s, incorrect type for \"directory\". It must be a JSON object.", + configPath); // for some reason cJSON_IsObject does not work. So we must do it + // like this. + + // we can't just iterate over directoriesArray as we replaced ~ to $HOME + // we must iterate over the json entries + cJSON *directoryEntry = NULL; + int i = 0; + cJSON_ArrayForEach( + directoryEntry, + dirJson) { // iterate over all the elements of the array _i. e._ over all the dirs + // we don't need to type error handle as we did it before + char *directoryEntryPath = cJSON_GetStringValue(directoryEntry); + cJSON *pathToBackupForIthDirectoryJSON = + cJSON_GetObjectItem(pathToBackupJSON, directoryEntryPath); + if (pathToBackupForIthDirectoryJSON) { // if the entry exist + char *textPath = cJSON_GetStringValue(pathToBackupForIthDirectoryJSON); + if (textPath[0] == '~') { + textPath++; // cuts the ~ + backupDirectoriesArray[i] = malloc(PATH_MAX); + error(backupDirectoriesArray[i] == NULL, "program", "malloc failed"); + snprintf(backupDirectoriesArray[i], PATH_MAX, "%s%s", homedir, textPath); + } else { + backupDirectoriesArray[i] = strdup(textPath); + } + debug("%s will be backed up to %s", directoriesArray[i], + backupDirectoriesArray[i]); + } else { // we set it to NULL to be sure we won't backup it + backupDirectoriesArray[i] = NULL; + debug("%s won't be backed up", directoriesArray[i]); + } + i++; + } + + // handles the interval of backup + cJSON *intervalJSON = cJSON_GetObjectItem(backupJSON, "interval"); + if (intervalJSON && cJSON_IsString(intervalJSON)) { + char *temp = cJSON_GetStringValue( + intervalJSON); // se comment higher. temp will be freed when calling free(json) + if (strcmp(temp, "daily") == 0) { + interval = DAILY; + debug("interval in %s is set to \"daily\" which is %d", configPath, DAILY); + } else if (strcmp(temp, "weekly") == 0) { + interval = WEEKLY; + debug("interval in %s is set to \"weekly\" which is %d", configPath, WEEKLY); + } else if (strcmp(temp, "monthly") == 0) { + interval = MONTHLY; + debug("interval in %s is set to \"monthly\" which is %d", configPath, MONTHLY); + } else { + error(1, "user", + "Unexpected string %s for entry \"interval\" in %s. You must put an int " + "(number of seconds) or \"daily\" or \"weekly\" or \"monthly\".", + intervalJSON->valuestring, configPath); + } + } else if (intervalJSON && cJSON_IsNumber(intervalJSON)) { + interval = (int)cJSON_GetNumberValue(intervalJSON); + debug("interval in %s is set to %d", configPath, interval); } else { - backupDirectoriesArray[i] = strdup(textPath); + error(1, "user", + "%s did not contained an interval value inside the backup section or the " + "value is from an unexpected type", + configPath); } - debug("%s will be backed up to %s", directoriesArray[i], backupDirectoriesArray[i]); - } else { // we set it to NULL to be sure we won't backup it - backupDirectoriesArray[i] = NULL; - debug("%s won't be backed up", directoriesArray[i]); - } - i++; + } else { + error(1, "user", + "%s did not contained a enable value 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)) { - char *temp = cJSON_GetStringValue(intervalJSON); // se comment higher. temp will be freed when calling free(json) - if (strcmp(temp, "daily") == 0) { - interval = DAILY; - debug("interval in %s is set to \"daily\" which is %d", configPath, DAILY); - } else if (strcmp(temp, "weekly") == 0) { - interval = WEEKLY; - debug("interval in %s is set to \"weekly\" which is %d", configPath, WEEKLY); - } else if (strcmp(temp, "monthly") == 0) { - interval = MONTHLY; - debug("interval in %s is set to \"monthly\" which is %d", configPath, MONTHLY); - } else {error(1, "user", "Unexpected string %s for entry \"interval\" in %s. You must put an int (number of seconds) or \"daily\" or \"weekly\" or \"monthly\".", intervalJSON->valuestring, configPath);} - } else if (intervalJSON && cJSON_IsNumber(intervalJSON)) { - interval = (int)cJSON_GetNumberValue(intervalJSON); - debug("interval in %s is set to %d", configPath, interval); - } else {error(1, "user", "%s did not contained an interval value inside the backup section or the value is from an unexpected type", configPath);} - } else{error(1, "user", "%s did not contained a enable value inside the backup section or the value is from an unexpected type", configPath);} - // handle rsyncs array of arguments. - cJSON *rsyncArgsJSON = cJSON_GetObjectItem(backupJSON, "rsyncArgs"); - if (rsyncArgsJSON && cJSON_IsArray(rsyncArgsJSON)) { // if it is what we expected - rsyncArgsNumber = cJSON_GetArraySize(rsyncArgsJSON); - debug("In %s, rsyncArgsNumber is calculated to %d.", configPath, rsyncArgsNumber); - rsyncArgs = realloc(rsyncArgs, (size_t)rsyncArgsNumber); - debug("In %c, rsyncArgs are:", configPath); - for (int i = 0; i < rsyncArgsNumber; i++) { - cJSON *argJSON = cJSON_GetArrayItem(rsyncArgsJSON, i); - if (argJSON && cJSON_IsString(argJSON)) { - rsyncArgs[i] = strdup(cJSON_GetStringValue(argJSON)); - altDebug("%s\n", rsyncArgs[i]); - } else {error(1, "user", "One element in rsyncArgs array in %s is not a string", configPath);} + // handle rsyncs array of arguments. + cJSON *rsyncArgsJSON = cJSON_GetObjectItem(backupJSON, "rsyncArgs"); + if (rsyncArgsJSON && cJSON_IsArray(rsyncArgsJSON)) { // if it is what we expected + rsyncArgsNumber = cJSON_GetArraySize(rsyncArgsJSON); + debug("In %s, rsyncArgsNumber is calculated to %d.", configPath, rsyncArgsNumber); + rsyncArgs = realloc(rsyncArgs, (size_t)rsyncArgsNumber); + debug("In %c, rsyncArgs are:", configPath); + for (int i = 0; i < rsyncArgsNumber; i++) { + cJSON *argJSON = cJSON_GetArrayItem(rsyncArgsJSON, i); + if (argJSON && cJSON_IsString(argJSON)) { + rsyncArgs[i] = strdup(cJSON_GetStringValue(argJSON)); + altDebug("%s\n", rsyncArgs[i]); + } else { + error(1, "user", "One element in rsyncArgs array in %s is not a string", + configPath); + } + } + } else { + error(1, "user", + "%s did not contained a rsyncArgs array inside the backup section or the value " + "is from an unexpected type", + configPath); } - } else {error(1, "user", "%s did not contained a rsyncArgs array inside the backup section or the value is from an unexpected type", configPath);} } else { - debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to {\"enable\": false}.", configPath); + debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to " + "{\"enable\": false}.", + configPath); } -backup_config_end: - - - //cleans up +backup_config_end: + + // cleans up cJSON_Delete(json); free(data); debug("Finished parsing the JSON config"); //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- - + // flags and arguments overwrite the config - debug("Parsing the attribute flags.\n This flags might overwrite the options in the config file."); - // thoses bypasses are used if some specific flags are passed as args. This allows to bypass the TUI selectors + debug("Parsing the attribute flags.\n This flags might overwrite the options in the config " + "file."); + // thoses bypasses are used if some specific flags are passed as args. This allows to bypass the + // TUI selectors int bypassSelectionVault = 0; char *bypassSelectionVaultValue = NULL; int bypassSelectionNote = 0; char *bypassSelectionNoteValue = NULL; -for (int i = 1; i < argc; i++) { - char *arg = argv[i]; - - if (strncmp(arg, "--", 2) == 0) { - - if (strcmp(arg, "--verbose") == 0) { - shouldDebug = 1; - debug("--verbose enabled"); - - } else if (strcmp(arg, "--config") == 0) { - error(i + 1 == argc, "user", "Missing argument for --config"); - overwriteConfigPath = ++i; - debug("--config set to %s", argv[i]); - - } else if (strcmp(arg, "--editor") == 0) { - error(i + 1 == argc, "user", "Missing argument for --editor"); - editorToOpen = argv[++i]; - defaultEditor = 0; - debug("--editor set to %s", editorToOpen); - - } else if (strcmp(arg, "--note") == 0) { - error(i + 1 == argc, "user", "Missing argument for --note"); - bypassSelectionNote = 1; - bypassSelectionNoteValue = argv[++i]; - debug("--note set to %s", bypassSelectionNoteValue); - - } else if (strcmp(arg, "--vault") == 0) { - error(i + 1 == argc, "user", "Missing argument for --vault"); - bypassSelectionVault = 1; - bypassSelectionVaultValue = argv[++i]; - debug("--vault set to %s", bypassSelectionVaultValue); - - } else if (strcmp(arg, "--render") == 0) { - shouldRender = 1; - debug("--render enabled"); - - } else if (strcmp(arg, "--no-render") == 0) { - shouldRender = 0; - debug("--render disabled"); - - } else if (strcmp(arg, "--jump") == 0) { - shouldJumpToEnd = 1; - debug("--jump enabled"); - - } else if (strcmp(arg, "--no-jump") == 0) { - shouldJumpToEnd = 0; - debug("--jump disabled"); - - } else if (strcmp(arg, "--help") == 0) { - printf("Usage: notewrapper [options]\n"); - printf("Options:\n"); - printf(" -c, --config Specify the config file.\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"); - printf(" -J, --no-jump Do not jump to the end of the file\n"); - printf(" -n, --note Specify the note (or journal).\n"); - printf(" -r, --render Renders the note with Vivify.\n"); - printf(" -R, --no-render Do not render.\n"); - printf(" -v, --vault Specify the vault.\n"); - printf(" --version Display the program version and the GPL3 notice.\n"); - printf(" -V, --verbose Show debug information.\n"); - return 0; - - } else if (strcmp(arg, "--version") == 0) { - printf("NoteWrapper %s\n", VERSION); - return 0; + for (int i = 1; i < argc; i++) { + char *arg = argv[i]; + + if (strncmp(arg, "--", 2) == 0) { + + if (strcmp(arg, "--verbose") == 0) { + shouldDebug = 1; + debug("--verbose enabled"); + + } else if (strcmp(arg, "--config") == 0) { + error(i + 1 == argc, "user", "Missing argument for --config"); + overwriteConfigPath = ++i; + debug("--config set to %s", argv[i]); + + } else if (strcmp(arg, "--editor") == 0) { + error(i + 1 == argc, "user", "Missing argument for --editor"); + editorToOpen = argv[++i]; + defaultEditor = 0; + debug("--editor set to %s", editorToOpen); + + } else if (strcmp(arg, "--note") == 0) { + error(i + 1 == argc, "user", "Missing argument for --note"); + bypassSelectionNote = 1; + bypassSelectionNoteValue = argv[++i]; + debug("--note set to %s", bypassSelectionNoteValue); + + } else if (strcmp(arg, "--vault") == 0) { + error(i + 1 == argc, "user", "Missing argument for --vault"); + bypassSelectionVault = 1; + bypassSelectionVaultValue = argv[++i]; + debug("--vault set to %s", bypassSelectionVaultValue); + + } else if (strcmp(arg, "--render") == 0) { + shouldRender = 1; + debug("--render enabled"); + + } else if (strcmp(arg, "--no-render") == 0) { + shouldRender = 0; + debug("--render disabled"); + + } else if (strcmp(arg, "--jump") == 0) { + shouldJumpToEnd = 1; + debug("--jump enabled"); + + } else if (strcmp(arg, "--no-jump") == 0) { + shouldJumpToEnd = 0; + debug("--jump disabled"); + + } else if (strcmp(arg, "--help") == 0) { + printf("Usage: notewrapper [options]\n"); + printf("Options:\n"); + printf(" -c, --config Specify the config file.\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"); + printf(" -J, --no-jump Do not jump to the end of " + "the file\n"); + printf(" -n, --note Specify the note (or " + "journal).\n"); + printf(" -r, --render Renders the note with " + "Vivify.\n"); + printf(" -R, --no-render Do not render.\n"); + printf(" -v, --vault Specify the vault.\n"); + printf(" --version Display the program version " + "and the GPL3 notice.\n"); + printf(" -V, --verbose Show debug information.\n"); + return 0; + + } else if (strcmp(arg, "--version") == 0) { + printf("NoteWrapper %s\n", VERSION); + return 0; - } else { - error(1, "user", "Unknown option \"%s\"", arg); - } + } else { + error(1, "user", "Unknown option \"%s\"", arg); + } - } else if (arg[0] == '-' && arg[1] != '\0') { + } else if (arg[0] == '-' && arg[1] != '\0') { - for (int j = 1; arg[j] != '\0'; j++) { - char opt = arg[j]; + for (int j = 1; arg[j] != '\0'; j++) { + char opt = arg[j]; - switch (opt) { + switch (opt) { // -------- flags without arguments (can be combined) -------- case 'r': @@ -399,17 +468,25 @@ for (int i = 1; i < argc; i++) { case 'h': printf("Usage: notewrapper [options]\n"); printf("Options:\n"); - printf(" -c, --config Specify the config file.\n"); + printf( + " -c, --config Specify the config file.\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"); - printf(" -J, --no-jump Do not jump to the end of the file\n"); - printf(" -n, --note Specify the note (or journal).\n"); - printf(" -r, --render Renders the note with Vivify.\n"); + printf(" -e, --editor Specify the editor to " + "open.\n"); + printf(" -j, --jump Jumps to the end of the " + "file on opening.\n"); + printf(" -J, --no-jump Do not jump to the end " + "of the file\n"); + printf(" -n, --note Specify the note (or " + "journal).\n"); + printf(" -r, --render Renders the note with " + "Vivify.\n"); printf(" -R, --no-render Do not render.\n"); printf(" -v, --vault Specify the vault.\n"); - printf(" --version Display the program version and the GPL3 notice.\n"); - printf(" -V, --verbose Show debug information.\n"); + printf(" --version Display the program " + "version and the GPL3 notice.\n"); + printf( + " -V, --verbose Show debug information.\n"); return 0; // -------- flags with arguments (MUST be last in group) -------- @@ -418,39 +495,36 @@ for (int i = 1; i < argc; i++) { case 'v': case 'c': { - error(arg[j + 1] != '\0', - "user", - "-%c must not be combined with other flags", opt); + error(arg[j + 1] != '\0', "user", "-%c must not be combined with other flags", + opt); - error(i + 1 == argc, - "user", - "Missing argument for -%c", opt); + error(i + 1 == argc, "user", "Missing argument for -%c", opt); char *value = argv[++i]; switch (opt) { - case 'e': - editorToOpen = value; - defaultEditor = 0; - debug("-e set editor to %s", value); - break; - - case 'n': - bypassSelectionNote = 1; - bypassSelectionNoteValue = value; - debug("-n set note to %s", value); - break; - - case 'v': - bypassSelectionVault = 1; - bypassSelectionVaultValue = value; - debug("-v set vault to %s", value); - break; - - case 'c': - overwriteConfigPath = i; - debug("-c set config to %s", value); - break; + case 'e': + editorToOpen = value; + defaultEditor = 0; + debug("-e set editor to %s", value); + break; + + case 'n': + bypassSelectionNote = 1; + bypassSelectionNoteValue = value; + debug("-n set note to %s", value); + break; + + case 'v': + bypassSelectionVault = 1; + bypassSelectionVaultValue = value; + debug("-v set vault to %s", value); + break; + + case 'c': + overwriteConfigPath = i; + debug("-c set config to %s", value); + break; } goto next_arg; @@ -458,200 +532,264 @@ for (int i = 1; i < argc; i++) { default: error(1, "user", "Unknown option -%c", opt); + } } + + } else { + error(1, "user", "Unexpected argument \"%s\"", arg); } - } else { - error(1, "user", "Unexpected argument \"%s\"", arg); + next_arg:; } - -next_arg: - ; -} // if -n or --note is set but not -v or --vaults it gives an error - error(!bypassSelectionVault && bypassSelectionNote, "user", "If you want to specify the note, you must also specify the vault with -v or --vault ."); + error(!bypassSelectionVault && bypassSelectionNote, "user", + "If you want to specify the note, you must also specify the vault with -v " + "or --vault ."); debug("Finished parsing the attribute flags"); - isEditorValid(editorToOpen, defaultEditor, shouldDebug); // check if editor is supported and if it is installed. If not, it will throw an error. + isEditorValid(editorToOpen, defaultEditor, + shouldDebug); // check if editor is supported and if it is installed. If not, it + // will throw an error. if (doesBackup) { - debug("Handling backup"); - handleBackups(directoriesArray, numDirectories, backupDirectoriesArray, homedir, interval, (const char**)rsyncArgs, rsyncArgsNumber, shouldDebug); + debug("Handling backup"); + handleBackups(directoriesArray, numDirectories, backupDirectoriesArray, homedir, interval, + (const char **)rsyncArgs, rsyncArgsNumber, shouldDebug); } - - initscr(); //initialize ncurses + + initscr(); // initialize ncurses int shouldExit = 0; - while(!shouldExit) { - // this loop is the vault selector - // select a vault - char *vaultSelected = NULL; - - int vaultsCount = 0; - 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) { - // bypasses the vault selection if the flag is -v. If the vault doesn't exist, just create a new one - vaultSelected = bypassSelectionVaultValue; - debug("bypassing vault selection"); - if (!isStringInArray(bypassSelectionVaultValue, (const char **)vaultsArray, vaultsCount)) { // we pass just vaultsCount and not vaultsCount + extraOptions to avoid matching with the extraOptions. - debug("[BYPASS] %s did not exist. Creating a new vault"); - goto vault_creation; - } else { - goto note_selection; - debug("[BYPASS] %s does exist. Going to note selection"); + while (!shouldExit) { + // this loop is the vault selector + // select a vault + char *vaultSelected = NULL; + + int vaultsCount = 0; + 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) { + // bypasses the vault selection if the flag is -v. If the vault doesn't exist, just + // create a new one + vaultSelected = bypassSelectionVaultValue; + debug("bypassing vault selection"); + if (!isStringInArray( + bypassSelectionVaultValue, (const char **)vaultsArray, + vaultsCount)) { // we pass just vaultsCount and not vaultsCount + extraOptions + // to avoid matching with the extraOptions. + debug("[BYPASS] %s did not exist. Creating a new vault"); + goto vault_creation; + } else { + goto note_selection; + debug("[BYPASS] %s does exist. Going to note selection"); + } + } + debug("┌--------------------------------\nAvailable vaults:"); + if (shouldDebug) { + for (int i = 0; i < vaultsCount; i++) { + altDebug("%s\n", vaultsArray[i]); + } + altDebug("└ ------------------------------\n"); } - } - debug("┌--------------------------------\nAvailable vaults:"); - if (shouldDebug) { + // adds "create a new vault" into the vaultsArray + const int extraOptions = 3; + vaultsArray = + realloc(vaultsArray, (vaultsCount + extraOptions) * + sizeof(char *)); // resize vaultsArray to fit the extra options + vaultsArray[vaultsCount] = "Create a new vault"; // some more options that are not vaults + vaultsArray[vaultsCount + 1] = "Settings"; + vaultsArray[vaultsCount + 2] = "Quit (Ctrl+C)"; + + vaultSelected = ncursesSelect( + vaultsArray, "Select vault to open (Use arrows or WASD, Enter to select):", vaultsCount, + extraOptions, " ", "Or select an option below", "", shouldDebug); + + // now that we won't use vaultsArray in this iteration of the loop, we should free it and + // all its elements. (As this is memory in the heap and not the stack and thus is our + // responsability to manage) for (int i = 0; i < vaultsCount; i++) { - altDebug("%s\n", vaultsArray[i]); - } - altDebug("└ ------------------------------\n"); - } - - // adds "create a new vault" into the vaultsArray - const int extraOptions = 3; - vaultsArray = realloc(vaultsArray, (vaultsCount + extraOptions)*sizeof(char*)); // resize vaultsArray to fit the extra options - vaultsArray[vaultsCount] = "Create a new vault"; // some more options that are not vaults - vaultsArray[vaultsCount+1] = "Settings"; - vaultsArray[vaultsCount+2] = "Quit (Ctrl+C)"; - - - vaultSelected = ncursesSelect(vaultsArray, "Select vault to open (Use arrows or WASD, Enter to select):", vaultsCount, extraOptions, " ", "Or select an option below", "", shouldDebug); - - // now that we won't use vaultsArray in this iteration of the loop, we should free it and all its elements. (As this is memory in the heap and not the stack and thus is our responsability to manage) - for (int i = 0; i < vaultsCount; i++) { - if (vaultSelected != vaultsArray[i]) { // i forgot this condition before. and freed the pointer equal to vaultSelected... So don't remove this condition - free(vaultsArray[i]); // we must only free the vaults options and not the extraOptions to avoid segfault - } - } - - 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; - char **filesArray = getNotesFromVault(notesDirectoryString, vaultSelected, journalRegex, &filesCount, shouldDebug); - - int journalCount = 0; - char **journalArray = getJournalsFromVault(notesDirectoryString, vaultSelected, journalRegex, &journalCount, shouldDebug); - - // appends the journal at the end of filesArray - filesArray = realloc(filesArray, (filesCount + journalCount)*sizeof(char*)); - for (int i = 0; i < journalCount; i++) { - filesArray[i + filesCount] = journalArray[i]; - } - filesCount = filesCount + journalCount; - - qsort(filesArray, filesCount, sizeof(const char *), compareString); - debug("Available notes and journals:"); - if (shouldDebug) { - for (int i = 0; i < filesCount; i++) { - altDebug("%s\n", filesArray[i]); - } - altDebug("└------------------------------\n"); - } - // adds options - int extraNotesOptions = 4; - filesArray = realloc(filesArray, (filesCount + extraNotesOptions)*sizeof(char*)); // resize filesArray to fit the extra options - filesArray[filesCount] = "Create new note"; - filesArray[filesCount+1] = "Back to vault selection"; - filesArray[filesCount+2] = "Delete vault"; - filesArray[filesCount+3] = "Quit (Ctrl+C)"; - char *noteSelected; - // if we set to bypass the note selector - if (bypassSelectionNote) { - debug("We are bypassing note selection."); - noteSelected = bypassSelectionNoteValue; - if (isStringInArray(noteSelected, (const char **)filesArray, filesCount)) {// we just give filesCount and not filesCount + extraOptions to avoid matching with an extra options. - debug("The note specified with -n or --note does exist. Opening it."); - goto open_note; - } else { // if the specified note doesn't exist. We creat it - debug("The note specified with -n or --note doesn't exist. Creating it."); - goto note_creation; - } - } - noteSelected = ncursesSelect(filesArray, "Select note or journal to open (Use arrows or WASD, Enter to select):", filesCount, extraNotesOptions, " ", "Or select an option below", "", shouldDebug); - // now that we won't use filesArray in this iteration of the loop, we should free it and all its elements. (As this is memory in the heap and not the stack and thus is our responsability to manage) - for (int i = 0; i < filesCount; i++) { - if (noteSelected != filesArray[i]) { // we must prevent noteSelected to be freed. It will cause a lot of problems - free(filesArray[i]); // we must only free the files options and not the extraOptions to avoid segfault - } - } - free(filesArray); - debug("Selected note: %s", noteSelected); - if (strcmp(noteSelected, "Create new note") != 0 && strcmp(noteSelected,"Back to vault selection") != 0 && strcmp(noteSelected, "Delete vault") != 0 && strcmp(noteSelected,"Quit (Ctrl+C)") != 0) { -open_note: - bypassSelectionNote = 0; // we must reset bypassSelectionNote to avoid getting into an infinite loop of bypassing the note selection - char *fullPath = malloc(PATH_MAX); - snprintf(fullPath, PATH_MAX, "%s/%s/%s", notesDirectoryString, vaultSelected, noteSelected); - // if it is a journal we must update it before - regex_t regex; - int regexReturn = regcomp(®ex, journalRegex, 0); - error(regexReturn, "program", "Regex compilation failed."); - regexReturn = regexec(®ex, noteSelected, 0, NULL, 0); - - - int *journalWasUpdated = malloc(sizeof(int)); - *journalWasUpdated = 0; - if (!regexReturn) { // if the regex matches -> it's a journal - debug("%s is a journal. Updating it...", noteSelected); - fullPath = updateJournal(fullPath, noteSelected, timeFormat, journalWasUpdated, shouldDebug); // we return the path. As if it is a divided journal we must point to the correct entry + if (vaultSelected != + vaultsArray[i]) { // i forgot this condition before. and freed the pointer equal to + // vaultSelected... So don't remove this condition + free(vaultsArray[i]); // we must only free the vaults options and not the + // extraOptions to avoid segfault } - if (newLineOnOpening) { - if (*journalWasUpdated) { - appendToFile(fullPath, " \n", shouldDebug); // when updating the journal it adds a \n char at the end. So appendToFile(\n) does not work. We append a (special and rare) whitespace character + \n to bypass this issue. - } - appendToFile(fullPath, "\n", shouldDebug); - } - openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug); - free(fullPath); - } else if (strcmp(noteSelected,"Create new note") == 0) { -note_creation: - noteSelected = createNewNote(notesDirectoryString, vaultSelected, bypassSelectionNote, bypassSelectionNoteValue, journalRegex, shouldDebug); - // we can just go back to open_note - goto open_note; - } 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) { - // delete the vault after confirmation by the user - char pathToRMRF[PATH_MAX]; - snprintf(pathToRMRF, PATH_MAX, "%s/%s", notesDirectoryString, vaultSelected); - debug("Removed the directory: %s", pathToRMRF); - rmrf(pathToRMRF, shouldDebug); - shouldChangeVault = 1; + } + + 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; + char **filesArray = getNotesFromVault(notesDirectoryString, vaultSelected, + journalRegex, &filesCount, shouldDebug); + + int journalCount = 0; + char **journalArray = getJournalsFromVault( + notesDirectoryString, vaultSelected, journalRegex, &journalCount, shouldDebug); + + // appends the journal at the end of filesArray + filesArray = realloc(filesArray, (filesCount + journalCount) * sizeof(char *)); + for (int i = 0; i < journalCount; i++) { + filesArray[i + filesCount] = journalArray[i]; + } + filesCount = filesCount + journalCount; + + qsort(filesArray, filesCount, sizeof(const char *), compareString); + debug("Available notes and journals:"); + if (shouldDebug) { + for (int i = 0; i < filesCount; i++) { + altDebug("%s\n", filesArray[i]); + } + altDebug("└------------------------------\n"); + } + // adds options + int extraNotesOptions = 4; + filesArray = realloc( + filesArray, (filesCount + extraNotesOptions) * + sizeof(char *)); // resize filesArray to fit the extra options + filesArray[filesCount] = "Create new note"; + filesArray[filesCount + 1] = "Back to vault selection"; + filesArray[filesCount + 2] = "Delete vault"; + filesArray[filesCount + 3] = "Quit (Ctrl+C)"; + char *noteSelected; + // if we set to bypass the note selector + if (bypassSelectionNote) { + debug("We are bypassing note selection."); + noteSelected = bypassSelectionNoteValue; + if (isStringInArray( + noteSelected, (const char **)filesArray, + filesCount)) { // we just give filesCount and not filesCount + + // extraOptions to avoid matching with an extra options. + debug("The note specified with -n or --note does exist. Opening it."); + goto open_note; + } else { // if the specified note doesn't exist. We creat it + debug("The note specified with -n or --note doesn't exist. Creating it."); + goto note_creation; + } + } + noteSelected = ncursesSelect( + filesArray, + "Select note or journal to open (Use arrows or WASD, Enter to select):", + filesCount, extraNotesOptions, " ", "Or select an option below", "", + shouldDebug); + // now that we won't use filesArray in this iteration of the loop, we should free it + // and all its elements. (As this is memory in the heap and not the stack and thus + // is our responsability to manage) + for (int i = 0; i < filesCount; i++) { + if (noteSelected != filesArray[i]) { // we must prevent noteSelected to be + // freed. It will cause a lot of problems + free(filesArray[i]); // we must only free the files options and not the + // extraOptions to avoid segfault + } + } + free(filesArray); + debug("Selected note: %s", noteSelected); + if (strcmp(noteSelected, "Create new note") != 0 && + strcmp(noteSelected, "Back to vault selection") != 0 && + strcmp(noteSelected, "Delete vault") != 0 && + strcmp(noteSelected, "Quit (Ctrl+C)") != 0) { + open_note: + bypassSelectionNote = + 0; // we must reset bypassSelectionNote to avoid getting into an infinite + // loop of bypassing the note selection + char *fullPath = malloc(PATH_MAX); + snprintf(fullPath, PATH_MAX, "%s/%s/%s", notesDirectoryString, vaultSelected, + noteSelected); + // if it is a journal we must update it before + regex_t regex; + int regexReturn = regcomp(®ex, journalRegex, 0); + error(regexReturn, "program", "Regex compilation failed."); + regexReturn = regexec(®ex, noteSelected, 0, NULL, 0); + + int *journalWasUpdated = malloc(sizeof(int)); + *journalWasUpdated = 0; + if (!regexReturn) { // if the regex matches -> it's a journal + debug("%s is a journal. Updating it...", noteSelected); + fullPath = updateJournal( + fullPath, noteSelected, timeFormat, journalWasUpdated, + shouldDebug); // we return the path. As if it is a divided journal we + // must point to the correct entry + } + if (newLineOnOpening) { + if (*journalWasUpdated) { + appendToFile( + fullPath, " \n", + shouldDebug); // when updating the journal it adds a \n char at the + // end. So appendToFile(\n) does not work. We append a + // (special and rare) whitespace character + \n to + // bypass this issue. + } + appendToFile(fullPath, "\n", shouldDebug); + } + openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug); + free(fullPath); + } else if (strcmp(noteSelected, "Create new note") == 0) { + note_creation: + noteSelected = + createNewNote(notesDirectoryString, vaultSelected, bypassSelectionNote, + bypassSelectionNoteValue, journalRegex, shouldDebug); + // we can just go back to open_note + goto open_note; + } 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) { + // delete the vault after confirmation by the user + char pathToRMRF[PATH_MAX]; + snprintf(pathToRMRF, PATH_MAX, "%s/%s", notesDirectoryString, + vaultSelected); + debug("Removed the directory: %s", pathToRMRF); + rmrf(pathToRMRF, shouldDebug); + shouldChangeVault = 1; + } + } else if (strcmp(noteSelected, "Quit (Ctrl+C)") == 0) { + debug("The program was exited."); + shouldExit = 1; + } } - } else if (strcmp(noteSelected,"Quit (Ctrl+C)") == 0) { - debug("The program was exited."); + free(vaultsArray); + } else if (strcmp(vaultSelected, "Create a new vault") == 0) { + vault_creation: + 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 + } else if (strcmp(vaultSelected, "Quit (Ctrl+C)") == 0) { + debug("The program was exited"); shouldExit = 1; - } } - free(vaultsArray); - } else if (strcmp(vaultSelected,"Create a new vault") == 0) { -vault_creation: - 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 - } else if (strcmp(vaultSelected,"Quit (Ctrl+C)") == 0) { - debug("The program was exited"); - shouldExit = 1; - } } free(configPath); free(directoriesArray); diff --git a/src/notes.c b/src/notes.c index 770d575..cc029b3 100644 --- a/src/notes.c +++ b/src/notes.c @@ -1,67 +1,81 @@ #include "notes.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); - debug("Here are how many vaults there is per directory:"); - for (int i = 0; i < directoryNumber; 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; +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); + debug("Here are how many vaults there is per directory:"); + for (int i = 0; i < directoryNumber; 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++; - } - } - 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."; + 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++; + } + } + 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) { +char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, + int shouldDebug) { debug("Searching %s for journals", vault); - // originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ + // originally from + // https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ struct dirent *vaultEntry; char tempPath[PATH_MAX]; - snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, vault); // sets the full absolute path to fullPathEntry + snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, + vault); // sets the full absolute path to fullPathEntry DIR *vaultDirectory = opendir(tempPath); - error(vaultDirectory==NULL, "program", "Could not open directory %s", tempPath); + error(vaultDirectory == NULL, "program", "Could not open directory %s", tempPath); char **journalsArray = NULL; // will contain all the notes - int journalsCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc - + int journalsCount = + 0; // we need to count how many notes there is to always readjust how many memory we alloc + // https://stackoverflow.com/a/1085120 for regex code regex_t regex; int regexReturn; // compiles the regex regexReturn = regcomp(®ex, journalRegex, 0); - error(regexReturn, "program", "Regex could not compile. Perhaps there is an error with the regex string"); + error(regexReturn, "program", + "Regex could not compile. Perhaps there is an error with the regex string"); debug("Regex compiled succesfully"); // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html // for readdir() debug("┌------------------------------------------\nDetected files and dirs from the vault"); - while ((vaultEntry = readdir(vaultDirectory)) != NULL) { // we iterate over every entry from the dir. So files and dirs (. and .. included) - altDebug("%s ", vaultEntry->d_name); - if (vaultEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files) - regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0); - if (!regexReturn) { // if the regex matches - altDebug("matched with the regex. It is a journal.\n"); - journalsArray = realloc(journalsArray, (journalsCount + 1)*sizeof(char*)); // resize notesArray so that - journalsArray[journalsCount] = strdup(vaultEntry->d_name); // copy the dir name into notesArray - journalsCount++; + while ( + (vaultEntry = readdir(vaultDirectory)) != + NULL) { // we iterate over every entry from the dir. So files and dirs (. and .. included) + altDebug("%s ", vaultEntry->d_name); + if (vaultEntry->d_name[0] != + '.') { // if the entry don't start with a dot (so hidden dirs and hidden files) + regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0); + if (!regexReturn) { // if the regex matches + altDebug("matched with the regex. It is a journal.\n"); + journalsArray = + realloc(journalsArray, + (journalsCount + 1) * sizeof(char *)); // resize notesArray so that + journalsArray[journalsCount] = + strdup(vaultEntry->d_name); // copy the dir name into notesArray + journalsCount++; + } else { + altDebug("did not matched with the regex. It is a note.\n"); + } } else { - altDebug("did not matched with the regex. It is a note.\n"); + altDebug(" was ignored\n"); } - } else { - altDebug(" was ignored\n"); - } } altDebug("└ ------------------------------\n"); // free's some used memory @@ -70,42 +84,57 @@ char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, return journalsArray; } -char** getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) { - // this function is inputed a path to a vault (which was selected before) and outpus all the suitable notes (so not the hidden ones) - // originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ +char **getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, + int shouldDebug) { + // this function is inputed a path to a vault (which was selected before) and outpus all the + // suitable notes (so not the hidden ones) originally from + // https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/ debug("Searching %s for notes", vault); - struct dirent *vaultEntry; + struct dirent *vaultEntry; char tempPath[PATH_MAX]; - snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, vault); // sets the full absolute path to fullPathEntry + snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, + vault); // sets the full absolute path to fullPathEntry DIR *vaultDirectory = opendir(tempPath); - error(vaultDirectory==NULL, "program", "Could not open directory %s", tempPath); + error(vaultDirectory == NULL, "program", "Could not open directory %s", tempPath); char **notesArray = NULL; // will contain all the notes - int notesCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc + int notesCount = + 0; // we need to count how many notes there is to always readjust how many memory we alloc // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html // for readdir() debug("┌------------------------------\nDetected Files and dirs from the vault:"); while ((vaultEntry = readdir(vaultDirectory)) != NULL) { - char *entryName = vaultEntry->d_name; - int entryLenght = strlen(entryName); - altDebug("%s ", entryName); - // for the regex code https://stackoverflow.com/a/1085120 - regex_t regex; - int regexReturn; - // compiles the regex - regexReturn = regcomp(®ex, journalRegex, 0); - if (entryName[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/%s", pathToVault, vault, entryName); // sets the full absolute path to fullPathEntry - // check if it matches the regex. If it does not match regexReturn != 0. - regexReturn = regexec(®ex, entryName, 0, NULL, 0); - struct stat metadataPathEntry; - if (stat(fullPathEntry, &metadataPathEntry) == 0 && entryName[entryLenght - 3] == '.' && entryName[entryLenght - 2] == 'm' && entryName[entryLenght - 1] == 'd' && regexReturn && S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a file ending in .md and that does no match the regex - notesArray = realloc(notesArray, (notesCount + 1)*sizeof(char*)); // resize notesArray so that - notesArray[notesCount] = strdup(entryName); // copy the dir name into notesArray - notesCount++; - altDebug("did not match with the regex. It is a note.\n"); - } else if (!regexReturn) {altDebug("matched with the regex. It is a journal.\n");} - } else {altDebug("was ignored\n");} + char *entryName = vaultEntry->d_name; + int entryLenght = strlen(entryName); + altDebug("%s ", entryName); + // for the regex code https://stackoverflow.com/a/1085120 + regex_t regex; + int regexReturn; + // compiles the regex + regexReturn = regcomp(®ex, journalRegex, 0); + if (entryName[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/%s", pathToVault, vault, + entryName); // sets the full absolute path to fullPathEntry + // check if it matches the regex. If it does not match regexReturn != 0. + regexReturn = regexec(®ex, entryName, 0, NULL, 0); + struct stat metadataPathEntry; + if (stat(fullPathEntry, &metadataPathEntry) == 0 && entryName[entryLenght - 3] == '.' && + entryName[entryLenght - 2] == 'm' && entryName[entryLenght - 1] == 'd' && + regexReturn && + S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a file ending in .md and + // that does no match the regex + notesArray = realloc(notesArray, (notesCount + 1) * + sizeof(char *)); // resize notesArray so that + notesArray[notesCount] = strdup(entryName); // copy the dir name into notesArray + notesCount++; + altDebug("did not match with the regex. It is a note.\n"); + } else if (!regexReturn) { + altDebug("matched with the regex. It is a journal.\n"); + } + } else { + altDebug("was ignored\n"); + } } altDebug("└ ------------------------------\n"); @@ -115,166 +144,214 @@ char** getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int return notesArray; } -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. - 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/ - struct dirent *vaultsDirectoryEntry; - 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() - 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); - 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"); - 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.)"); +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. + 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/ + struct dirent *vaultsDirectoryEntry; + 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() + 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); + 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"); + 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.)"); + } + } else { + altDebug(" is not a vault (hidden file/dir)"); + } } - } else { - altDebug(" is not a vault (hidden file/dir)"); - } + // 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; + for (int i = 0; i < directoryNumber; i++) { + *count += vaultsPerDirectoryNumber[i]; } - // 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; - for (int i = 0; i < directoryNumber; i++) { - *count += vaultsPerDirectoryNumber[i]; - } - return vaultsArray; + return vaultsArray; } -char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, int shouldDebug) { - path[PATH_MAX] = '\0'; // it assures it is a string (Most cases this does nothing). But rewriting at least one bytes make the compile happy. He doesn't want to return an unchanged input. - debug("Handling the journal %s", path); - - char *date = getFormatedTime(timeFormat, shouldDebug); - debug("Formated time for the journal's entry is %s", date); - char dateWithExtension[PATH_MAX]; - snprintf(dateWithExtension, PATH_MAX, "%s.md", date); - 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; - 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 - appendToFile(path, date, shouldDebug); - *journalWasUpdated = 1; - } // if there is an entry do nothing - } else if (S_ISDIR(metadata.st_mode)) { - debug("%s is a divided journal.", path); - struct dirent *dividedJournalEntry; - // snprintf does not like to have the same variable as input and output so we use a buffer - char temp[PATH_MAX]; - snprintf(temp, PATH_MAX, "%s/", path); // appends a /. For safety - strncpy(path, temp, PATH_MAX); - DIR *dividedJournalDirectory = opendir(path); - error(dividedJournalDirectory==NULL, "program", "Could not open directory %s", path); - /*char **entryArray = NULL; // will contain all the entries of the divided journal - // This time it will be different. We must add "invert" the extraOptions to the options so that the Create new entry in journal is on top - entryArray = realloc(entryArray, sizeof(char*));*/ - // simpler to just malloc 2 and later realloc - const int extraOptions = 3; - char **entryArray = malloc(extraOptions*sizeof(char*)); - char *createEntryMessage = malloc(PATH_MAX); - snprintf(createEntryMessage, PATH_MAX, "Create new entry for the journal %s", journal); - entryArray[0] = createEntryMessage; - entryArray[1] = "Open random entry"; - entryArray[2] = "Search inside entries"; - int entryCount = extraOptions; // we need to count how many dirs there is to always readjust how many memory we alloc - // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html - // for readdir() - debug("┌------------------------------\n Detected files and dirs from %s:", path); - // iterates over all the entries from the dir - while ((dividedJournalEntry = readdir(dividedJournalDirectory)) != NULL) { - altDebug("%s\n", dividedJournalEntry->d_name); - if (dividedJournalEntry->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", path, dividedJournalEntry->d_name); // sets the full absolute path to fullPathEntry - - struct stat metadataPathEntry; - if (stat(fullPathEntry, &metadataPathEntry) == 0 && S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a directory - entryArray = realloc(entryArray, (entryCount + 1)*sizeof(char*)); - entryArray[entryCount] = strdup(dividedJournalEntry->d_name); // copy the dir name into entryArray - entryCount++; - } - } - } - altDebug("└------------------------------\n"); - // free's some used memory - closedir(dividedJournalDirectory); - qsort(entryArray + extraOptions, entryCount - extraOptions, sizeof(const char *), reverseCompareString); // sorts the journals entries alphabetically. // the + extraOptions - extraOptions is to not sort "Create new entry for the journal" or Open random entry which is the first element +char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, + int shouldDebug) { + path[PATH_MAX] = + '\0'; // it assures it is a string (Most cases this does nothing). But rewriting at least + // one bytes make the compile happy. He doesn't want to return an unchanged input. + debug("Handling the journal %s", path); - // we must now select to create new entry or to enter in old one - char *selectedOption = ncursesSelect(entryArray, "Create new entry or acces old entry (Use arrows or WASD, Enter to select):", extraOptions, entryCount - extraOptions, " ", " ", "", shouldDebug); // the "Create new entry for the journal %s" will be the only options. All other will be extraOptions. This is made so that "Create [...] %s" will always be on top - debug("Selected option from journal entry selection: %s", selectedOption); - if (strcmp(selectedOption, createEntryMessage) == 0) { // create new entry - char temp[PATH_MAX]; - error(strlen(path)+1+strlen(dateWithExtension)+1>PATH_MAX, "Error file path too long. %s/%s must not exceed PATH_MAX", path, dateWithExtension); - snprintf(temp, PATH_MAX, "%s/%s", path, dateWithExtension); - strncpy(path, temp, PATH_MAX); - if (!isStringInArray(dateWithExtension, (const char **)entryArray, entryCount)) { // it only creates it if it doesn't already exist - // if it does exist it will just pass the full path - debug("Creating entry %s inside %s", date, path); - FILE *file; - file = fopen(path, "w"); - error(file == NULL, "program", "%s couldn't be created", path); - debug("Writing %s\\n to %s", date, path); - fprintf(file, "%s\n", date); - fclose(file); - free(createEntryMessage); - *journalWasUpdated = 1; - } else { - debug("Today's entry (%s) already exist. We won't create a new one.", dateWithExtension); - } - } else if (strcmp(selectedOption, entryArray[1]) == 0) { // if we choosed to open a random entry - // setting up the seed for rand() - srand(time(NULL)); // not totaly random, just pseudorandom - int randomEntry = extraOptions + (rand() % (entryCount - extraOptions)); // we get a random number between extraOptions and entryCount (so all journal entries and not extraOptions) - char temp[PATH_MAX]; - snprintf(temp, PATH_MAX, "%s/%s", path, entryArray[randomEntry]); // reconstruct the path - strncpy(path, temp, PATH_MAX); - debug("Selected random entry %s. It's path is %s.", entryArray[randomEntry], path); - } else if (strcmp(selectedOption, entryArray[2]) == 0) { // if we choosed to search - char *temp = fzfSelect(path, "Input text to be searched", shouldDebug); // uses ripgrep and fzf to search inside the files - char *selectedOption = malloc(PATH_MAX); // we can't just use path as both input and output of snprintf - snprintf(selectedOption, PATH_MAX, "%s/%s", path, temp); - path = strdup(selectedOption); - free(selectedOption); - } else { // we just recreate the path to the selected entry + char *date = getFormatedTime(timeFormat, shouldDebug); + debug("Formated time for the journal's entry is %s", date); + char dateWithExtension[PATH_MAX]; + snprintf(dateWithExtension, PATH_MAX, "%s.md", date); + 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; + 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 + appendToFile(path, date, shouldDebug); + *journalWasUpdated = 1; + } // if there is an entry do nothing + } else if (S_ISDIR(metadata.st_mode)) { + debug("%s is a divided journal.", path); + struct dirent *dividedJournalEntry; // snprintf does not like to have the same variable as input and output so we use a buffer char temp[PATH_MAX]; - snprintf(temp, PATH_MAX, "%s/%s", path, selectedOption); + snprintf(temp, PATH_MAX, "%s/", path); // appends a /. For safety strncpy(path, temp, PATH_MAX); - debug("Returning path to the selected entry: %s", path); - } - } else { - error(1, "program", "%s is not a file and not a directory.", path); - } - - return path; + DIR *dividedJournalDirectory = opendir(path); + error(dividedJournalDirectory == NULL, "program", "Could not open directory %s", path); + /*char **entryArray = NULL; // will contain all the entries of the divided journal + // This time it will be different. We must add "invert" the extraOptions to the options so + that the Create new entry in journal is on top entryArray = realloc(entryArray, + sizeof(char*));*/ + // simpler to just malloc 2 and later realloc + const int extraOptions = 3; + char **entryArray = malloc(extraOptions * sizeof(char *)); + char *createEntryMessage = malloc(PATH_MAX); + snprintf(createEntryMessage, PATH_MAX, "Create new entry for the journal %s", journal); + entryArray[0] = createEntryMessage; + entryArray[1] = "Open random entry"; + entryArray[2] = "Search inside entries"; + int entryCount = extraOptions; // we need to count how many dirs there is to always readjust + // how many memory we alloc + // Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html + // for readdir() + debug("┌------------------------------\n Detected files and dirs from %s:", path); + // iterates over all the entries from the dir + while ((dividedJournalEntry = readdir(dividedJournalDirectory)) != NULL) { + altDebug("%s\n", dividedJournalEntry->d_name); + if (dividedJournalEntry->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", path, + dividedJournalEntry->d_name); // sets the full absolute path to fullPathEntry + + struct stat metadataPathEntry; + if (stat(fullPathEntry, &metadataPathEntry) == 0 && + S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a directory + entryArray = realloc(entryArray, (entryCount + 1) * sizeof(char *)); + entryArray[entryCount] = + strdup(dividedJournalEntry->d_name); // copy the dir name into entryArray + entryCount++; + } + } + } + altDebug("└------------------------------\n"); + // free's some used memory + closedir(dividedJournalDirectory); + qsort(entryArray + extraOptions, entryCount - extraOptions, sizeof(const char *), + reverseCompareString); // sorts the journals entries alphabetically. // the + + // extraOptions - extraOptions is to not sort "Create new entry + // for the journal" or Open random entry which is the first + // element + + // we must now select to create new entry or to enter in old one + char *selectedOption = ncursesSelect( + entryArray, + "Create new entry or acces old entry (Use arrows or WASD, Enter to select):", + extraOptions, entryCount - extraOptions, " ", " ", "", + shouldDebug); // the "Create new entry for the journal %s" will be the only options. All + // other will be extraOptions. This is made so that "Create [...] %s" will + // always be on top + debug("Selected option from journal entry selection: %s", selectedOption); + if (strcmp(selectedOption, createEntryMessage) == 0) { // create new entry + char temp[PATH_MAX]; + error(strlen(path) + 1 + strlen(dateWithExtension) + 1 > PATH_MAX, + "Error file path too long. %s/%s must not exceed PATH_MAX", path, + dateWithExtension); + snprintf(temp, PATH_MAX, "%s/%s", path, dateWithExtension); + strncpy(path, temp, PATH_MAX); + if (!isStringInArray(dateWithExtension, (const char **)entryArray, + entryCount)) { // it only creates it if it doesn't already exist + // if it does exist it will just pass the full path + debug("Creating entry %s inside %s", date, path); + FILE *file; + file = fopen(path, "w"); + error(file == NULL, "program", "%s couldn't be created", path); + debug("Writing %s\\n to %s", date, path); + fprintf(file, "%s\n", date); + fclose(file); + free(createEntryMessage); + *journalWasUpdated = 1; + } else { + debug("Today's entry (%s) already exist. We won't create a new one.", + dateWithExtension); + } + } else if (strcmp(selectedOption, entryArray[1]) == + 0) { // if we choosed to open a random entry + // setting up the seed for rand() + srand(time(NULL)); // not totaly random, just pseudorandom + int randomEntry = + extraOptions + + (rand() % + (entryCount - + extraOptions)); // we get a random number between extraOptions and entryCount (so + // all journal entries and not extraOptions) + char temp[PATH_MAX]; + snprintf(temp, PATH_MAX, "%s/%s", path, + entryArray[randomEntry]); // reconstruct the path + strncpy(path, temp, PATH_MAX); + debug("Selected random entry %s. It's path is %s.", entryArray[randomEntry], path); + } else if (strcmp(selectedOption, entryArray[2]) == 0) { // if we choosed to search + char *temp = fzfSelect(path, "Input text to be searched", + shouldDebug); // uses ripgrep and fzf to search inside the files + char *selectedOption = + malloc(PATH_MAX); // we can't just use path as both input and output of snprintf + snprintf(selectedOption, PATH_MAX, "%s/%s", path, temp); + path = strdup(selectedOption); + free(selectedOption); + } else { // we just recreate the path to the selected entry + // snprintf does not like to have the same variable as input and output so we use a + // buffer + char temp[PATH_MAX]; + snprintf(temp, PATH_MAX, "%s/%s", path, selectedOption); + strncpy(path, temp, PATH_MAX); + debug("Returning path to the selected entry: %s", path); + } + } else { + error(1, "program", "%s is not a file and not a directory.", path); + } + + return path; } diff --git a/src/notes.h b/src/notes.h index 7b44eca..a254d66 100644 --- a/src/notes.h +++ b/src/notes.h @@ -1,23 +1,29 @@ #ifndef NOTES_H #define NOTES_H -#include "utils.h" #include "ui.h" +#include "utils.h" // find which directory contains targetVault -char *getDirectoryFromVault(char *targetVault, char **vaultsArray, int vaultTotalNumber, int *vaultNumberPerDirectory, char **directoryArray, int directoryNumber, int shouldDebug); +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. -char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug); -// 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); -// 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. +// 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. +char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, + int shouldDebug); +// 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); +// 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); +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 @@ -25,5 +31,6 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber // creates new entry with date. // for divided select if we want to acces to a new entry or a old one. // returns the path to the file that needs to be opened. -char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, int shouldDebug); +char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, + int shouldDebug); #endif diff --git a/src/ui.c b/src/ui.c index acf7c5d..61578b7 100644 --- a/src/ui.c +++ b/src/ui.c @@ -1,163 +1,187 @@ #include "ui.h" #include "utils.h" -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 +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(); - keypad(stdscr, FALSE); - // color code from https://stackoverflow.com/a/73396575 - start_color(); - clear(); - use_default_colors(); - init_pair(1, COLOR_WHITE, -1); // -1 is blank - init_pair(2, COLOR_RED, -1); - attron(COLOR_PAIR(1)); - printw("Enter the name of the new vault: "); - mvprintw(3, 0, "Unsafe characters such as \\ and / will be replaced by _"); - attroff(COLOR_PAIR(1)); - if (duplicateWarning) { - attron(COLOR_PAIR(2)); - mvprintw(4, 0, "A vault with this name already exists. Please input another name"); - attroff(COLOR_PAIR(2)); + // 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(); + keypad(stdscr, FALSE); + // color code from https://stackoverflow.com/a/73396575 + start_color(); + clear(); + use_default_colors(); + init_pair(1, COLOR_WHITE, -1); // -1 is blank + init_pair(2, COLOR_RED, -1); + attron(COLOR_PAIR(1)); + printw("Enter the name of the new vault: "); + mvprintw(3, 0, "Unsafe characters such as \\ and / will be replaced by _"); + attroff(COLOR_PAIR(1)); + if (duplicateWarning) { + attron(COLOR_PAIR(2)); + mvprintw(4, 0, "A vault with this name already exists. Please input another name"); + attroff(COLOR_PAIR(2)); + } + if (emptyWarning) { + attron(COLOR_PAIR(2)); + mvprintw(5, 0, "The vault's name must not be empty. Please input another name"); + attroff(COLOR_PAIR(2)); + } + move(1, 1); // replace cursor + wgetnstr(stdscr, vaultName, PATH_MAX - 1); + refresh(); + endwin(); + reset_shell_mode(); + fflush(stdout); + fflush(stderr); + } else { + strncpy(vaultName, bypassvalue, PATH_MAX - 2); // -2 (and later -1) because indexing + vaultName[PATH_MAX - 1] = '\0'; } - if (emptyWarning) { - attron(COLOR_PAIR(2)); - mvprintw(5, 0, "The vault's name must not be empty. Please input another name"); - attroff(COLOR_PAIR(2)); + // check if no empty string + if (strcmp(vaultName, "") == 0) { + emptyWarning = 1; + goto input_screen; } - move(1, 1); // replace cursor - wgetnstr(stdscr, vaultName, PATH_MAX-1); - refresh(); - endwin(); - reset_shell_mode(); - fflush(stdout); - fflush(stderr); - } else { - strncpy(vaultName, bypassvalue, PATH_MAX -2); // -2 (and later -1) because indexing - vaultName[PATH_MAX-1] = '\0'; - } - // check if no empty string - if (strcmp(vaultName, "") == 0) { - emptyWarning = 1; - goto input_screen; - } - debug("Inputed vaultName=%s", vaultName); - sanitize(vaultName); - debug("Sanitized vaultName=%s", vaultName); + debug("Inputed vaultName=%s", vaultName); + sanitize(vaultName); + debug("Sanitized vaultName=%s", vaultName); - - 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); + 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 { - duplicateWarning = 1; - goto input_screen; - } - free(vaultName); + mkdir(vaultFullPath, 0744); + } else { + duplicateWarning = 1; + goto input_screen; + } + free(vaultName); } -char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, char *journalRegex, int shouldDebug) { - // input from user for the name - char *fileName = malloc(BUFFER_SIZE); - if (!bypass) { // if we don't bypass. (if -n or --note weren't set.) - echo(); - keypad(stdscr, FALSE); - clear(); - printw("Enter the name of the new note: "); - mvprintw(3, 0, "Unsafe characters such as \\, and / will be replaced by _"); - mvprintw(4, 0, "If the name matches with the regex for a journal (%s), it will create a journal instead of a note.", journalRegex); - move(1,1); - wgetnstr(stdscr, fileName, BUFFER_SIZE-4); //limits the buffer to prevent overflow (-4 to account indexing and from ".md" in case we need to add it later) - refresh(); - endwin(); - reset_shell_mode(); - fflush(stdout); - fflush(stderr); - } else { // bypasses user input if we bypass is set to 1 - strncpy(fileName, bypassvalue, BUFFER_SIZE); - fileName[BUFFER_SIZE-1] = '\0'; - } - error(strcmp(fileName, "") == 0, "user", "fileName is empty"); // replace this with a warning and add a warning if duplicate file and handle case where multiple warnings (if such case is possible) - // check/sanitize the input - debug("Inputed fileName=%s", fileName); - sanitize(fileName); - debug("Sanitized fileName=%s (We might append .md later", fileName); - // we trow an error if the file already exists (previous behaviour was overwriting the file) - char path[PATH_MAX]; - snprintf(path, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName); - struct stat st; - error(stat(path, &st) == 0, "user", "%s already exists", fileName); - // if it matches with the journalRegex we treat it as a journal instead of a note - regex_t regex; - int regexReturn = regcomp(®ex, journalRegex, 0); - error(regexReturn, "program", "Regex compilation failed."); - regexReturn = regexec(®ex, fileName, 0, NULL, 0); - if (!regexReturn) { - debug("%s matches with %s treating it as a journal", fileName, journalRegex); - char **options = malloc(32); // the number of bytes is exactly what in the two strings - options[0] = "Divided journal"; - options[1] = "Unified journal"; - char *optionSelected = ncursesSelect(options, "Select which type of journal you want to create (Use arrows or WASD, Enter to select):", 2, 0, "", "", " ", shouldDebug); - debug("%s was selected to be a %s", fileName, optionSelected); - if (strcmp(optionSelected, options[0]) == 0) { // if it is a divided journal - char *fileFullPath = malloc(PATH_MAX); - 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"); - } else { - error(1, "program", "%s could not be created", fileFullPath); - } - free(fileFullPath); - } else { // if it is a unified journal - char *fileFullPath = malloc(PATH_MAX); - snprintf(fileFullPath, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName); - int len = strlen(fileFullPath); - if (fileFullPath[len-3] != '.' || fileFullPath[len-2] != 'm' || fileFullPath[len-1] != 'd') { // there might be a cleaner way to do this - error(len > PATH_MAX - 3, "user", "%s is too big (greater than PATH_MAX-3) and we can't append .md", fileFullPath); - strncat(fileFullPath, ".md", PATH_MAX); - // we checked before if fileName didn't already exist. - // we must redo it as we add a .mode - struct stat st; - error(stat(fileFullPath, &st) == 0, "user", "%s already exists", fileFullPath); - } - FILE *filePointer; - filePointer = fopen(fileFullPath, "w"); // creates and opens the file - error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath); - fprintf(filePointer, "### %s\n", fileName); - fclose(filePointer); // closes the file so that nvim could open it - free(fileFullPath); +char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, + char *journalRegex, int shouldDebug) { + // input from user for the name + char *fileName = malloc(BUFFER_SIZE); + if (!bypass) { // if we don't bypass. (if -n or --note weren't set.) + echo(); + keypad(stdscr, FALSE); + clear(); + printw("Enter the name of the new note: "); + mvprintw(3, 0, "Unsafe characters such as \\, and / will be replaced by _"); + mvprintw(4, 0, + "If the name matches with the regex for a journal (%s), it will create a journal " + "instead of a note.", + journalRegex); + move(1, 1); + wgetnstr(stdscr, fileName, + BUFFER_SIZE - 4); // limits the buffer to prevent overflow (-4 to account indexing + // and from ".md" in case we need to add it later) + refresh(); + endwin(); + reset_shell_mode(); + fflush(stdout); + fflush(stderr); + } else { // bypasses user input if we bypass is set to 1 + strncpy(fileName, bypassvalue, BUFFER_SIZE); + fileName[BUFFER_SIZE - 1] = '\0'; } - free(options); // they are useless now - } else { // normal process for a note - debug("%s does not match with %s treating it as a note", fileName, journalRegex); - // if there is no .md add an .md - int len = strlen(fileName); - if (fileName[len-3] != '.' || fileName[len-2] != 'm' || fileName[len-1] != 'd') { // there might be a cleaner way to do this - strcat(fileName, ".md"); // this should not cause an overflow issue as we get at most 252 chars (+'.'+'m'+'d'+'\0' makes it to 256) with wgetnstr + error( + strcmp(fileName, "") == 0, "user", + "fileName is empty"); // replace this with a warning and add a warning if duplicate file and + // handle case where multiple warnings (if such case is possible) + // check/sanitize the input + debug("Inputed fileName=%s", fileName); + sanitize(fileName); + debug("Sanitized fileName=%s (We might append .md later", fileName); + // we trow an error if the file already exists (previous behaviour was overwriting the file) + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName); + struct stat st; + error(stat(path, &st) == 0, "user", "%s already exists", fileName); + // if it matches with the journalRegex we treat it as a journal instead of a note + regex_t regex; + int regexReturn = regcomp(®ex, journalRegex, 0); + error(regexReturn, "program", "Regex compilation failed."); + regexReturn = regexec(®ex, fileName, 0, NULL, 0); + if (!regexReturn) { + debug("%s matches with %s treating it as a journal", fileName, journalRegex); + char **options = malloc(32); // the number of bytes is exactly what in the two strings + options[0] = "Divided journal"; + options[1] = "Unified journal"; + char *optionSelected = ncursesSelect(options, + "Select which type of journal you want to create " + "(Use arrows or WASD, Enter to select):", + 2, 0, "", "", " ", shouldDebug); + debug("%s was selected to be a %s", fileName, optionSelected); + if (strcmp(optionSelected, options[0]) == 0) { // if it is a divided journal + char *fileFullPath = malloc(PATH_MAX); + 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"); + } else { + error(1, "program", "%s could not be created", fileFullPath); + } + free(fileFullPath); + } else { // if it is a unified journal + char *fileFullPath = malloc(PATH_MAX); + snprintf(fileFullPath, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName); + int len = strlen(fileFullPath); + if (fileFullPath[len - 3] != '.' || fileFullPath[len - 2] != 'm' || + fileFullPath[len - 1] != 'd') { // there might be a cleaner way to do this + error(len > PATH_MAX - 3, "user", + "%s is too big (greater than PATH_MAX-3) and we can't append .md", + fileFullPath); + strncat(fileFullPath, ".md", PATH_MAX); + // we checked before if fileName didn't already exist. + // we must redo it as we add a .mode + struct stat st; + error(stat(fileFullPath, &st) == 0, "user", "%s already exists", fileFullPath); + } + FILE *filePointer; + filePointer = fopen(fileFullPath, "w"); // creates and opens the file + error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath); + fprintf(filePointer, "### %s\n", fileName); + fclose(filePointer); // closes the file so that nvim could open it + free(fileFullPath); + } + free(options); // they are useless now + } else { // normal process for a note + debug("%s does not match with %s treating it as a note", fileName, journalRegex); + // if there is no .md add an .md + int len = strlen(fileName); + if (fileName[len - 3] != '.' || fileName[len - 2] != 'm' || + fileName[len - 1] != 'd') { // there might be a cleaner way to do this + strcat(fileName, ".md"); // this should not cause an overflow issue as we get at most + // 252 chars (+'.'+'m'+'d'+'\0' makes it to 256) with wgetnstr + } + char *fileFullPath = malloc(PATH_MAX); // this dinamically allocated because we use it in + // the main function to call openEditor + sprintf(fileFullPath, "%s/%s/%s", dirToVault, vaultFromDir, fileName); + FILE *filePointer; + filePointer = fopen(fileFullPath, "w"); // creates and opens the file + error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath); + fprintf(filePointer, "### %s\n", fileName); + fclose(filePointer); // closes the file so that nvim could open it + free(fileFullPath); } - char *fileFullPath = malloc(PATH_MAX); // this dinamically allocated because we use it in the main function to call openEditor - sprintf(fileFullPath, "%s/%s/%s", dirToVault, vaultFromDir, fileName); - FILE *filePointer; - filePointer = fopen(fileFullPath, "w"); // creates and opens the file - error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath); - fprintf(filePointer, "### %s\n", fileName); - fclose(filePointer); // closes the file so that nvim could open it - free(fileFullPath); - } - return fileName; + return fileName; } -char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) { - // we are gonna write each line of each files (with the filename and the line number to an index) +char *fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) { + // we are gonna write each line of each files (with the filename and the line number to an + // index) char command[CMD_BUFFER]; char indexFile[] = "/tmp/notewrapper_index_XXXXXX"; @@ -170,12 +194,9 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) { * Build index once into temp file * format: file:line:content */ - snprintf(command, sizeof(command), - "rg --line-number --no-heading --color=never . \"%s\" > %s", - pathToFiles, - indexFile - ); - + snprintf(command, sizeof(command), "rg --line-number --no-heading --color=never . \"%s\" > %s", + pathToFiles, indexFile); + debug("INDEX CMD: %s", command); if (system(command) != 0) { @@ -189,47 +210,47 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) { */ /* EXPLANATION: - + cat %s → feeds prebuilt index file (format: file:line:content) into fzf - + fzf --delimiter ':' → splits each line into fields: {1} = file path {2} = line number {3} = content - + --prompt → UI prompt text shown in fzf - + --preview → runs a shell command for selected item: file={1} → current file line={2} → line number of match - + nl -ba "$file" → prints file with line numbers - + sed -n "$((line-5)),$((line+5))p" → extracts a window of 5 lines above and below match - + sed "... -> ..." → highlights the matched line (middle of window) with red arrow - + --preview-window=right:60%:hidden → preview appears on right, 60% width, initially hidden - + --bind 'change:show-preview' → preview only appears after first selection change (not at startup) */ - snprintf(command, sizeof(command), - "cat %s | fzf --delimiter ':' --prompt='%s' " - "--preview 'file={1}; line={2}; nl -ba \"$file\" | sed -n \"$((line-5)),$((line+5))p\" | sed \"$((line-$(($((line-5))))+1))s/^/\\x1b[31m-> \\x1b[0m/\"' " - "--preview-window=right:60%%:hidden " - "--bind 'change:show-preview'", - indexFile, - selectText ? selectText : "> " - ); + snprintf( + command, sizeof(command), + "cat %s | fzf --delimiter ':' --prompt='%s' " + "--preview 'file={1}; line={2}; nl -ba \"$file\" | sed -n \"$((line-5)),$((line+5))p\" | " + "sed \"$((line-$(($((line-5))))+1))s/^/\\x1b[31m-> \\x1b[0m/\"' " + "--preview-window=right:60%%:hidden " + "--bind 'change:show-preview'", + indexFile, selectText ? selectText : "> "); debug("FZF CMD: %s", command); FILE *fzfPipe = popen(command, "r"); @@ -252,8 +273,8 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) { char *token = strtok(result, "/"); result = NULL; while (token != NULL) { - result = token; - token = strtok(NULL, "/"); //Passing NULL means “don’t start a new string, resume + result = token; + token = strtok(NULL, "/"); // Passing NULL means “don’t start a new string, resume } // clean up @@ -263,81 +284,86 @@ 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) { // TODO we should see how it handles larges strings (like large directories) - int highlight = 0; //curently highlighted option +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; - cbreak(); // disable line buffering - noecho(); // don't echo key presses - keypad(stdscr, TRUE); // enable arrow keys + cbreak(); // disable line buffering + noecho(); // don't echo key presses + keypad(stdscr, TRUE); // enable arrow keys start_color(); - curs_set(0); // sets the cursor to invisible. (We will emulate the cursor with a hightlight that go up and down). + curs_set(0); // sets the cursor to invisible. (We will emulate the cursor with a hightlight that + // go up and down). use_default_colors(); init_pair(1, COLOR_WHITE, -1); // color for optionsNumber (so notes, vaults, etc.) - init_pair(2, COLOR_BLUE, -1); // color for extraOptionsNumber (so settings, delete notes, create vault, etc.) + init_pair(2, COLOR_BLUE, + -1); // color for extraOptionsNumber (so settings, delete notes, create vault, etc.) while (1) { - clear(); - attron(COLOR_PAIR(1)); - mvprintw(0,0, "%s", optionsText); - int offset = 1; - if (strcmp(bottomText, "") != 0) { - mvprintw(offset, 1, "%s", bottomText); - offset++; - } - // Print options with highlighting - for (int i = 0; i < optionsNumber; i++) { - if (i == highlight) { - attron(A_REVERSE); - } // highlight selected - mvprintw(i+offset, 2, "%s", options[i]); - if (i == highlight) { - attroff(A_REVERSE); + clear(); + attron(COLOR_PAIR(1)); + mvprintw(0, 0, "%s", optionsText); + int offset = 1; + if (strcmp(bottomText, "") != 0) { + mvprintw(offset, 1, "%s", bottomText); + offset++; } - } - attroff(COLOR_PAIR(1)); - if (strcmp(middleText, "") != 0) { - mvprintw(offset+optionsNumber, 1, "%s", middleText); - offset++; - } - // Printf extraOptions with highlighting - attron(COLOR_PAIR(2)); - for (int k = optionsNumber; k < optionsNumber + extraOptionsNumber; k++) { - if (k == highlight) { - attron(A_REVERSE); + // Print options with highlighting + for (int i = 0; i < optionsNumber; i++) { + if (i == highlight) { + attron(A_REVERSE); + } // highlight selected + mvprintw(i + offset, 2, "%s", options[i]); + if (i == highlight) { + attroff(A_REVERSE); + } } - mvprintw(k+offset, 2, "%s", options[k]); - if (k == highlight) { - attroff(A_REVERSE); + attroff(COLOR_PAIR(1)); + if (strcmp(middleText, "") != 0) { + mvprintw(offset + optionsNumber, 1, "%s", middleText); + offset++; } - } - attroff(COLOR_PAIR(2)); - attron(COLOR_PAIR(1)); - mvprintw(offset+optionsNumber+extraOptionsNumber, 1, "%s", topText); - attroff(COLOR_PAIR(1)); - key = getch(); //get some int which value correspond to some key being pressed + // Printf extraOptions with highlighting + attron(COLOR_PAIR(2)); + for (int k = optionsNumber; k < optionsNumber + extraOptionsNumber; k++) { + if (k == highlight) { + attron(A_REVERSE); + } + mvprintw(k + offset, 2, "%s", options[k]); + if (k == highlight) { + attroff(A_REVERSE); + } + } + attroff(COLOR_PAIR(2)); + attron(COLOR_PAIR(1)); + mvprintw(offset + optionsNumber + extraOptionsNumber, 1, "%s", topText); + attroff(COLOR_PAIR(1)); + key = getch(); // get some int which value correspond to some key being pressed - switch(key) { + switch (key) { case KEY_UP: case 'w': case 'W': - highlight--; - if (highlight < 0) { - highlight = optionsNumber + extraOptionsNumber - 1;// can't select the -1nth option - } - break; + highlight--; + if (highlight < 0) { + highlight = optionsNumber + extraOptionsNumber - 1; // can't select the -1nth option + } + break; case 's': case 'S': case KEY_DOWN: - highlight++; - if (highlight >= optionsNumber + extraOptionsNumber) { - highlight = 0; - } - break; + highlight++; + if (highlight >= optionsNumber + extraOptionsNumber) { + highlight = 0; + } + break; case 10: //\n - goto end_loop; - } + goto end_loop; + } } - end_loop: +end_loop: endwin(); // end ncurses mode fflush(stderr); debug("Selected option: %s", options[highlight]); diff --git a/src/ui.h b/src/ui.h index f9583fa..ab7bf72 100644 --- a/src/ui.h +++ b/src/ui.h @@ -1,47 +1,47 @@ #ifndef UI_H #define UI_H +#include "utils.h" +#include +#include +#include +#include +#include #include #include #include -#include -#include #include -#include -#include -#include -#include -#include "utils.h" -#define CMD_BUFFER 4096 // for fzfSelect +#include +#define CMD_BUFFER 4096 // for fzfSelect #define RESULT_BUFFER 1024 // for fzfSelect -/*Uses ncurses to get an input from the user. -Creates a new note with this input. -also can create a journal if the name matches with journalRegex. -returns the path to the note. +/*Uses ncurses to get an input from the user. +Creates a new note with this input. +also can create a journal if the name matches with journalRegex. +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); +char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, + char *journalRegex, int debug); /* 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. +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 **directoriesArray, int directoryCount, char **vaultsArray, int vaultCount, 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. -options is the array of strings with all the options. -optionsText is the text that will be printed at the top (For example: "Please select ..."). -we distinguish options from extraOptions. +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. options is the array of strings with all the options. optionsText is the text that will be +printed at the top (For example: "Please select ..."). we distinguish options from extraOptions. options could be the list of all notes or all vaults. -extraOptions are options that are special and have a special color (for ex: "Delete vault", "Settings", etc.). -note: extraOptions should be at the end of options. -topText is printed between options text and the start of options. -middleText (usally \n) is printed between options and extraOptions. -bottomText is printed bellow. -topText and middle must be exactly one line. If you want empty lines use " " and not "". -returns the selected option. */ -char* ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int debug); +extraOptions are options that are special and have a special color (for ex: "Delete vault", +"Settings", etc.). note: extraOptions should be at the end of options. topText is printed between +options text and the start of options. middleText (usally \n) is printed between options and +extraOptions. bottomText is printed bellow. topText and middle must be exactly one line. If you want +empty lines use " " and not "". returns the selected option. */ +char *ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, + char *bottomText, char *middleText, char *topText, int debug); #endif diff --git a/src/utils.c b/src/utils.c index 4925341..f44f8cb 100644 --- a/src/utils.c +++ b/src/utils.c @@ -11,88 +11,95 @@ int compareString(const void *a, const void *b) { int reverseCompareString(const void *a, const void *b) { const char *str1 = *(const char **)a; const char *str2 = *(const char **)b; - return -1*strcmp(str1, str2); + return -1 * strcmp(str1, str2); } void getCurrentTime(int *hour, int *minute, int *second) { - time_t now = time(NULL); // Get current time in seconds since epoch - struct tm *local = localtime(&now); // Convert to local time structure + time_t now = time(NULL); // Get current time in seconds since epoch + struct tm *local = localtime(&now); // Convert to local time structure - *hour = local->tm_hour; // Extract hour - *minute = local->tm_min; // Extract minutes - *second = local->tm_sec; // Extract seconds + *hour = local->tm_hour; // Extract hour + *minute = local->tm_min; // Extract minutes + *second = local->tm_sec; // Extract seconds } -void _debug(const int d, const char *file, const int line, const char *function, const char *message, ...) { // use for formatted debug - if (d) { - fflush(stdout); - fflush(stderr); - va_list args; //variadic function stuff - va_start(args, message); - int h, m, s; - getCurrentTime(&h, &m, &s); - fprintf(stderr, "\e[0;32m[DEBUG -- %d:%d:%d] From file %s line %d function %s:\e[0m\n", h, m, s, file, line, function); - vfprintf(stderr, message, args); - fprintf(stderr, "\e[0m\n"); - va_end(args); - } +void _debug(const int d, const char *file, const int line, const char *function, + const char *message, ...) { // use for formatted debug + if (d) { + fflush(stdout); + fflush(stderr); + va_list args; // variadic function stuff + va_start(args, message); + int h, m, s; + getCurrentTime(&h, &m, &s); + fprintf(stderr, "\e[0;32m[DEBUG -- %d:%d:%d] From file %s line %d function %s:\e[0m\n", h, + m, s, file, line, function); + vfprintf(stderr, message, args); + fprintf(stderr, "\e[0m\n"); + va_end(args); + } } -void _altDebug(const int d, const char *message, ...) { // use for less formal debuggin. (usefull if enumerating or making a list - if (d) { - fflush(stdout); - fflush(stderr); - va_list args; - va_start(args, message); - vfprintf(stderr, message, args); - va_end(args); - } +void _altDebug(const int d, const char *message, + ...) { // use for less formal debuggin. (usefull if enumerating or making a list + if (d) { + fflush(stdout); + fflush(stderr); + va_list args; + va_start(args, message); + vfprintf(stderr, message, args); + va_end(args); + } } -void _error(const int shouldDebug, const int condition, const char *type, const char *file, const int line, const char *function, const char *message, ...) { // used for formatted errors - if (condition) { - int h, m, s; - getCurrentTime(&h, &m, &s); - fprintf(stderr, "\e[0;31m[%s ERROR -- %d:%d:%d] From file %s line %d function %s:\n", type, h, m, s, file, line, function); - if (errno != 0) { - fprintf(stderr, " (System-level error message: %s)\n", strerror(errno)); - } else { - fprintf(stderr, " (No system-level error; issue is application-level)\n"); - } - va_list args; - va_start(args, message); - vfprintf(stderr, message, args); - va_end(args); - if (!shouldDebug) { - fprintf(stderr, "\nRunning notewrapper -V might give you more information."); +void _error(const int shouldDebug, const int condition, const char *type, const char *file, + const int line, const char *function, const char *message, + ...) { // used for formatted errors + if (condition) { + int h, m, s; + getCurrentTime(&h, &m, &s); + fprintf(stderr, "\e[0;31m[%s ERROR -- %d:%d:%d] From file %s line %d function %s:\n", type, + h, m, s, file, line, function); + if (errno != 0) { + fprintf(stderr, " (System-level error message: %s)\n", strerror(errno)); + } else { + fprintf(stderr, " (No system-level error; issue is application-level)\n"); + } + va_list args; + va_start(args, message); + vfprintf(stderr, message, args); + va_end(args); + if (!shouldDebug) { + fprintf(stderr, "\nRunning notewrapper -V might give you more information."); + } + fprintf(stderr, "\e[0m\n"); + exit(1); } - fprintf(stderr, "\e[0m\n"); - exit(1); - } } -static void copyDir(const char *source, const char *destination, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug) { +static void copyDir(const char *source, const char *destination, const char **rsyncArgs, + const int rsyncArgsNumber, const int shouldDebug) { debug("Backuping... source: %s and destination: %s", source, destination); debug("Rsync has %d extra arguments", rsyncArgsNumber); pid_t pid = fork(); - + error(pid < 0, "program", "fork() faild. pid = %d", pid); if (pid == 0) { // Child process: execute rsync - char **args = malloc((4 + rsyncArgsNumber)*sizeof(char*)); + char **args = malloc((4 + rsyncArgsNumber) * sizeof(char *)); args[0] = "rsync"; for (int i = 0; i < rsyncArgsNumber; i++) { - args[i+1] = (char *)rsyncArgs[i]; - if (i == rsyncArgsNumber-1) { // last loop - args[i+2] = (char*)source; - args[i+3] = (char*)destination; - args[i+4] = NULL; // execvp expect last arg to be NULL - debug("rsync command:"); - for (int k = 0; k <= i+4; k++) { - altDebug("%s ", args[k]); + args[i + 1] = (char *)rsyncArgs[i]; + if (i == rsyncArgsNumber - 1) { // last loop + args[i + 2] = (char *)source; + args[i + 3] = (char *)destination; + args[i + 4] = NULL; // execvp expect last arg to be NULL + debug("rsync command:"); + for (int k = 0; k <= i + 4; k++) { + altDebug("%s ", args[k]); + } + altDebug("\n"); } - altDebug("\n"); - } } execvp("rsync", args); // If execvp returns, there was an error @@ -115,24 +122,21 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) { // sometimes .cache and .config doesn't exist. Such as with github actions machines char config_dir[PATH_MAX]; char cache_dir[PATH_MAX]; - - snprintf(config_dir, sizeof(config_dir), - "%s/.config/", home); - snprintf(cache_dir, sizeof(cache_dir), - "%s/.cache/", home); + snprintf(config_dir, sizeof(config_dir), "%s/.config/", home); + + snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/", home); ensureDir(config_dir, shouldDebug); ensureDir(cache_dir, shouldDebug); - + strncat(config_dir, "notewrapper/", PATH_MAX); strncat(cache_dir, "notewrapper/", PATH_MAX); ensureDir(config_dir, shouldDebug); ensureDir(cache_dir, shouldDebug); - - char config_file[PATH_MAX+12]; + char config_file[PATH_MAX + 12]; snprintf(config_file, sizeof(config_file), "%s/config.json", config_dir); FILE *f = fopen(config_file, "r"); @@ -143,29 +147,34 @@ 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, //TODO CHANGE default json -"{\n" -" \"directory\": [\"~/Documents/\"],\n"// I personally use ~/Documents/Notes. But the dir doesn't exist most times. So for the default user it is better to put ~/Documents -" \"render\": true,\n" -" \"jumpToEndOfFileOnLaunch\": true,\n" -" \"editor\": \"neovim\",\n" -" \"journalRegex\": \".*journal.*\",\n" -" \"dateEntry\": \"# %%Y %%m %%d %%a\",\n" -" \"newLineOnOpening\": true,\n" -" \"backup\": {\n" -" \"enable\": false,\n" -" \"directory\": {\n" -" \"~/Documents/\": \"path/to/backup/\"\n" -" },\n" -" \"interval\": \"weekly\",\n" -" \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n" -" }\n" -"}\n"); + fprintf(w, // TODO CHANGE default json + "{\n" + " \"directory\": [\"~/Documents/\"],\n" // I personally use ~/Documents/Notes. But the + // dir doesn't exist most times. So for the + // default user it is better to put ~/Documents + " \"render\": true,\n" + " \"jumpToEndOfFileOnLaunch\": true,\n" + " \"editor\": \"neovim\",\n" + " \"journalRegex\": \".*journal.*\",\n" + " \"dateEntry\": \"# %%Y %%m %%d %%a\",\n" + " \"newLineOnOpening\": true,\n" + " \"backup\": {\n" + " \"enable\": false,\n" + " \"directory\": {\n" + " \"~/Documents/\": \"path/to/backup/\"\n" + " },\n" + " \"interval\": \"weekly\",\n" + " \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n" + " }\n" + "}\n"); fclose(w); } -void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **destinationDirectoryArray, 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 +211,8 @@ void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **d } if (difftime(now, lastBackupTime) > interval) { - debug("(difftime) %d is greater than (interval) %d -> backuping...", difftime(now, lastBackupTime), interval); + debug("(difftime) %d is greater than (interval) %d -> backuping...", + difftime(now, lastBackupTime), interval); shouldBackup = 1; cacheFile = fopen(cacheFilePATH, "w"); if (!cacheFile) { @@ -211,41 +221,57 @@ void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **d fprintf(cacheFile, "%ld\n", (long)now); fclose(cacheFile); } else { - debug("(difftime) %d is smaller than (interval) %d -> no need to backup.", difftime(now, lastBackupTime), interval); + debug("(difftime) %d is smaller than (interval) %d -> no need to backup.", + difftime(now, lastBackupTime), interval); } } if (shouldBackup) { - debug("A backup is needed"); - for (int i = 0; i < sourceNumber; i++) { - debug("%s", destinationDirectoryArray[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); + debug("A backup is needed"); + for (int i = 0; i < sourceNumber; i++) { + debug("%s", destinationDirectoryArray[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); + } } - } } } -int isEditorValid (char *editorToCheck, int useDefaultEditor, int shouldDebug) { // check if editor is supported and if it is installed - // check if supported +int isEditorValid(char *editorToCheck, int useDefaultEditor, + int shouldDebug) { // check if editor is supported and if it is installed + // check if supported if (useDefaultEditor) { // we use a different error message if it defaulted to $EDITOR - error(!isStringInArray(editorToCheck, supportedEditor, numEditors), "user", "%s is not a supported editor.\n(defaulted to $EDITOR as neither \"editor\" was set in the configuration file nor -e/--editor was set.)\n See https://github.com/tomasriveral/NoteWrapper#editor-support for a list of supported editors.", editorToCheck); + error(!isStringInArray(editorToCheck, supportedEditor, numEditors), "user", + "%s is not a supported editor.\n(defaulted to $EDITOR as neither \"editor\" was set " + "in the configuration file nor -e/--editor was set.)\n See " + "https://github.com/tomasriveral/NoteWrapper#editor-support for a list of supported " + "editors.", + editorToCheck); } else { - error(!isStringInArray(editorToCheck, supportedEditor, numEditors), "user", "%s is not a supported editor.\n See https://github.com/tomasriveral/NoteWrapper#editor-support for a list of supported editors.", editorToCheck); + error(!isStringInArray(editorToCheck, supportedEditor, numEditors), "user", + "%s is not a supported editor.\n See " + "https://github.com/tomasriveral/NoteWrapper#editor-support for a list of supported " + "editors.", + editorToCheck); } // check if installed - char *editor; - if (strcmp(editorToCheck, "neovim") == 0) { // some executables are not name the same as the project - editor = strdup("nvim"); // we must use strdup and not just copy as we would have modified editorToOpen in main + char *editor; + if (strcmp(editorToCheck, "neovim") == + 0) { // some executables are not name the same as the project + editor = strdup("nvim"); // we must use strdup and not just copy as we would have modified + // editorToOpen in main } else if (strcmp(editorToCheck, "helix") == 0) { - editor = strdup("hx"); + editor = strdup("hx"); } else if (strcmp(editorToCheck, "kakoune") == 0) { - editor = strdup("kak"); + editor = strdup("kak"); } else { - editor = strdup(editorToCheck); + editor = strdup(editorToCheck); } char *path_env = getenv("PATH"); - error(!path_env, "program", "getenv(\"PATH\") failed to get your path. NoteWrapper is unable to check if your desired editor is installed\n"); + error(!path_env, "program", + "getenv(\"PATH\") failed to get your path. NoteWrapper is unable to check if your " + "desired editor is installed\n"); debug("Your PATH is %s", path_env); char *paths = strdup(path_env); // duplicate because strtok modifies the string char *dir = strtok(paths, ":"); @@ -266,43 +292,47 @@ int isEditorValid (char *editorToCheck, int useDefaultEditor, int shouldDebug) { } char *getFormatedTime(char *format, int shouldDebug) { - debug("Inputed time format is %s", format); - time_t t = time(NULL); - struct tm *tm = localtime(&t); - char *buf = malloc(BUFFER_SIZE); - strftime(buf, BUFFER_SIZE, format, tm); - debug("Formated time is %s", buf); - return buf; + debug("Inputed time format is %s", format); + time_t t = time(NULL); + struct tm *tm = localtime(&t); + char *buf = malloc(BUFFER_SIZE); + strftime(buf, BUFFER_SIZE, format, tm); + debug("Formated time is %s", buf); + return buf; } int isStringInArray(const char *string, const char **array, const int len) { - for (int i = 0; i < len; i++) { - if (strcmp(string, array[i]) == 0) { - return 1; + for (int i = 0; i < len; i++) { + if (strcmp(string, array[i]) == 0) { + return 1; + } } - } - return 0; + return 0; } int isStringInFile(const char *path, const char *string, const int shouldDebug) { - FILE *file = fopen(path, "r"); - error(file == NULL, "program", "While searching for \"%s\", we could not open file %s", string, path); - char buf[BUFFER_SIZE]; - - while (fgets(buf, BUFFER_SIZE, file) != NULL) { - if (strstr(buf, string) != NULL) { - debug("%s contains the substring %s", buf, string); - return 1; + FILE *file = fopen(path, "r"); + error(file == NULL, "program", "While searching for \"%s\", we could not open file %s", string, + path); + char buf[BUFFER_SIZE]; + + while (fgets(buf, BUFFER_SIZE, file) != NULL) { + if (strstr(buf, string) != NULL) { + debug("%s contains the substring %s", buf, string); + return 1; + } } - } - debug("The string %s is not inside %s", string, path); - return 0; + debug("The string %s is not inside %s", string, path); + return 0; } void appendToFile(const char *path, const char *string, const int shouldDebug) { FILE *file = fopen(path, "r"); char lastLine[1024] = {0}; error(file == NULL, "program", "could not open %s", path); - char buffer[BUFFER_SIZE]; // it does not matter if we have a small buffer. string is relatively small (most time \n or the name of the file). So it is under BUFFER_SIZE. If the last line is more than BUFFER_SIZE. It can't be equal to string + char buffer[BUFFER_SIZE]; // it does not matter if we have a small buffer. string is relatively + // small (most time \n or the name of the file). So it is under + // BUFFER_SIZE. If the last line is more than BUFFER_SIZE. It can't be + // equal to string // Read file line by line to get the last one while (fgets(buffer, sizeof(buffer), file) != NULL) { @@ -329,17 +359,18 @@ void appendToFile(const char *path, const char *string, const int shouldDebug) { } void sanitize(char *string) { - size_t stringLenght = strlen(string); - for (size_t i = 0; i < stringLenght; i++) { - if ((!isalnum((unsigned char)string[i]) && strchr("~/\\:*?\"\'|!$[]{}<>\n\r\t", string[i]))) { // replace unwanted chars by '_' - string[i] = '_'; + size_t stringLenght = strlen(string); + for (size_t i = 0; i < stringLenght; i++) { + if ((!isalnum((unsigned char)string[i]) && + strchr("~/\\:*?\"\'|!$[]{}<>\n\r\t", string[i]))) { // replace unwanted chars by '_' + string[i] = '_'; + } + } + // removes any leading '. + size_t i = 0; + while (i < stringLenght && string[i] == '.') { + string[i++] = '_'; } - } - // removes any leading '. - size_t i = 0; - while (i < stringLenght && string[i] == '.') { - string[i++] = '_'; - } } // both functions are from https://stackoverflow.com/a/5467788 @@ -347,301 +378,296 @@ void sanitize(char *string) { // remove() can't delete directories with files // so it walks the file tree and deletes it's content before removing the directory int unlink_cb(const char *filePath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { - (void)sb; - (void)typeflag; - (void)ftwbuf; - - int shouldDebug = 1; //normally shouldDebug should be passed to the function. However, it's to difficult here and the best it to set it to 1. - - // some sanity checks to be sure that we don't delete something we shouldn't - error(strcmp(filePath, "") == 0, "program", "filePath is empty. Refusing to delete directory"); - error(filePath[0] == '.', "program", "%s starts with \".\". For security reasons, use absolute path and not relative path.", filePath); - - int rv = remove(filePath); - error(rv, "program", "remove() failed to delete %s", filePath); - return rv; + (void)sb; + (void)typeflag; + (void)ftwbuf; + + int shouldDebug = 1; // normally shouldDebug should be passed to the function. However, it's to + // difficult here and the best it to set it to 1. + + // some sanity checks to be sure that we don't delete something we shouldn't + error(strcmp(filePath, "") == 0, "program", "filePath is empty. Refusing to delete directory"); + error(filePath[0] == '.', "program", + "%s starts with \".\". For security reasons, use absolute path and not relative path.", + filePath); + + int rv = remove(filePath); + error(rv, "program", "remove() failed to delete %s", filePath); + return rv; } int rmrf(char *path, int shouldDebug) { - error(strcmp(path, "/") == 0, "program", "Refusing to delete root directory"); - return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS); + error(strcmp(path, "/") == 0, "program", "Refusing to delete root directory"); + return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS); } - int openEditor(char *path, char *editor, int render, int shouldJumpToEndOfFile, int shouldDebug) { -// this ensures that ncurses won't affect the editor behaviour -pid_t editor_pid = fork(); -error(editor_pid < 0, "program", "fork() failed."); - -// instead of reusing part of the codes, for any new editor copy an example and adapt it. This is in case we need a custom fix for an editor. - -if (editor_pid == 0) { - // ========================= - // CHILD: launch editor - // ========================= - - // ---- NEOVIM / VIM ---- - if (strcmp(editor, "neovim") == 0 || strcmp(editor, "vim") == 0) { - const char *bin = (strcmp(editor, "neovim") == 0) ? "nvim" : "vim"; - - if (render) { - if (shouldJumpToEndOfFile) { - debug("Running %s +:$ +:Vivify %s", bin, path); - execlp(bin, bin, "+:$", "+:Vivify", path, NULL); - } else { - debug("Running %s +:Vivify %s", bin, path); - execlp(bin, bin, "+:Vivify", path, NULL); - } - } else { - if (shouldJumpToEndOfFile) { - debug("Running %s +:$ %s", bin, path); - execlp(bin, bin, "+:$", path, NULL); - } else { - debug("Running %s %s", bin, path); - execlp(bin, bin, path, NULL); - } - } - - error(1, "program", "execlp() failed."); - // ---- MICRO ---- - } else if (strcmp(editor, "micro") == 0) { - - if (render) { - if (shouldJumpToEndOfFile) { - debug("Running MICRO_VIVIFY=1 micro %s +99999", path); - setenv("MICRO_VIVIFY", "1", 1); - execlp("micro", "micro", path, "+999999", NULL); - } else { - debug("Running MICRO_VIVIFY=1 micro %s", path); - setenv("MICRO_VIVIFY", "1", 1); - execlp("micro", "micro", path, NULL); - } - } else { - if (shouldJumpToEndOfFile) { - debug("Running micro %s +999999", path); - execlp("micro", "micro", path, "+999999", NULL); - } else { - debug("Running micro %s", path); - execlp("micro", "micro", path, NULL); - } - } - - error(1, "program", "execlp() failed."); - } - - // ---- NANO ---- - else if (strcmp(editor, "nano") == 0) { - - // If render enabled → spawn viv in parallel - if (render) { - debug("Running Vivify..."); - pid_t viv_pid = fork(); - error(viv_pid < 0, "program", "fork() failed."); - - if (viv_pid == 0) { - // GRANDCHILD → viv - + // this ensures that ncurses won't affect the editor behaviour + pid_t editor_pid = fork(); + error(editor_pid < 0, "program", "fork() failed."); + + // instead of reusing part of the codes, for any new editor copy an example and adapt it. This + // is in case we need a custom fix for an editor. + + if (editor_pid == 0) { + // ========================= + // CHILD: launch editor + // ========================= + + // ---- NEOVIM / VIM ---- + if (strcmp(editor, "neovim") == 0 || strcmp(editor, "vim") == 0) { + const char *bin = (strcmp(editor, "neovim") == 0) ? "nvim" : "vim"; + + if (render) { + if (shouldJumpToEndOfFile) { + debug("Running %s +:$ +:Vivify %s", bin, path); + execlp(bin, bin, "+:$", "+:Vivify", path, NULL); + } else { + debug("Running %s +:Vivify %s", bin, path); + execlp(bin, bin, "+:Vivify", path, NULL); + } + } else { + if (shouldJumpToEndOfFile) { + debug("Running %s +:$ %s", bin, path); + execlp(bin, bin, "+:$", path, NULL); + } else { + debug("Running %s %s", bin, path); + execlp(bin, bin, path, NULL); + } + } - char viv_path[PATH_MAX]; - strncpy(viv_path, path, PATH_MAX - 1); - viv_path[PATH_MAX - 1] = '\0'; + error(1, "program", "execlp() failed."); + // ---- MICRO ---- + } else if (strcmp(editor, "micro") == 0) { + + if (render) { + if (shouldJumpToEndOfFile) { + debug("Running MICRO_VIVIFY=1 micro %s +99999", path); + setenv("MICRO_VIVIFY", "1", 1); + execlp("micro", "micro", path, "+999999", NULL); + } else { + debug("Running MICRO_VIVIFY=1 micro %s", path); + setenv("MICRO_VIVIFY", "1", 1); + execlp("micro", "micro", path, NULL); + } + } else { + if (shouldJumpToEndOfFile) { + debug("Running micro %s +999999", path); + execlp("micro", "micro", path, "+999999", NULL); + } else { + debug("Running micro %s", path); + execlp("micro", "micro", path, NULL); + } + } - if (shouldJumpToEndOfFile) { - strncat(viv_path, ":99999", - PATH_MAX - strlen(viv_path) - 1); + error(1, "program", "execlp() failed."); } - debug("Running viv %s", viv_path); - execlp("viv", "viv", viv_path, NULL); - error(1, "program", "execlp() failed."); - } - // IMPORTANT: do NOT wait for viv - } + // ---- NANO ---- + else if (strcmp(editor, "nano") == 0) { - // Now run nano (this replaces the child process) - if (shouldJumpToEndOfFile) { - debug("Running nano + %s", path); - execlp("nano", "nano", "+", path, NULL); - } else { - debug("Running nano %s", path); - execlp("nano", "nano", path, NULL); - } + // If render enabled → spawn viv in parallel + if (render) { + debug("Running Vivify..."); + pid_t viv_pid = fork(); + error(viv_pid < 0, "program", "fork() failed."); - error(1, "program", "execlp() failed."); - // ---- HELIX ---- - } else if (strcmp(editor, "helix") == 0) { + if (viv_pid == 0) { + // GRANDCHILD → viv - // If render enabled → spawn viv in parallel - if (render) { - debug("Running Vivify..."); - pid_t viv_pid = fork(); - error(viv_pid < 0, "program", "fork() failed."); + char viv_path[PATH_MAX]; + strncpy(viv_path, path, PATH_MAX - 1); + viv_path[PATH_MAX - 1] = '\0'; - if (viv_pid == 0) { - // GRANDCHILD → viv - + if (shouldJumpToEndOfFile) { + strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1); + } - char viv_path[PATH_MAX]; - strncpy(viv_path, path, PATH_MAX - 1); - viv_path[PATH_MAX - 1] = '\0'; + debug("Running viv %s", viv_path); + execlp("viv", "viv", viv_path, NULL); + error(1, "program", "execlp() failed."); + } + // IMPORTANT: do NOT wait for viv + } - if (shouldJumpToEndOfFile) { - strncat(viv_path, ":99999", - PATH_MAX - strlen(viv_path) - 1); - } + // Now run nano (this replaces the child process) + if (shouldJumpToEndOfFile) { + debug("Running nano + %s", path); + execlp("nano", "nano", "+", path, NULL); + } else { + debug("Running nano %s", path); + execlp("nano", "nano", path, NULL); + } - debug("Running viv %s", viv_path); - execlp("viv", "viv", viv_path, NULL); - error(1, "program", "execlp() failed."); - } - // IMPORTANT: do NOT wait for viv - } + error(1, "program", "execlp() failed."); + // ---- HELIX ---- + } else if (strcmp(editor, "helix") == 0) { - // Now run helix (this replaces the child process) - if (shouldJumpToEndOfFile) { - strncat(path, ":99999", PATH_MAX); - debug("Running hx %s", path); - execlp("hx", "hx", path, NULL); - } else { - debug("Running hx %s", path); - execlp("hx", "hx", path, NULL); - } + // If render enabled → spawn viv in parallel + if (render) { + debug("Running Vivify..."); + pid_t viv_pid = fork(); + error(viv_pid < 0, "program", "fork() failed."); - error(1, "program", "execlp() failed."); - } - // ---- kakoune ---- - else if (strcmp(editor, "kakoune") == 0) { + if (viv_pid == 0) { + // GRANDCHILD → viv - // If render enabled → spawn viv in parallel - if (render) { - debug("Running Vivify..."); - pid_t viv_pid = fork(); - error(viv_pid < 0, "program", "fork() failed."); + char viv_path[PATH_MAX]; + strncpy(viv_path, path, PATH_MAX - 1); + viv_path[PATH_MAX - 1] = '\0'; - if (viv_pid == 0) { - // GRANDCHILD → viv - + if (shouldJumpToEndOfFile) { + strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1); + } + + debug("Running viv %s", viv_path); + execlp("viv", "viv", viv_path, NULL); + error(1, "program", "execlp() failed."); + } + // IMPORTANT: do NOT wait for viv + } - char viv_path[PATH_MAX]; - strncpy(viv_path, path, PATH_MAX - 1); - viv_path[PATH_MAX - 1] = '\0'; + // Now run helix (this replaces the child process) + if (shouldJumpToEndOfFile) { + strncat(path, ":99999", PATH_MAX); + debug("Running hx %s", path); + execlp("hx", "hx", path, NULL); + } else { + debug("Running hx %s", path); + execlp("hx", "hx", path, NULL); + } - if (shouldJumpToEndOfFile) { - strncat(viv_path, ":99999", - PATH_MAX - strlen(viv_path) - 1); + error(1, "program", "execlp() failed."); } + // ---- kakoune ---- + else if (strcmp(editor, "kakoune") == 0) { + + // If render enabled → spawn viv in parallel + if (render) { + debug("Running Vivify..."); + pid_t viv_pid = fork(); + error(viv_pid < 0, "program", "fork() failed."); + + if (viv_pid == 0) { + // GRANDCHILD → viv + + char viv_path[PATH_MAX]; + strncpy(viv_path, path, PATH_MAX - 1); + viv_path[PATH_MAX - 1] = '\0'; + + if (shouldJumpToEndOfFile) { + strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1); + } + + debug("Running viv %s", viv_path); + execlp("viv", "viv", viv_path, NULL); + error(1, "program", "execlp() failed."); + } + // IMPORTANT: do NOT wait for viv + } - debug("Running viv %s", viv_path); - execlp("viv", "viv", viv_path, NULL); - error(1, "program", "execlp() failed."); - } - // IMPORTANT: do NOT wait for viv - } + // Now run nano (this replaces the child process) + if (shouldJumpToEndOfFile) { + debug("Running kak + %s", path); + execlp("kak", "kak", "+", path, NULL); + } else { + debug("Running kak %s", path); + execlp("kak", "kak", path, NULL); + } - // Now run nano (this replaces the child process) - if (shouldJumpToEndOfFile) { - debug("Running kak + %s", path); - execlp("kak", "kak", "+", path, NULL); - } else { - debug("Running kak %s", path); - execlp("kak", "kak", path, NULL); - } + error(1, "program", "execlp() failed."); + // ---- VI ---- + } else if (strcmp(editor, "vi") == 0) { - error(1, "program", "execlp() failed."); - // ---- VI ---- - } else if (strcmp(editor, "vi") == 0) { + // If render enabled → spawn viv in parallel + if (render) { + debug("Running Vivify..."); + pid_t viv_pid = fork(); + error(viv_pid < 0, "program", "fork() failed."); - // If render enabled → spawn viv in parallel - if (render) { - debug("Running Vivify..."); - pid_t viv_pid = fork(); - error(viv_pid < 0, "program", "fork() failed."); + if (viv_pid == 0) { + // GRANDCHILD → viv - if (viv_pid == 0) { - // GRANDCHILD → viv - + char viv_path[PATH_MAX]; + strncpy(viv_path, path, PATH_MAX - 1); + viv_path[PATH_MAX - 1] = '\0'; - char viv_path[PATH_MAX]; - strncpy(viv_path, path, PATH_MAX - 1); - viv_path[PATH_MAX - 1] = '\0'; + if (shouldJumpToEndOfFile) { + strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1); + } - if (shouldJumpToEndOfFile) { - strncat(viv_path, ":99999", - PATH_MAX - strlen(viv_path) - 1); - } + debug("Running viv %s", viv_path); + execlp("viv", "viv", viv_path, NULL); + error(1, "program", "execlp() failed."); + } + // IMPORTANT: do NOT wait for viv + } - debug("Running viv %s", viv_path); - execlp("viv", "viv", viv_path, NULL); - error(1, "program", "execlp() failed."); - } - // IMPORTANT: do NOT wait for viv - } + // Now run vi (this replaces the child process) + if (shouldJumpToEndOfFile) { + debug("Running vi +:$ %s", path); + execlp("vi", "vi", "+:$", path, NULL); + } else { + debug("Running vi %s", path); + execlp("vi", "vi", path, NULL); + } - // Now run vi (this replaces the child process) - if (shouldJumpToEndOfFile) { - debug("Running vi +:$ %s", path); - execlp("vi", "vi", "+:$", path, NULL); - } else { - debug("Running vi %s", path); - execlp("vi", "vi", path, NULL); - } + error(1, "program", "execlp() failed."); + // ---- Jed ---- + } else if (strcmp(editor, "jed") == 0) { - error(1, "program", "execlp() failed."); - // ---- Jed ---- - } else if (strcmp(editor, "jed") == 0) { + // If render enabled → spawn viv in parallel + if (render) { + debug("Running Vivify..."); + pid_t viv_pid = fork(); + error(viv_pid < 0, "program", "fork() failed."); - // If render enabled → spawn viv in parallel - if (render) { - debug("Running Vivify..."); - pid_t viv_pid = fork(); - error(viv_pid < 0, "program", "fork() failed."); + if (viv_pid == 0) { + // GRANDCHILD → viv - if (viv_pid == 0) { - // GRANDCHILD → viv - + char viv_path[PATH_MAX]; + strncpy(viv_path, path, PATH_MAX - 1); + viv_path[PATH_MAX - 1] = '\0'; - char viv_path[PATH_MAX]; - strncpy(viv_path, path, PATH_MAX - 1); - viv_path[PATH_MAX - 1] = '\0'; + if (shouldJumpToEndOfFile) { + strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1); + } - if (shouldJumpToEndOfFile) { - strncat(viv_path, ":99999", - PATH_MAX - strlen(viv_path) - 1); - } + debug("Running viv %s", viv_path); + execlp("viv", "viv", viv_path, NULL); + error(1, "program", "execlp() failed."); + } + // IMPORTANT: do NOT wait for viv + } - debug("Running viv %s", viv_path); - execlp("viv", "viv", viv_path, NULL); - error(1, "program", "execlp() failed."); - } - // IMPORTANT: do NOT wait for viv - } + // Now run jed (this replaces the child process) + if (shouldJumpToEndOfFile) { + debug("Running jed %s -g 99999999", path); + execlp("jed", "jed", path, "-g", "99999999", NULL); + } else { + debug("Running jed %s", path); + execlp("jed", "jed", path, NULL); + } - // Now run jed (this replaces the child process) - if (shouldJumpToEndOfFile) { - debug("Running jed %s -g 99999999", path); - execlp("jed", "jed", path, "-g", "99999999", NULL); - } else { - debug("Running jed %s", path); - execlp("jed", "jed", path, NULL); + error(1, "program", "execlp() failed."); + // ---- UNKNOWN EDITOR ---- + } else { + error(1, "program", "Unknown editor."); + } } - error(1, "program", "execlp() failed."); - // ---- UNKNOWN EDITOR ---- - } else { - error(1, "program", "Unknown editor."); - } - } - // ========================= // PARENT: wait ONLY editor // ========================= int status; - while (waitpid(editor_pid, &status, 0) == -1) { // we can't just use waitpid(). Because when resizing the terminal, waitpid() is returned so we loop to see if there is not a problem - if (errno != EINTR) { - perror("waitpid"); - break; - } + while (waitpid(editor_pid, &status, 0) == + -1) { // we can't just use waitpid(). Because when resizing the terminal, waitpid() is + // returned so we loop to see if there is not a problem + if (errno != EINTR) { + perror("waitpid"); + break; + } } return 0; } diff --git a/src/utils.h b/src/utils.h index a805024..046356e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,60 +1,60 @@ #ifndef UTILS_H #define UTILS_H +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include #include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include #include -#include +#include #ifndef VERSION #define VERSION "dev" #endif -#define BUFFER_SIZE 256 //standard buffer size. -// the three supported values are "daily" "weekly" and "monthly" (for months we use the avarage lenght of a month in a non leap year). All values were calculated on my loyal TI-30 ECO RS +#define BUFFER_SIZE 256 // standard buffer size. +// the three supported values are "daily" "weekly" and "monthly" (for months we use the avarage +// lenght of a month in a non leap year). All values were calculated on my loyal TI-30 ECO RS #define DAILY 86400 #define WEEKLY 604800 #define MONTHLY 2628000 // calculated from the average lenght of a non leap year -#define debug(message, ...) \ +#define debug(message, ...) \ _debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__) -#define altDebug(message, ...) \ - _altDebug(shouldDebug, message, ##__VA_ARGS__) -#define error(condition, type, message, ...) \ - _error(shouldDebug, condition, type, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__) +#define altDebug(message, ...) _altDebug(shouldDebug, message, ##__VA_ARGS__) +#define error(condition, type, message, ...) \ + _error(shouldDebug, condition, type, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__) // you must edit this two values if you want to add suport for an editor extern const char *supportedEditor[]; // array of supported editors -extern const int numEditors; // number of supported editors +extern const int numEditors; // number of supported editors - -//compares two strings alphabetically. -//this function is used for qsort +// compares two strings alphabetically. +// this function is used for qsort int compareString(const void *a, const void *b); // compares two strings in reversed alphabetical order // this function is used for qsort int reverseCompareString(const void *a, const void *b); // checks if editor is supported and if it installed. // this basically checks all the dirs from your path for the editor. This is a safety check. -// If the executable from an editor is not the editor name (for example neovim and nvim), you must handle at the start of the function. -// This can return an error and stop the program. +// If the executable from an editor is not the editor name (for example neovim and nvim), you must +// handle at the start of the function. This can return an error and stop the program. int isEditorValid(char *editorToCheck, int useDefaultEditor, int debug); // please use the macro debug instead of _debug. -//formated debugging. -void _debug(const int d, const char *file, const int line, const char *function, const char *message, ...); -//use for less formal debuggin. (usefull if enumerating or making a list). +// formated debugging. +void _debug(const int d, const char *file, const int line, const char *function, + const char *message, ...); +// use for less formal debuggin. (usefull if enumerating or making a list). void _altDebug(const int d, const char *message, ...); // formated error. -void _error(const int shouldDebug, const int condition, const char *type, const char *file, const int line, const char *function, const char *message, ...); +void _error(const int shouldDebug, const int condition, const char *type, const char *file, + const int line, const char *function, const char *message, ...); // Returns 1 if the string is in the array. // Returns 0 if the string is not in the array. // If you want to only check the first n elements of the array, pass n as len. @@ -68,8 +68,8 @@ void appendToFile(const char *path, const char *string, const int shouldDebug); // replace unwanted chars by '_'. // '.' is replaced if it is only the first two chars void sanitize(char *string); -//from https://stackoverflow.com/a/5467788. -//deletes an entire directory. Use with parsimony and carefullness. +// from https://stackoverflow.com/a/5467788. +// deletes an entire directory. Use with parsimony and carefullness. int rmrf(char *path, int shouldDebug); // Inputs are the path to the file, the editor to open and some rendering option. // render: if we render the .md file with Vivify. @@ -84,6 +84,9 @@ char *getFormatedTime(char *format, int shouldDebug); 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(char **sourceDirectoryArray, const int sourceDirectoryNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug); +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(char **sourceDirectoryArray, const int sourceDirectoryNumber, + char **destinationDirectoryArray, const char *homeDir, const int interval, + const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug); #endif