log.c (13201B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <errno.h> 5 #include <ftw.h> 6 #include <unistd.h> 7 #include <time.h> 8 #include <dirent.h> 9 #include <sys/stat.h> 10 #include <libgen.h> 11 #include <linux/limits.h> 12 13 #include "config.h" 14 15 char *get_log_dir() { 16 char *xdg_data_home = getenv("XDG_DATA_HOME"); 17 char *dirname = "log"; 18 char *log_dir = NULL; 19 if (xdg_data_home == NULL) { 20 xdg_data_home = getenv("HOME"); 21 if (xdg_data_home == NULL) { 22 fprintf(stderr, "log: HOME and XDG_DATA_HOME environment variable not set\n"); 23 exit(EXIT_FAILURE); 24 } 25 } 26 log_dir = malloc(sizeof(char) * (strlen(xdg_data_home) + strlen(dirname) + 2)); 27 if (log_dir == NULL) { 28 fprintf(stderr, "log: Error allocating memory\n"); 29 exit(EXIT_FAILURE); 30 } 31 sprintf(log_dir, "%s/%s", xdg_data_home, dirname); 32 struct stat dir_stat; 33 if (!((stat(log_dir, &dir_stat) == 0) && S_ISDIR(dir_stat.st_mode))) { // check defined path is directory 34 if (mkdir(log_dir, S_IRWXU)) { // 700 35 fprintf(stderr, "log: Cannot create log directory\n"); 36 exit(EXIT_FAILURE); 37 } 38 } 39 return log_dir; 40 } 41 42 int compare(const void *a, const void *b) { 43 return strcmp(*(const char **)a, *(const char **)b); 44 } 45 46 void tree(const char *basepath, int depth) { 47 char path[1000]; 48 struct dirent *dp; 49 DIR *dir = opendir(basepath); 50 51 if (!dir) 52 return; 53 54 char *files[1000]; 55 int file_count = 0; 56 57 while ((dp = readdir(dir)) != NULL) { 58 if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) { 59 files[file_count] = strdup(dp->d_name); 60 file_count++; 61 } 62 } 63 64 qsort(files, file_count, sizeof(char *), compare); 65 66 for (int i = 0; i < file_count; i++) { 67 for (int j = 0; j < depth - 1; j++) { 68 printf("│ "); 69 } 70 71 if (depth > 0) { 72 printf("├── "); 73 } 74 75 printf("%s\n", files[i]); 76 77 strcpy(path, basepath); 78 strcat(path, "/"); 79 strcat(path, files[i]); 80 81 tree(path, depth + 1); 82 83 free(files[i]); 84 } 85 86 closedir(dir); 87 } 88 89 int list_dir(const char *dir) { 90 if (dir == NULL || *dir == '\0') { 91 fprintf(stderr, "log: Invalid directory path\n"); 92 return 1; 93 } 94 struct stat dir_stat; 95 if (stat(dir, &dir_stat) == -1) { 96 perror("log"); 97 return errno; 98 } 99 if (S_ISDIR(dir_stat.st_mode)) { 100 tree(dir, 0); 101 return 0; 102 103 } else { 104 fprintf(stderr, "log: %s is not a directory\n", dir); 105 return 1; 106 } 107 } 108 109 void find_note(const char *basepath, const char *filename, char **filepaths, int *filecount) { 110 DIR *dir; 111 struct dirent *entry; 112 113 if (!(dir = opendir(basepath))) { 114 return; 115 } 116 117 while ((entry = readdir(dir)) != NULL) { 118 if (entry->d_type == DT_DIR) { 119 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { 120 continue; 121 } 122 char path[1024]; 123 snprintf(path, sizeof(path), "%s/%s", basepath, entry->d_name); 124 find_note(path, filename, filepaths, filecount); 125 } else { 126 char *ext = strrchr(entry->d_name, '.'); 127 if (ext == NULL) { 128 continue; 129 } 130 // remove extension 131 char *bname = strdup(entry->d_name); 132 bname[strlen(bname) - strlen(DEFAULT_EXT)] = '\0'; 133 if (strcmp(bname, filename) == 0) { 134 int count = *filecount; 135 size_t len = strlen(basepath) + strlen(bname) + 2 + strlen(DEFAULT_EXT); 136 filepaths[count] = malloc(sizeof(char) * len); 137 snprintf(filepaths[count], len, "%s/%s%s", basepath, bname, DEFAULT_EXT); 138 *filecount += 1; 139 } 140 } 141 } 142 closedir(dir); 143 } 144 145 // check if note exists, if yes return the path 146 char *check_note_exist(char *filename) { 147 char *log_dir = get_log_dir(); 148 char **file_paths = malloc(sizeof(char *) * 128); // max 128 same file names 149 int file_count = 0; 150 find_note(log_dir, (const char *) filename, file_paths, &file_count); 151 if (file_count == 0) { 152 free(log_dir); 153 return NULL; 154 } else if (file_count == 1) { 155 char *filepath = malloc(sizeof(char) * (strlen(file_paths[0]) + 1)); 156 if (filepath == NULL) { 157 fprintf(stderr, "log: Error allocating memory\n"); 158 free(log_dir); 159 exit(EXIT_FAILURE); 160 } 161 strcpy(filepath, file_paths[0]); 162 free(log_dir); 163 return filepath; 164 } else { 165 printf("Multiple files found with the same name:\n"); 166 for (int i = 0; i < file_count; i++) { 167 printf("%d. %s\n", i + 1, file_paths[i]); 168 } 169 printf("Enter the number corresponding to the file you want to use: "); 170 int choice; 171 scanf("%d", &choice); 172 if (choice < 1 || choice > file_count) { 173 fprintf(stderr, "log: Invalid choice\n"); 174 free(log_dir); 175 return NULL; 176 } 177 char *filepath = malloc(sizeof(char) * (strlen(file_paths[choice - 1]) + 1)); 178 if (filepath == NULL) { 179 fprintf(stderr, "log: Error allocating memory\n"); 180 free(log_dir); 181 exit(EXIT_FAILURE); 182 } 183 strcpy(filepath, file_paths[choice - 1]); 184 free(log_dir); 185 return filepath; 186 } 187 } 188 189 void add_boiler_plate(FILE *file, const char *filename, const char *ext) { 190 time_t t = time(NULL); 191 struct tm tm = *localtime(&t); 192 if (strcmp(ext, ".md") == 0) { 193 fprintf(file, "---\n"); 194 fprintf(file, "title: %s\n", filename); 195 fprintf(file, "description: \n"); 196 fprintf(file, "tags: \n"); 197 fprintf(file, "created: \"%d-%02d-%02d\"\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); 198 fprintf(file, "lastmod: \"%d-%02d-%02d\"\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); 199 fprintf(file, "---\n"); 200 } else if (strcmp(ext, ".txt") == 0) { 201 fprintf(file, "Title: %s\n", filename); 202 fprintf(file, "Description: \n"); 203 fprintf(file, "Tags: \n"); 204 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); 205 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); 206 } else if (strcmp(ext, ".org") == 0) { 207 fprintf(file, "#+TITLE: %s\n", filename); 208 fprintf(file, "#+DESCRIPTION: \n"); 209 fprintf(file, "#+TAGS: \n"); 210 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); 211 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); 212 } else if (strcmp(ext, ".html") == 0) { 213 fprintf(file, "<!DOCTYPE html>\n"); 214 fprintf(file, "<html>\n"); 215 fprintf(file, "\t<head>\n"); 216 fprintf(file, "\t\t<title>%s</title>\n", filename); 217 fprintf(file, "\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"); 218 fprintf(file, "\t\t<meta charset=\"UTF-8\">\n"); 219 fprintf(file, "\t</head>\n"); 220 fprintf(file, "\t<body>\n"); 221 fprintf(file, "\t\t<h1>%s</h1>\n", filename); 222 fprintf(file, "\t\t<p>Description: </p>\n"); 223 fprintf(file, "\t\t<p>Tags: </p>\n"); 224 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); 225 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); 226 fprintf(file, "\t</body>\n"); 227 fprintf(file, "</html>\n"); 228 } else { 229 fprintf(stderr, "log: Unsupported file extension\n"); 230 } 231 } 232 void print_note(char *filename) { 233 if (filename == NULL) { 234 fprintf(stderr, "log: filename is required\n"); 235 return; 236 } 237 char *filepath = check_note_exist(filename); 238 if (filepath != NULL) { 239 FILE *file = fopen(filepath, "r"); 240 if (file == NULL) { 241 perror("log"); 242 free(filepath); 243 return; 244 } 245 char *line = NULL; 246 size_t len = 0; 247 while (getline(&line, &len, file) != -1) { 248 printf("%s", line); 249 } 250 free(line); 251 fclose(file); 252 } else { 253 fprintf(stderr, "log: %s is not in log\n", filename); 254 } 255 } 256 257 void edit_note(char *filename) { 258 char *filepath = check_note_exist(filename); 259 char *editor = getenv("EDITOR"); 260 if (editor == NULL) { 261 fprintf(stderr, "log: EDITOR environment variable must be set\n"); 262 free(filepath); 263 return; 264 } 265 if (filepath != NULL) { 266 char *cmd = malloc(sizeof(char) * (strlen(editor) + strlen(filepath) + 2)); 267 if (cmd == NULL) { 268 fprintf(stderr, "log: Error allocating memory\n"); 269 free(filepath); 270 return; 271 } 272 sprintf(cmd, "%s %s", editor, filepath); 273 system(cmd); 274 free(cmd); 275 } else { 276 char *log_dir = get_log_dir(); 277 filepath = malloc(sizeof(char) * (strlen(log_dir) + strlen(filename) + 2 + strlen(DEFAULT_EXT))); 278 if (filepath == NULL) { 279 fprintf(stderr, "log: Error allocating memory\n"); 280 free(log_dir); 281 return; 282 } 283 sprintf(filepath, "%s/%s%s", log_dir, filename, DEFAULT_EXT); 284 FILE *file = fopen(filepath, "w"); 285 if (file == NULL) { 286 perror("log"); 287 free(filepath); 288 return; 289 } 290 add_boiler_plate(file, filename, DEFAULT_EXT); 291 fclose(file); 292 char *cmd = malloc(sizeof(char) * (strlen(editor) + strlen(filepath) + 2)); 293 if (cmd == NULL) { 294 fprintf(stderr, "log: Error allocating memory\n"); 295 free(filepath); 296 return; 297 } 298 sprintf(cmd, "%s %s", editor, filepath); 299 system(cmd); 300 free(cmd); 301 } 302 } 303 304 void remove_note(char *filename) { 305 char *filepath = check_note_exist(filename); 306 if (filepath != NULL) { 307 if (remove(filepath) == -1) { 308 perror("log"); 309 } else { 310 printf("log: %s is now removed from log\n", filename); 311 } 312 } else { 313 fprintf(stderr, "log: %s is not in log\n", filename); 314 } 315 } 316 317 void search_note(char *filename) { 318 char *filepath = check_note_exist(filename); 319 if (filepath != NULL) { 320 printf("Path of %s: %s\n", filename, filepath); 321 } else { 322 fprintf(stderr, "log: %s is not in log\n", filename); 323 } 324 } 325 326 void usage(char **argv) { 327 fprintf(stderr, "Usage: %s [-l] [-i] [-v] [-h]\n", argv[0]); 328 fprintf(stderr, " %s [-I|-Q|-R|-S] [filename]\n", argv[0]); 329 } 330 331 int main(int argc, char **argv) { 332 int res; 333 char *log_dir = get_log_dir(); 334 if (log_dir == NULL) { 335 return 1; 336 } 337 338 if (argc == 2) { 339 if (strcmp(argv[1], "-l") == 0) { 340 // read dir and print recursively 341 res = list_dir(log_dir); 342 free(log_dir); 343 return res; 344 } else if (strcmp(argv[1], "-i") == 0) { 345 printf("log directory: %s\n", log_dir); 346 } else if (strcmp(argv[1], "-v") == 0) { 347 printf("log: %s\n", VERSION); 348 } else if (strcmp(argv[1], "-h") == 0) { 349 usage(argv); 350 printf("log is a minimalistic command line note manager written in C.\n"); 351 printf("Options:\n"); 352 printf(" -l\t\tLists the notes in the directory in a tree format.\n"); 353 printf(" -i\t\tPrints the directory where the notes are stored.\n"); 354 printf(" -v\t\tPrints the version of the program.\n"); 355 printf(" -h\t\tShow the help message.\n"); 356 printf(" -I [filename]\tCreates a new note with the name specified in the filename.\n"); 357 printf(" -Q [filename]\tSearches for the note with specified name and prints its path to the stdout.\n"); 358 printf(" -R [filename]\tRemoves the note specified in the filename.\n"); 359 printf(" -S [filename]\tPrints the content of the note specified in the filename.\n"); 360 } else { 361 usage(argv); 362 return 0; 363 } 364 } else if (argc == 3) { 365 if (strcmp(argv[1], "-I") == 0) { 366 if (argv[2] != NULL && argv[2][0] != '-') { 367 edit_note(argv[2]); 368 } else { 369 printf("log: filename cannot be start with '-'\n"); 370 usage(argv); 371 } 372 } else if (strcmp(argv[1], "-Q") == 0) { 373 search_note(argv[2]); 374 } else if (strcmp(argv[1], "-R") == 0) { 375 remove_note(argv[2]); 376 } else if (strcmp(argv[1], "-S") == 0) { 377 print_note(argv[2]); 378 } else { 379 usage(argv); 380 return 0; 381 } 382 } else { 383 usage(argv); 384 return 0; 385 } 386 }