diff options
Diffstat (limited to 'src/hotload.c')
-rw-r--r-- | src/hotload.c | 288 |
1 files changed, 212 insertions, 76 deletions
diff --git a/src/hotload.c b/src/hotload.c index ad02d21..88d286a 100644 --- a/src/hotload.c +++ b/src/hotload.c @@ -8,23 +8,59 @@ #define FSEVENT_CALLBACK(name) void name(ConstFSEventStreamRef stream,\ void *context,\ - size_t count,\ - void *paths,\ + size_t file_count,\ + void *file_paths,\ const FSEventStreamEventFlags *flags,\ const FSEventStreamEventId *ids) +enum watch_kind +{ + WATCH_KIND_INVALID, + WATCH_KIND_CATALOG, + WATCH_KIND_FILE +}; + +struct watched_catalog +{ + char *directory; + char *extension; +}; + +struct watched_file +{ + char *absolutepath; + char *directory; + char *filename; +}; + +struct watched_entry +{ + enum watch_kind kind; + union { + struct watched_file file_info; + struct watched_catalog catalog_info; + }; +}; + +internal inline bool +same_string(const char *a, const char *b) +{ + bool result = a && b && strcmp(a, b) == 0; + return result; +} + internal char * copy_string(const char *s) { unsigned length = strlen(s); - char *result = malloc(length + 1); + char *result = (char *) malloc(length + 1); memcpy(result, s, length); result[length] = '\0'; return result; } internal char * -file_directory(const char *file) +file_directory(char *file) { char *last_slash = strrchr(file, '/'); *last_slash = '\0'; @@ -34,7 +70,7 @@ file_directory(const char *file) } internal char * -file_name(const char *file) +file_name(char *file) { char *last_slash = strrchr(file, '/'); char *name = copy_string(last_slash + 1); @@ -42,50 +78,73 @@ file_name(const char *file) } internal char * -resolve_symlink(char *file) +resolve_symlink(const char *file) { struct stat buffer; if (lstat(file, &buffer) != 0) { return NULL; } + if (S_ISDIR(buffer.st_mode)) { + return copy_string(file); + } + if (!S_ISLNK(buffer.st_mode)) { - return file; + return copy_string(file); } - ssize_t size = buffer.st_size + 1; - char *result = malloc(size); - ssize_t read = readlink(file, result, size); + char *result = realpath(file, NULL); + return result; +} - if (read != -1) { - result[read] = '\0'; - return result; +internal enum watch_kind +resolve_watch_kind(char *file) +{ + struct stat buffer; + if (lstat(file, &buffer) != 0) { + return WATCH_KIND_INVALID; } - free(result); - return NULL; + if (S_ISDIR(buffer.st_mode)) { + return WATCH_KIND_CATALOG; + } + + if (!S_ISLNK(buffer.st_mode)) { + return WATCH_KIND_FILE; + } + + return WATCH_KIND_INVALID; } -internal struct watched_file * -hotloader_watched_file(struct hotloader *hotloader, char *absolutepath) +internal char * +same_catalog(char *absolutepath, struct watched_catalog *catalog_info) { - struct watched_file *result = NULL; - for (unsigned index = 0; result == NULL && index < hotloader->watch_count; ++index) { - struct watched_file *watch_info = hotloader->watch_list + index; + char *last_slash = strrchr(absolutepath, '/'); + if (!last_slash) return NULL; - char *directory = file_directory(absolutepath); - char *filename = file_name(absolutepath); + char *filename = NULL; - if (strcmp(watch_info->directory, directory) == 0) { - if (strcmp(watch_info->filename, filename) == 0) { - result = watch_info; - } - } + // NOTE(koekeisihya): null terminate '/' to cut off filename + *last_slash = '\0'; - free(filename); - free(directory); + if (same_string(absolutepath, catalog_info->directory)) { + filename = !catalog_info->extension + ? last_slash + 1 + : same_string(catalog_info->extension, strrchr(last_slash + 1, '.')) + ? last_slash + 1 + : NULL; } + // NOTE(koekeisihya): revert '/' to restore filename + *last_slash = '/'; + + return filename; +} + +internal inline bool +same_file(char *absolutepath, struct watched_file *file_info) +{ + bool result = same_string(absolutepath, file_info->absolutepath); return result; } @@ -93,59 +152,120 @@ internal FSEVENT_CALLBACK(hotloader_handler) { /* NOTE(koekeishiya): We sometimes get two events upon file save. */ struct hotloader *hotloader = (struct hotloader *) context; - char **files = (char **) paths; + char **files = (char **) file_paths; - struct watched_file *watch_info; - for (unsigned index = 0; index < count; ++index) { - char *absolutepath = files[index]; - if ((watch_info = hotloader_watched_file(hotloader, absolutepath))) { - hotloader->callback(absolutepath, watch_info->directory, watch_info->filename); + for (unsigned file_index = 0; file_index < file_count; ++file_index) { + for (unsigned watch_index = 0; watch_index < hotloader->watch_count; ++watch_index) { + struct watched_entry *watch_info = hotloader->watch_list + watch_index; + if (watch_info->kind == WATCH_KIND_CATALOG) { + char *filename = same_catalog(files[file_index], &watch_info->catalog_info); + if (!filename) continue; + + hotloader->callback(files[file_index], + watch_info->catalog_info.directory, + filename); + break; + } else if (watch_info->kind == WATCH_KIND_FILE) { + bool match = same_file(files[file_index], &watch_info->file_info); + if (!match) continue; + + hotloader->callback(watch_info->file_info.absolutepath, + watch_info->file_info.directory, + watch_info->file_info.filename); + break; + } } } } -void hotloader_add_file(struct hotloader *hotloader, char *file) +internal inline void +hotloader_add_watched_entry(struct hotloader *hotloader, struct watched_entry entry) { - if (!hotloader->enabled) { - char *real_path = resolve_symlink(file); - if (real_path) { - struct watched_file watch_info; - watch_info.directory = file_directory(real_path); - watch_info.filename = file_name(real_path); + if (!hotloader->watch_list) { + hotloader->watch_capacity = 32; + hotloader->watch_list = (struct watched_entry *) malloc(hotloader->watch_capacity * sizeof(struct watched_entry)); + } - if (real_path != file) { - free(real_path); - } + if (hotloader->watch_count >= hotloader->watch_capacity) { + hotloader->watch_capacity = (unsigned) ceil(hotloader->watch_capacity * 1.5f); + hotloader->watch_list = (struct watched_entry *) realloc(hotloader->watch_list, hotloader->watch_capacity * sizeof(struct watched_entry)); + } + + hotloader->watch_list[hotloader->watch_count++] = entry; +} - hotloader->watch_list[hotloader->watch_count++] = watch_info; - printf("hotload: watching file '%s' in directory '%s'\n", watch_info.filename, watch_info.directory); - } else { - fprintf(stderr, "hotload: could not watch file '%s'\n", file); +bool hotloader_add_catalog(struct hotloader *hotloader, const char *directory, const char *extension) +{ + if (hotloader->enabled) return false; + + char *real_path = resolve_symlink(directory); + if (!real_path) return false; + + enum watch_kind kind = resolve_watch_kind(real_path); + if (kind != WATCH_KIND_CATALOG) return false; + + hotloader_add_watched_entry(hotloader, (struct watched_entry) { + .kind = WATCH_KIND_CATALOG, + .catalog_info = { + .directory = real_path, + .extension = extension + ? copy_string(extension) + : NULL } - } + }); + + return true; +} + +bool hotloader_add_file(struct hotloader *hotloader, const char *file) +{ + if (hotloader->enabled) return false; + + char *real_path = resolve_symlink(file); + if (!real_path) return false; + + enum watch_kind kind = resolve_watch_kind(real_path); + if (kind != WATCH_KIND_FILE) return false; + + hotloader_add_watched_entry(hotloader, (struct watched_entry) { + .kind = WATCH_KIND_FILE, + .file_info = { + .absolutepath = real_path, + .directory = file_directory(real_path), + .filename = file_name(real_path) + } + }); + + return true; } bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback) { - if ((hotloader->enabled) || - (!hotloader->watch_count)) { - return false; - } + if (hotloader->enabled || !hotloader->watch_count) return false; CFStringRef string_refs[hotloader->watch_count]; for (unsigned index = 0; index < hotloader->watch_count; ++index) { + struct watched_entry *watch_info = hotloader->watch_list + index; + char *directory = watch_info->kind == WATCH_KIND_FILE + ? watch_info->file_info.directory + : watch_info->catalog_info.directory; string_refs[index] = CFStringCreateWithCString(kCFAllocatorDefault, - hotloader->watch_list[index].directory, + directory, kCFStringEncodingUTF8); } - FSEventStreamContext context = {}; - context.info = (void *) hotloader; + FSEventStreamContext context = { + .info = hotloader + }; + + hotloader->path = (CFArrayRef) CFArrayCreate(NULL, + (const void **) string_refs, + hotloader->watch_count, + &kCFTypeArrayCallBacks); + + hotloader->flags = kFSEventStreamCreateFlagNoDefer | + kFSEventStreamCreateFlagFileEvents; - hotloader->enabled = true; - hotloader->callback = callback; - hotloader->path = (CFArrayRef) CFArrayCreate(NULL, (const void **) string_refs, hotloader->watch_count, &kCFTypeArrayCallBacks); - hotloader->flags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents; hotloader->stream = FSEventStreamCreate(NULL, hotloader_handler, &context, @@ -153,27 +273,43 @@ bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback) kFSEventStreamEventIdSinceNow, 0.5, hotloader->flags); - FSEventStreamScheduleWithRunLoop(hotloader->stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + + FSEventStreamScheduleWithRunLoop(hotloader->stream, + CFRunLoopGetMain(), + kCFRunLoopDefaultMode); + + hotloader->callback = callback; FSEventStreamStart(hotloader->stream); + hotloader->enabled = true; + return true; } void hotloader_end(struct hotloader *hotloader) { - if (hotloader->enabled) { - FSEventStreamStop(hotloader->stream); - FSEventStreamInvalidate(hotloader->stream); - FSEventStreamRelease(hotloader->stream); - - CFIndex count = CFArrayGetCount(hotloader->path); - for (unsigned index = 0; index < count; ++index) { - CFStringRef string_ref = (CFStringRef) CFArrayGetValueAtIndex(hotloader->path, index); - free(hotloader->watch_list[index].directory); - free(hotloader->watch_list[index].filename); - CFRelease(string_ref); - } + if (!hotloader->enabled) return; + + FSEventStreamStop(hotloader->stream); + FSEventStreamInvalidate(hotloader->stream); + FSEventStreamRelease(hotloader->stream); - CFRelease(hotloader->path); - memset(hotloader, 0, sizeof(struct hotloader)); + CFIndex count = CFArrayGetCount(hotloader->path); + for (unsigned index = 0; index < count; ++index) { + struct watched_entry *watch_info = hotloader->watch_list + index; + if (watch_info->kind == WATCH_KIND_FILE) { + free(watch_info->file_info.absolutepath); + free(watch_info->file_info.directory); + free(watch_info->file_info.filename); + } else if (watch_info->kind == WATCH_KIND_CATALOG) { + free(watch_info->catalog_info.directory); + if (watch_info->catalog_info.extension) { + free(watch_info->catalog_info.extension); + } + } + CFRelease(CFArrayGetValueAtIndex(hotloader->path, index)); } + + CFRelease(hotloader->path); + free(hotloader->watch_list); + memset(hotloader, 0, sizeof(struct hotloader)); } |