pub const LocalProviderId = "local"; // TODO: the container might be freed while this runs // Tab should hold something at pass it here fn loadChildren(container: *Container) void { defer { container.childrenLoading = false; } var dir = std.fs.cwd().openDir(container.item.nativePath.path, .{ .iterate = true }) catch { // const errorContent = std.fmt.allocPrint(container.item.allocator, "Could not open directory '{s}'.", .{container.item.nativePath.path}) catch return; // container.item.errors.append(.{ .content = errorContent }) catch return; return; }; defer dir.close(); var it = dir.iterate(); while (it.next() catch return) |entry| { const child = container.item.fullName.getChild(entry.name, container.item.allocator) catch return; container.children.mutex.lock(); defer container.children.mutex.unlock(); container.children.data.append(child) catch return; } } const VTable: ProviderVTable = .{ .getItemByFullName = getItemByFullNameGeneric, .canHandle = canHandleGeneric, .deinit = deinitGeneric, }; pub fn getItemByFullNameGeneric( context: *anyopaque, fullName: FullName, initContext: *const InitContext, allocator: std.mem.Allocator, ) GetItemsError!*Item { const self: *LocalContentProvider = @ptrCast(@alignCast(context)); return self.getItemByFullName(fullName, initContext, allocator); } pub fn canHandleGeneric( context: *anyopaque, fullName: FullName, ) bool { const self: *LocalContentProvider = @ptrCast(@alignCast(context)); return self.canHandle(fullName); } pub fn deinitGeneric(_: *anyopaque) void {} pub fn getFullNameByNativePath(allocator: std.mem.Allocator, nativePath: NativePath) !FullName { const correct_sep = try std.mem.replaceOwned(u8, allocator, nativePath.path, std.fs.path.sep_str, "/"); defer allocator.free(correct_sep); const asd = if (std.mem.startsWith(u8, correct_sep, "/")) correct_sep[1..] else correct_sep; const full_name_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ LocalProviderId, asd }); return .{ .path = full_name_path }; } pub fn getNativePathByFullName(allocator: std.mem.Allocator, fullName: FullName) ![]u8 { std.debug.assert(!std.mem.endsWith(u8, fullName.path, "/")); if (fullName.path.len < LocalProviderId.len or !std.mem.eql(u8, fullName.path[0..LocalProviderId.len], LocalProviderId)) { return ProviderError.WrongProvider; } //NOTE: their might be an other edge case where the fullname is local/ that is not handled by this if (fullName.path.len == LocalProviderId.len) { if (native_os == .linux) return try std.fmt.allocPrint(allocator, "/", .{}) else return ProviderError.WrongProvider; } const fullNameWithoutId = fullName.path[LocalProviderId.len + 1 ..]; var native_path = try std.mem.replaceOwned(u8, allocator, fullNameWithoutId, "/", std.fs.path.sep_str); if (native_os == .linux) { const linux_native_path = try std.fmt.allocPrint(allocator, "/{s}", .{native_path}); allocator.free(native_path); native_path = linux_native_path; } return native_path; } pub const LocalContentProvider = struct { threadPool: *std.Thread.Pool, pub fn getItemByFullName( self: *LocalContentProvider, fullName: FullName, initContext: *const InitContext, allocator: std.mem.Allocator, ) GetItemsError!*Item { const native_path = try getNativePathByFullName(allocator, fullName); defer allocator.free(native_path); const kind: union(enum) { directory, file: struct { size: u64 }, } = blk: { // FIXME: properly handle different errors var dir = std.fs.cwd().openDir(native_path, .{}); if (dir) |*d| { d.close(); break :blk .directory; } else |_| {} var file = std.fs.cwd().openFile(native_path, .{}); if (file) |*f| { const size = size: { const stat = f.stat() catch break :size 0; break :size stat.size; }; f.close(); break :blk .{ .file = .{ .size = size } }; } else |_| {} return GetItemsError.NotExists; }; return switch (kind) { .directory => blk: { const container = try allocator.create(Container); container.* = Container{ .children = locked(std.ArrayList(FullName)){ .data = std.ArrayList(FullName).init(allocator), }, .childrenLoading = true, .item = undefined, }; const val: ItemEnum = .{ .container = container, }; try initItem( self, &container.item, val, fullName, allocator, ); if (!initContext.skipChildInit) { try self.threadPool.spawn(loadChildren, .{container}); } else { container.childrenLoading = false; } break :blk &container.item; }, .file => |file| blk: { const element = try allocator.create(Element); element.* = Element{ .item = undefined, .content_size = file.size, }; const val: ItemEnum = .{ .element = element, }; try initItem( self, &element.item, val, fullName, allocator, ); break :blk &element.item; }, }; } fn initItem( contentProvider: *LocalContentProvider, item: *Item, innerItem: ItemEnum, fullName: FullName, allocator: std.mem.Allocator, ) !void { const basename = std.fs.path.basename(fullName.path); const name = try allocator.dupe(u8, basename); const displayName = try allocator.dupe(u8, basename); const fullName2 = try allocator.dupe(u8, fullName.path); const nativePath = try getNativePathByFullName(allocator, fullName); const parentFullName = try fullName.getParent(allocator); const parent = if (parentFullName) |p| models.AbsolutePath{ .path = p, .type = .container } else null; item.* = Item{ .allocator = allocator, .provider = contentProvider.provider(), .name = name, .displayName = displayName, .fullName = .{ .path = fullName2 }, .nativePath = models.NativePath{ .path = nativePath }, .parent = parent, .item = innerItem, .errors = std.ArrayList(models.Error).init(allocator), }; } pub fn canHandle( _: *LocalContentProvider, fullName: FullName, ) bool { return std.mem.startsWith(u8, fullName.path, "local/"); } pub fn provider(self: *LocalContentProvider) Provider { return Provider{ .object = self, .vtable = &VTable, }; } }; const std = @import("std"); const models = @import("../models.zig"); const Provider = @import("provider.zig").Provider; const ProviderVTable = @import("provider.zig").VTable; const GetItemsError = @import("provider.zig").GetItemsError; const ProviderError = @import("provider.zig").ProviderError; const InitContext = @import("provider.zig").InitContext; const FullName = models.FullName; const NativePath = models.NativePath; const Item = models.Item; const ItemEnum = models.ItemEnum; const Element = models.Element; const Container = models.Container; const native_os = @import("builtin").os.tag; const locked = @import("../sync.zig").locked;