#define WIN32_LEAN_AND_MEAN #include #include #include #include struct b_directory { struct b_object base; HANDLE handle; struct b_path *abs_path; }; struct z__b_directory_iterator { b_queue state_stack; }; struct iteration_state { const b_path *search_path; b_queue_entry entry; HANDLE search; WIN32_FIND_DATAA data; bool child_search_complete; }; static void directory_release(struct b_object *obj); static struct b_object_type directory_type = { .t_name = "corelib::directory", .t_flags = B_OBJECT_FUNDAMENTAL, .t_id = B_OBJECT_TYPE_PATH, .t_instance_size = sizeof(struct b_directory), .t_release = directory_release, }; static enum b_status status_from_win32_error(int error, enum b_status default_value) { switch (error) { case ERROR_FILE_NOT_FOUND: return B_ERR_NO_ENTRY; default: return default_value; } } enum b_status b_directory_open( struct b_directory *root, const struct b_path *path, struct b_directory **out) { enum b_status status = B_SUCCESS; const b_path *parts[] = { root ? root->abs_path : NULL, path, }; b_path *new_path = b_path_join(parts, sizeof parts / sizeof *parts); if (!new_path) { return B_ERR_NO_MEMORY; } HANDLE dir_handle = CreateFileA( b_path_ptr(new_path), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, INVALID_HANDLE_VALUE); if (dir_handle == INVALID_HANDLE_VALUE) { status = status_from_win32_error(GetLastError(), B_ERR_IO_FAILURE); b_path_release(new_path); return status; } BY_HANDLE_FILE_INFORMATION dir_info; if (!GetFileInformationByHandle(dir_handle, &dir_info)) { status = status_from_win32_error(GetLastError(), B_ERR_IO_FAILURE); CloseHandle(dir_handle); b_path_release(new_path); return status; } if (!(dir_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { CloseHandle(dir_handle); b_path_release(new_path); return B_ERR_NOT_DIRECTORY; } struct b_directory *dir = (struct b_directory *)b_object_type_instantiate(&directory_type); if (!path) { CloseHandle(dir_handle); b_path_release(new_path); return B_ERR_NO_MEMORY; } dir->abs_path = new_path; dir->handle = dir_handle; *out = dir; return B_SUCCESS; } static struct iteration_state *get_iteration_state(struct z__b_directory_iterator *it) { b_queue_entry *last = b_queue_last(&it->state_stack); if (!last) { return NULL; } return b_unbox(struct iteration_state, last, entry); } static struct iteration_state *push_iteration_state(struct z__b_directory_iterator *it) { struct iteration_state *state = malloc(sizeof *state); if (!state) { return NULL; } memset(state, 0x0, sizeof *state); b_queue_push_back(&it->state_stack, &state->entry); return state; } static void pop_iteration_state(struct z__b_directory_iterator *it) { struct iteration_state *state = get_iteration_state(it); if (!state) { return; } b_queue_pop_back(&it->state_stack); FindClose(state->search); free(state); } static void cleanup_iterator(struct b_directory_iterator *it) { while (!b_queue_empty(&it->_z->state_stack)) { pop_iteration_state(it->_z); } free(it->_z); it->_z = NULL; } static void update_iterator_data(struct b_directory_iterator *it) { if (it->filepath) { b_path_release(B_PATH(it->filepath)); it->filepath = NULL; } struct iteration_state *state = get_iteration_state(it->_z); if (state) { it->filename = state->data.cFileName; struct b_path *filename = b_path_create_from_cstr(it->filename); const struct b_path *parts[] = { state->search_path, filename, }; it->filepath = b_path_join(parts, sizeof parts / sizeof parts[0]); } } static bool move_into_directory(struct b_directory_iterator *it, const char *dir_name) { struct iteration_state *state = get_iteration_state(it->_z); struct b_path *dir_name_p = b_path_create_from_cstr(dir_name); struct b_path *wildcard = b_path_create_from_cstr("*"); const struct b_path *parts[] = { state ? state->search_path : NULL, dir_name_p, }; struct b_path *dir_path = b_path_join(parts, sizeof parts / sizeof *parts); parts[0] = dir_path; parts[1] = wildcard; struct b_path *search_path = b_path_join(parts, sizeof parts / sizeof *parts); state = push_iteration_state(it->_z); state->search_path = dir_path; state->search = FindFirstFileA(b_path_ptr(search_path), &state->data); b_path_release(search_path); b_path_release(wildcard); b_path_release(dir_name_p); if (state->search == INVALID_HANDLE_VALUE) { pop_iteration_state(it->_z); return false; } while (!strcmp(state->data.cFileName, ".") || !strcmp(state->data.cFileName, "..")) { BOOL ok = FindNextFileA(state->search, &state->data); if (!ok) { pop_iteration_state(it->_z); return false; } } return true; } static bool move_to_first_item(struct b_directory_iterator *it, const struct b_path *root_dir) { bool has_results = move_into_directory(it, b_path_ptr(root_dir)); if (!has_results) { return false; } if (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST) { return true; } while (true) { struct iteration_state *state = get_iteration_state(it->_z); if (!(state->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { break; } has_results = move_into_directory(it, state->data.cFileName); if (!has_results) { pop_iteration_state(it->_z); break; } } return true; } static bool move_to_next_item(struct b_directory_iterator *it) { while (true) { struct iteration_state *state = get_iteration_state(it->_z); if (!state->child_search_complete && (state->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST)) { if (move_into_directory(it, state->data.cFileName)) { return true; } } bool has_items = FindNextFileA(state->search, &state->data); if (!has_items) { pop_iteration_state(it->_z); state = get_iteration_state(it->_z); if (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST && state != NULL) { state->child_search_complete = true; continue; } return state != NULL; } state->child_search_complete = false; if (!(state->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { return true; } if (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST) { return true; } while (true) { state = get_iteration_state(it->_z); has_items = move_into_directory(it, state->data.cFileName); if (!has_items) { pop_iteration_state(it->_z); return true; } state = get_iteration_state(it->_z); if (!(state->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { return true; } } } return true; } int b_directory_iterator_begin( struct b_directory *directory, struct b_directory_iterator *it, enum b_directory_iterator_flags flags) { if (b_directory_iterator_is_valid(it)) { cleanup_iterator(it); } it->flags = flags; struct z__b_directory_iterator *it_data = malloc(sizeof *it_data); memset(it_data, 0x0, sizeof *it_data); it->_z = it_data; move_to_first_item(it, directory->abs_path); update_iterator_data(it); return 0; } bool b_directory_iterator_next(struct b_directory_iterator *it) { if (!it->_z) { return false; } bool result = move_to_next_item(it); update_iterator_data(it); return result; } enum b_status b_directory_iterator_erase(struct b_directory_iterator *it) { return B_SUCCESS; } bool b_directory_iterator_is_valid(const struct b_directory_iterator *it) { if (!it->_z) { return false; } if (!get_iteration_state(it->_z)) { return false; } return true; } static void directory_release(struct b_object *obj) { }