diff --git a/src/browser.zig b/src/browser.zig index a485bc478acd33a72f5cd759591f293be6391ac3..e09071b8f47a544ca8740fbec3a952a6216d56a2 100644 --- a/src/browser.zig +++ b/src/browser.zig @@ -10,8 +10,8 @@ const ui = @import("ui.zig"); const c = @cImport(@cInclude("time.h")); usingnamespace @import("util.zig"); -// Currently opened directory and its parents. -pub var dir_parents = model.Parents{}; +// Currently opened directory. +pub var dir_parent: *model.Dir = undefined; // Sorted list of all items in the currently opened directory. // (first item may be null to indicate the "parent directory" item) @@ -44,12 +44,12 @@ const View = struct { fn save(self: *@This()) void { self.cursor_hash = if (dir_items.items.len == 0) 0 else hashEntry(dir_items.items[cursor_idx]); - opened_dir_views.put(@ptrToInt(dir_parents.top()), self.*) catch {}; + opened_dir_views.put(@ptrToInt(dir_parent), self.*) catch {}; } - // Should be called after dir_parents or dir_items has changed, will load the last saved view and find the proper cursor_idx. + // Should be called after dir_parent or dir_items has changed, will load the last saved view and find the proper cursor_idx. fn load(self: *@This(), sel: ?*const model.Entry) void { - if (opened_dir_views.get(@ptrToInt(dir_parents.top()))) |v| self.* = v + if (opened_dir_views.get(@ptrToInt(dir_parent))) |v| self.* = v else self.* = @This(){}; cursor_idx = 0; for (dir_items.items) |e, i| { @@ -123,7 +123,7 @@ fn sortDir(next_sel: ?*const model.Entry) void { } // Must be called when: -// - dir_parents changes (i.e. we change directory) +// - dir_parent changes (i.e. we change directory) // - config.show_hidden changes // - files in this dir have been added or removed pub fn loadDir(next_sel: ?*const model.Entry) void { @@ -132,9 +132,9 @@ pub fn loadDir(next_sel: ?*const model.Entry) void { dir_max_blocks = 1; dir_has_shared = false; - if (!dir_parents.isRoot()) + if (dir_parent != model.root) dir_items.append(null) catch unreachable; - var it = dir_parents.top().sub; + var it = dir_parent.sub; while (it) |e| { if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks; if (e.size > dir_max_size) dir_max_size = e.size; @@ -219,8 +219,8 @@ const Row = struct { if (main.config.show_graph == .both or main.config.show_graph == .percent) { self.bg.fg(.num); ui.addprint("{d:>5.1}", .{ 100* - if (main.config.show_blocks) @intToFloat(f32, item.blocks) / @intToFloat(f32, std.math.max(1, dir_parents.top().entry.blocks)) - else @intToFloat(f32, item.size) / @intToFloat(f32, std.math.max(1, dir_parents.top().entry.size)) + if (main.config.show_blocks) @intToFloat(f32, item.blocks) / @intToFloat(f32, std.math.max(1, dir_parent.entry.blocks)) + else @intToFloat(f32, item.size) / @intToFloat(f32, std.math.max(1, dir_parent.entry.size)) }); self.bg.fg(.default); ui.addch('%'); @@ -276,7 +276,7 @@ const Row = struct { if (!main.config.show_mtime or self.col + 37 > ui.cols) return; defer self.col += 27; ui.move(self.row, self.col+1); - const ext = (if (self.item) |e| e.ext() else @as(?*model.Ext, null)) orelse dir_parents.top().entry.ext(); + const ext = (if (self.item) |e| e.ext() else @as(?*model.Ext, null)) orelse dir_parent.entry.ext(); if (ext) |e| ui.addts(self.bg, e.mtime) else ui.addstr(" no mtime"); } @@ -359,7 +359,7 @@ const info = struct { state = .info; tab = t; if (tab == .links and links == null) { - links = model.LinkPaths.find(&dir_parents, e.?.link().?); + links = model.LinkPaths.find(dir_parent, e.?.link().?); for (links.?.paths.items) |n,i| { if (&n.node.entry == e) { links_idx = i; @@ -536,8 +536,7 @@ const info = struct { return true; if (ch == 10) { // Enter - go to selected entry const p = links.?.paths.items[links_idx]; - dir_parents.stack.shrinkRetainingCapacity(0); - dir_parents.stack.appendSlice(p.path.stack.items) catch unreachable; + dir_parent = p.path; loadDir(&p.node.entry); set(null, .info); } @@ -730,7 +729,7 @@ pub fn draw() void { ui.style(.dir); var pathbuf = std.ArrayList(u8).init(main.allocator); - dir_parents.fmtPath(true, &pathbuf); + dir_parent.fmtPath(true, &pathbuf); ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5))); pathbuf.deinit(); @@ -760,12 +759,12 @@ pub fn draw() void { ui.move(ui.rows-1, 1); ui.style(if (main.config.show_blocks) .bold_hd else .hd); ui.addstr("Total disk usage: "); - ui.addsize(.hd, blocksToSize(dir_parents.top().entry.blocks)); + ui.addsize(.hd, blocksToSize(dir_parent.entry.blocks)); ui.style(if (main.config.show_blocks) .hd else .bold_hd); ui.addstr(" Apparent size: "); - ui.addsize(.hd, dir_parents.top().entry.size); + ui.addsize(.hd, dir_parent.entry.size); ui.addstr(" Items: "); - ui.addnum(.hd, dir_parents.top().items); + ui.addnum(.hd, dir_parent.items); switch (state) { .main => {}, @@ -832,7 +831,7 @@ pub fn keyInput(ch: i32) void { message = "Directory imported from file, refreshing is disabled." else { main.state = .refresh; - scan.setupRefresh(dir_parents.copy()); + scan.setupRefresh(dir_parent); } }, 'b' => { @@ -855,7 +854,7 @@ pub fn keyInput(ch: i32) void { if (cursor_idx+1 < dir_items.items.len) dir_items.items[cursor_idx+1] else if (cursor_idx == 0) null else dir_items.items[cursor_idx-1]; - delete.setup(dir_parents.copy(), e, next); + delete.setup(dir_parent, e, next); } }, @@ -890,20 +889,20 @@ pub fn keyInput(ch: i32) void { if (dir_items.items.len == 0) { } else if (dir_items.items[cursor_idx]) |e| { if (e.dir()) |d| { - dir_parents.push(d); + dir_parent = d; loadDir(null); state = .main; } - } else if (!dir_parents.isRoot()) { - dir_parents.pop(); + } else if (dir_parent.parent) |p| { + dir_parent = p; loadDir(null); state = .main; } }, 'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => { - if (!dir_parents.isRoot()) { - const e = dir_parents.top(); - dir_parents.pop(); + if (dir_parent.parent) |p| { + const e = dir_parent; + dir_parent = p; loadDir(&e.entry); state = .main; } diff --git a/src/delete.zig b/src/delete.zig index b690d805992fa83f12cbd686a1f6fb1b37a678e6..d999a0c59bf072030a84250c6508f18c5f178dc6 100644 --- a/src/delete.zig +++ b/src/delete.zig @@ -8,7 +8,7 @@ const ui = @import("ui.zig"); const browser = @import("browser.zig"); usingnamespace @import("util.zig"); -var parents: model.Parents = .{}; +var parent: *model.Dir = undefined; var entry: *model.Entry = undefined; var next_sel: ?*model.Entry = undefined; // Which item to select if deletion succeeds var state: enum { confirm, busy, err } = .confirm; @@ -16,9 +16,8 @@ var confirm: enum { yes, no, ignore } = .no; var error_option: enum { abort, ignore, all } = .abort; var error_code: anyerror = undefined; -// ownership of p is passed to this function -pub fn setup(p: model.Parents, e: *model.Entry, n: ?*model.Entry) void { - parents = p; +pub fn setup(p: *model.Dir, e: *model.Entry, n: ?*model.Entry) void { + parent = p; entry = e; next_sel = n; state = if (main.config.confirm_delete) .confirm else .busy; @@ -49,8 +48,8 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) var fd = dir.openDirZ(path, .{ .access_sub_paths = true, .iterate = false }) catch |e| return err(e); var it = &d.sub; - parents.push(d); - defer parents.pop(); + parent = d; + defer parent = parent.parent.?; while (it.*) |n| { if (deleteItem(fd, n.name(), it)) { fd.close(); @@ -64,14 +63,13 @@ fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) return if (e != error.DirNotEmpty or d.sub == null) err(e) else false; } else dir.deleteFileZ(path) catch |e| return err(e); - ptr.*.?.delStats(&parents); + ptr.*.?.delStats(parent); ptr.* = ptr.*.?.next; return false; } // Returns the item that should be selected in the browser. pub fn delete() ?*model.Entry { - defer parents.deinit(); while (main.state == .delete and state == .confirm) main.handleEvent(true, false); if (main.state != .delete) @@ -79,14 +77,14 @@ pub fn delete() ?*model.Entry { // Find the pointer to this entry const e = entry; - var it = &parents.top().sub; + var it = &parent.sub; while (it.*) |n| : (it = &n.next) if (it.* == entry) break; var path = std.ArrayList(u8).init(main.allocator); defer path.deinit(); - parents.fmtPath(true, &path); + parent.fmtPath(true, &path); if (path.items.len == 0 or path.items[path.items.len-1] != '/') path.append('/') catch unreachable; path.appendSlice(entry.name()) catch unreachable; @@ -125,7 +123,7 @@ fn drawConfirm() void { fn drawProgress() void { var path = std.ArrayList(u8).init(main.allocator); defer path.deinit(); - parents.fmtPath(false, &path); + parent.fmtPath(false, &path); path.append('/') catch unreachable; path.appendSlice(entry.name()) catch unreachable; @@ -145,7 +143,7 @@ fn drawProgress() void { fn drawErr() void { var path = std.ArrayList(u8).init(main.allocator); defer path.deinit(); - parents.fmtPath(false, &path); + parent.fmtPath(false, &path); path.append('/') catch unreachable; path.appendSlice(entry.name()) catch unreachable; diff --git a/src/main.zig b/src/main.zig index a6ca14bf06b9b05691d1c209fae3789dd2bc9b60..e12ffbe124727cba32e7009115874c84584508c3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -187,7 +187,7 @@ fn spawnShell() void { var path = std.ArrayList(u8).init(allocator); defer path.deinit(); - browser.dir_parents.fmtPath(true, &path); + browser.dir_parent.fmtPath(true, &path); var env = std.process.getEnvMap(allocator) catch unreachable; defer env.deinit(); @@ -338,6 +338,7 @@ pub fn main() void { config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode. ui.init(); state = .browse; + browser.dir_parent = model.root; browser.loadDir(null); while (true) { diff --git a/src/model.zig b/src/model.zig index 991d87d4f7c79b1d9bab9de847654f04fbe89121..25de64852814bc483120d7e279364764ae8c9a89 100644 --- a/src/model.zig +++ b/src/model.zig @@ -99,29 +99,27 @@ pub const Entry = packed struct { } // Set the 'err' flag on Dirs and Files, propagating 'suberr' to parents. - pub fn set_err(self: *Self, parents: *const Parents) void { + pub fn setErr(self: *Self, parent: *Dir) void { if (self.dir()) |d| d.err = true else if (self.file()) |f| f.err = true else unreachable; - var it = parents.iter(); - if (&parents.top().entry == self) _ = it.next(); - while (it.next()) |p| { + var it: ?*Dir = if (&parent.entry == self) parent.parent else parent; + while (it) |p| : (it = p.parent) { if (p.suberr) break; p.suberr = true; } } - pub fn addStats(self: *Entry, parents: *const Parents) void { + pub fn addStats(self: *Entry, parent: *Dir) void { if (self.counted) return; self.counted = true; - const dev = parents.top().dev; // Set if this is the first time we've found this hardlink in the bottom-most directory of the given dev. // Means we should count it for other-dev parent dirs, too. var new_hl = false; - var it = parents.iter(); - while(it.next()) |p| { + var it: ?*Dir = parent; + while(it) |p| : (it = p.parent) { var add_total = false; if (self.ext()) |e| @@ -130,12 +128,12 @@ pub const Entry = packed struct { p.items = saturateAdd(p.items, 1); // Hardlink in a subdirectory with a different device, only count it the first time. - if (self.etype == .link and dev != p.dev) { + if (self.etype == .link and parent.dev != p.dev) { add_total = new_hl; } else if (self.link()) |l| { const n = devices.HardlinkNode{ .ino = l.ino, .dir = p }; - var d = devices.list.items[dev].hardlinks.getOrPut(n) catch unreachable; + var d = devices.list.items[parent.dev].hardlinks.getOrPut(n) catch unreachable; new_hl = !d.found_existing; // First time we encounter this file in this dir, count it. if (!d.found_existing) { @@ -175,29 +173,28 @@ pub const Entry = packed struct { // // This function assumes that, for directories, all sub-entries have // already been un-counted. - pub fn delStats(self: *Entry, parents: *const Parents) void { + pub fn delStats(self: *Entry, parent: *Dir) void { if (!self.counted) return; self.counted = false; - const dev = parents.top().dev; var del_hl = false; - var it = parents.iter(); - while(it.next()) |p| { + var it: ?*Dir = parent; + while(it) |p| : (it = p.parent) { var del_total = false; p.items = saturateSub(p.items, 1); - if (self.etype == .link and dev != p.dev) { + if (self.etype == .link and parent.dev != p.dev) { del_total = del_hl; } else if (self.link()) |l| { const n = devices.HardlinkNode{ .ino = l.ino, .dir = p }; - var dp = devices.list.items[dev].hardlinks.getEntry(n); + var dp = devices.list.items[parent.dev].hardlinks.getEntry(n); if (dp) |d| { d.value_ptr.* -= 1; del_total = d.value_ptr.* == 0; del_hl = del_total; if (del_total) - _ = devices.list.items[dev].hardlinks.remove(n); + _ = devices.list.items[parent.dev].hardlinks.remove(n); } } else del_total = true; @@ -208,28 +205,13 @@ pub const Entry = packed struct { } } - pub fn delStatsRec(self: *Entry, parents: *Parents) void { + pub fn delStatsRec(self: *Entry, parent: *Dir) void { if (self.dir()) |d| { - parents.push(d); var it = d.sub; while (it) |e| : (it = e.next) - e.delStatsRec(parents); - parents.pop(); + e.delStatsRec(d); } - self.delStats(parents); - } - - // Insert this entry into the tree at the given directory, updating parent sizes and item counts. - pub fn insert(self: *Entry, parents: *const Parents) void { - self.next = parents.top().sub; - parents.top().sub = self; - if (self.dir()) |d| std.debug.assert(d.sub == null); - - // Links with nlink == 0 are counted after we're done scanning. - if (if (self.link()) |l| l.nlink == 0 else false) - link_count.add(parents.top().dev, self.link().?.ino) - else - self.addStats(parents); + self.delStats(parent); } }; @@ -239,6 +221,7 @@ pub const Dir = packed struct { entry: Entry, sub: ?*Entry, + parent: ?*Dir, // entry.{blocks,size}: Total size of all unique files + dirs. Non-shared hardlinks are counted only once. // (i.e. the space you'll need if you created a filesystem with only this dir) @@ -258,6 +241,23 @@ pub const Dir = packed struct { // Only used to find the @byteOffsetOff, the name is written at this point as a 0-terminated string. // (Old C habits die hard) name: u8, + + pub fn fmtPath(self: *const @This(), withRoot: bool, out: *std.ArrayList(u8)) void { + var components = std.ArrayList([:0]const u8).init(main.allocator); + defer components.deinit(); + var it: ?*const @This() = self; + while (it) |e| : (it = e.parent) + if (withRoot or e != root) + components.append(e.entry.name()) catch unreachable; + + var i: usize = components.items.len-1; + while (true) { + if (i != components.items.len-1) out.append('/') catch unreachable; + out.appendSlice(components.items[i]) catch unreachable; + if (i == 0) break; + i -= 1; + } + } }; // File that's been hardlinked (i.e. nlink > 1) @@ -420,23 +420,16 @@ pub const link_count = struct { if (d.found_existing) d.key_ptr.*.count += 1; } - var final_dir: Parents = undefined; - - fn final_rec() void { - var it = final_dir.top().sub; + fn finalRec(parent: *Dir) void { + var it = parent.sub; while (it) |e| : (it = e.next) { - if (e.dir()) |d| { - final_dir.push(d); - final_rec(); - final_dir.pop(); - continue; - } + if (e.dir()) |d| finalRec(d); const l = e.link() orelse continue; if (l.nlink > 0) continue; - const s = Node{ .dev = final_dir.top().dev, .ino = l.ino, .count = 0 }; + const s = Node{ .dev = parent.dev, .ino = l.ino, .count = 0 }; if (nodes.getEntry(s)) |n| { l.nlink = n.key_ptr.*.count; - e.addStats(&final_dir); + e.addStats(parent); } } } @@ -445,96 +438,30 @@ pub const link_count = struct { // find all links, update their nlink count and parent sizes. pub fn final() void { if (nodes.count() == 0) return; - final_dir = Parents{}; - final_rec(); + finalRec(root); nodes.clearAndFree(); - final_dir.deinit(); } }; pub var root: *Dir = undefined; -// Stack of parent directories, convenient helper when constructing and traversing the tree. -// The 'root' node is always implicitely at the bottom of the stack. -pub const Parents = struct { - stack: std.ArrayList(*Dir) = std.ArrayList(*Dir).init(main.allocator), - - const Self = @This(); - - pub fn push(self: *Self, dir: *Dir) void { - return self.stack.append(dir) catch unreachable; - } - - pub fn isRoot(self: *Self) bool { - return self.stack.items.len == 0; - } - - // Attempting to remove the root node is considered a bug. - pub fn pop(self: *Self) void { - _ = self.stack.pop(); - } - - pub fn top(self: *const Self) *Dir { - return if (self.stack.items.len == 0) root else self.stack.items[self.stack.items.len-1]; - } - - pub const Iterator = struct { - lst: *const Self, - index: usize = 0, // 0 = top of the stack, counts upwards to go down - - pub fn next(it: *Iterator) ?*Dir { - const len = it.lst.stack.items.len; - if (it.index > len) return null; - it.index += 1; - return if (it.index > len) root else it.lst.stack.items[len-it.index]; - } - }; - - // Iterate from top to bottom of the stack. - pub fn iter(self: *const Self) Iterator { - return .{ .lst = self }; - } - - // Append the path to the given arraylist. The list is assumed to use main.allocator, so it can't fail. - pub fn fmtPath(self: *const Self, withRoot: bool, out: *std.ArrayList(u8)) void { - const r = root.entry.name(); - if (withRoot) out.appendSlice(r) catch unreachable; - var i: usize = 0; - while (i < self.stack.items.len) { - if (i != 0 or (withRoot and r[r.len-1] != '/')) out.append('/') catch unreachable; - out.appendSlice(self.stack.items[i].entry.name()) catch unreachable; - i += 1; - } - } - - pub fn copy(self: *const Self) Self { - var c = Self{}; - c.stack.appendSlice(self.stack.items) catch unreachable; - return c; - } - - pub fn deinit(self: *Self) void { - self.stack.deinit(); - } -}; - // List of paths for the same inode. pub const LinkPaths = struct { paths: std.ArrayList(Path) = std.ArrayList(Path).init(main.allocator), pub const Path = struct { - path: Parents, + path: *Dir, node: *Link, fn lt(_: void, a: Path, b: Path) bool { - var i: usize = 0; - while (i < a.path.stack.items.len and i < b.path.stack.items.len) : (i += 1) - if (a.path.stack.items[i] != b.path.stack.items[i]) - return std.mem.lessThan(u8, a.path.stack.items[i].entry.name(), b.path.stack.items[i].entry.name()); - if (a.path.stack.items.len != b.path.stack.items.len) - return a.path.stack.items.len < b.path.stack.items.len; - return std.mem.lessThan(u8, a.node.entry.name(), b.node.entry.name()); + var pa = std.ArrayList(u8).init(main.allocator); + var pb = std.ArrayList(u8).init(main.allocator); + defer pa.deinit(); + defer pb.deinit(); + a.fmtPath(false, &pa); + b.fmtPath(false, &pb); + return std.mem.lessThan(u8, pa.items, pb.items); } pub fn fmtPath(self: Path, withRoot: bool, out: *std.ArrayList(u8)) void { @@ -546,42 +473,36 @@ pub const LinkPaths = struct { const Self = @This(); - fn findRec(self: *Self, parent: *Parents, node: *const Link) void { - var entry = parent.top().sub; + fn findRec(self: *Self, parent: *Dir, node: *const Link) void { + var entry = parent.sub; while (entry) |e| : (entry = e.next) { if (e.link()) |l| { if (l.ino == node.ino) - self.paths.append(Path{ .path = parent.copy(), .node = l }) catch unreachable; - } - if (e.dir()) |d| { - if (d.dev == parent.top().dev) { - parent.push(d); - self.findRec(parent, node); - parent.pop(); - } + self.paths.append(Path{ .path = parent, .node = l }) catch unreachable; } + if (e.dir()) |d| + if (d.dev == parent.dev) + self.findRec(d, node); } } // Find all paths for the given link - pub fn find(parents_: *const Parents, node: *const Link) Self { - var parents = parents_.copy(); + pub fn find(parent_: *Dir, node: *const Link) Self { + var parent = parent_; var self = Self{}; // First find the bottom-most parent that has no shared_size, // all links are guaranteed to be inside that directory. - while (!parents.isRoot() and parents.top().shared_size > 0) - parents.pop(); - self.findRec(&parents, node); + while (parent.parent != null and parent.shared_size > 0) + parent = parent.parent.?; + self.findRec(parent, node); // TODO: Zig's sort() implementation is type-generic and not very // small. I suspect we can get a good save on our binary size by using // a smaller or non-generic sort. This doesn't have to be very fast. std.sort.sort(Path, self.paths.items, @as(void, undefined), Path.lt); - parents.deinit(); return self; } pub fn deinit(self: *Self) void { - for (self.paths.items) |*p| p.path.deinit(); self.paths.deinit(); } }; diff --git a/src/scan.zig b/src/scan.zig index dfa4e8c8395f35ccce36162435c8a81f0f3cef2e..c53d98356ced29c8b886939bec1bbd5677a68da1 100644 --- a/src/scan.zig +++ b/src/scan.zig @@ -107,6 +107,8 @@ fn writeJsonString(wr: anytype, s: []const u8) !void { // entries read from disk can be merged into, without doing an O(1) lookup for // each entry. const ScanDir = struct { + dir: *model.Dir, + // Lookup table for name -> *entry. // null is never stored in the table, but instead used pass a name string // as out-of-band argument for lookups. @@ -130,21 +132,24 @@ const ScanDir = struct { const Self = @This(); - fn init(parents: *const model.Parents) Self { - var self = Self{ .entries = Map.initContext(main.allocator, HashContext{}) }; + fn init(dir: *model.Dir) Self { + var self = Self{ + .dir = dir, + .entries = Map.initContext(main.allocator, HashContext{}), + }; var count: Map.Size = 0; - var it = parents.top().sub; + var it = dir.sub; while (it) |e| : (it = e.next) count += 1; self.entries.ensureCapacity(count) catch unreachable; - it = parents.top().sub; + it = dir.sub; while (it) |e| : (it = e.next) self.entries.putAssumeCapacity(e, @as(void,undefined)); return self; } - fn addSpecial(self: *Self, parents: *model.Parents, name: []const u8, t: Context.Special) void { + fn addSpecial(self: *Self, name: []const u8, t: Context.Special) void { var e = blk: { if (self.entries.getEntryAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name })) |entry| { // XXX: If the type doesn't match, we could always do an @@ -153,32 +158,32 @@ const ScanDir = struct { var e = entry.key_ptr.*.?; if (e.etype == .file) { if (e.size > 0 or e.blocks > 0) { - e.delStats(parents); + e.delStats(self.dir); e.size = 0; e.blocks = 0; - e.addStats(parents); + e.addStats(self.dir); } e.file().?.resetFlags(); _ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name }); break :blk e; - } else e.delStatsRec(parents); + } else e.delStatsRec(self.dir); } var e = model.Entry.create(.file, false, name); - e.next = parents.top().sub; - parents.top().sub = e; - e.addStats(parents); + e.next = self.dir.sub; + self.dir.sub = e; + e.addStats(self.dir); break :blk e; }; var f = e.file().?; switch (t) { - .err => e.set_err(parents), + .err => e.setErr(self.dir), .other_fs => f.other_fs = true, .kernfs => f.kernfs = true, .excluded => f.excluded = true, } } - fn addStat(self: *Self, parents: *model.Parents, name: []const u8, stat: *Stat) *model.Entry { + fn addStat(self: *Self, name: []const u8, stat: *Stat) *model.Entry { const etype = if (stat.dir) model.EType.dir else if (stat.hlinkc) model.EType.link else model.EType.file; @@ -192,11 +197,11 @@ const ScanDir = struct { if (e.etype == etype and samedev and sameino) { _ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name }); break :blk e; - } else e.delStatsRec(parents); + } else e.delStatsRec(self.dir); } var e = model.Entry.create(etype, main.config.extended, name); - e.next = parents.top().sub; - parents.top().sub = e; + e.next = self.dir.sub; + self.dir.sub = e; break :blk e; }; // Ignore the new size/blocks field for directories, as we don't know @@ -205,11 +210,14 @@ const ScanDir = struct { // sizes. The current approach may result in incorrect sizes after // refresh, but I expect the difference to be fairly minor. if (!(e.etype == .dir and e.counted) and (e.blocks != stat.blocks or e.size != stat.size)) { - e.delStats(parents); + e.delStats(self.dir); e.blocks = stat.blocks; e.size = stat.size; } - if (e.dir()) |d| d.dev = model.devices.getId(stat.dev); + if (e.dir()) |d| { + d.parent = self.dir; + d.dev = model.devices.getId(stat.dev); + } if (e.file()) |f| { f.resetFlags(); f.notreg = !stat.dir and !stat.reg; @@ -228,19 +236,19 @@ const ScanDir = struct { // Assumption: l.link == 0 only happens on import, not refresh. if (if (e.link()) |l| l.nlink == 0 else false) - model.link_count.add(parents.top().dev, e.link().?.ino) + model.link_count.add(self.dir.dev, e.link().?.ino) else - e.addStats(parents); + e.addStats(self.dir); return e; } - fn final(self: *Self, parents: *model.Parents) void { + fn final(self: *Self) void { if (self.entries.count() == 0) // optimization for the common case return; - var it = &parents.top().sub; + var it = &self.dir.sub; while (it.*) |e| { if (self.entries.contains(e)) { - e.delStatsRec(parents); + e.delStatsRec(self.dir); it.* = e.next; } else it = &e.next; @@ -264,8 +272,7 @@ const ScanDir = struct { // const Context = struct { // When scanning to RAM - parents: ?model.Parents = null, - parent_entries: std.ArrayList(ScanDir) = std.ArrayList(ScanDir).init(main.allocator), + parents: ?std.ArrayList(ScanDir) = std.ArrayList(ScanDir).init(main.allocator), // When scanning to a file wr: ?*Writer = null, @@ -303,10 +310,10 @@ const Context = struct { return self; } - // Ownership of p is passed to the object, it will be deallocated on deinit(). - fn initMem(p: model.Parents) *Self { + fn initMem(dir: ?*model.Dir) *Self { var self = main.allocator.create(Self) catch unreachable; - self.* = .{ .parents = p }; + self.* = .{ .parents = std.ArrayList(ScanDir).init(main.allocator) }; + if (dir) |d| self.parents.?.append(ScanDir.init(d)) catch unreachable; return self; } @@ -335,10 +342,11 @@ const Context = struct { if (self.stat.dir) { if (self.parents) |*p| { - var d = self.parent_entries.pop(); - d.final(p); - d.deinit(); - if (!p.isRoot()) p.pop(); + if (p.items.len > 0) { + var d = p.pop(); + d.final(); + d.deinit(); + } } if (self.wr) |w| w.writer().writeByte(']') catch |e| writeErr(e); } else @@ -352,7 +360,7 @@ const Context = struct { // Set a flag to indicate that there was an error listing file entries in the current directory. // (Such errors are silently ignored when exporting to a file, as the directory metadata has already been written) fn setDirlistError(self: *Self) void { - if (self.parents) |*p| p.top().entry.set_err(p); + if (self.parents) |*p| p.items[p.items.len-1].dir.entry.setErr(p.items[p.items.len-1].dir); } const Special = enum { err, other_fs, kernfs, excluded }; @@ -383,7 +391,7 @@ const Context = struct { } if (self.parents) |*p| - self.parent_entries.items[self.parent_entries.items.len-1].addSpecial(p, self.name, t) + p.items[p.items.len-1].addSpecial(self.name, t) else if (self.wr) |wr| self.writeSpecial(wr.writer(), t) catch |e| writeErr(e); @@ -410,7 +418,7 @@ const Context = struct { // Insert current path as a counted file/dir/hardlink, with information from self.stat fn addStat(self: *Self, dir_dev: u64) void { if (self.parents) |*p| { - var e = if (self.items_seen == 0) blk: { + var e = if (p.items.len == 0) blk: { // Root entry var e = model.Entry.create(.dir, main.config.extended, self.name); e.blocks = self.stat.blocks; @@ -420,12 +428,10 @@ const Context = struct { model.root.dev = model.devices.getId(self.stat.dev); break :blk e; } else - self.parent_entries.items[self.parent_entries.items.len-1].addStat(p, self.name, &self.stat); + p.items[p.items.len-1].addStat(self.name, &self.stat); - if (e.dir()) |d| { // Enter the directory - if (self.items_seen != 0) p.push(d); - self.parent_entries.append(ScanDir.init(p)) catch unreachable; - } + if (e.dir()) |d| // Enter the directory + p.append(ScanDir.init(d)) catch unreachable; } else if (self.wr) |wr| self.writeStat(wr.writer(), dir_dev) catch |e| writeErr(e); @@ -435,11 +441,13 @@ const Context = struct { fn deinit(self: *Self) void { if (self.last_error) |p| main.allocator.free(p); - if (self.parents) |*p| p.deinit(); + if (self.parents) |*p| { + for (p.items) |*i| i.deinit(); + p.deinit(); + } if (self.wr) |p| main.allocator.destroy(p); self.path.deinit(); self.path_indices.deinit(); - self.parent_entries.deinit(); main.allocator.destroy(self); } }; @@ -533,7 +541,7 @@ fn scanDir(ctx: *Context, dir: std.fs.Dir, dir_dev: u64) void { } pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void { - active_context = if (out) |f| Context.initFile(f) else Context.initMem(.{}); + active_context = if (out) |f| Context.initFile(f) else Context.initMem(null); const full_path = std.fs.realpathAlloc(main.allocator, path) catch null; defer if (full_path) |p| main.allocator.free(p); @@ -545,16 +553,14 @@ pub fn scanRoot(path: []const u8, out: ?std.fs.File) !void { scan(); } -pub fn setupRefresh(parents: model.Parents) void { - active_context = Context.initMem(parents); +pub fn setupRefresh(parent: *model.Dir) void { + active_context = Context.initMem(parent); var full_path = std.ArrayList(u8).init(main.allocator); defer full_path.deinit(); - parents.fmtPath(true, &full_path); + parent.fmtPath(true, &full_path); active_context.pushPath(full_path.items); - active_context.parent_entries.append(ScanDir.init(&parents)) catch unreachable; active_context.stat.dir = true; - active_context.stat.dev = model.devices.getDev(parents.top().dev); - active_context.items_seen = 1; // The "root" item has already been added. + active_context.stat.dev = model.devices.getDev(parent.dev); } // To be called after setupRefresh() (or from scanRoot()) @@ -969,7 +975,7 @@ pub fn importRoot(path: [:0]const u8, out: ?std.fs.File) void { catch |e| ui.die("Error reading file: {s}.\n", .{ui.errorString(e)}); defer fd.close(); - active_context = if (out) |f| Context.initFile(f) else Context.initMem(.{}); + active_context = if (out) |f| Context.initFile(f) else Context.initMem(null); var imp = Import{ .ctx = active_context, .rd = fd }; defer imp.ctx.deinit(); imp.root();