mirror of
https://github.com/tomasriveral/NoteWrapper.git
synced 2026-10-10 17:03:04 +02:00
Compare commits
71
Commits
v2.1.1
..
1506d43ffb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1506d43ffb | ||
|
|
e1c63a1694 | ||
|
|
e01e580162 | ||
|
|
b2d76adb88 | ||
|
|
7403bed29c | ||
|
|
13bacbb6d6 | ||
|
|
886eff21d3 | ||
|
|
bcf30a4576 | ||
|
|
72bf03bc9f | ||
|
|
7ca0bf1561 | ||
|
|
94d659db2c | ||
|
|
e01be6ecea | ||
|
|
713fa27893 | ||
|
|
dd7345c2fa | ||
|
|
dd58a86fce | ||
|
|
19610c1c51 | ||
|
|
39a3cb6bba | ||
|
|
0d5da72b13 | ||
|
|
e78a9ca669 | ||
|
|
c83a504648 | ||
|
|
2e301b0b52 | ||
|
|
c7a9d45a65 | ||
|
|
62da4cfb2e | ||
|
|
d56e7d9e4f | ||
|
|
449a9eaca6 | ||
|
|
edc979dccb | ||
|
|
5f37e48030 | ||
|
|
df0301fdcd | ||
|
|
518d4fd09c | ||
|
|
a83da3e1aa | ||
|
|
eb0d6b6e5d | ||
|
|
82d016fdd7 | ||
|
|
2e03edac2b | ||
|
|
689c3074c0 | ||
|
|
e54933e5fa | ||
|
|
3f9471531d | ||
|
|
ab1928551a | ||
|
|
e75c8e5815 | ||
|
|
693e85d974 | ||
|
|
b9f71f580a | ||
|
|
c6aac80951 | ||
|
|
b395fe1072 | ||
|
|
27cc3de27d | ||
|
|
18538f3250 | ||
|
|
6c7cc413da | ||
|
|
e670ef935f | ||
|
|
29d86955f4 | ||
|
|
0f9ec32bfc | ||
|
|
5ad8f2f99e | ||
|
|
c6e590e57c | ||
|
|
d47bdb82eb | ||
|
|
50748eeac0 | ||
|
|
19a8f7e07f | ||
|
|
1c17c62835 | ||
|
|
e666ca32de | ||
|
|
2a8392ac2e | ||
|
|
292688296e | ||
|
|
4c18c2dda4 | ||
|
|
1bfb7f82a5 | ||
|
|
a32f97d229 | ||
|
|
6555b7247e | ||
|
|
2b2a3455d2 | ||
|
|
d8a11bbc16 | ||
|
|
1d95983d5d | ||
|
|
30449ca473 | ||
|
|
7af79c76b1 | ||
|
|
27b8b310cb | ||
|
|
4d228fb911 | ||
|
|
34a2546990 | ||
|
|
7231741f2d | ||
|
|
e86d7e7e03 |
No files matched your search
@@ -48,7 +48,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
gh pr create \
|
gh pr create \
|
||||||
--base main \
|
--base main \
|
||||||
--head release/v${{ inputs.version }} \
|
--head release/${{ inputs.version }} \
|
||||||
--title "[Automatic] Release PR ${{ inputs.version }}" \
|
--title "[Automatic] Release PR ${{ inputs.version }}" \
|
||||||
--body "Automated release PR.\nAfter the test's succesfull run, create manually a release with a new tag matching ${{ inputs.version }}"
|
--body "Automated release PR.\nAfter the test's succesfull run, create manually a release with a new tag matching ${{ inputs.version }}"
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ jobs:
|
|||||||
pkg-config \
|
pkg-config \
|
||||||
libcjson-dev \
|
libcjson-dev \
|
||||||
libncurses-dev \
|
libncurses-dev \
|
||||||
|
libgit2-dev \
|
||||||
git \
|
git \
|
||||||
neovim
|
neovim
|
||||||
- name: Prepare environnement
|
- name: Prepare environnement
|
||||||
|
|||||||
+3
-1
@@ -10,7 +10,7 @@ This project aims to remain minimal, terminal-focused, and easy to extend. Contr
|
|||||||
|
|
||||||
Please use the standardized debugging and error-handling functions defined in [/src/utils.c](./src/utils.c):
|
Please use the standardized debugging and error-handling functions defined in [/src/utils.c](./src/utils.c):
|
||||||
|
|
||||||
* `debug(...)`
|
* `debug("message", ...)`
|
||||||
Used for formatted debugging output (printf-style formatting).
|
Used for formatted debugging output (printf-style formatting).
|
||||||
|
|
||||||
* `altDebug(...)`
|
* `altDebug(...)`
|
||||||
@@ -21,6 +21,8 @@ Please use the standardized debugging and error-handling functions defined in [/
|
|||||||
|
|
||||||
* `"user"`: errors caused by user input or configuration
|
* `"user"`: errors caused by user input or configuration
|
||||||
* `"program"`: internal or unexpected program errors
|
* `"program"`: internal or unexpected program errors
|
||||||
|
* `warn(condition, "message", ...)`
|
||||||
|
Used for formatted warning (printf-style formatting).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ Note: This project fullfills all my usecases for now, so my main source for feat
|
|||||||
|
|
||||||
[](https://deepwiki.com/tomasriveral/notewrapper)
|
[](https://deepwiki.com/tomasriveral/notewrapper)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
I started journaling and note-taking with **[Obsidian](https://obsidian.md/)** but wanted to use only free software. I also tried **[Logseq](https://logseq.com/)** and **[Joplin](https://github.com/laurent22/joplin)**, but I preferred a terminal-based workflow.
|
I started journaling and note-taking with **[Obsidian](https://obsidian.md/)** but wanted to use only free software. I also tried **[Logseq](https://logseq.com/)** and **[Joplin](https://github.com/laurent22/joplin)**, but I preferred a terminal-based workflow.
|
||||||
@@ -41,6 +43,7 @@ Before building NoteWrapper, you must install the following dependencies:
|
|||||||
* `sed`
|
* `sed`
|
||||||
* `ripgrep`
|
* `ripgrep`
|
||||||
* `fzf`
|
* `fzf`
|
||||||
|
* `libgit2`
|
||||||
|
|
||||||
You must also have a [supported editor (and their associated plugin if needed)](#editor-support) installed:
|
You must also have a [supported editor (and their associated plugin if needed)](#editor-support) installed:
|
||||||
|
|
||||||
@@ -157,11 +160,13 @@ NoteWrapper relies on certain editor features, so not all functionality is suppo
|
|||||||
|
|
||||||
### Features requiring editor support:
|
### Features requiring editor support:
|
||||||
|
|
||||||
* **Bufferless rendering**: updates the rendered view while typing (without saving)
|
* **Bufferless rendering**: updates the rendered view while typing (without saving). You won't need to save the file to see it updated in your browser.
|
||||||
* **Cursor following**: rendered view follows the cursor position
|
* **Cursor following**: rendered view follows the cursor position. When you scroll in your editor, it will automatically scroll your browser view.
|
||||||
* **Jump to end on open**: automatically moves the cursor to the end of the file
|
* **Jump to end on open**: automatically moves the cursor to the end of the file when oppening.
|
||||||
|
|
||||||
The first two features depend on [Vivify's editor integration](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#existing-integration) and are mainly useful if you want external Markdown rendering in your browser.
|
The first two features depend on [Vivify's editor integration](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#existing-integration)[^1] and are mainly useful if you want external Markdown rendering in your browser.
|
||||||
|
|
||||||
|
[^1]: Eventhough the features are Vivify-related, please report bugs and ask questions in this repo.
|
||||||
|
|
||||||
If your editor does not support these features, you can implement a plugin using [Vivify's API](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#editor-support).
|
If your editor does not support these features, you can implement a plugin using [Vivify's API](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#editor-support).
|
||||||
|
|
||||||
@@ -200,7 +205,12 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
|
|||||||
"/other/paths/": "path/to/backup2"
|
"/other/paths/": "path/to/backup2"
|
||||||
},
|
},
|
||||||
"interval": "weekly",
|
"interval": "weekly",
|
||||||
"rsyncArgs": ["-Lqah", "--update"]
|
"rsyncArgs": ["-Lqah", "--update"],
|
||||||
|
"git": {
|
||||||
|
"enable": false,
|
||||||
|
"name": "Example Example",
|
||||||
|
"email": "mail@example.com"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -218,16 +228,21 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
|
|||||||
* `backup.directory`: backup's destination for each directory
|
* `backup.directory`: backup's destination for each directory
|
||||||
* `backup.interval`: backup frequency (`daily`, `weekly`, `monthly`, or integer)
|
* `backup.interval`: backup frequency (`daily`, `weekly`, `monthly`, or integer)
|
||||||
* `backup.rsyncArgs`: arguments passed to `rsync`
|
* `backup.rsyncArgs`: arguments passed to `rsync`
|
||||||
|
* `git.enable`: enable git version control for backups
|
||||||
|
* `git.name` and `git.email` are for the commits signature
|
||||||
|
|
||||||
Note:
|
> [!NOTE]
|
||||||
* Directories must end with `/`.
|
> * The git backup implementation relies a lot on the IO speed of your drive. A low speed one (or a web mounted one) could create blanks between closing a document and the TUI opening.
|
||||||
* All directories used must exist.
|
> * Directories must end with `/`.
|
||||||
* Elements of `directory` and `backup.directory` must match exactly.
|
> * All directories used must exist.
|
||||||
* Directories set in `directory` but not in `backup.directory` won't be backed up.
|
> * Elements of `directory` and `backup.directory` must match exactly.
|
||||||
* Directories set in `backup.directory` but not in `directory` will be ignored.
|
> * Directories set in `directory` but not in `backup.directory` won't be backed up.
|
||||||
* `journalRegex` must match if the file name ends with `.md` and if it doesn't.
|
> * Directories set in `backup.directory` but not in `directory` will be ignored.
|
||||||
* It is recommended to keep `-q` or `--quiet` flag in `rsyncArgs` to avoid interference with `ncurses`.
|
> * `journalRegex` must match if the file name ends with `.md` and if it doesn't.
|
||||||
* If `rsync` fails, you will see it inside `ncurses`.
|
> * It is recommended to keep `-q` or `--quiet` flag in `rsyncArgs` to avoid interference with `ncurses`.
|
||||||
|
> * If `rsync` fails, you will see it inside `ncurses`.
|
||||||
|
> * If you want the git backup but without rsync, you can leave `backup.directory` empty or map each directory to itself. When enabled, git will apply to all your vaults.
|
||||||
|
> * Each vault gets its own git repository that will be created the first time you select the vault with the option enabled.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
Require echo
|
||||||
|
|
||||||
|
Set Shell "zsh"
|
||||||
|
Set TypingSpeed 35ms
|
||||||
|
Set FontSize 18
|
||||||
|
Set Width 1800
|
||||||
|
Set Height 900
|
||||||
|
|
||||||
|
Output demo.gif
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Introduction
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Type "echo 'Welcome to NoteWrapper!'"
|
||||||
|
Sleep 800ms
|
||||||
|
Enter
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
Type "notewrapper --help"
|
||||||
|
Sleep 500ms
|
||||||
|
Enter
|
||||||
|
Sleep 4000ms
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Create / open a unified journal
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Type "notewrapper"
|
||||||
|
Sleep 700ms
|
||||||
|
Enter
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
Down
|
||||||
|
Sleep 500ms
|
||||||
|
Down
|
||||||
|
Sleep 500ms
|
||||||
|
Down
|
||||||
|
Sleep 700ms
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Sleep 1200ms
|
||||||
|
|
||||||
|
Down
|
||||||
|
Sleep 600ms
|
||||||
|
Enter
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
# Enter the editor
|
||||||
|
Type "i"
|
||||||
|
Sleep 800ms
|
||||||
|
|
||||||
|
Type "NoteWrapper uses standard Markdown files, so you can open your notes in any other application that supports Markdown."
|
||||||
|
Sleep 700ms
|
||||||
|
Enter
|
||||||
|
Sleep 800ms
|
||||||
|
|
||||||
|
Type "This is a unified journal: all your daily entries live in the same file. You can also configure NoteWrapper to use monthly or other time-based entries."
|
||||||
|
Sleep 700ms
|
||||||
|
Enter
|
||||||
|
Sleep 800ms
|
||||||
|
|
||||||
|
Type "And, although you can't see it in this demo, this Markdown file is rendered live in my browser thanks to Vivify!"
|
||||||
|
Sleep 1500ms
|
||||||
|
Sleep 3000ms
|
||||||
|
|
||||||
|
# Leave the editor
|
||||||
|
Escape
|
||||||
|
Sleep 500ms
|
||||||
|
Type ":q!"
|
||||||
|
Sleep 800ms
|
||||||
|
Enter
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Create / open a divided journal
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Down
|
||||||
|
Sleep 500ms
|
||||||
|
Down
|
||||||
|
Sleep 500ms
|
||||||
|
Down
|
||||||
|
Sleep 700ms
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Sleep 2000ms
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Sleep 1000ms
|
||||||
|
|
||||||
|
Type "i"
|
||||||
|
Sleep 800ms
|
||||||
|
|
||||||
|
Type "And this is a divided journal."
|
||||||
|
Sleep 500ms
|
||||||
|
Type " It uses one file per entry."
|
||||||
|
Sleep 700ms
|
||||||
|
Enter
|
||||||
|
Sleep 1000ms
|
||||||
|
|
||||||
|
Type "Find the dates and journal divisions a little too cumbersome?"
|
||||||
|
Sleep 700ms
|
||||||
|
Enter
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
Type "Don't worry: NoteWrapper also works with basic notes."
|
||||||
|
Sleep 800ms
|
||||||
|
|
||||||
|
Type " It distinguishes them using a regular expression, which defaults to `.*journal.*`."
|
||||||
|
Sleep 800ms
|
||||||
|
Enter
|
||||||
|
Sleep 700ms
|
||||||
|
|
||||||
|
Type " The unified or divided journal mode is chosen when you first create the journal."
|
||||||
|
Sleep 1000ms
|
||||||
|
Enter
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Search
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Type "Let me show you one last cool feature."
|
||||||
|
Sleep 1000ms
|
||||||
|
|
||||||
|
Type " Have you ever forgotten when or where you wrote something?"
|
||||||
|
Sleep 1000ms
|
||||||
|
Enter
|
||||||
|
Sleep 800ms
|
||||||
|
|
||||||
|
Type "Well ... NoteWrapper was made for you!"
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
Escape
|
||||||
|
Sleep 500ms
|
||||||
|
Type ":q!"
|
||||||
|
Sleep 800ms
|
||||||
|
Enter
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
# Navigate to search
|
||||||
|
Down
|
||||||
|
Sleep 500ms
|
||||||
|
Down
|
||||||
|
Sleep 500ms
|
||||||
|
Down
|
||||||
|
Sleep 800ms
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
Down
|
||||||
|
Sleep 500ms
|
||||||
|
Down
|
||||||
|
Sleep 800ms
|
||||||
|
|
||||||
|
Enter
|
||||||
|
Sleep 2000ms
|
||||||
|
|
||||||
|
# Search all notes with fzf
|
||||||
|
Type "c"
|
||||||
|
Sleep 300ms
|
||||||
|
Type "o"
|
||||||
|
Sleep 300ms
|
||||||
|
Type "o"
|
||||||
|
Sleep 300ms
|
||||||
|
Type "l"
|
||||||
|
Sleep 1500ms
|
||||||
|
|
||||||
|
# Show the search result / preview
|
||||||
|
Up
|
||||||
|
Sleep 2000ms
|
||||||
|
Enter
|
||||||
|
Sleep 1000ms
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
# Final note
|
||||||
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Type "i"
|
||||||
|
Sleep 700ms
|
||||||
|
|
||||||
|
Type "Never lose a note."
|
||||||
|
Sleep 1500ms
|
||||||
|
Enter
|
||||||
|
Sleep 1000ms
|
||||||
|
|
||||||
|
Type "P. S. Don't mind the broken fzf preview. Normally, you'll see the context around your search result, but sometimes `sed` breaks when fzf is opened for the first time."
|
||||||
|
Sleep 5000ms
|
||||||
Generated
+4
-21
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1779560665,
|
"lastModified": 1791048980,
|
||||||
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
|
"narHash": "sha256-KgItSKML8Xvte0B7/uGnBDsYzSnnKHcOaiUbgWBXLXw=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
|
"rev": "a7868a727837f3c09cee2ce0ca671c76b1589fed",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -34,27 +34,10 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notewrapper": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1779788695,
|
|
||||||
"narHash": "sha256-JdTeS6rxDEa+A+rQjbFJajBhlwauBApIP3UwvrPbaf4=",
|
|
||||||
"owner": "tomasriveral",
|
|
||||||
"repo": "NoteWrapper",
|
|
||||||
"rev": "d537357802940e924d77ebd0fc1a658f884138ea",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "tomasriveral",
|
|
||||||
"repo": "NoteWrapper",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs"
|
||||||
"notewrapper": "notewrapper"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
|
|||||||
@@ -4,22 +4,18 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
notewrapper = {
|
|
||||||
url = "github:tomasriveral/NoteWrapper";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, notewrapper }:
|
outputs = { self, nixpkgs, flake-utils}:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs { inherit system; };
|
||||||
in {
|
in {
|
||||||
packages.default = pkgs.stdenv.mkDerivation (finalAttrs: {
|
packages.default = pkgs.stdenv.mkDerivation (finalAttrs: {
|
||||||
pname = "notewrapper";
|
pname = "notewrapper";
|
||||||
version = "v2.1.1";
|
version = "v2.3.0";
|
||||||
|
|
||||||
src = notewrapper;
|
src = ./.;
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
pkg-config
|
pkg-config
|
||||||
@@ -34,6 +30,7 @@
|
|||||||
ripgrep
|
ripgrep
|
||||||
fzf
|
fzf
|
||||||
gnused
|
gnused
|
||||||
|
libgit2
|
||||||
];
|
];
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
@@ -55,6 +52,8 @@
|
|||||||
valgrind
|
valgrind
|
||||||
clang-tools
|
clang-tools
|
||||||
gnumake
|
gnumake
|
||||||
|
libgit2
|
||||||
|
vhs
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ VERSION := $(shell git describe --tags --always --dirty)
|
|||||||
|
|
||||||
CFLAGS := -Wall -Wextra -Werror -O2 \
|
CFLAGS := -Wall -Wextra -Werror -O2 \
|
||||||
-DVERSION=\"$(VERSION)\" \
|
-DVERSION=\"$(VERSION)\" \
|
||||||
$(shell pkg-config --cflags libcjson ncurses)
|
$(shell pkg-config --cflags libcjson ncurses libgit2)
|
||||||
|
|
||||||
LDFLAGS := $(shell pkg-config --libs libcjson ncurses)
|
LDFLAGS := $(shell pkg-config --libs libcjson ncurses libgit2)
|
||||||
|
|
||||||
SRC := src/main.c src/ui.c src/utils.c src/notes.c
|
SRC := src/main.c src/ui.c src/utils.c src/notes.c
|
||||||
TARGET := notewrapper
|
TARGET := notewrapper
|
||||||
|
|||||||
+130
-36
@@ -96,7 +96,14 @@ int main(int argc, char *argv[]) {
|
|||||||
debug("Parsing the JSON config");
|
debug("Parsing the JSON config");
|
||||||
|
|
||||||
cJSON *json = cJSON_Parse(data);
|
cJSON *json = cJSON_Parse(data);
|
||||||
|
|
||||||
if (!json) {
|
if (!json) {
|
||||||
|
const char *error_ptr = cJSON_GetErrorPtr();
|
||||||
|
|
||||||
|
if (error_ptr) {
|
||||||
|
error(1, "program", "JSON parse error near: %.30s", error_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
error(!json, "program", "JSON parse error");
|
error(!json, "program", "JSON parse error");
|
||||||
@@ -210,6 +217,9 @@ int main(int argc, char *argv[]) {
|
|||||||
char **backupDirectoriesArray = malloc(numDirectories * sizeof(char *));
|
char **backupDirectoriesArray = malloc(numDirectories * sizeof(char *));
|
||||||
char **rsyncArgs = NULL;
|
char **rsyncArgs = NULL;
|
||||||
int rsyncArgsNumber = 0;
|
int rsyncArgsNumber = 0;
|
||||||
|
int gitEnabled = 0;
|
||||||
|
char *gitSignatureName = NULL;
|
||||||
|
char *gitSignatureEmail = NULL;
|
||||||
cJSON *backupJSON = cJSON_GetObjectItem(json, "backup");
|
cJSON *backupJSON = cJSON_GetObjectItem(json, "backup");
|
||||||
if (backupJSON && cJSON_IsObject(backupJSON)) {
|
if (backupJSON && cJSON_IsObject(backupJSON)) {
|
||||||
cJSON *doesBackupJSON = cJSON_GetObjectItem(backupJSON, "enable");
|
cJSON *doesBackupJSON = cJSON_GetObjectItem(backupJSON, "enable");
|
||||||
@@ -285,11 +295,36 @@ int main(int argc, char *argv[]) {
|
|||||||
"value is from an unexpected type",
|
"value is from an unexpected type",
|
||||||
configPath);
|
configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure Git backup
|
||||||
|
cJSON *gitJSON = cJSON_GetObjectItem(backupJSON, "git");
|
||||||
|
|
||||||
|
if (gitJSON && cJSON_IsObject(gitJSON)) {
|
||||||
|
cJSON *gitEnableJSON = cJSON_GetObjectItem(gitJSON, "enable");
|
||||||
|
cJSON *gitNameJSON = cJSON_GetObjectItem(gitJSON, "name");
|
||||||
|
cJSON *gitEmailJSON = cJSON_GetObjectItem(gitJSON, "email");
|
||||||
|
|
||||||
|
error(!gitEnableJSON || !cJSON_IsBool(gitEnableJSON), "user", "Entry \"git.enable\" in %s must be a bool.", configPath);
|
||||||
|
error(!gitNameJSON || !cJSON_IsString(gitNameJSON), "user", "Entry \"git.name\" in %s must be a string.", configPath);
|
||||||
|
error(!gitEmailJSON || !cJSON_IsString(gitEmailJSON), "user", "Entry \"git.email\" in %s must be a string.", configPath);
|
||||||
|
|
||||||
|
gitEnabled = cJSON_IsTrue(gitEnableJSON) ? 1 : 0;
|
||||||
|
|
||||||
|
if (gitEnabled) {
|
||||||
|
gitSignatureName = strdup(cJSON_GetStringValue(gitNameJSON));
|
||||||
|
gitSignatureEmail = strdup(cJSON_GetStringValue(gitEmailJSON));
|
||||||
|
|
||||||
|
error(gitSignatureName == NULL || gitSignatureEmail == NULL, "program", "malloc failed");
|
||||||
|
|
||||||
|
debug("git in %s is enabled with name \"%s\" and email \"%s\"", configPath, gitSignatureName, gitSignatureEmail);
|
||||||
|
} else {
|
||||||
|
debug("git in %s is disabled", configPath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error(1, "user", "Entry \"git\" in %s must be an object containing \"enable\", \"name\", and \"email\".", configPath);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error(1, "user",
|
error(1, "user", "%s did not contained a enable value inside the backup section or the value is from an unexpected type", configPath);
|
||||||
"%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.
|
// handle rsyncs array of arguments.
|
||||||
cJSON *rsyncArgsJSON = cJSON_GetObjectItem(backupJSON, "rsyncArgs");
|
cJSON *rsyncArgsJSON = cJSON_GetObjectItem(backupJSON, "rsyncArgs");
|
||||||
@@ -314,9 +349,7 @@ int main(int argc, char *argv[]) {
|
|||||||
configPath);
|
configPath);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to "
|
debug("In %s, \"backup\" wasn't set or we encountered a abnormal type. Defaulting to {\"enable\": false}.", configPath);
|
||||||
"{\"enable\": false}.",
|
|
||||||
configPath);
|
|
||||||
}
|
}
|
||||||
backup_config_end:
|
backup_config_end:
|
||||||
|
|
||||||
@@ -529,13 +562,24 @@ backup_config_end:
|
|||||||
shouldDebug); // check if editor is supported and if it is installed. If not, it
|
shouldDebug); // check if editor is supported and if it is installed. If not, it
|
||||||
// will throw an error.
|
// will throw an error.
|
||||||
|
|
||||||
|
int *backupMessage = malloc(sizeof(int)); // Used later to set the backuping message.
|
||||||
|
*backupMessage = 0; // default don't show the message
|
||||||
|
int forceBackup = 0;
|
||||||
if (doesBackup) {
|
if (doesBackup) {
|
||||||
debug("Handling backup");
|
debug("Handling normal backup");
|
||||||
handleBackups(directoriesArray, numDirectories, backupDirectoriesArray, homedir, interval, (const char **)rsyncArgs, rsyncArgsNumber, shouldDebug);
|
forceBackup = 0;
|
||||||
|
handleBackups(directoriesArray, numDirectories, backupDirectoriesArray, homedir, interval, (const char **)rsyncArgs, rsyncArgsNumber, forceBackup, backupMessage, shouldDebug);
|
||||||
|
debug("backupMessage was set to %d by a normal backup", *backupMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
initscr(); // initialize ncurses
|
initscr(); // initialize ncurses
|
||||||
|
|
||||||
|
// initialized libgit2
|
||||||
|
if (gitEnabled) {
|
||||||
|
int libgit2InitializationTimes = git_libgit2_init();
|
||||||
|
error(libgit2InitializationTimes != 1, "program", "libgit2 has been initialized %d times (instead of one)", libgit2InitializationTimes);
|
||||||
|
}
|
||||||
|
|
||||||
int shouldExit = 0;
|
int shouldExit = 0;
|
||||||
while (!shouldExit) {
|
while (!shouldExit) {
|
||||||
// this loop is the vault selector
|
// this loop is the vault selector
|
||||||
@@ -572,15 +616,29 @@ backup_config_end:
|
|||||||
}
|
}
|
||||||
altDebug("└ ------------------------------\n");
|
altDebug("└ ------------------------------\n");
|
||||||
}
|
}
|
||||||
|
int extraOptions = 3;
|
||||||
// adds "create a new vault" into the vaultsArray
|
if (doesBackup) {
|
||||||
const int extraOptions = 3;
|
extraOptions++; // space for "Backup now" button
|
||||||
|
}
|
||||||
|
if (*backupMessage) {
|
||||||
|
extraOptions++;
|
||||||
|
}
|
||||||
vaultsArray = realloc(vaultsArray, (vaultsCount + extraOptions) * sizeof(char *)); // resize vaultsArray to fit the extra options
|
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
|
int index = 0;
|
||||||
vaultsArray[vaultsCount + 1] = "Settings";
|
vaultsArray[vaultsCount + index++] = "Create a new vault"; // some more options that are not vaults
|
||||||
vaultsArray[vaultsCount + 2] = "Quit (Ctrl+C)";
|
vaultsArray[vaultsCount + index++] = "Settings";
|
||||||
|
if (doesBackup) {
|
||||||
|
vaultsArray[vaultsCount + index++] = "Backup now";
|
||||||
|
}
|
||||||
|
vaultsArray[vaultsCount + index++] = "Quit (Ctrl+C)";
|
||||||
|
if (*backupMessage) {
|
||||||
|
vaultsArray[vaultsCount + index++] = "A backup was launched. Do not immediatly close the program. After a few seconds, you can safely ignore this message.";
|
||||||
|
};
|
||||||
|
|
||||||
vaultSelected = ncursesSelect(vaultsArray, "Select vault to open (Use arrows or WASD, Enter to select):", vaultsCount, extraOptions, " ", "Or select an option below", "", shouldDebug);
|
*backupMessage = 0; // we reset it to show the message only once
|
||||||
|
|
||||||
|
vaultSelected = ncursesSelect(vaultsArray, "Select vault to open (Use arrows or WASD, Enter to select or use 1-9 to quick select or use 1-9 to quick 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
|
// 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
|
// all its elements. (As this is memory in the heap and not the stack and thus is our
|
||||||
@@ -594,13 +652,19 @@ backup_config_end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug("Selected vault: %s", vaultSelected);
|
debug("Selected vault: %s", vaultSelected);
|
||||||
if (strcmp(vaultSelected, "Create a new vault") != 0 && strcmp(vaultSelected, "Settings") != 0 && strcmp(vaultSelected, "Quit (Ctrl+C)") != 0) {
|
if (strcmp(vaultSelected, "Create a new vault") != 0 && strcmp(vaultSelected, "Settings") != 0 && strcmp(vaultSelected, "Quit (Ctrl+C)") != 0 && strcmp(vaultSelected, "Backup now") != 0 &&
|
||||||
|
strcmp(vaultSelected, "A backup was launched. Do not immediatly close the program. After a few seconds, you can safely ignore this message.") != 0) {
|
||||||
note_selection:
|
note_selection:
|
||||||
bypassSelectionVault = 0; // we must reset bypassSelectionVault to not get stuck in a
|
bypassSelectionVault = 0; // we must reset bypassSelectionVault to not get stuck in a
|
||||||
// infinite loop of bypassing
|
// infinite loop of bypassing
|
||||||
int shouldChangeVault = 0;
|
int shouldChangeVault = 0;
|
||||||
// we must find the directory from which the vault comes again.
|
// we must find the directory from which the vault comes again.
|
||||||
char *notesDirectoryString = getDirectoryFromVault(vaultSelected, vaultsArray, vaultsCount, vaultsCountForEachDirectory, directoriesArray, numDirectories, shouldDebug);
|
char *notesDirectoryString = getDirectoryFromVault(vaultSelected, vaultsArray, vaultsCount, vaultsCountForEachDirectory, directoriesArray, numDirectories, shouldDebug);
|
||||||
|
|
||||||
|
if (gitEnabled) { // we check if we need to git init the vault each time we enter it
|
||||||
|
ensureGitDirectory(notesDirectoryString, vaultSelected, gitSignatureName, gitSignatureEmail, shouldDebug);
|
||||||
|
}
|
||||||
|
|
||||||
while (!shouldExit && !shouldChangeVault) {
|
while (!shouldExit && !shouldChangeVault) {
|
||||||
// this loop is the note selector
|
// this loop is the note selector
|
||||||
int filesCount = 0;
|
int filesCount = 0;
|
||||||
@@ -625,12 +689,13 @@ backup_config_end:
|
|||||||
altDebug("└------------------------------\n");
|
altDebug("└------------------------------\n");
|
||||||
}
|
}
|
||||||
// adds options
|
// adds options
|
||||||
int extraNotesOptions = 4;
|
int extraNotesOptions = 5;
|
||||||
filesArray = realloc(filesArray, (filesCount + extraNotesOptions) * sizeof(char *)); // resize filesArray to fit the extra options
|
filesArray = realloc(filesArray, (filesCount + extraNotesOptions) * sizeof(char *)); // resize filesArray to fit the extra options
|
||||||
filesArray[filesCount] = "Create new note";
|
filesArray[filesCount] = "Create new note";
|
||||||
filesArray[filesCount + 1] = "Back to vault selection";
|
filesArray[filesCount + 1] = "Search inside the vault";
|
||||||
filesArray[filesCount + 2] = "Delete vault";
|
filesArray[filesCount + 2] = "Back to vault selection";
|
||||||
filesArray[filesCount + 3] = "Quit (Ctrl+C)";
|
filesArray[filesCount + 3] = "Delete vault";
|
||||||
|
filesArray[filesCount + 4] = "Quit (Ctrl+C)";
|
||||||
char *noteSelected;
|
char *noteSelected;
|
||||||
// if we set to bypass the note selector
|
// if we set to bypass the note selector
|
||||||
if (bypassSelectionNote) {
|
if (bypassSelectionNote) {
|
||||||
@@ -646,8 +711,9 @@ backup_config_end:
|
|||||||
goto note_creation;
|
goto note_creation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
noteSelected =
|
char vaultMessage[PATH_MAX];
|
||||||
ncursesSelect(filesArray, "Select note or journal to open (Use arrows or WASD, Enter to select):", filesCount, extraNotesOptions, " ", "Or select an option below", "", shouldDebug);
|
snprintf(vaultMessage, PATH_MAX, "%s: Select note or journal to open (Use arrows or WASD, Enter to select or use 1-9 to quick select):", vaultSelected);
|
||||||
|
noteSelected = ncursesSelect(filesArray, vaultMessage, 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
|
// 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
|
// and all its elements. (As this is memory in the heap and not the stack and thus
|
||||||
// is our responsability to manage)
|
// is our responsability to manage)
|
||||||
@@ -661,7 +727,7 @@ backup_config_end:
|
|||||||
free(filesArray);
|
free(filesArray);
|
||||||
debug("Selected note: %s", noteSelected);
|
debug("Selected note: %s", noteSelected);
|
||||||
if (strcmp(noteSelected, "Create new note") != 0 && strcmp(noteSelected, "Back to vault selection") != 0 && strcmp(noteSelected, "Delete vault") != 0 &&
|
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) {
|
strcmp(noteSelected, "Quit (Ctrl+C)") != 0 && strcmp(noteSelected, "Search inside the vault") != 0) {
|
||||||
open_note:
|
open_note:
|
||||||
bypassSelectionNote = 0; // we must reset bypassSelectionNote to avoid getting into an infinite
|
bypassSelectionNote = 0; // we must reset bypassSelectionNote to avoid getting into an infinite
|
||||||
// loop of bypassing the note selection
|
// loop of bypassing the note selection
|
||||||
@@ -675,29 +741,52 @@ backup_config_end:
|
|||||||
|
|
||||||
int *journalWasUpdated = malloc(sizeof(int));
|
int *journalWasUpdated = malloc(sizeof(int));
|
||||||
*journalWasUpdated = 0;
|
*journalWasUpdated = 0;
|
||||||
|
|
||||||
|
// used to go back to note selection
|
||||||
|
int *returnNoteSelection = malloc(sizeof(int));
|
||||||
|
*returnNoteSelection = 0;
|
||||||
|
|
||||||
if (!regexReturn) { // if the regex matches -> it's a journal
|
if (!regexReturn) { // if the regex matches -> it's a journal
|
||||||
debug("%s is a journal. Updating it...", noteSelected);
|
debug("%s is a journal. Updating it...", noteSelected);
|
||||||
fullPath = updateJournal(fullPath, noteSelected, timeFormat, journalWasUpdated,
|
fullPath = updateJournal(fullPath, noteSelected, timeFormat, journalWasUpdated, returnNoteSelection,
|
||||||
shouldDebug); // we return the path. As if it is a divided journal we
|
shouldDebug); // we return the path. As if it is a divided journal we
|
||||||
// must point to the correct entry
|
// must point to the correct entry
|
||||||
}
|
}
|
||||||
if (newLineOnOpening) {
|
if (!*returnNoteSelection) { // if we don't need to return to the note selection
|
||||||
if (*journalWasUpdated) {
|
if (newLineOnOpening) {
|
||||||
appendToFile(fullPath, " \n",
|
if (*journalWasUpdated) {
|
||||||
shouldDebug); // when updating the journal it adds a \n char at the
|
appendToFile(fullPath, " \n",
|
||||||
// end. So appendToFile(\n) does not work. We append a
|
shouldDebug); // when updating the journal it adds a \n char at the
|
||||||
// (special and rare) whitespace character + \n to
|
// end. So appendToFile(\n) does not work. We append a
|
||||||
// bypass this issue.
|
// (special and rare) whitespace character + \n to
|
||||||
|
// bypass this issue.
|
||||||
|
}
|
||||||
|
appendToFile(fullPath, "\n", shouldDebug);
|
||||||
}
|
}
|
||||||
appendToFile(fullPath, "\n", shouldDebug);
|
openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug);
|
||||||
|
free(fullPath);
|
||||||
|
}
|
||||||
|
free(returnNoteSelection);
|
||||||
|
if (gitEnabled) { // After closing a file, we need to update the git repo (if git is enable
|
||||||
|
char *date = getDateAndTime();
|
||||||
|
char *commitMsg = malloc(29);
|
||||||
|
snprintf(commitMsg, 29, "Update - %s", date);
|
||||||
|
gitBackupUpdate(notesDirectoryString, vaultSelected, gitSignatureName, gitSignatureEmail, commitMsg, shouldDebug);
|
||||||
|
free(date);
|
||||||
|
free(commitMsg);
|
||||||
}
|
}
|
||||||
openEditor(fullPath, editorToOpen, shouldRender, shouldJumpToEnd, shouldDebug);
|
|
||||||
free(fullPath);
|
|
||||||
} else if (strcmp(noteSelected, "Create new note") == 0) {
|
} else if (strcmp(noteSelected, "Create new note") == 0) {
|
||||||
note_creation:
|
note_creation:
|
||||||
noteSelected = createNewNote(notesDirectoryString, vaultSelected, bypassSelectionNote, bypassSelectionNoteValue, journalRegex, shouldDebug);
|
noteSelected = createNewNote(notesDirectoryString, vaultSelected, bypassSelectionNote, bypassSelectionNoteValue, journalRegex, shouldDebug);
|
||||||
// we can just go back to open_note
|
// we can just go back to open_note
|
||||||
goto open_note;
|
goto open_note;
|
||||||
|
} else if (strcmp(noteSelected, "Search inside the vault") == 0) {
|
||||||
|
char *vaultPath = malloc(PATH_MAX);
|
||||||
|
snprintf(vaultPath, PATH_MAX, "%s/%s/", notesDirectoryString, vaultSelected);
|
||||||
|
char *result = fzfSelect(vaultPath, "Input text to be searched", shouldDebug);
|
||||||
|
bypassSelectionNote = 1;
|
||||||
|
bypassSelectionNoteValue = result;
|
||||||
|
free(vaultPath);
|
||||||
} else if (strcmp(noteSelected, "Back to vault selection") == 0) {
|
} else if (strcmp(noteSelected, "Back to vault selection") == 0) {
|
||||||
shouldChangeVault = 1;
|
shouldChangeVault = 1;
|
||||||
} else if (strcmp(noteSelected, "Delete vault") == 0) {
|
} else if (strcmp(noteSelected, "Delete vault") == 0) {
|
||||||
@@ -706,7 +795,7 @@ backup_config_end:
|
|||||||
const char *yesNo[] = {"No, go back to note selection.", "Yes."};
|
const char *yesNo[] = {"No, go back to note selection.", "Yes."};
|
||||||
char *answer = ncursesSelect((char **)yesNo,
|
char *answer = ncursesSelect((char **)yesNo,
|
||||||
"Are you sure you want to delete the entire vault? This can "
|
"Are you sure you want to delete the entire vault? This can "
|
||||||
"not be undone (Use arrows or WASD, Enter to select):",
|
"not be undone (Use arrows or WASD, Enter to select or use 1-9 to quick select):",
|
||||||
1, 1, " ", "", "", shouldDebug);
|
1, 1, " ", "", "", shouldDebug);
|
||||||
debug("You answered: %s for deleting the vault %s", answer, vaultSelected);
|
debug("You answered: %s for deleting the vault %s", answer, vaultSelected);
|
||||||
if (strcmp(answer, "Yes.") == 0) {
|
if (strcmp(answer, "Yes.") == 0) {
|
||||||
@@ -731,6 +820,11 @@ backup_config_end:
|
|||||||
} else if (strcmp(vaultSelected, "Settings") == 0) {
|
} else if (strcmp(vaultSelected, "Settings") == 0) {
|
||||||
openEditor(configPath, editorToOpen, 0, 0,
|
openEditor(configPath, editorToOpen, 0, 0,
|
||||||
shouldDebug); // as this is not a md file we set render and jumptoEnfOfFile to 0
|
shouldDebug); // as this is not a md file we set render and jumptoEnfOfFile to 0
|
||||||
|
} else if (strcmp(vaultSelected, "Backup now") == 0) {
|
||||||
|
debug("Running a forced backup");
|
||||||
|
forceBackup = 1;
|
||||||
|
handleBackups(directoriesArray, numDirectories, backupDirectoriesArray, homedir, interval, (const char **)rsyncArgs, rsyncArgsNumber, forceBackup, backupMessage, shouldDebug);
|
||||||
|
debug("backupMessage was set to %d by a normal backup", *backupMessage);
|
||||||
} else if (strcmp(vaultSelected, "Quit (Ctrl+C)") == 0) {
|
} else if (strcmp(vaultSelected, "Quit (Ctrl+C)") == 0) {
|
||||||
debug("The program was exited");
|
debug("The program was exited");
|
||||||
shouldExit = 1;
|
shouldExit = 1;
|
||||||
|
|||||||
+10
-6
@@ -188,7 +188,7 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber
|
|||||||
return vaultsArray;
|
return vaultsArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, int shouldDebug) {
|
char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, int *returnNoteSelection, int shouldDebug) {
|
||||||
path[PATH_MAX] = '\0'; // it assures it is a string (Most cases this does nothing). But rewriting at least
|
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.
|
// one bytes make the compile happy. He doesn't want to return an unchanged input.
|
||||||
debug("Handling the journal %s", path);
|
debug("Handling the journal %s", path);
|
||||||
@@ -221,13 +221,14 @@ char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWas
|
|||||||
that the Create new entry in journal is on top entryArray = realloc(entryArray,
|
that the Create new entry in journal is on top entryArray = realloc(entryArray,
|
||||||
sizeof(char*));*/
|
sizeof(char*));*/
|
||||||
// simpler to just malloc 2 and later realloc
|
// simpler to just malloc 2 and later realloc
|
||||||
const int extraOptions = 3;
|
const int extraOptions = 4;
|
||||||
char **entryArray = malloc(extraOptions * sizeof(char *));
|
char **entryArray = malloc(extraOptions * sizeof(char *));
|
||||||
char *createEntryMessage = malloc(PATH_MAX);
|
char *createEntryMessage = malloc(PATH_MAX);
|
||||||
snprintf(createEntryMessage, PATH_MAX, "Create new entry for the journal %s", journal);
|
snprintf(createEntryMessage, PATH_MAX, "Create new entry for the journal %s", journal);
|
||||||
entryArray[0] = createEntryMessage;
|
entryArray[0] = createEntryMessage;
|
||||||
entryArray[1] = "Open random entry";
|
entryArray[1] = "Open random entry";
|
||||||
entryArray[2] = "Search inside entries";
|
entryArray[2] = "Search inside entries";
|
||||||
|
entryArray[3] = "Return to the note selection";
|
||||||
int entryCount = extraOptions; // we need to count how many dirs there is to always readjust
|
int entryCount = extraOptions; // we need to count how many dirs there is to always readjust
|
||||||
// how many memory we alloc
|
// how many memory we alloc
|
||||||
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
||||||
@@ -259,10 +260,11 @@ char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWas
|
|||||||
// element
|
// element
|
||||||
|
|
||||||
// we must now select to create new entry or to enter in old one
|
// 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, " ", " ", "",
|
char *selectedOption =
|
||||||
shouldDebug); // the "Create new entry for the journal %s" will be the only options. All
|
ncursesSelect(entryArray, "Create new entry or acces old entry (Use arrows or WASD, Enter to select or use 1-9 to quick select):", extraOptions, entryCount - extraOptions, " ", " ", "",
|
||||||
// other will be extraOptions. This is made so that "Create [...] %s" will
|
shouldDebug); // the "Create new entry for the journal %s" will be the only options. All
|
||||||
// always be on top
|
// 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);
|
debug("Selected option from journal entry selection: %s", selectedOption);
|
||||||
if (strcmp(selectedOption, createEntryMessage) == 0) { // create new entry
|
if (strcmp(selectedOption, createEntryMessage) == 0) { // create new entry
|
||||||
char temp[PATH_MAX];
|
char temp[PATH_MAX];
|
||||||
@@ -301,6 +303,8 @@ char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWas
|
|||||||
snprintf(selectedOption, PATH_MAX, "%s/%s", path, temp);
|
snprintf(selectedOption, PATH_MAX, "%s/%s", path, temp);
|
||||||
path = strdup(selectedOption);
|
path = strdup(selectedOption);
|
||||||
free(selectedOption);
|
free(selectedOption);
|
||||||
|
} else if (strcmp(selectedOption, "Return to the note selection") == 0) {
|
||||||
|
*returnNoteSelection = 1;
|
||||||
} else { // we just recreate the path to the selected entry
|
} 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
|
// snprintf does not like to have the same variable as input and output so we use a
|
||||||
// buffer
|
// buffer
|
||||||
|
|||||||
+2
-1
@@ -25,6 +25,7 @@ char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber
|
|||||||
// handles both type of journal (divided and unified).
|
// handles both type of journal (divided and unified).
|
||||||
// creates new entry with date.
|
// creates new entry with date.
|
||||||
// for divided select if we want to acces to a new entry or a old one.
|
// for divided select if we want to acces to a new entry or a old one.
|
||||||
|
// returnNoteSelection tells us if the user wants to go back to the note selection.
|
||||||
// returns the path to the file that needs to be opened.
|
// 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 *returnNoteSelection, int shouldDebug);
|
||||||
#endif
|
#endif
|
||||||
@@ -6,7 +6,8 @@ void createNewVault(char **directoriesArray, int directoryCount, char **vaultsAr
|
|||||||
int emptyWarning = 0; // set to 1 later if inpted empty name
|
int emptyWarning = 0; // set to 1 later if inpted empty name
|
||||||
input_screen:
|
input_screen:
|
||||||
// choose in which directory the vault will be created
|
// 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 *dirToVault =
|
||||||
|
ncursesSelect(directoriesArray, "Select a directory, in which the vault will remain (Use arrows or WASD, Enter to select or use 1-9 to quick select)", directoryCount, 0, "", "", " ", shouldDebug);
|
||||||
char *vaultName = malloc(PATH_MAX);
|
char *vaultName = malloc(PATH_MAX);
|
||||||
if (!bypass) { // if won't bypass (if -v or --vault weren't set)
|
if (!bypass) { // if won't bypass (if -v or --vault weren't set)
|
||||||
echo();
|
echo();
|
||||||
@@ -179,13 +180,7 @@ char *fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
|||||||
error(fd == -1, "program", "mkstemp failed");
|
error(fd == -1, "program", "mkstemp failed");
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
/*
|
|
||||||
* STEP 1:
|
|
||||||
* 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);
|
debug("INDEX CMD: %s", command);
|
||||||
|
|
||||||
if (system(command) != 0) {
|
if (system(command) != 0) {
|
||||||
@@ -193,45 +188,6 @@ char *fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
|||||||
error(1, "program", "rg indexing failed");
|
error(1, "program", "rg indexing failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* STEP 2:
|
|
||||||
* Use fzf on the index file (NO rg anymore)
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
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),
|
snprintf(command, sizeof(command),
|
||||||
"cat %s | fzf --delimiter ':' --prompt='%s' "
|
"cat %s | fzf --delimiter ':' --prompt='%s' "
|
||||||
"--preview 'file={1}; line={2}; nl -ba \"$file\" | sed -n \"$((line-5)),$((line+5))p\" | "
|
"--preview 'file={1}; line={2}; nl -ba \"$file\" | sed -n \"$((line-5)),$((line+5))p\" | "
|
||||||
@@ -254,16 +210,25 @@ char *fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
|||||||
buffer[strcspn(buffer, "\n")] = '\0';
|
buffer[strcspn(buffer, "\n")] = '\0';
|
||||||
result = strdup(buffer);
|
result = strdup(buffer);
|
||||||
}
|
}
|
||||||
|
debug("Buffer: %s", buffer);
|
||||||
|
debug("result: %s", result);
|
||||||
|
|
||||||
// cut at first ':'
|
// cut at first ':'
|
||||||
result = strtok(result, ":");
|
result = strtok(result, ":");
|
||||||
// cut at / (because we need the full paths before and we only need to return the journal entry)
|
// we need to cut out the whole pathToFiles
|
||||||
char *token = strtok(result, "/");
|
int pathToFilesLen = strlen(pathToFiles);
|
||||||
result = NULL;
|
int cutOut = 0;
|
||||||
while (token != NULL) {
|
for (int i = 0; i < pathToFilesLen; i++) {
|
||||||
result = token;
|
if (pathToFiles[i] == result[i]) {
|
||||||
token = strtok(NULL, "/"); // Passing NULL means “don’t start a new string, resume
|
cutOut = i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
result += cutOut + 1; // the + 1 cuts the first /
|
||||||
|
|
||||||
|
// in case we select a journal, we won't open the entry just yet. We only need to return the journal name (everything before [now the first] /
|
||||||
|
result = strtok(result, "/");
|
||||||
|
|
||||||
|
debug("Cleaned note: %s", result);
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
pclose(fzfPipe);
|
pclose(fzfPipe);
|
||||||
@@ -345,6 +310,69 @@ char *ncursesSelect(char **options, char *optionsText, int optionsNumber, int ex
|
|||||||
highlight = 0;
|
highlight = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case '1':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 1.");
|
||||||
|
highlight = 0;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 2.");
|
||||||
|
highlight = 1;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 3.");
|
||||||
|
highlight = 2;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 4.");
|
||||||
|
highlight = 3;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 5.");
|
||||||
|
highlight = 4;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '6':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 6.");
|
||||||
|
highlight = 5;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '7':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 7.");
|
||||||
|
highlight = 6;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '8':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 8.");
|
||||||
|
highlight = 7;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '9':
|
||||||
|
if (optionsNumber + extraOptionsNumber >= 1) {
|
||||||
|
debug("Quickselected option 9.");
|
||||||
|
highlight = 8;
|
||||||
|
goto end_loop;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 10: //\n
|
case 10: //\n
|
||||||
goto end_loop;
|
goto end_loop;
|
||||||
}
|
}
|
||||||
|
|||||||
+215
-5
@@ -71,6 +71,26 @@ void _error(const int shouldDebug, const int condition, const char *type, const
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void _warn(const int condition, const char *file, const int line, const char *function, const char *message, ...) { // use for warnings that should always be shown
|
||||||
|
if (condition) {
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, message);
|
||||||
|
|
||||||
|
int h, m, s;
|
||||||
|
getCurrentTime(&h, &m, &s);
|
||||||
|
|
||||||
|
fprintf(stdout, "\e[0;33m[WARNING -- %d:%d:%d] From file %s line %d function %s:\e[0m\n", h, m, s, file, line, function);
|
||||||
|
|
||||||
|
vfprintf(stdout, message, args);
|
||||||
|
|
||||||
|
fprintf(stdout, "\e[0m\n");
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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("Backuping... source: %s and destination: %s", source, destination);
|
||||||
@@ -113,6 +133,175 @@ static void ensureDir(const char *path, const int shouldDebug) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void slowGitWarning(struct timespec start, int *wasShown) {
|
||||||
|
if (!*wasShown) {
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
|
||||||
|
double elapsed = (double)(now.tv_sec - start.tv_sec) + (double)(now.tv_nsec - start.tv_nsec) / 1e9;
|
||||||
|
|
||||||
|
warn(elapsed > 0.3, "Git operations are taking too much time. You drive might be slow. Please do not stop the program.");
|
||||||
|
if (elapsed > 0.3) {
|
||||||
|
*wasShown = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensureGitDirectory(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const int shouldDebug) {
|
||||||
|
git_repository *repo = NULL;
|
||||||
|
char *fullPath = malloc(PATH_MAX);
|
||||||
|
snprintf(fullPath, PATH_MAX, "%s/%s", path, vault);
|
||||||
|
|
||||||
|
int git_repo_error_code = git_repository_open(&repo, fullPath);
|
||||||
|
if (git_repo_error_code) { // error (most probably that the git repo wasn't initialized)
|
||||||
|
int git_init_error_code = git_repository_init(&repo, fullPath, 0); // the 0 indicates that the directory isn't bare. See https://libgit2.org/docs/reference/main/repository/git_repository_init.html
|
||||||
|
error(git_init_error_code, "program", "git initialization didn't worked. Error code : %d\nSee https://libgit2.org/docs/reference/main/repository/git_repository_init.html for documentation\n\n%s",
|
||||||
|
git_init_error_code, git_error_last()->message);
|
||||||
|
debug("git repo %s was initialized.", fullPath);
|
||||||
|
|
||||||
|
// we need to setup the .gitignore
|
||||||
|
char *gitignorePath = malloc(PATH_MAX);
|
||||||
|
snprintf(gitignorePath, PATH_MAX, "%s/.gitignore", fullPath);
|
||||||
|
FILE *gitignore = fopen(gitignorePath, "a");
|
||||||
|
error(gitignore == NULL, "program", "failed to open/create %s\n%s", gitignorePath, git_error_last()->message);
|
||||||
|
fputs("*\n", gitignore);
|
||||||
|
fputs("!*/\n", gitignore);
|
||||||
|
fputs("!*.md\n", gitignore);
|
||||||
|
fclose(gitignore);
|
||||||
|
debug("Succesfully wrote .gitignore to %s", gitignorePath);
|
||||||
|
git_repository_free(repo); // we free to be able to open it in gitBackupUpdate()
|
||||||
|
char *date = getDateAndTime();
|
||||||
|
char *commitMsg = malloc(37);
|
||||||
|
snprintf(commitMsg, 37, "Initial commit - %s", date);
|
||||||
|
gitBackupUpdate(path, vault, signatureName, signatureEmail, commitMsg, shouldDebug);
|
||||||
|
free(date);
|
||||||
|
free(commitMsg);
|
||||||
|
} else { // the git repo already exists
|
||||||
|
debug("%s is already a git repo. Skipping initialization...", fullPath);
|
||||||
|
git_repository_free(repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gitBackupUpdate(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const char *commitMsg, const int shouldDebug) {
|
||||||
|
// on slow drive or WebDav mounted drive this git operations are pretty slow. We'll just show a warning to avoid the user closing the program.
|
||||||
|
struct timespec start;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||||
|
|
||||||
|
git_repository *repo = NULL;
|
||||||
|
git_index *index = NULL;
|
||||||
|
git_tree *tree = NULL;
|
||||||
|
git_tree *head_tree = NULL;
|
||||||
|
git_commit *head_commit = NULL;
|
||||||
|
git_signature *signature = NULL;
|
||||||
|
git_diff *diff = NULL;
|
||||||
|
|
||||||
|
git_oid tree_id;
|
||||||
|
git_oid commit_id;
|
||||||
|
|
||||||
|
int slowGitWarningAlreadyShown = 0;
|
||||||
|
|
||||||
|
char *fullPath = malloc(PATH_MAX);
|
||||||
|
|
||||||
|
error(fullPath == NULL, "program", "Failed to allocate path");
|
||||||
|
|
||||||
|
snprintf(fullPath, PATH_MAX, "%s/%s", path, vault);
|
||||||
|
|
||||||
|
/* Open repository */
|
||||||
|
int return_code = git_repository_open(&repo, fullPath);
|
||||||
|
|
||||||
|
error(return_code, "program", "Failed to open Git repository\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
/* Get the index */
|
||||||
|
return_code = git_repository_index(&index, repo);
|
||||||
|
error(return_code, "program", "Failed to get Git index\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
/* git add . */
|
||||||
|
return_code = git_index_add_all(index, NULL, 0, NULL, NULL);
|
||||||
|
error(return_code, "program", "Failed to add files\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
return_code = git_index_write(index);
|
||||||
|
error(return_code, "program", "Failed to write index\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check whether there are changes to commit.
|
||||||
|
*
|
||||||
|
* For a new repository there is no HEAD yet, so the index
|
||||||
|
* itself tells us whether there is anything to commit.
|
||||||
|
*/
|
||||||
|
int has_changes = 0;
|
||||||
|
|
||||||
|
if (git_repository_head_unborn(repo)) {
|
||||||
|
has_changes = git_index_entrycount(index) > 0;
|
||||||
|
} else {
|
||||||
|
git_oid head_oid;
|
||||||
|
|
||||||
|
return_code = git_reference_name_to_id(&head_oid, repo, "HEAD");
|
||||||
|
error(return_code, "program", "Failed to get HEAD\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
return_code = git_commit_lookup(&head_commit, repo, &head_oid);
|
||||||
|
error(return_code, "program", "Failed to lookup HEAD commit\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
return_code = git_commit_tree(&head_tree, head_commit);
|
||||||
|
error(return_code, "program", "Failed to get HEAD tree\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
return_code = git_diff_tree_to_index(&diff, repo, head_tree, index, NULL);
|
||||||
|
error(return_code, "program", "Failed to create diff\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
has_changes = git_diff_num_deltas(diff) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nothing changed, so don't create a commit */
|
||||||
|
if (!has_changes) {
|
||||||
|
debug("No changes to commit in %s", fullPath);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* index → tree */
|
||||||
|
return_code = git_index_write_tree(&tree_id, index);
|
||||||
|
error(return_code, "program", "Failed to write tree\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
return_code = git_tree_lookup(&tree, repo, &tree_id);
|
||||||
|
error(return_code, "program", "Failed to lookup tree\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
/* Author/committer */
|
||||||
|
return_code = git_signature_now(&signature, signatureName, signatureEmail);
|
||||||
|
error(return_code, "program", "Failed to create signature\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
/* Create commit */
|
||||||
|
if (git_repository_head_unborn(repo)) {
|
||||||
|
return_code = git_commit_create_v(&commit_id, repo, "HEAD", signature, signature, NULL, commitMsg, tree, 0);
|
||||||
|
} else {
|
||||||
|
return_code = git_commit_create_v(&commit_id, repo, "HEAD", signature, signature, NULL, commitMsg, tree, 1, head_commit);
|
||||||
|
}
|
||||||
|
error(return_code, "program", "Failed to create commit\n%s", git_error_last()->message);
|
||||||
|
slowGitWarning(start, &slowGitWarningAlreadyShown);
|
||||||
|
|
||||||
|
debug("Created commit %s in %s", commitMsg, fullPath);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
git_diff_free(diff);
|
||||||
|
git_signature_free(signature);
|
||||||
|
git_tree_free(tree);
|
||||||
|
git_tree_free(head_tree);
|
||||||
|
git_commit_free(head_commit);
|
||||||
|
git_index_free(index);
|
||||||
|
git_repository_free(repo);
|
||||||
|
free(fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
void initAppFilesAndDirs(const char *home, const int shouldDebug) {
|
void initAppFilesAndDirs(const char *home, const int shouldDebug) {
|
||||||
// sometimes .cache and .config doesn't exist. Such as with github actions machines
|
// sometimes .cache and .config doesn't exist. Such as with github actions machines
|
||||||
char config_dir[PATH_MAX];
|
char config_dir[PATH_MAX];
|
||||||
@@ -157,18 +346,34 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) {
|
|||||||
" \"enable\": false,\n"
|
" \"enable\": false,\n"
|
||||||
" \"directory\": {\n"
|
" \"directory\": {\n"
|
||||||
" \"~/Documents/\": \"path/to/backup/\"\n"
|
" \"~/Documents/\": \"path/to/backup/\"\n"
|
||||||
" },\n"
|
" },\n"
|
||||||
" \"interval\": \"weekly\",\n"
|
" \"interval\": \"weekly\",\n"
|
||||||
" \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n"
|
" \"rsyncArgs\": [\"-Lqah\", \"--update\"],\n"
|
||||||
|
" \"git\": {\n"
|
||||||
|
" \"enable\": false,\n"
|
||||||
|
" \"email\": \"mail@example.com\",\n"
|
||||||
|
" \"name\": \"Example Example\"\n"
|
||||||
|
" }\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"}\n");
|
"}\n");
|
||||||
|
|
||||||
fclose(w);
|
fclose(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *getDateAndTime() {
|
||||||
|
char *return_value = malloc(20);
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm tm_now;
|
||||||
|
localtime_r(&now, &tm_now);
|
||||||
|
|
||||||
|
strftime(return_value, 20, "%Y-%m-%d %H:%M:%S", &tm_now);
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber,
|
void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber,
|
||||||
const int shouldDebug) {
|
const int forceBackup, int *backupMessage, const int shouldDebug) {
|
||||||
int shouldBackup = 0;
|
int shouldBackup = 0;
|
||||||
|
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
debug("Time since epoch is %ld", (long)now);
|
debug("Time since epoch is %ld", (long)now);
|
||||||
char cacheFilePATH[PATH_MAX];
|
char cacheFilePATH[PATH_MAX];
|
||||||
@@ -203,8 +408,12 @@ void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **d
|
|||||||
error(1, "program", "Invalid timestamp in cache file: %s", line);
|
error(1, "program", "Invalid timestamp in cache file: %s", line);
|
||||||
}
|
}
|
||||||
double deltaTime = difftime(now, lastBackupTime);
|
double deltaTime = difftime(now, lastBackupTime);
|
||||||
if (deltaTime > interval) {
|
if (deltaTime > interval || forceBackup) {
|
||||||
debug("(difftime) %f is greater than (interval) %d -> backuping...", deltaTime, interval);
|
if (!forceBackup) {
|
||||||
|
debug("(difftime) %f is greater than (interval) %d -> backuping...", deltaTime, interval);
|
||||||
|
} else {
|
||||||
|
debug("Backup was forced. backuping...");
|
||||||
|
}
|
||||||
shouldBackup = 1;
|
shouldBackup = 1;
|
||||||
cacheFile = fopen(cacheFilePATH, "w");
|
cacheFile = fopen(cacheFilePATH, "w");
|
||||||
if (!cacheFile) {
|
if (!cacheFile) {
|
||||||
@@ -218,6 +427,7 @@ void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **d
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldBackup) {
|
if (shouldBackup) {
|
||||||
|
*backupMessage = 1;
|
||||||
debug("A backup is needed");
|
debug("A backup is needed");
|
||||||
for (int i = 0; i < sourceNumber; i++) {
|
for (int i = 0; i < sourceNumber; i++) {
|
||||||
debug("%s", destinationDirectoryArray[i]);
|
debug("%s", destinationDirectoryArray[i]);
|
||||||
|
|||||||
+16
-2
@@ -6,6 +6,7 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <ftw.h>
|
#include <ftw.h>
|
||||||
|
#include <git2.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#ifndef VERSION
|
#ifndef VERSION
|
||||||
#define VERSION "dev"
|
#define VERSION "dev"
|
||||||
#endif
|
#endif
|
||||||
@@ -29,6 +31,7 @@
|
|||||||
#define debug(message, ...) _debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
#define debug(message, ...) _debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||||
#define altDebug(message, ...) _altDebug(shouldDebug, 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 error(condition, type, message, ...) _error(shouldDebug, condition, type, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||||
|
#define warn(condition, message, ...) _warn(condition, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||||
// you must edit this two values if you want to add suport for an editor
|
// 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 char *supportedEditor[]; // array of supported editors
|
||||||
extern const int numEditors; // number of supported editors
|
extern const int numEditors; // number of supported editors
|
||||||
@@ -51,6 +54,8 @@ void _debug(const int d, const char *file, const int line, const char *function,
|
|||||||
void _altDebug(const int d, const char *message, ...);
|
void _altDebug(const int d, const char *message, ...);
|
||||||
// formated error.
|
// 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, ...);
|
||||||
|
// formated warn
|
||||||
|
void _warn(const int condition, const char *file, const int line, const char *function, const char *message, ...);
|
||||||
// Returns 1 if the string is in the array.
|
// Returns 1 if the string is in the array.
|
||||||
// Returns 0 if the string is not 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.
|
// If you want to only check the first n elements of the array, pass n as len.
|
||||||
@@ -81,7 +86,16 @@ If need launches in the background rsync to do the backuping.
|
|||||||
Each pair of source/destination will launch one rsync process.
|
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.
|
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
|
rsyncArgs is the array of arguments to be passed to rsync. Do not inclue destination or source. It
|
||||||
will be added in the function.*/
|
will be added in the function.
|
||||||
|
backupMessage will be set to 1 if a backup was launched.*/
|
||||||
void handleBackups(char **sourceDirectoryArray, const int sourceDirectoryNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber,
|
void handleBackups(char **sourceDirectoryArray, const int sourceDirectoryNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber,
|
||||||
const int shouldDebug);
|
const int forceBackup, int *backupMessage, const int shouldDebug);
|
||||||
|
/*Equivalent of git init.
|
||||||
|
If the directory wasn't git initialized. We do it.*/
|
||||||
|
void ensureGitDirectory(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const int shouldDebug);
|
||||||
|
/*Configures git, stages everything, commits everything*/
|
||||||
|
void gitBackupUpdate(const char *path, const char *vault, const char *signatureName, const char *signatureEmail, const char *commitMsg, const int shouldDebug);
|
||||||
|
// yyyy-mm-dd hh:mm:ss
|
||||||
|
// Don't forget to free the pointer.
|
||||||
|
char *getDateAndTime();
|
||||||
#endif
|
#endif
|
||||||
Reference in new issue
Block a user