main.c (9993B)
1 #include <dirent.h> 2 #include <limits.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <sys/stat.h> 7 #include <unistd.h> 8 9 #include "../command.h" 10 #include "cofyc/argparse.h" 11 #include "common/fs-utils.h" 12 13 static int cmd_repository_list(int argc, const char **argv); 14 static int cmd_repository_add(int argc, const char **argv); 15 static int cmd_repository_remove(int argc, const char **argv); 16 static int cmd_repository_clean_cache(int argc, const char **argv); 17 18 static const char *const usages[] = { 19 "repository <subcommand> [options]", 20 NULL, 21 }; 22 23 static int cmd_repository(int argc, const char **argv) { 24 struct argparse_option options[] = { 25 OPT_HELP(), 26 OPT_END(), 27 }; 28 struct argparse argparse; 29 argparse_init(&argparse, options, usages, 0); 30 argc = argparse_parse(&argparse, argc, argv); 31 32 // If no subcommand provided, show available subcommands 33 if (argc < 1) { 34 printf("Available subcommands:\n"); 35 printf(" list List the names of the repositories\n"); 36 printf(" add Add a repository: add <name> <url>\n"); 37 printf(" remove Remove a repository: remove <name>\n"); 38 printf(" clean-cache Remove all cached manifest files\n"); 39 return 0; 40 } 41 42 // Dispatch to the appropriate subcommand handler 43 if (!strcmp(argv[0], "list")) { 44 return cmd_repository_list(argc - 1, argv + 1); 45 } else if (!strcmp(argv[0], "add")) { 46 return cmd_repository_add(argc - 1, argv + 1); 47 } else if (!strcmp(argv[0], "remove")) { 48 return cmd_repository_remove(argc - 1, argv + 1); 49 } else if (!strcmp(argv[0], "clean-cache")) { 50 return cmd_repository_clean_cache(argc - 1, argv + 1); 51 } else { 52 fprintf(stderr, "Error: unknown subcommand '%s'\n", argv[0]); 53 return 1; 54 } 55 } 56 57 // List all repository names from files in the repository directory 58 // Files are parsed line by line, with '#' starting comments 59 static int cmd_repository_list(int argc, const char **argv) { 60 (void)argc; 61 (void)argv; 62 63 char *repo_dir = get_repo_dir(); 64 if (!repo_dir) { 65 return 1; 66 } 67 DIR *dir = opendir(repo_dir); 68 free(repo_dir); 69 if (!dir) { 70 fprintf(stderr, "Error: could not open repository directory\n"); 71 return 1; 72 } 73 74 struct dirent *entry; 75 // Iterate through all files in the repository directory 76 while ((entry = readdir(dir)) != NULL) { 77 // Skip . and .. entries 78 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; 79 80 struct stat st; 81 char *repo_dir2 = get_repo_dir(); 82 if (!repo_dir2) { 83 closedir(dir); 84 return 1; 85 } 86 char filepath[PATH_MAX]; 87 snprintf(filepath, sizeof(filepath), "%s%s", repo_dir2, entry->d_name); 88 free(repo_dir2); 89 // Skip non-regular files 90 if (stat(filepath, &st) < 0 || !S_ISREG(st.st_mode)) continue; 91 92 FILE *f = fopen(filepath, "r"); 93 if (!f) continue; 94 95 // Parse each line in the file 96 char line[LINE_MAX]; 97 while (fgets(line, sizeof(line), f)) { 98 // Remove trailing newline 99 line[strcspn(line, "\n")] = '\0'; 100 // Strip comments (everything after '#') 101 char *comment = strchr(line, '#'); 102 if (comment) *comment = '\0'; 103 // Extract the first word as the repository name 104 char *name = strtok(line, " \t"); 105 if (name && name[0] != '\0') { 106 printf("%s\n", name); 107 } 108 } 109 fclose(f); 110 } 111 112 closedir(dir); 113 return 0; 114 } 115 116 // Add a repository entry to the 00-managed file 117 // Format: <name> <url> 118 static int cmd_repository_add(int argc, const char **argv) { 119 if (argc < 2) { 120 fprintf(stderr, "Error: add requires <name> and <url>\n"); 121 fprintf(stderr, "Usage: repository add <name> <url>\n"); 122 return 1; 123 } 124 125 const char *name = argv[0]; 126 const char *url = argv[1]; 127 128 // Build the file path: ~/.config/finwo/dep/repositories.d/00-managed 129 char *repo_dir = get_repo_dir(); 130 if (!repo_dir) { 131 return 1; 132 } 133 char filepath[PATH_MAX]; 134 snprintf(filepath, sizeof(filepath), "%s00-managed", repo_dir); 135 free(repo_dir); 136 137 // Open the file in append mode 138 FILE *f = fopen(filepath, "a"); 139 if (!f) { 140 fprintf(stderr, "Error: could not open file '%s' for writing\n", filepath); 141 return 1; 142 } 143 144 // Write the repository entry: name url 145 fprintf(f, "%s %s\n", name, url); 146 fclose(f); 147 148 printf("Repository '%s' added.\n", name); 149 return 0; 150 } 151 152 // Remove a repository entry by name from all files in the repository directory 153 // If a file becomes empty after removal, it is deleted 154 static int cmd_repository_remove(int argc, const char **argv) { 155 if (argc < 1) { 156 fprintf(stderr, "Error: remove requires <name>\n"); 157 fprintf(stderr, "Usage: repository remove <name>\n"); 158 return 1; 159 } 160 161 const char *name = argv[0]; 162 int found = 0; 163 164 char *repo_dir = get_repo_dir(); 165 if (!repo_dir) { 166 return 1; 167 } 168 DIR *dir = opendir(repo_dir); 169 free(repo_dir); 170 if (!dir) { 171 fprintf(stderr, "Error: could not open repository directory\n"); 172 return 1; 173 } 174 175 struct dirent *entry; 176 // Iterate through all files in the repository directory 177 while ((entry = readdir(dir)) != NULL) { 178 // Skip . and .. entries 179 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; 180 181 char *repo_dir2 = get_repo_dir(); 182 if (!repo_dir2) { 183 closedir(dir); 184 return 1; 185 } 186 char filepath[PATH_MAX]; 187 snprintf(filepath, sizeof(filepath), "%s%s", repo_dir2, entry->d_name); 188 free(repo_dir2); 189 190 struct stat st; 191 // Skip non-regular files 192 if (stat(filepath, &st) < 0 || !S_ISREG(st.st_mode)) continue; 193 194 // Create a temporary file for writing the modified content 195 char temp_path[PATH_MAX]; 196 snprintf(temp_path, sizeof(temp_path), "%s.tmp", filepath); 197 198 FILE *in = fopen(filepath, "r"); 199 FILE *out = fopen(temp_path, "w"); 200 if (!in || !out) { 201 fprintf(stderr, "Error: could not open files for processing '%s'\n", filepath); 202 if (in) fclose(in); 203 if (out) fclose(out); 204 closedir(dir); 205 return 1; 206 } 207 208 // Process each line, removing lines that match the repository name 209 char line[LINE_MAX]; 210 while (fgets(line, sizeof(line), in)) { 211 // Make a copy for parsing (preserving original line for output) 212 char line_copy[LINE_MAX]; 213 strncpy(line_copy, line, sizeof(line_copy) - 1); 214 line_copy[sizeof(line_copy) - 1] = '\0'; 215 216 // Strip comments before matching 217 line_copy[strcspn(line_copy, "\n")] = '\0'; 218 char *comment = strchr(line_copy, '#'); 219 if (comment) *comment = '\0'; 220 char *line_name = strtok(line_copy, " \t"); 221 222 // Skip lines that match the repository name 223 if (line_name && !strcmp(line_name, name)) { 224 found = 1; 225 continue; 226 } 227 // Write the original line back to the temp file 228 fputs(line, out); 229 } 230 231 fclose(in); 232 fclose(out); 233 234 // Replace original file with the temporary file 235 if (rename(temp_path, filepath) < 0) { 236 fprintf(stderr, "Error: could not replace file '%s'\n", filepath); 237 closedir(dir); 238 return 1; 239 } 240 241 // If the file is now empty, remove it 242 if (stat(filepath, &st) == 0 && st.st_size == 0) { 243 if (remove(filepath) < 0) { 244 fprintf(stderr, "Error: could not remove empty file '%s'\n", filepath); 245 closedir(dir); 246 return 1; 247 } 248 } 249 } 250 251 closedir(dir); 252 253 if (!found) { 254 fprintf(stderr, "Warning: repository '%s' not found\n", name); 255 } else { 256 printf("Repository '%s' removed.\n", name); 257 } 258 259 return 0; 260 } 261 262 static int cmd_repository_clean_cache(int argc, const char **argv) { 263 (void)argc; 264 (void)argv; 265 266 char *cache_dir = get_cache_dir(); 267 if (!cache_dir) { 268 return 1; 269 } 270 271 DIR *dir = opendir(cache_dir); 272 if (!dir) { 273 free(cache_dir); 274 return 0; 275 } 276 277 struct dirent *entry; 278 int removed = 0; 279 280 while ((entry = readdir(dir)) != NULL) { 281 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; 282 283 char filepath[PATH_MAX]; 284 snprintf(filepath, sizeof(filepath), "%s%s", cache_dir, entry->d_name); 285 286 struct stat st; 287 if (stat(filepath, &st) < 0 || !S_ISREG(st.st_mode)) continue; 288 289 if (remove(filepath) == 0) { 290 removed++; 291 } 292 } 293 294 closedir(dir); 295 free(cache_dir); 296 297 printf("Removed %d cached manifest file%s.\n", removed, removed == 1 ? "" : "s"); 298 return 0; 299 } 300 301 // Register the repository command with the command system 302 void __attribute__((constructor)) cmd_repository_setup(void) { 303 struct cmd_struct *cmd = calloc(1, sizeof(struct cmd_struct)); 304 if (!cmd) { 305 fprintf(stderr, "Failed to allocate memory for repository command\n"); 306 return; 307 } 308 cmd->next = commands; 309 cmd->fn = cmd_repository; 310 static const char *repository_names[] = {"repository", "repo", "r", NULL}; 311 cmd->name = repository_names; 312 cmd->display = "r(epo(sitory))"; 313 cmd->description = "Repository management"; 314 cmd->help_text = 315 "dep repository - Repository management\n" 316 "\n" 317 "Usage:\n" 318 " dep repository list\n" 319 " dep repository add <name> <url>\n" 320 " dep repository remove <name>\n" 321 " dep repository clean-cache\n" 322 "\n" 323 "Description:\n" 324 " dep can use custom repositories to discover packages. Repositories are\n" 325 " configured in ~/.config/finwo/dep/repositories.d/.\n" 326 "\n" 327 "Subcommands:\n" 328 " list List the names of the configured repositories\n" 329 " add Add a repository: dep repository add <name> <url>\n" 330 " remove Remove a repository: dep repository remove <name>\n" 331 " clean-cache Remove all cached manifest files\n" 332 "\n" 333 "Examples:\n" 334 " dep repository add myorg https://example.com/path/to/manifest\n" 335 " dep repository list\n" 336 " dep repository remove myorg\n" 337 " dep repository clean-cache\n"; 338 commands = cmd; 339 }