2024-02-18 00:41:59 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <ftw.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <libgen.h>
|
2024-10-22 02:18:18 +02:00
|
|
|
#include <limits.h>
|
2024-02-18 00:41:59 +01:00
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
2024-11-05 00:47:52 +01:00
|
|
|
char *get_log_dir(void) {
|
2024-02-18 00:41:59 +01:00
|
|
|
char *xdg_data_home = getenv("XDG_DATA_HOME");
|
2024-07-05 11:52:27 +02:00
|
|
|
char *dirname = "log";
|
|
|
|
char *log_dir = NULL;
|
2024-02-18 00:41:59 +01:00
|
|
|
if (xdg_data_home == NULL) {
|
|
|
|
xdg_data_home = getenv("HOME");
|
|
|
|
if (xdg_data_home == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: HOME and XDG_DATA_HOME environment variable not set\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
2024-07-05 11:52:27 +02:00
|
|
|
log_dir = malloc(sizeof(char) * (strlen(xdg_data_home) + strlen(dirname) + 2));
|
|
|
|
if (log_dir == NULL) {
|
|
|
|
fprintf(stderr, "log: Error allocating memory\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
2024-07-05 11:52:27 +02:00
|
|
|
sprintf(log_dir, "%s/%s", xdg_data_home, dirname);
|
2024-02-18 00:41:59 +01:00
|
|
|
struct stat dir_stat;
|
2024-07-05 11:52:27 +02:00
|
|
|
if (!((stat(log_dir, &dir_stat) == 0) && S_ISDIR(dir_stat.st_mode))) { // check defined path is directory
|
|
|
|
if (mkdir(log_dir, S_IRWXU)) { // 700
|
|
|
|
fprintf(stderr, "log: Cannot create log directory\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
2024-07-05 11:52:27 +02:00
|
|
|
return log_dir;
|
2024-02-18 00:41:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int compare(const void *a, const void *b) {
|
|
|
|
return strcmp(*(const char **)a, *(const char **)b);
|
|
|
|
}
|
|
|
|
|
|
|
|
void tree(const char *basepath, int depth) {
|
|
|
|
char path[1000];
|
|
|
|
struct dirent *dp;
|
|
|
|
DIR *dir = opendir(basepath);
|
|
|
|
|
|
|
|
if (!dir)
|
|
|
|
return;
|
|
|
|
|
|
|
|
char *files[1000];
|
|
|
|
int file_count = 0;
|
|
|
|
|
|
|
|
while ((dp = readdir(dir)) != NULL) {
|
|
|
|
if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) {
|
|
|
|
files[file_count] = strdup(dp->d_name);
|
|
|
|
file_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
qsort(files, file_count, sizeof(char *), compare);
|
|
|
|
|
|
|
|
for (int i = 0; i < file_count; i++) {
|
|
|
|
for (int j = 0; j < depth - 1; j++) {
|
|
|
|
printf("│ ");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (depth > 0) {
|
|
|
|
printf("├── ");
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("%s\n", files[i]);
|
|
|
|
|
|
|
|
strcpy(path, basepath);
|
|
|
|
strcat(path, "/");
|
|
|
|
strcat(path, files[i]);
|
|
|
|
|
|
|
|
tree(path, depth + 1);
|
|
|
|
|
|
|
|
free(files[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
int list_dir(const char *dir) {
|
|
|
|
if (dir == NULL || *dir == '\0') {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: Invalid directory path\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
struct stat dir_stat;
|
|
|
|
if (stat(dir, &dir_stat) == -1) {
|
2024-07-05 11:52:27 +02:00
|
|
|
perror("log");
|
2024-02-18 00:41:59 +01:00
|
|
|
return errno;
|
|
|
|
}
|
|
|
|
if (S_ISDIR(dir_stat.st_mode)) {
|
|
|
|
tree(dir, 0);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: %s is not a directory\n", dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void find_note(const char *basepath, const char *filename, char **filepaths, int *filecount) {
|
|
|
|
DIR *dir;
|
|
|
|
struct dirent *entry;
|
|
|
|
|
|
|
|
if (!(dir = opendir(basepath))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
|
|
if (entry->d_type == DT_DIR) {
|
|
|
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
char path[1024];
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", basepath, entry->d_name);
|
|
|
|
find_note(path, filename, filepaths, filecount);
|
|
|
|
} else {
|
|
|
|
char *ext = strrchr(entry->d_name, '.');
|
|
|
|
if (ext == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// remove extension
|
|
|
|
char *bname = strdup(entry->d_name);
|
|
|
|
bname[strlen(bname) - strlen(DEFAULT_EXT)] = '\0';
|
|
|
|
if (strcmp(bname, filename) == 0) {
|
|
|
|
int count = *filecount;
|
|
|
|
size_t len = strlen(basepath) + strlen(bname) + 2 + strlen(DEFAULT_EXT);
|
|
|
|
filepaths[count] = malloc(sizeof(char) * len);
|
|
|
|
snprintf(filepaths[count], len, "%s/%s%s", basepath, bname, DEFAULT_EXT);
|
|
|
|
*filecount += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if note exists, if yes return the path
|
|
|
|
char *check_note_exist(char *filename) {
|
2024-07-05 11:52:27 +02:00
|
|
|
char *log_dir = get_log_dir();
|
2024-02-18 00:41:59 +01:00
|
|
|
char **file_paths = malloc(sizeof(char *) * 128); // max 128 same file names
|
|
|
|
int file_count = 0;
|
2024-07-05 11:52:27 +02:00
|
|
|
find_note(log_dir, (const char *) filename, file_paths, &file_count);
|
2024-02-18 00:41:59 +01:00
|
|
|
if (file_count == 0) {
|
2024-07-05 11:52:27 +02:00
|
|
|
free(log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
return NULL;
|
|
|
|
} else if (file_count == 1) {
|
|
|
|
char *filepath = malloc(sizeof(char) * (strlen(file_paths[0]) + 1));
|
|
|
|
if (filepath == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: Error allocating memory\n");
|
|
|
|
free(log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
strcpy(filepath, file_paths[0]);
|
2024-07-05 11:52:27 +02:00
|
|
|
free(log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
return filepath;
|
|
|
|
} else {
|
|
|
|
printf("Multiple files found with the same name:\n");
|
|
|
|
for (int i = 0; i < file_count; i++) {
|
|
|
|
printf("%d. %s\n", i + 1, file_paths[i]);
|
|
|
|
}
|
|
|
|
printf("Enter the number corresponding to the file you want to use: ");
|
|
|
|
int choice;
|
|
|
|
scanf("%d", &choice);
|
|
|
|
if (choice < 1 || choice > file_count) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: Invalid choice\n");
|
|
|
|
free(log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
char *filepath = malloc(sizeof(char) * (strlen(file_paths[choice - 1]) + 1));
|
|
|
|
if (filepath == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: Error allocating memory\n");
|
|
|
|
free(log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
strcpy(filepath, file_paths[choice - 1]);
|
2024-07-05 11:52:27 +02:00
|
|
|
free(log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
return filepath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_boiler_plate(FILE *file, const char *filename, const char *ext) {
|
|
|
|
time_t t = time(NULL);
|
|
|
|
struct tm tm = *localtime(&t);
|
|
|
|
if (strcmp(ext, ".md") == 0) {
|
|
|
|
fprintf(file, "---\n");
|
|
|
|
fprintf(file, "title: %s\n", filename);
|
|
|
|
fprintf(file, "description: \n");
|
|
|
|
fprintf(file, "tags: \n");
|
|
|
|
fprintf(file, "created: \"%d-%02d-%02d\"\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
|
|
|
fprintf(file, "lastmod: \"%d-%02d-%02d\"\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
|
|
|
fprintf(file, "---\n");
|
|
|
|
} else if (strcmp(ext, ".txt") == 0) {
|
|
|
|
fprintf(file, "Title: %s\n", filename);
|
|
|
|
fprintf(file, "Description: \n");
|
|
|
|
fprintf(file, "Tags: \n");
|
|
|
|
fprintf(file, "Created: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
fprintf(file, "Last Modified: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
} else if (strcmp(ext, ".org") == 0) {
|
|
|
|
fprintf(file, "#+TITLE: %s\n", filename);
|
|
|
|
fprintf(file, "#+DESCRIPTION: \n");
|
|
|
|
fprintf(file, "#+TAGS: \n");
|
|
|
|
fprintf(file, "#+CREATED: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
fprintf(file, "#+LASTMOD: %d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
} else if (strcmp(ext, ".html") == 0) {
|
|
|
|
fprintf(file, "<!DOCTYPE html>\n");
|
|
|
|
fprintf(file, "<html>\n");
|
|
|
|
fprintf(file, "\t<head>\n");
|
|
|
|
fprintf(file, "\t\t<title>%s</title>\n", filename);
|
|
|
|
fprintf(file, "\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n");
|
|
|
|
fprintf(file, "\t\t<meta charset=\"UTF-8\">\n");
|
|
|
|
fprintf(file, "\t</head>\n");
|
|
|
|
fprintf(file, "\t<body>\n");
|
|
|
|
fprintf(file, "\t\t<h1>%s</h1>\n", filename);
|
|
|
|
fprintf(file, "\t\t<p>Description: </p>\n");
|
|
|
|
fprintf(file, "\t\t<p>Tags: </p>\n");
|
|
|
|
fprintf(file, "\t\t<p>Created: %d-%02d-%02d %02d:%02d:%02d</p>\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
fprintf(file, "\t\t<p>Last Modified: %d-%02d-%02d %02d:%02d:%02d</p>\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
fprintf(file, "\t</body>\n");
|
|
|
|
fprintf(file, "</html>\n");
|
|
|
|
} else {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: Unsupported file extension\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
void print_note(char *filename) {
|
|
|
|
if (filename == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: filename is required\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
char *filepath = check_note_exist(filename);
|
|
|
|
if (filepath != NULL) {
|
|
|
|
FILE *file = fopen(filepath, "r");
|
|
|
|
if (file == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
perror("log");
|
2024-02-18 00:41:59 +01:00
|
|
|
free(filepath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
char *line = NULL;
|
|
|
|
size_t len = 0;
|
|
|
|
while (getline(&line, &len, file) != -1) {
|
|
|
|
printf("%s", line);
|
|
|
|
}
|
|
|
|
free(line);
|
|
|
|
fclose(file);
|
|
|
|
} else {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: %s is not in log\n", filename);
|
2024-02-18 00:41:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void edit_note(char *filename) {
|
|
|
|
char *filepath = check_note_exist(filename);
|
|
|
|
char *editor = getenv("EDITOR");
|
|
|
|
if (editor == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: EDITOR environment variable must be set\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
free(filepath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (filepath != NULL) {
|
|
|
|
char *cmd = malloc(sizeof(char) * (strlen(editor) + strlen(filepath) + 2));
|
|
|
|
if (cmd == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: Error allocating memory\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
free(filepath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sprintf(cmd, "%s %s", editor, filepath);
|
|
|
|
system(cmd);
|
|
|
|
free(cmd);
|
|
|
|
} else {
|
2024-07-05 11:52:27 +02:00
|
|
|
char *log_dir = get_log_dir();
|
|
|
|
filepath = malloc(sizeof(char) * (strlen(log_dir) + strlen(filename) + 2 + strlen(DEFAULT_EXT)));
|
2024-02-18 00:41:59 +01:00
|
|
|
if (filepath == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: Error allocating memory\n");
|
|
|
|
free(log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
return;
|
|
|
|
}
|
2024-07-05 11:52:27 +02:00
|
|
|
sprintf(filepath, "%s/%s%s", log_dir, filename, DEFAULT_EXT);
|
2024-02-18 00:41:59 +01:00
|
|
|
FILE *file = fopen(filepath, "w");
|
|
|
|
if (file == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
perror("log");
|
2024-02-18 00:41:59 +01:00
|
|
|
free(filepath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
add_boiler_plate(file, filename, DEFAULT_EXT);
|
|
|
|
fclose(file);
|
|
|
|
char *cmd = malloc(sizeof(char) * (strlen(editor) + strlen(filepath) + 2));
|
|
|
|
if (cmd == NULL) {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: Error allocating memory\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
free(filepath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sprintf(cmd, "%s %s", editor, filepath);
|
|
|
|
system(cmd);
|
|
|
|
free(cmd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void remove_note(char *filename) {
|
|
|
|
char *filepath = check_note_exist(filename);
|
|
|
|
if (filepath != NULL) {
|
|
|
|
if (remove(filepath) == -1) {
|
2024-07-05 11:52:27 +02:00
|
|
|
perror("log");
|
2024-02-18 00:41:59 +01:00
|
|
|
} else {
|
2024-07-05 11:52:27 +02:00
|
|
|
printf("log: %s is now removed from log\n", filename);
|
2024-02-18 00:41:59 +01:00
|
|
|
}
|
|
|
|
} else {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: %s is not in log\n", filename);
|
2024-02-18 00:41:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void search_note(char *filename) {
|
|
|
|
char *filepath = check_note_exist(filename);
|
|
|
|
if (filepath != NULL) {
|
|
|
|
printf("Path of %s: %s\n", filename, filepath);
|
|
|
|
} else {
|
2024-07-05 11:52:27 +02:00
|
|
|
fprintf(stderr, "log: %s is not in log\n", filename);
|
2024-02-18 00:41:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void usage(char **argv) {
|
|
|
|
fprintf(stderr, "Usage: %s [-l] [-i] [-v] [-h]\n", argv[0]);
|
|
|
|
fprintf(stderr, " %s [-I|-Q|-R|-S] [filename]\n", argv[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
int res;
|
2024-07-05 11:52:27 +02:00
|
|
|
char *log_dir = get_log_dir();
|
|
|
|
if (log_dir == NULL) {
|
2024-02-18 00:41:59 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argc == 2) {
|
|
|
|
if (strcmp(argv[1], "-l") == 0) {
|
|
|
|
// read dir and print recursively
|
2024-07-05 11:52:27 +02:00
|
|
|
res = list_dir(log_dir);
|
|
|
|
free(log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
return res;
|
|
|
|
} else if (strcmp(argv[1], "-i") == 0) {
|
2024-07-05 11:52:27 +02:00
|
|
|
printf("log directory: %s\n", log_dir);
|
2024-02-18 00:41:59 +01:00
|
|
|
} else if (strcmp(argv[1], "-v") == 0) {
|
2024-07-05 11:52:27 +02:00
|
|
|
printf("log: %s\n", VERSION);
|
2024-02-18 00:41:59 +01:00
|
|
|
} else if (strcmp(argv[1], "-h") == 0) {
|
|
|
|
usage(argv);
|
2024-07-05 11:52:27 +02:00
|
|
|
printf("log is a minimalistic command line note manager written in C.\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
printf("Options:\n");
|
|
|
|
printf(" -l\t\tLists the notes in the directory in a tree format.\n");
|
|
|
|
printf(" -i\t\tPrints the directory where the notes are stored.\n");
|
|
|
|
printf(" -v\t\tPrints the version of the program.\n");
|
|
|
|
printf(" -h\t\tShow the help message.\n");
|
|
|
|
printf(" -I [filename]\tCreates a new note with the name specified in the filename.\n");
|
|
|
|
printf(" -Q [filename]\tSearches for the note with specified name and prints its path to the stdout.\n");
|
|
|
|
printf(" -R [filename]\tRemoves the note specified in the filename.\n");
|
|
|
|
printf(" -S [filename]\tPrints the content of the note specified in the filename.\n");
|
|
|
|
} else {
|
|
|
|
usage(argv);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else if (argc == 3) {
|
|
|
|
if (strcmp(argv[1], "-I") == 0) {
|
|
|
|
if (argv[2] != NULL && argv[2][0] != '-') {
|
|
|
|
edit_note(argv[2]);
|
|
|
|
} else {
|
2024-07-05 11:52:27 +02:00
|
|
|
printf("log: filename cannot be start with '-'\n");
|
2024-02-18 00:41:59 +01:00
|
|
|
usage(argv);
|
|
|
|
}
|
|
|
|
} else if (strcmp(argv[1], "-Q") == 0) {
|
|
|
|
search_note(argv[2]);
|
|
|
|
} else if (strcmp(argv[1], "-R") == 0) {
|
|
|
|
remove_note(argv[2]);
|
|
|
|
} else if (strcmp(argv[1], "-S") == 0) {
|
|
|
|
print_note(argv[2]);
|
|
|
|
} else {
|
|
|
|
usage(argv);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
usage(argv);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|