pub const Tab = struct { allocator: std.mem.Allocator, currentLocation: ?*Container, currentItems: locked(?std.ArrayList(Arc(*Item))), currentParentItems: locked(?std.ArrayList(Arc(*Item))), currentChildren: locked(?std.ArrayList(Arc(*Item))), childrenLoaded: Observable(*Tab), currentLocationChanged: Observable(?*Container), currentItemFullName: ?models.FullName, currentItem: ?Arc(*models.Item), currentRequested: ?union(enum) { item: Arc(*models.Item), name: models.FullName }, itemComparer: *const fn (item1: *const models.Item, item2: *const models.Item) std.math.Order, threadPool: *std.Thread.Pool, rootProvider: *RootProvider, pub fn init( self: *Tab, threadPool: *std.Thread.Pool, rootProvider: *RootProvider, allocator: std.mem.Allocator, ) void { self.* = Tab{ .allocator = allocator, .currentItems = .{ .data = null }, .currentParentItems = .{ .data = null }, .currentChildren = .{ .data = null }, .currentLocationChanged = Observable(?*Container).init(allocator), .childrenLoaded = Observable(*Tab).init(allocator), .currentLocation = null, .currentRequested = null, .currentItem = null, .currentItemFullName = null, .itemComparer = order.orderByDisplayName, .threadPool = threadPool, .rootProvider = rootProvider, }; } pub fn setCurrentLocation(self: *Tab, newLocationFullName: models.FullName) !void { if (self.currentLocation) |c| { c.item.deinit(); } self.cleanCurrentItem(); const newLocationContainer = blk: { const newLocation = try self.rootProvider.getItemByFullName(newLocationFullName, &.{}, self.allocator); errdefer { newLocation.deinit(); self.currentLocation = null; self.currentLocationChanged.notify(null); } const newLocationContainer = switch (newLocation.item) { .container => |c| c, .element => return error.NotContainer, }; self.currentLocation = newLocationContainer; break :blk newLocationContainer; }; self.currentLocationChanged.notify(newLocationContainer); self.updateCurrentItem() catch { self.currentItemFullName = null; self.currentItem = null; }; //TODO: Proper error handling std.Thread.Pool.spawn(self.threadPool, loadItemsWrapper, .{ self, newLocationContainer }) catch unreachable; std.Thread.Pool.spawn(self.threadPool, loadParentItemsWrapper, .{ self, newLocationContainer }) catch unreachable; } pub fn selectItem(self: *Tab, requested_selected: models.FullName) !void { self.currentItems.mutex.lock(); defer self.currentItems.mutex.unlock(); self.cleanCurrentRequested(); self.currentRequested = .{ .name = models.FullName{ .path = try self.allocator.dupe(u8, requested_selected.path), }, }; return self.updateCurrentItem(); } fn cleanCurrentRequested(self: *Tab) void { const requested = self.currentRequested orelse return; switch (requested) { .item => |item| { if (item.releaseUnwrap()) |i| i.deinit(); }, .name => |fullName| self.allocator.free(fullName.path), } self.currentRequested = null; } fn updateCurrentItem(self: *Tab) !void { const currentRequested = self.currentRequested orelse return; const requestedFullName = switch (currentRequested) { .item => |*item| item.value.*.fullName, .name => |fullName| fullName, }; // The requested is already selected if (self.currentItemFullName) |currentItem| { if (std.mem.order(u8, requestedFullName.path, currentItem.path) == .eq) { return; } } const currentItems = self.currentItems.data orelse return error.Error; if (currentItems.items.len == 0) return error.Error; // Looking for the requested const foundIndex = index: for (0..currentItems.items.len) |i| { const arc_item = currentItems.items[i]; const arc_copy = arc_item.retain(); defer if (arc_copy.releaseUnwrap()) |item| item.deinit(); switch (currentRequested) { .item => |*itemArc| { const comp = self.itemComparer(itemArc.value.*, arc_copy.value.*); if (comp == .eq) { break :index i; } if (comp == .lt) { break :index i -| 1; } }, .name => |fullName| { if (std.mem.order(u8, fullName.path, arc_copy.value.*.fullName.path) == .eq) { break :index i; } }, } } else null; if (foundIndex) |i| { const arc_item = currentItems.items[i]; self.cleanCurrentRequested(); self.currentRequested = .{ .item = arc_item.retain(), }; try self.setSelectedItemInner(arc_item.retain()); return; } //TODO: use lastdeepestselectedpath // Fallback to first item { const arc_item = currentItems.items[0]; try self.setSelectedItemInner(arc_item.retain()); } } fn setSelectedItemInner(self: *Tab, newSelectedItemArc: Arc(*models.Item)) !void { const arc = newSelectedItemArc.retain(); defer if (arc.releaseUnwrap()) |item| item.deinit(); self.cleanCurrentItem(); self.currentItem = newSelectedItemArc; self.currentItemFullName = models.FullName{ .path = try self.allocator.dupe(u8, arc.value.*.fullName.path), }; switch (arc.value.*.item) { .container => { std.Thread.Pool.spawn(self.threadPool, loadChildrenWrapper, .{ self, arc.retain() }) catch unreachable; }, else => {}, } } fn loadItemsWrapper(self: *Tab, location: *Container) void { loadItems(*Tab, self, location, &self.currentItems, updateCurrentItemCallback, self.allocator) catch {}; } fn updateCurrentItemCallback(self: *Tab) void { self.updateCurrentItem() catch {}; } fn loadParentItemsWrapper(self: *Tab, location: *Container) void { const parentAbsolutePath = location.item.parent orelse return; var arena = std.heap.ArenaAllocator.init(self.allocator); defer arena.deinit(); const allocator = arena.allocator(); const parentFullName = models.FullName{ .path = allocator.dupe(u8, parentAbsolutePath.path.path) catch return, }; const resolvedParent = location.item.provider.getItemByFullName(parentFullName, &.{}, allocator) catch |e| { std.debug.print("error {} {s}\r\n", .{ e, parentFullName.path }); return; }; const parentContainer = switch (resolvedParent.item) { .container => |c| c, else => return, }; loadItems( void, {}, parentContainer, &self.currentParentItems, (struct { fn a(_: void) void {} }).a, self.allocator, ) catch {}; } fn loadChildrenWrapper(self: *Tab, newSelectedItemArc: Arc(*models.Item)) void { defer if (newSelectedItemArc.releaseUnwrap()) |item| item.deinit(); var arena = std.heap.ArenaAllocator.init(self.allocator); defer arena.deinit(); const allocator = arena.allocator(); const resolvedChild = newSelectedItemArc.value.*.provider.getItemByFullName(newSelectedItemArc.value.*.fullName, &.{}, allocator) catch |e| { std.debug.print("error {} {s}\r\n", .{ e, newSelectedItemArc.value.*.fullName.path }); return; }; const childContainer = switch (resolvedChild.item) { .container => |c| c, else => return, }; loadItems( void, {}, childContainer, &self.currentChildren, (struct { fn a(_: void) void {} }).a, self.allocator, ) catch {}; self.childrenLoaded.notify(self); } pub fn deinit(self: *Tab) void { if (self.currentLocation) |c| { c.item.deinit(); } const data = self.currentItems.data; self.currentItems.data = null; if (data) |currentItems| { for (currentItems.items) |arc_item| { if (arc_item.releaseUnwrap()) |item| { item.deinit(); } } currentItems.deinit(); } const data2 = self.currentParentItems.data; self.currentParentItems.data = null; if (data2) |currentItems| { for (currentItems.items) |arc_item| { if (arc_item.releaseUnwrap()) |item| { item.deinit(); } } currentItems.deinit(); } const data3 = self.currentChildren.data; self.currentChildren.data = null; if (data3) |currentItems| { for (currentItems.items) |arc_item| { if (arc_item.releaseUnwrap()) |item| { item.deinit(); } } currentItems.deinit(); } self.cleanCurrentItem(); self.cleanCurrentRequested(); self.currentLocationChanged.deinit(); self.childrenLoaded.deinit(); self.allocator.destroy(self); } fn cleanCurrentItem(self: *Tab) void { if (self.currentItemFullName) |currentItem| { self.allocator.free(currentItem.path); self.currentItemFullName = null; } if (self.currentItem) |currentItem| { if (currentItem.releaseUnwrap()) |item| item.deinit(); self.currentItem = null; } } }; fn loadItems( comptime T: type, ctx: T, location: *Container, sourceItems: *locked(?std.ArrayList(Arc(*Item))), itemLoadedCallback: *const fn (T) void, allocator: std.mem.Allocator, ) !void { { sourceItems.mutex.lock(); defer sourceItems.mutex.unlock(); const data = sourceItems.data; sourceItems.data = null; if (data) |currentItems| { for (currentItems.items) |*item| { if (item.releaseUnwrap()) |i| i.deinit(); } currentItems.deinit(); } } { sourceItems.mutex.lock(); defer sourceItems.mutex.unlock(); sourceItems.data = std.ArrayList(Arc(*Item)).init(allocator); } errdefer { sourceItems.data.?.deinit(); sourceItems.data = null; } //TODO: add async var processed_number: usize = 0; while (true) { std.Thread.sleep(100 * std.time.ns_per_ms); location.children.mutex.lock(); defer location.children.mutex.unlock(); std.debug.assert(processed_number <= location.children.data.items.len); if (!location.childrenLoading and processed_number == location.children.data.items.len) break; if (processed_number == location.children.data.items.len) continue; defer processed_number = location.children.data.items.len; for (location.children.data.items[processed_number..]) |item_fullname| { const resolvedItem = location.item.provider.getItemByFullName(item_fullname, &.{ .skipChildInit = true }, allocator) catch |e| { //TODO: save error to container errors std.debug.print("error {} {s}\r\n", .{ e, item_fullname.path }); continue; }; sourceItems.mutex.lock(); defer sourceItems.mutex.unlock(); const insertIndex = for (sourceItems.data.?.items, 0..) |*item, i| { if (order.orderByDisplayName(resolvedItem, item.value.*) == .lt) break i; } else null; const arc = try Arc(*Item).init(allocator, resolvedItem); if (insertIndex) |i| { try sourceItems.data.?.insert(i, arc); } else { try sourceItems.data.?.append(arc); } itemLoadedCallback(ctx); } } } const std = @import("std"); const assert = std.debug.assert; const Mutex = std.Thread.Mutex; const order = @import("./order.zig"); const models = @import("../models.zig"); const Container = models.Container; const Item = models.Item; const locked = @import("../sync.zig").locked; const Observable = @import("../observable.zig").Observable; const RootProvider = @import("../provider/root.zig").RootProvider; const Arc = @import("zigrc").Arc;