diff --git a/Makefile b/Makefile index 803dc1c..7bc94f5 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,15 @@ CC = cc CFLAGS = -Wall -Wextra -O3 CURL_LIBS = -lcurl -SOURCES = sb-cpu.c sb-date.c sb-memory.c sb-time.c sb-weather.c sb-battery.c +SOURCES = vidir.c sb-cpu.c sb-date.c sb-memory.c sb-time.c sb-weather.c sb-battery.c EXECUTABLES = $(SOURCES:.c=) all: $(EXECUTABLES) +vidir: vidir.c + $(CC) $(CFLAGS) $< -o $@ + sb-weather: sb-weather.c $(CC) $(CFLAGS) $< -o $@ $(CURL_LIBS) diff --git a/vidir.c b/vidir.c new file mode 100644 index 0000000..2d51024 --- /dev/null +++ b/vidir.c @@ -0,0 +1,198 @@ +// vidir.c - rewrite of Joey Hess's vidir in C +// usage: ./vidir [--verbose] [dir|file|-] + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_FILES 10000 + +typedef struct { + int id; + char *path; +} Item; + +static int verbose = 0; +static Item items[MAX_FILES]; +static int n_items = 0; +static int error_flag = 0; + +void die(const char *msg) { + perror(msg); + exit(1); +} + +void add_item(const char *path) { + if (n_items >= MAX_FILES) { + fprintf(stderr, "too many files\n"); + exit(1); + } + items[n_items].id = n_items + 1; + items[n_items].path = strdup(path); + n_items++; +} + +void read_dir(const char *dirname) { + DIR *dir = opendir(dirname); + if (!dir) { + fprintf(stderr, "cannot read %s: %s\n", dirname, strerror(errno)); + exit(1); + } + struct dirent *ent; + while ((ent = readdir(dir))) { + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + continue; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", dirname, ent->d_name); + add_item(path); + } + closedir(dir); +} + +int rm_path(const char *p) { + struct stat st; + if (lstat(p, &st) != 0) + return -1; + if (S_ISDIR(st.st_mode)) + return rmdir(p); + else + return unlink(p); +} + +int main(int argc, char *argv[]) { + char tmpname[] = "/tmp/vidirXXXXXX"; + int fd; + FILE *tmp; + char line[PATH_MAX + 64]; + char *editor; + char buf[PATH_MAX + 64]; + + // parse args + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { + verbose = 1; + } else if (!strcmp(argv[i], "-")) { + // read stdin list + while (fgets(line, sizeof(line), stdin)) { + line[strcspn(line, "\r\n")] = 0; + if (*line) + add_item(line); + } + } else { + struct stat st; + if (stat(argv[i], &st) == 0 && S_ISDIR(st.st_mode)) + read_dir(argv[i]); + else + add_item(argv[i]); + } + } + if (n_items == 0) + read_dir("."); + + // write numbered list + fd = mkstemp(tmpname); + if (fd < 0) + die("mkstemp"); + tmp = fdopen(fd, "w"); + if (!tmp) + die("fdopen"); + int digits = snprintf(NULL, 0, "%d", n_items); + for (int i = 0; i < n_items; i++) + fprintf(tmp, "%0*d\t%s\n", digits, items[i].id, items[i].path); + fclose(tmp); + + // pick editor + editor = getenv("VISUAL"); + if (!editor) + editor = getenv("EDITOR"); + if (!editor) + editor = "vi"; + + snprintf(buf, sizeof(buf), "%s %s", editor, tmpname); + int ret = system(buf); + if (ret != 0) { + fprintf(stderr, "editor exited nonzero, aborting\n"); + unlink(tmpname); + exit(1); + } + + // re-read edited list + tmp = fopen(tmpname, "r"); + if (!tmp) + die("fopen tmp"); + + char newname[PATH_MAX]; + while (fgets(line, sizeof(line), tmp)) { + line[strcspn(line, "\r\n")] = 0; + if (strlen(line) == 0) + continue; + int num; + if (sscanf(line, "%d%[^\n]", &num, newname) < 1) + continue; + char *p = newname; + while (isspace((unsigned char)*p)) + p++; + if (num <= 0 || num > n_items) + continue; + char *old = items[num - 1].path; + if (*p == '\0') { + // deleted line -> remove file + if (rm_path(old) != 0) { + fprintf(stderr, "failed to remove %s: %s\n", old, strerror(errno)); + error_flag = 1; + } else if (verbose) { + printf("removed '%s'\n", old); + } + } else if (strcmp(p, old) != 0) { + // rename + char dirbuf[PATH_MAX]; + strcpy(dirbuf, p); + char *d = dirname(dirbuf); + mkdir(d, 0777); + if (access(p, F_OK) == 0) { + char tmpname[PATH_MAX + 16]; // allow space for suffixes + int suffix = 0; + snprintf(tmpname, sizeof(tmpname), "%s~", p); + while (access(tmpname, F_OK) == 0) { + suffix++; + snprintf(tmpname, sizeof(tmpname), "%s~%d", p, suffix); + } + if (rename(p, tmpname) != 0) { + fprintf(stderr, "failed to rename '%s' -> '%s': %s\n", p, tmpname, + strerror(errno)); + error_flag = 1; + } else if (verbose) { + printf("renamed '%s' -> '%s'\n", p, tmpname); + } + // update internal mapping so future renames see the new tmp location + for (int j = 0; j < n_items; j++) { + if (strcmp(items[j].path, p) == 0) { + free(items[j].path); + items[j].path = strdup(tmpname); + } + } + } + + // now perform the intended rename + if (rename(old, p) != 0) { + fprintf(stderr, "failed to rename %s -> %s: %s\n", old, p, + strerror(errno)); + error_flag = 1; + } else if (verbose) { + printf("renamed '%s' -> '%s'\n", old, p); + } + free(items[num - 1].path); + items[num - 1].path = strdup(p); + } + } + fclose(tmp); + unlink(tmpname); + return error_flag; +}