mirror of
https://github.com/tomasriveral/NoteWrapper.git
synced 2026-08-12 02:18:38 +02:00
Added support for backup using rsync
This commit is contained in:
@@ -6,44 +6,95 @@ Using my Neovim setup, I explored plugins like **[neorg](https://github.com/nvim
|
||||
The goal was a terminal-based interface for accessing vaults and notes using standard Markdown, with minimal extra features. Although it currently works only with Neovim, it is designed as a standalone wrapper that could be adapted for other editors with minimal changes.
|
||||
|
||||
### How to install
|
||||
1. Have installed `ncurses`, `cjson` and `pkg-config`.
|
||||
1. Have installed `ncurses`, `cjson`, `make`, `Vivify`, `rsync` and `pkg-config`. <TODO LATER we should put hyprlinks to each program.>
|
||||
2. Have a supported editor: `vim`, `neovim` and `nano` (for the moment)
|
||||
3. Clone the repository.
|
||||
```shell
|
||||
git clone https://github.com/Totorile1/NoteWrapper.git
|
||||
```
|
||||
4. Compile the project. If you are on NixOS and have direnv installed, run `direnv allow`. It will create the nix-shell which will get all the necessary libraries.
|
||||
4. Compile the project. If you are on NixOS and have direnv installed, run `direnv allow`. It will create the nix-shell which will get all the necessary build-time libraries.
|
||||
```
|
||||
cd NoteWrapper
|
||||
make
|
||||
```
|
||||
5. configure `./config.json`
|
||||
5. [configure](#configuration) `./config.json`
|
||||
6. run the binary `notewrapper`
|
||||
|
||||
### Usage
|
||||
If you want to render with Vivify with the flags `-r`, `--render` or with the option in the config file, you must have installed the plugins for your specific editor ([for Vim and Neovim](https://github.com/jannis-baum/vivify.vim))
|
||||
|
||||
|
||||
```
|
||||
Usage: notewrapper [options]
|
||||
Options:
|
||||
-c, --config <path/to/config> Specify the config file.
|
||||
-d, --directory <path/to/directory> Specify the vaults' directory.
|
||||
-h, --help Display this message.
|
||||
-e, --editor Specify the editor to open.
|
||||
-j, --jump Jumps to the end of the file on opening.
|
||||
-J, --no-jump Do not jump to the end of the file
|
||||
-n, --note <note's name> Specify the note (or journal).
|
||||
-r, --render Renders the note with Vivify.
|
||||
-R, --no-render Do not render.
|
||||
-v, --vault <vault's name> Specify the vault.
|
||||
--version Display the program version and the GPL3 notice.
|
||||
-V, --verbose Show debug information.
|
||||
```
|
||||
### Editor support
|
||||
|
||||
`NoteWrapper` relies on certain editor features, so not all of its functionality is supported by every editor.
|
||||
|
||||
Here are the features that depend on editor support:
|
||||
|
||||
- **Bufferless rendering**: As soon as you write something in the note (even without saving), it is rendered.
|
||||
- **Cursor following**: The rendered view follows your cursor. For example, if you move to the bottom of a note, the rendered view will also scroll to the bottom.
|
||||
- **Jump to end on open**: Automatically jumps to the end of the file when opening it.
|
||||
|
||||
The first two features depend on [Vivify's plugin for editors](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#existing-integration).
|
||||
The first two features depend on [Vivify's plugin for editors](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#existing-integration) and are only usefull if you want to render the markdown externally on your browser. If your editor misses these features, you can just create a plugin or an extension which uses [Vivify's simple API](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#editor-support).
|
||||
|
||||
| Editor | Bufferless | Cursor | Jump to end |
|
||||
|---------|------------|--------|-------------|
|
||||
| Neovim | ✅ | ✅ | ✅ |
|
||||
| Vim | ✅ | ✅ | ✅ |
|
||||
| Nano | ❌ | ❌ | ✅ |
|
||||
|
||||
| Editor | Bufferless | Cursor | Jump to end | Necessary plugin |
|
||||
|---------|------------|--------|-------------|------------------|
|
||||
| Neovim | ✅ | ✅ | ✅ | [vivify-vim](https://github.com/jannis-baum/vivify.vim) |
|
||||
| Vim | ✅ | ✅ | ✅ | [vivify-vim](https://github.com/jannis-baum/vivify.vim) |
|
||||
| Nano | ❌ | ❌ | ✅ | |
|
||||
|
||||
### Configuration
|
||||
Change `$/.config/notewrapper/config.json`. If it does not exist. On building, it should copy a default config.
|
||||
Change `~/.config/notewrapper/config.json`. If it does not exist. On building, it should copy a default config.
|
||||
|
||||
```json
|
||||
{
|
||||
"directory": "~/Documents/Notes/",
|
||||
"render": true,
|
||||
"jumpToEndOfFileOnLaunch": true,
|
||||
"editor": "neovim",
|
||||
"journalRegex": ".*journal.*",
|
||||
"dateEntry": "# %a %d %m %Y",
|
||||
"newLineOnOpening": true,
|
||||
"backup": {
|
||||
"enable": false,
|
||||
"directory": "/path/to/backup",
|
||||
"interval": "weekly"
|
||||
}
|
||||
}
|
||||
```
|
||||
`directory` is the directory where the program will search for vaults.
|
||||
|
||||
`render` is a boolean that tells the program if it needs to render the markdown file using `Vivify`
|
||||
|
||||
`jumpToEndOfFileOnLaunch` is a boolean that tells the programs if it needs to set the cursor at the end of the file on launch.
|
||||
|
||||
`editor` is the editor the program will call. (It must be a supported editor.
|
||||
|
||||
`journalRegex` is a regex code. If the note name matches with this code, the program will treat it as a journal.
|
||||
|
||||
`dateEntry` is the style of the file title or paragraph title (depending on the type of journal). See [strftime](https://pubs.opengroup.org/onlinepubs/7908799/xsh/strftime.html?) for more info.
|
||||
|
||||
`newLineOnOpening` is a boolean that tells the program to append a newline when opening a note.
|
||||
|
||||
`backup.enable` enable the backups. The program relies on `rsync` for backuping.
|
||||
|
||||
`backup.directory` is the directory where the backup will go.
|
||||
|
||||
`backup.interval` can either be `daily`, `weekly`, `monthly` or an integer. It specifies the interval between two backups.
|
||||
|
||||
|
||||
|
||||
### What needs to be done (a lot)
|
||||
- [x] Add a way to create vaults
|
||||
@@ -57,15 +108,14 @@ Change `$/.config/notewrapper/config.json`. If it does not exist. On building, i
|
||||
- [x] Actually open vivify when opening nvim
|
||||
- [x] Write the journaling code (separate files or one big journal files)
|
||||
- [x] Adapt createNewNote with journals
|
||||
- [ ] Automatic backups
|
||||
- [x] Automatic backups
|
||||
- [x] Add flags for customization
|
||||
- [ ] Write a good README.md
|
||||
- [x] Option to not render and to only open nvim
|
||||
- [ ] Figure out this vivify issue https://github.com/jannis-baum/Vivify/issues/291
|
||||
- [ ] Maybe port this to other editors
|
||||
- [ ] Port this to other editors
|
||||
- [x] Port to vim
|
||||
- [ ] Fixe all the small stuff marked //(TODO LATER) in the code
|
||||
- [ ] Add a way to delete notes
|
||||
- [x] Comply with GPL-3 notice (add info about no waranty, etc.)
|
||||
- [ ] A converter for both type of journals
|
||||
|
||||
|
||||
+7
-2
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"directory": "$/Documents/Notes/",
|
||||
"directory": "~/Documents/Notes/",
|
||||
"render": true,
|
||||
"jumpToEndOfFileOnLaunch": true,
|
||||
"editor": "neovim",
|
||||
"journalRegex": ".*journal.*",
|
||||
"dateEntry": "# %a %d %m %Y",
|
||||
"newLineOnOpening": true
|
||||
"newLineOnOpening": true,
|
||||
"backup": {
|
||||
"enable": false,
|
||||
"directory": "/path/to/backup",
|
||||
"interval": "weekly"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Configuration
|
||||
CONFIG_DIR := $(HOME)/.config/notewrapper
|
||||
CONFIG_FILE := $(CONFIG_DIR)/config.json
|
||||
CACHE_DIR := $(HOME)/.cache/notewrapper
|
||||
|
||||
# Compiler and flags
|
||||
CC := gcc
|
||||
@@ -18,10 +19,10 @@ SRC := $(SRC_DIR)/main.c $(SRC_DIR)/ui.c $(SRC_DIR)/utils.c $(SRC_DIR)/notes.c
|
||||
TARGET := notewrapper
|
||||
|
||||
# Phony targets
|
||||
.PHONY: all clean run install_config
|
||||
.PHONY: all clean run install_config create_cache_dir
|
||||
|
||||
# Default target
|
||||
all: install_config $(TARGET)
|
||||
all: install_config create_cache_dir $(TARGET)
|
||||
|
||||
# Build target
|
||||
$(TARGET): $(SRC)
|
||||
@@ -46,3 +47,6 @@ install_config:
|
||||
else \
|
||||
echo "$(CONFIG_FILE) already exists, skipping installing default config file"; \
|
||||
fi
|
||||
# creates a dir in ~/.cache/ which is used to store data, such as time of last backup
|
||||
create_cache_dir:
|
||||
@mkdir -p $(CACHE_DIR)
|
||||
|
||||
+46
-2
@@ -77,7 +77,7 @@ int main(int argc, char *argv[]) {
|
||||
char *notesDirectoryString = malloc(PATH_MAX);
|
||||
if (dirJson && cJSON_IsString(dirJson) && dirJson->valuestring != NULL) {
|
||||
char *rawPath = dirJson->valuestring;
|
||||
if (rawPath[0] == '$') { // expands $ in the path
|
||||
if (rawPath[0] == '~') { // expands ~ in the path
|
||||
snprintf(notesDirectoryString, PATH_MAX, "%s/%s", homedir, rawPath+1);
|
||||
} else {
|
||||
notesDirectoryString = rawPath;
|
||||
@@ -131,6 +131,46 @@ int main(int argc, char *argv[]) {
|
||||
newLineOnOpening = cJSON_IsTrue(newLineOnOpeningJSON) ? 1 : 0;
|
||||
} else {debug("config.json did not contained a correct \"newLineOnOpening\" value. The default is true.");}
|
||||
|
||||
|
||||
int doesBackup = 0;
|
||||
int interval = 0; // this is an int. But some times it will be inputed a string. We must translate it.
|
||||
char *pathToBackup = malloc(PATH_MAX);
|
||||
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);
|
||||
cJSON *pathToBackupJSON = cJSON_GetObjectItem(backupJSON, "directory");
|
||||
if (pathToBackupJSON && cJSON_IsString(pathToBackupJSON)) {
|
||||
if (pathToBackupJSON->valuestring[0] == '~') { // expands ~
|
||||
char *temp = pathToBackupJSON->valuestring;
|
||||
temp++;
|
||||
snprintf(pathToBackup, PATH_MAX, "%s/%s", homedir, temp);
|
||||
debug("~ was expanded to %s\nThe backup path is %s", homedir, pathToBackup);
|
||||
} else {
|
||||
pathToBackup = strndup(pathToBackupJSON->valuestring, PATH_MAX);
|
||||
debug("Directory for backup is set to %s in config.json", pathToBackup); // (TODO LATER) Maybe when in debug message when we say config.json get the actual config name.
|
||||
}
|
||||
} else {error(1, "user", "config.json did not contained a directory inside the backup section or the value is from an unexpected type");}
|
||||
cJSON *intervalJSON = cJSON_GetObjectItem(backupJSON, "interval");
|
||||
if (intervalJSON && cJSON_IsString(intervalJSON)) {
|
||||
if (strcmp(intervalJSON->valuestring, "daily") == 0) {
|
||||
interval = DAILY;
|
||||
debug("interval in config.json is set to \"daily\" which is %d", DAILY);
|
||||
} else if (strcmp(intervalJSON->valuestring, "weekly") == 0) {
|
||||
interval = WEEKLY;
|
||||
debug("interval in config.json is set to \"weekly\" which is %d", WEEKLY);
|
||||
} else if (strcmp(intervalJSON->valuestring, "monthly") == 0) {
|
||||
interval = MONTHLY;
|
||||
debug("interval in config.json is set to \"monthly\" which is %d", MONTHLY);
|
||||
} else {error(1, "user", "Unexpected string %s for entry \"interval\" in config.json. You must put an int (number of seconds) or \"daily\" or \"weekly\" or \"monthly\".", intervalJSON->valuestring);}
|
||||
} else if (intervalJSON && cJSON_IsNumber(intervalJSON)) {
|
||||
interval = intervalJSON->valueint; // (TODO LATER) "writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead". we should pass all of these JSON->value... to the designed functions
|
||||
debug("interval in config.json is set to %d", interval);
|
||||
} else {error(1, "user", "config.json did not contained an interval value inside the backup section or the value is from an unexpected type");}
|
||||
} else{error(1, "user", "config.json did not contained a enable value inside the backup section or the value is from an unexpected type");}
|
||||
} else {debug("config.json did not contained a correct \"backup\" section. The default is {\"enable\": false}");}
|
||||
//cleans up
|
||||
cJSON_Delete(json);
|
||||
free(data);
|
||||
@@ -158,7 +198,7 @@ int main(int argc, char *argv[]) {
|
||||
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 <note's name> Specify the note (or journal).");
|
||||
printf(" -n, --note <note's name> 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 <vault's name> Specify the vault.\n");
|
||||
@@ -202,6 +242,10 @@ int main(int argc, char *argv[]) {
|
||||
error(!doesEditorExist(editorToOpen, shouldDebug), "user", "%s is either not in your path or not installed.", editorToOpen);
|
||||
debug("Finished parsing the attribute flags");
|
||||
|
||||
if (doesBackup) { // (TODO LATER) If this takes too much time we should add a warning and when implementing multiple directories for vault we should verifiy this works.
|
||||
handleBackups(notesDirectoryString, pathToBackup, homedir, interval, shouldDebug);
|
||||
}
|
||||
|
||||
int shouldExit = 0;
|
||||
while(!shouldExit) {
|
||||
// this loop is the vault selector
|
||||
|
||||
+123
@@ -1,4 +1,5 @@
|
||||
#include "utils.h"
|
||||
#include <stdio.h>
|
||||
|
||||
const char *supportedEditor[] = {"neovim", "vim", "nano"};
|
||||
const int numEditors = 3;
|
||||
@@ -49,6 +50,128 @@ void _error(const int shouldDebug, const int condition, const char *type, const
|
||||
}
|
||||
}
|
||||
|
||||
void copyDir(const char *source, const char *destination, const int shouldDebug) {
|
||||
debug("Backuping... source: %s and destination: %s", source, destination);
|
||||
pid_t pid = fork();
|
||||
|
||||
error(pid < 0, "program", "fork() faild. pid = %d", pid);
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process: execute rsync
|
||||
char *args[] = { // (TODO LATER)
|
||||
"rsync",
|
||||
"-Lqah", // archive, follow links, human-readable // (TODO LATER) add a way to get verbose (-v) without it going on top of ncurses
|
||||
"--progress", // show progress
|
||||
"--update", // only update if newer
|
||||
(char *)source,
|
||||
(char *)destination,
|
||||
NULL
|
||||
};
|
||||
|
||||
execvp("rsync", args);
|
||||
// If execvp returns, there was an error
|
||||
error(1, "program", "execvp() failed");
|
||||
} else {
|
||||
debug("Backup started asynchronously with PID %d\n", pid);
|
||||
}
|
||||
}
|
||||
void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const char *homeDir, const int interval, const int shouldDebug) {
|
||||
int shouldBackup = 0;
|
||||
time_t now = time(NULL);
|
||||
debug("Time since epoch is %ld", (long)now);
|
||||
char cacheFilePATH[PATH_MAX];
|
||||
snprintf(cacheFilePATH, PATH_MAX, "%s/.cache/notewrapper/backupTime.txt", homeDir);
|
||||
|
||||
struct stat st;
|
||||
if (stat(cacheFilePATH, &st) != 0) { // file does not exist
|
||||
shouldBackup = 1;
|
||||
FILE *cacheFile = fopen(cacheFilePATH, "w");
|
||||
if (!cacheFile) {
|
||||
error(1, "program", "Failed to open cache file for writing: %s", cacheFilePATH);
|
||||
}
|
||||
fprintf(cacheFile, "%ld", (long)now);
|
||||
fclose(cacheFile);
|
||||
} else {
|
||||
FILE *cacheFile = fopen(cacheFilePATH, "r");
|
||||
if (!cacheFile) {
|
||||
error(1, "program", "Failed to open cache file for reading: %s", cacheFilePATH);
|
||||
}
|
||||
|
||||
char line[64];
|
||||
if (fgets(line, sizeof(line), cacheFile) == NULL) {
|
||||
error(1, "program", "Failed to read backup cache file (at %s)", cacheFilePATH);
|
||||
}
|
||||
fclose(cacheFile);
|
||||
|
||||
char *endptr;
|
||||
long lastBackupTime = strtol(line, &endptr, 10);
|
||||
if (endptr == line || (*endptr != '\n' && *endptr != '\0')) {
|
||||
error(1, "program", "Invalid timestamp in cache file: %s", line);
|
||||
}
|
||||
|
||||
if (difftime(now, lastBackupTime) > interval) {
|
||||
shouldBackup = 1;
|
||||
cacheFile = fopen(cacheFilePATH, "w");
|
||||
if (!cacheFile) {
|
||||
error(1, "program", "Failed to open cache file for writing");
|
||||
}
|
||||
fprintf(cacheFile, "%ld\n", (long)now);
|
||||
fclose(cacheFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldBackup) {
|
||||
copyDir(pathOfVaults, pathOfBackup, shouldDebug);
|
||||
}
|
||||
}
|
||||
/*void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const char *homeDir, const int interval, const int shouldDebug) {
|
||||
int shouldBackup = 0;
|
||||
time_t now = time(NULL);
|
||||
debug("Time since epoch is %ld", (long)now);
|
||||
char cacheFilePATH[PATH_MAX];
|
||||
// we store in this file the epoch time of the last backup to see if we need to backup
|
||||
snprintf(cacheFilePATH, PATH_MAX, "%s/.cache/notewrapper/backupTime.txt", homeDir);
|
||||
FILE *cacheFile = fopen(cacheFilePATH, "r");
|
||||
if (errno == ENOENT) { // if the files does not exists. We backup and set the backup time to now
|
||||
shouldBackup = 1;
|
||||
fclose(cacheFile);
|
||||
cacheFile = fopen(cacheFilePATH, "w");
|
||||
fprintf(cacheFile, "%ld", (long)now);
|
||||
fclose(cacheFile);
|
||||
} else {
|
||||
char line[64];
|
||||
if (fgets(line, sizeof(line), cacheFile) == NULL) {
|
||||
// Error reading the file
|
||||
error(1, "program", "Failed to read backup cache file (at %s)", cacheFilePATH);
|
||||
fclose(cacheFile);
|
||||
return;
|
||||
}
|
||||
fclose(cacheFile);
|
||||
|
||||
// Convert the read line to long
|
||||
char *endptr;
|
||||
long lastBackupTime = strtol(line, &endptr, 10);
|
||||
if (endptr == line || (*endptr != '\n' && *endptr != '\0')) {
|
||||
error(1, "program", "Invalid timestamp in cache file: %s", line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (difftime(now, lastBackupTime) > interval) {
|
||||
shouldBackup = 1;
|
||||
// Update the cache file with current time
|
||||
cacheFile = fopen(cacheFilePATH, "w");
|
||||
if (cacheFile == NULL) {
|
||||
error(1, "program", "Failed to open cache file for writing");
|
||||
return;
|
||||
}
|
||||
fprintf(cacheFile, "%ld\n", (long)now);
|
||||
fclose(cacheFile);
|
||||
}
|
||||
}
|
||||
if (shouldBackup) {
|
||||
copyDir(pathOfVaults, pathOfBackup, shouldDebug);
|
||||
}
|
||||
}*/
|
||||
|
||||
int doesEditorExist (char *editorToCheck, int shouldDebug) { // Some exectuables have not exaclty the same name as the editor.
|
||||
char *editor;
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#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, ...) \
|
||||
_debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||
#define altDebug(message, ...) \
|
||||
@@ -67,4 +71,7 @@ int openEditor(char *path, char *editor, int render, int shouldJumpToEndOfFile,
|
||||
// returns a string (buffered at 256 chars).
|
||||
// you should not forgot to free this string as it is in the heap.
|
||||
char *getFormatedTime(char *format, int shouldDebug);
|
||||
/* Caclulates if we need to do another backup (by reading ~/.cache/NoteWrapper/backupTime.txt.
|
||||
If need launches in the background rsync to do the backuping.*/
|
||||
void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const char *homeDir, const int interval, const int shouldDebug);
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user