mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-13 01:08:41 -09:00
Add parent node pointers to Dir struct + remove Parents abstraction
While this simplifies the code a bit, it's a regression in the sense that it increases memory use. This commit is yak shaving for another hard link counting approach I'd like to try out, which should be a *LOT* less memory hungry compared to the current approach. Even though it does, indeed, add an extra cost of these parent node pointers.
This commit is contained in:
parent
b94db184f4
commit
36bc405a69
5 changed files with 153 additions and 228 deletions
|
|
@ -10,8 +10,8 @@ const ui = @import("ui.zig");
|
||||||
const c = @cImport(@cInclude("time.h"));
|
const c = @cImport(@cInclude("time.h"));
|
||||||
usingnamespace @import("util.zig");
|
usingnamespace @import("util.zig");
|
||||||
|
|
||||||
// Currently opened directory and its parents.
|
// Currently opened directory.
|
||||||
pub var dir_parents = model.Parents{};
|
pub var dir_parent: *model.Dir = undefined;
|
||||||
|
|
||||||
// Sorted list of all items in the currently opened directory.
|
// Sorted list of all items in the currently opened directory.
|
||||||
// (first item may be null to indicate the "parent directory" item)
|
// (first item may be null to indicate the "parent directory" item)
|
||||||
|
|
@ -44,12 +44,12 @@ const View = struct {
|
||||||
fn save(self: *@This()) void {
|
fn save(self: *@This()) void {
|
||||||
self.cursor_hash = if (dir_items.items.len == 0) 0
|
self.cursor_hash = if (dir_items.items.len == 0) 0
|
||||||
else hashEntry(dir_items.items[cursor_idx]);
|
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 {
|
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(){};
|
else self.* = @This(){};
|
||||||
cursor_idx = 0;
|
cursor_idx = 0;
|
||||||
for (dir_items.items) |e, i| {
|
for (dir_items.items) |e, i| {
|
||||||
|
|
@ -123,7 +123,7 @@ fn sortDir(next_sel: ?*const model.Entry) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be called when:
|
// Must be called when:
|
||||||
// - dir_parents changes (i.e. we change directory)
|
// - dir_parent changes (i.e. we change directory)
|
||||||
// - config.show_hidden changes
|
// - config.show_hidden changes
|
||||||
// - files in this dir have been added or removed
|
// - files in this dir have been added or removed
|
||||||
pub fn loadDir(next_sel: ?*const model.Entry) void {
|
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_max_blocks = 1;
|
||||||
dir_has_shared = false;
|
dir_has_shared = false;
|
||||||
|
|
||||||
if (!dir_parents.isRoot())
|
if (dir_parent != model.root)
|
||||||
dir_items.append(null) catch unreachable;
|
dir_items.append(null) catch unreachable;
|
||||||
var it = dir_parents.top().sub;
|
var it = dir_parent.sub;
|
||||||
while (it) |e| {
|
while (it) |e| {
|
||||||
if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks;
|
if (e.blocks > dir_max_blocks) dir_max_blocks = e.blocks;
|
||||||
if (e.size > dir_max_size) dir_max_size = e.size;
|
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) {
|
if (main.config.show_graph == .both or main.config.show_graph == .percent) {
|
||||||
self.bg.fg(.num);
|
self.bg.fg(.num);
|
||||||
ui.addprint("{d:>5.1}", .{ 100*
|
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))
|
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_parents.top().entry.size))
|
else @intToFloat(f32, item.size) / @intToFloat(f32, std.math.max(1, dir_parent.entry.size))
|
||||||
});
|
});
|
||||||
self.bg.fg(.default);
|
self.bg.fg(.default);
|
||||||
ui.addch('%');
|
ui.addch('%');
|
||||||
|
|
@ -276,7 +276,7 @@ const Row = struct {
|
||||||
if (!main.config.show_mtime or self.col + 37 > ui.cols) return;
|
if (!main.config.show_mtime or self.col + 37 > ui.cols) return;
|
||||||
defer self.col += 27;
|
defer self.col += 27;
|
||||||
ui.move(self.row, self.col+1);
|
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)
|
if (ext) |e| ui.addts(self.bg, e.mtime)
|
||||||
else ui.addstr(" no mtime");
|
else ui.addstr(" no mtime");
|
||||||
}
|
}
|
||||||
|
|
@ -359,7 +359,7 @@ const info = struct {
|
||||||
state = .info;
|
state = .info;
|
||||||
tab = t;
|
tab = t;
|
||||||
if (tab == .links and links == null) {
|
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| {
|
for (links.?.paths.items) |n,i| {
|
||||||
if (&n.node.entry == e) {
|
if (&n.node.entry == e) {
|
||||||
links_idx = i;
|
links_idx = i;
|
||||||
|
|
@ -536,8 +536,7 @@ const info = struct {
|
||||||
return true;
|
return true;
|
||||||
if (ch == 10) { // Enter - go to selected entry
|
if (ch == 10) { // Enter - go to selected entry
|
||||||
const p = links.?.paths.items[links_idx];
|
const p = links.?.paths.items[links_idx];
|
||||||
dir_parents.stack.shrinkRetainingCapacity(0);
|
dir_parent = p.path;
|
||||||
dir_parents.stack.appendSlice(p.path.stack.items) catch unreachable;
|
|
||||||
loadDir(&p.node.entry);
|
loadDir(&p.node.entry);
|
||||||
set(null, .info);
|
set(null, .info);
|
||||||
}
|
}
|
||||||
|
|
@ -730,7 +729,7 @@ pub fn draw() void {
|
||||||
ui.style(.dir);
|
ui.style(.dir);
|
||||||
|
|
||||||
var pathbuf = std.ArrayList(u8).init(main.allocator);
|
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)));
|
ui.addstr(ui.shorten(ui.toUtf8(arrayListBufZ(&pathbuf)), saturateSub(ui.cols, 5)));
|
||||||
pathbuf.deinit();
|
pathbuf.deinit();
|
||||||
|
|
||||||
|
|
@ -760,12 +759,12 @@ pub fn draw() void {
|
||||||
ui.move(ui.rows-1, 1);
|
ui.move(ui.rows-1, 1);
|
||||||
ui.style(if (main.config.show_blocks) .bold_hd else .hd);
|
ui.style(if (main.config.show_blocks) .bold_hd else .hd);
|
||||||
ui.addstr("Total disk usage: ");
|
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.style(if (main.config.show_blocks) .hd else .bold_hd);
|
||||||
ui.addstr(" Apparent size: ");
|
ui.addstr(" Apparent size: ");
|
||||||
ui.addsize(.hd, dir_parents.top().entry.size);
|
ui.addsize(.hd, dir_parent.entry.size);
|
||||||
ui.addstr(" Items: ");
|
ui.addstr(" Items: ");
|
||||||
ui.addnum(.hd, dir_parents.top().items);
|
ui.addnum(.hd, dir_parent.items);
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
.main => {},
|
.main => {},
|
||||||
|
|
@ -832,7 +831,7 @@ pub fn keyInput(ch: i32) void {
|
||||||
message = "Directory imported from file, refreshing is disabled."
|
message = "Directory imported from file, refreshing is disabled."
|
||||||
else {
|
else {
|
||||||
main.state = .refresh;
|
main.state = .refresh;
|
||||||
scan.setupRefresh(dir_parents.copy());
|
scan.setupRefresh(dir_parent);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'b' => {
|
'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]
|
if (cursor_idx+1 < dir_items.items.len) dir_items.items[cursor_idx+1]
|
||||||
else if (cursor_idx == 0) null
|
else if (cursor_idx == 0) null
|
||||||
else dir_items.items[cursor_idx-1];
|
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) {
|
if (dir_items.items.len == 0) {
|
||||||
} else if (dir_items.items[cursor_idx]) |e| {
|
} else if (dir_items.items[cursor_idx]) |e| {
|
||||||
if (e.dir()) |d| {
|
if (e.dir()) |d| {
|
||||||
dir_parents.push(d);
|
dir_parent = d;
|
||||||
loadDir(null);
|
loadDir(null);
|
||||||
state = .main;
|
state = .main;
|
||||||
}
|
}
|
||||||
} else if (!dir_parents.isRoot()) {
|
} else if (dir_parent.parent) |p| {
|
||||||
dir_parents.pop();
|
dir_parent = p;
|
||||||
loadDir(null);
|
loadDir(null);
|
||||||
state = .main;
|
state = .main;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
|
'h', '<', ui.c.KEY_BACKSPACE, ui.c.KEY_LEFT => {
|
||||||
if (!dir_parents.isRoot()) {
|
if (dir_parent.parent) |p| {
|
||||||
const e = dir_parents.top();
|
const e = dir_parent;
|
||||||
dir_parents.pop();
|
dir_parent = p;
|
||||||
loadDir(&e.entry);
|
loadDir(&e.entry);
|
||||||
state = .main;
|
state = .main;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const ui = @import("ui.zig");
|
||||||
const browser = @import("browser.zig");
|
const browser = @import("browser.zig");
|
||||||
usingnamespace @import("util.zig");
|
usingnamespace @import("util.zig");
|
||||||
|
|
||||||
var parents: model.Parents = .{};
|
var parent: *model.Dir = undefined;
|
||||||
var entry: *model.Entry = undefined;
|
var entry: *model.Entry = undefined;
|
||||||
var next_sel: ?*model.Entry = undefined; // Which item to select if deletion succeeds
|
var next_sel: ?*model.Entry = undefined; // Which item to select if deletion succeeds
|
||||||
var state: enum { confirm, busy, err } = .confirm;
|
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_option: enum { abort, ignore, all } = .abort;
|
||||||
var error_code: anyerror = undefined;
|
var error_code: anyerror = undefined;
|
||||||
|
|
||||||
// ownership of p is passed to this function
|
pub fn setup(p: *model.Dir, e: *model.Entry, n: ?*model.Entry) void {
|
||||||
pub fn setup(p: model.Parents, e: *model.Entry, n: ?*model.Entry) void {
|
parent = p;
|
||||||
parents = p;
|
|
||||||
entry = e;
|
entry = e;
|
||||||
next_sel = n;
|
next_sel = n;
|
||||||
state = if (main.config.confirm_delete) .confirm else .busy;
|
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 })
|
var fd = dir.openDirZ(path, .{ .access_sub_paths = true, .iterate = false })
|
||||||
catch |e| return err(e);
|
catch |e| return err(e);
|
||||||
var it = &d.sub;
|
var it = &d.sub;
|
||||||
parents.push(d);
|
parent = d;
|
||||||
defer parents.pop();
|
defer parent = parent.parent.?;
|
||||||
while (it.*) |n| {
|
while (it.*) |n| {
|
||||||
if (deleteItem(fd, n.name(), it)) {
|
if (deleteItem(fd, n.name(), it)) {
|
||||||
fd.close();
|
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;
|
return if (e != error.DirNotEmpty or d.sub == null) err(e) else false;
|
||||||
} else
|
} else
|
||||||
dir.deleteFileZ(path) catch |e| return err(e);
|
dir.deleteFileZ(path) catch |e| return err(e);
|
||||||
ptr.*.?.delStats(&parents);
|
ptr.*.?.delStats(parent);
|
||||||
ptr.* = ptr.*.?.next;
|
ptr.* = ptr.*.?.next;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the item that should be selected in the browser.
|
// Returns the item that should be selected in the browser.
|
||||||
pub fn delete() ?*model.Entry {
|
pub fn delete() ?*model.Entry {
|
||||||
defer parents.deinit();
|
|
||||||
while (main.state == .delete and state == .confirm)
|
while (main.state == .delete and state == .confirm)
|
||||||
main.handleEvent(true, false);
|
main.handleEvent(true, false);
|
||||||
if (main.state != .delete)
|
if (main.state != .delete)
|
||||||
|
|
@ -79,14 +77,14 @@ pub fn delete() ?*model.Entry {
|
||||||
|
|
||||||
// Find the pointer to this entry
|
// Find the pointer to this entry
|
||||||
const e = entry;
|
const e = entry;
|
||||||
var it = &parents.top().sub;
|
var it = &parent.sub;
|
||||||
while (it.*) |n| : (it = &n.next)
|
while (it.*) |n| : (it = &n.next)
|
||||||
if (it.* == entry)
|
if (it.* == entry)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var path = std.ArrayList(u8).init(main.allocator);
|
var path = std.ArrayList(u8).init(main.allocator);
|
||||||
defer path.deinit();
|
defer path.deinit();
|
||||||
parents.fmtPath(true, &path);
|
parent.fmtPath(true, &path);
|
||||||
if (path.items.len == 0 or path.items[path.items.len-1] != '/')
|
if (path.items.len == 0 or path.items[path.items.len-1] != '/')
|
||||||
path.append('/') catch unreachable;
|
path.append('/') catch unreachable;
|
||||||
path.appendSlice(entry.name()) catch unreachable;
|
path.appendSlice(entry.name()) catch unreachable;
|
||||||
|
|
@ -125,7 +123,7 @@ fn drawConfirm() void {
|
||||||
fn drawProgress() void {
|
fn drawProgress() void {
|
||||||
var path = std.ArrayList(u8).init(main.allocator);
|
var path = std.ArrayList(u8).init(main.allocator);
|
||||||
defer path.deinit();
|
defer path.deinit();
|
||||||
parents.fmtPath(false, &path);
|
parent.fmtPath(false, &path);
|
||||||
path.append('/') catch unreachable;
|
path.append('/') catch unreachable;
|
||||||
path.appendSlice(entry.name()) catch unreachable;
|
path.appendSlice(entry.name()) catch unreachable;
|
||||||
|
|
||||||
|
|
@ -145,7 +143,7 @@ fn drawProgress() void {
|
||||||
fn drawErr() void {
|
fn drawErr() void {
|
||||||
var path = std.ArrayList(u8).init(main.allocator);
|
var path = std.ArrayList(u8).init(main.allocator);
|
||||||
defer path.deinit();
|
defer path.deinit();
|
||||||
parents.fmtPath(false, &path);
|
parent.fmtPath(false, &path);
|
||||||
path.append('/') catch unreachable;
|
path.append('/') catch unreachable;
|
||||||
path.appendSlice(entry.name()) catch unreachable;
|
path.appendSlice(entry.name()) catch unreachable;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ fn spawnShell() void {
|
||||||
|
|
||||||
var path = std.ArrayList(u8).init(allocator);
|
var path = std.ArrayList(u8).init(allocator);
|
||||||
defer path.deinit();
|
defer path.deinit();
|
||||||
browser.dir_parents.fmtPath(true, &path);
|
browser.dir_parent.fmtPath(true, &path);
|
||||||
|
|
||||||
var env = std.process.getEnvMap(allocator) catch unreachable;
|
var env = std.process.getEnvMap(allocator) catch unreachable;
|
||||||
defer env.deinit();
|
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.
|
config.scan_ui = .full; // in case we're refreshing from the UI, always in full mode.
|
||||||
ui.init();
|
ui.init();
|
||||||
state = .browse;
|
state = .browse;
|
||||||
|
browser.dir_parent = model.root;
|
||||||
browser.loadDir(null);
|
browser.loadDir(null);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
|
||||||
199
src/model.zig
199
src/model.zig
|
|
@ -99,29 +99,27 @@ pub const Entry = packed struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the 'err' flag on Dirs and Files, propagating 'suberr' to parents.
|
// 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
|
if (self.dir()) |d| d.err = true
|
||||||
else if (self.file()) |f| f.err = true
|
else if (self.file()) |f| f.err = true
|
||||||
else unreachable;
|
else unreachable;
|
||||||
var it = parents.iter();
|
var it: ?*Dir = if (&parent.entry == self) parent.parent else parent;
|
||||||
if (&parents.top().entry == self) _ = it.next();
|
while (it) |p| : (it = p.parent) {
|
||||||
while (it.next()) |p| {
|
|
||||||
if (p.suberr) break;
|
if (p.suberr) break;
|
||||||
p.suberr = true;
|
p.suberr = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addStats(self: *Entry, parents: *const Parents) void {
|
pub fn addStats(self: *Entry, parent: *Dir) void {
|
||||||
if (self.counted) return;
|
if (self.counted) return;
|
||||||
self.counted = true;
|
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.
|
// 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.
|
// Means we should count it for other-dev parent dirs, too.
|
||||||
var new_hl = false;
|
var new_hl = false;
|
||||||
|
|
||||||
var it = parents.iter();
|
var it: ?*Dir = parent;
|
||||||
while(it.next()) |p| {
|
while(it) |p| : (it = p.parent) {
|
||||||
var add_total = false;
|
var add_total = false;
|
||||||
|
|
||||||
if (self.ext()) |e|
|
if (self.ext()) |e|
|
||||||
|
|
@ -130,12 +128,12 @@ pub const Entry = packed struct {
|
||||||
p.items = saturateAdd(p.items, 1);
|
p.items = saturateAdd(p.items, 1);
|
||||||
|
|
||||||
// Hardlink in a subdirectory with a different device, only count it the first time.
|
// 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;
|
add_total = new_hl;
|
||||||
|
|
||||||
} else if (self.link()) |l| {
|
} else if (self.link()) |l| {
|
||||||
const n = devices.HardlinkNode{ .ino = l.ino, .dir = p };
|
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;
|
new_hl = !d.found_existing;
|
||||||
// First time we encounter this file in this dir, count it.
|
// First time we encounter this file in this dir, count it.
|
||||||
if (!d.found_existing) {
|
if (!d.found_existing) {
|
||||||
|
|
@ -175,29 +173,28 @@ pub const Entry = packed struct {
|
||||||
//
|
//
|
||||||
// This function assumes that, for directories, all sub-entries have
|
// This function assumes that, for directories, all sub-entries have
|
||||||
// already been un-counted.
|
// 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;
|
if (!self.counted) return;
|
||||||
self.counted = false;
|
self.counted = false;
|
||||||
|
|
||||||
const dev = parents.top().dev;
|
|
||||||
var del_hl = false;
|
var del_hl = false;
|
||||||
|
|
||||||
var it = parents.iter();
|
var it: ?*Dir = parent;
|
||||||
while(it.next()) |p| {
|
while(it) |p| : (it = p.parent) {
|
||||||
var del_total = false;
|
var del_total = false;
|
||||||
p.items = saturateSub(p.items, 1);
|
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;
|
del_total = del_hl;
|
||||||
} else if (self.link()) |l| {
|
} else if (self.link()) |l| {
|
||||||
const n = devices.HardlinkNode{ .ino = l.ino, .dir = p };
|
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| {
|
if (dp) |d| {
|
||||||
d.value_ptr.* -= 1;
|
d.value_ptr.* -= 1;
|
||||||
del_total = d.value_ptr.* == 0;
|
del_total = d.value_ptr.* == 0;
|
||||||
del_hl = del_total;
|
del_hl = del_total;
|
||||||
if (del_total)
|
if (del_total)
|
||||||
_ = devices.list.items[dev].hardlinks.remove(n);
|
_ = devices.list.items[parent.dev].hardlinks.remove(n);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
del_total = true;
|
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| {
|
if (self.dir()) |d| {
|
||||||
parents.push(d);
|
|
||||||
var it = d.sub;
|
var it = d.sub;
|
||||||
while (it) |e| : (it = e.next)
|
while (it) |e| : (it = e.next)
|
||||||
e.delStatsRec(parents);
|
e.delStatsRec(d);
|
||||||
parents.pop();
|
|
||||||
}
|
}
|
||||||
self.delStats(parents);
|
self.delStats(parent);
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -239,6 +221,7 @@ pub const Dir = packed struct {
|
||||||
entry: Entry,
|
entry: Entry,
|
||||||
|
|
||||||
sub: ?*Entry,
|
sub: ?*Entry,
|
||||||
|
parent: ?*Dir,
|
||||||
|
|
||||||
// entry.{blocks,size}: Total size of all unique files + dirs. Non-shared hardlinks are counted only once.
|
// 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)
|
// (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.
|
// Only used to find the @byteOffsetOff, the name is written at this point as a 0-terminated string.
|
||||||
// (Old C habits die hard)
|
// (Old C habits die hard)
|
||||||
name: u8,
|
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)
|
// 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;
|
if (d.found_existing) d.key_ptr.*.count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var final_dir: Parents = undefined;
|
fn finalRec(parent: *Dir) void {
|
||||||
|
var it = parent.sub;
|
||||||
fn final_rec() void {
|
|
||||||
var it = final_dir.top().sub;
|
|
||||||
while (it) |e| : (it = e.next) {
|
while (it) |e| : (it = e.next) {
|
||||||
if (e.dir()) |d| {
|
if (e.dir()) |d| finalRec(d);
|
||||||
final_dir.push(d);
|
|
||||||
final_rec();
|
|
||||||
final_dir.pop();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const l = e.link() orelse continue;
|
const l = e.link() orelse continue;
|
||||||
if (l.nlink > 0) 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| {
|
if (nodes.getEntry(s)) |n| {
|
||||||
l.nlink = n.key_ptr.*.count;
|
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.
|
// find all links, update their nlink count and parent sizes.
|
||||||
pub fn final() void {
|
pub fn final() void {
|
||||||
if (nodes.count() == 0) return;
|
if (nodes.count() == 0) return;
|
||||||
final_dir = Parents{};
|
finalRec(root);
|
||||||
final_rec();
|
|
||||||
nodes.clearAndFree();
|
nodes.clearAndFree();
|
||||||
final_dir.deinit();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub var root: *Dir = undefined;
|
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.
|
// List of paths for the same inode.
|
||||||
pub const LinkPaths = struct {
|
pub const LinkPaths = struct {
|
||||||
paths: std.ArrayList(Path) = std.ArrayList(Path).init(main.allocator),
|
paths: std.ArrayList(Path) = std.ArrayList(Path).init(main.allocator),
|
||||||
|
|
||||||
pub const Path = struct {
|
pub const Path = struct {
|
||||||
path: Parents,
|
path: *Dir,
|
||||||
node: *Link,
|
node: *Link,
|
||||||
|
|
||||||
fn lt(_: void, a: Path, b: Path) bool {
|
fn lt(_: void, a: Path, b: Path) bool {
|
||||||
var i: usize = 0;
|
var pa = std.ArrayList(u8).init(main.allocator);
|
||||||
while (i < a.path.stack.items.len and i < b.path.stack.items.len) : (i += 1)
|
var pb = std.ArrayList(u8).init(main.allocator);
|
||||||
if (a.path.stack.items[i] != b.path.stack.items[i])
|
defer pa.deinit();
|
||||||
return std.mem.lessThan(u8, a.path.stack.items[i].entry.name(), b.path.stack.items[i].entry.name());
|
defer pb.deinit();
|
||||||
if (a.path.stack.items.len != b.path.stack.items.len)
|
a.fmtPath(false, &pa);
|
||||||
return a.path.stack.items.len < b.path.stack.items.len;
|
b.fmtPath(false, &pb);
|
||||||
return std.mem.lessThan(u8, a.node.entry.name(), b.node.entry.name());
|
return std.mem.lessThan(u8, pa.items, pb.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmtPath(self: Path, withRoot: bool, out: *std.ArrayList(u8)) void {
|
pub fn fmtPath(self: Path, withRoot: bool, out: *std.ArrayList(u8)) void {
|
||||||
|
|
@ -546,42 +473,36 @@ pub const LinkPaths = struct {
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
fn findRec(self: *Self, parent: *Parents, node: *const Link) void {
|
fn findRec(self: *Self, parent: *Dir, node: *const Link) void {
|
||||||
var entry = parent.top().sub;
|
var entry = parent.sub;
|
||||||
while (entry) |e| : (entry = e.next) {
|
while (entry) |e| : (entry = e.next) {
|
||||||
if (e.link()) |l| {
|
if (e.link()) |l| {
|
||||||
if (l.ino == node.ino)
|
if (l.ino == node.ino)
|
||||||
self.paths.append(Path{ .path = parent.copy(), .node = l }) catch unreachable;
|
self.paths.append(Path{ .path = parent, .node = l }) catch unreachable;
|
||||||
}
|
|
||||||
if (e.dir()) |d| {
|
|
||||||
if (d.dev == parent.top().dev) {
|
|
||||||
parent.push(d);
|
|
||||||
self.findRec(parent, node);
|
|
||||||
parent.pop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (e.dir()) |d|
|
||||||
|
if (d.dev == parent.dev)
|
||||||
|
self.findRec(d, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all paths for the given link
|
// Find all paths for the given link
|
||||||
pub fn find(parents_: *const Parents, node: *const Link) Self {
|
pub fn find(parent_: *Dir, node: *const Link) Self {
|
||||||
var parents = parents_.copy();
|
var parent = parent_;
|
||||||
var self = Self{};
|
var self = Self{};
|
||||||
// First find the bottom-most parent that has no shared_size,
|
// First find the bottom-most parent that has no shared_size,
|
||||||
// all links are guaranteed to be inside that directory.
|
// all links are guaranteed to be inside that directory.
|
||||||
while (!parents.isRoot() and parents.top().shared_size > 0)
|
while (parent.parent != null and parent.shared_size > 0)
|
||||||
parents.pop();
|
parent = parent.parent.?;
|
||||||
self.findRec(&parents, node);
|
self.findRec(parent, node);
|
||||||
// TODO: Zig's sort() implementation is type-generic and not very
|
// 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
|
// 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.
|
// 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);
|
std.sort.sort(Path, self.paths.items, @as(void, undefined), Path.lt);
|
||||||
parents.deinit();
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
for (self.paths.items) |*p| p.path.deinit();
|
|
||||||
self.paths.deinit();
|
self.paths.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
106
src/scan.zig
106
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
|
// entries read from disk can be merged into, without doing an O(1) lookup for
|
||||||
// each entry.
|
// each entry.
|
||||||
const ScanDir = struct {
|
const ScanDir = struct {
|
||||||
|
dir: *model.Dir,
|
||||||
|
|
||||||
// Lookup table for name -> *entry.
|
// Lookup table for name -> *entry.
|
||||||
// null is never stored in the table, but instead used pass a name string
|
// null is never stored in the table, but instead used pass a name string
|
||||||
// as out-of-band argument for lookups.
|
// as out-of-band argument for lookups.
|
||||||
|
|
@ -130,21 +132,24 @@ const ScanDir = struct {
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
fn init(parents: *const model.Parents) Self {
|
fn init(dir: *model.Dir) Self {
|
||||||
var self = Self{ .entries = Map.initContext(main.allocator, HashContext{}) };
|
var self = Self{
|
||||||
|
.dir = dir,
|
||||||
|
.entries = Map.initContext(main.allocator, HashContext{}),
|
||||||
|
};
|
||||||
|
|
||||||
var count: Map.Size = 0;
|
var count: Map.Size = 0;
|
||||||
var it = parents.top().sub;
|
var it = dir.sub;
|
||||||
while (it) |e| : (it = e.next) count += 1;
|
while (it) |e| : (it = e.next) count += 1;
|
||||||
self.entries.ensureCapacity(count) catch unreachable;
|
self.entries.ensureCapacity(count) catch unreachable;
|
||||||
|
|
||||||
it = parents.top().sub;
|
it = dir.sub;
|
||||||
while (it) |e| : (it = e.next)
|
while (it) |e| : (it = e.next)
|
||||||
self.entries.putAssumeCapacity(e, @as(void,undefined));
|
self.entries.putAssumeCapacity(e, @as(void,undefined));
|
||||||
return self;
|
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: {
|
var e = blk: {
|
||||||
if (self.entries.getEntryAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name })) |entry| {
|
if (self.entries.getEntryAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name })) |entry| {
|
||||||
// XXX: If the type doesn't match, we could always do an
|
// XXX: If the type doesn't match, we could always do an
|
||||||
|
|
@ -153,32 +158,32 @@ const ScanDir = struct {
|
||||||
var e = entry.key_ptr.*.?;
|
var e = entry.key_ptr.*.?;
|
||||||
if (e.etype == .file) {
|
if (e.etype == .file) {
|
||||||
if (e.size > 0 or e.blocks > 0) {
|
if (e.size > 0 or e.blocks > 0) {
|
||||||
e.delStats(parents);
|
e.delStats(self.dir);
|
||||||
e.size = 0;
|
e.size = 0;
|
||||||
e.blocks = 0;
|
e.blocks = 0;
|
||||||
e.addStats(parents);
|
e.addStats(self.dir);
|
||||||
}
|
}
|
||||||
e.file().?.resetFlags();
|
e.file().?.resetFlags();
|
||||||
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
|
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
|
||||||
break :blk e;
|
break :blk e;
|
||||||
} else e.delStatsRec(parents);
|
} else e.delStatsRec(self.dir);
|
||||||
}
|
}
|
||||||
var e = model.Entry.create(.file, false, name);
|
var e = model.Entry.create(.file, false, name);
|
||||||
e.next = parents.top().sub;
|
e.next = self.dir.sub;
|
||||||
parents.top().sub = e;
|
self.dir.sub = e;
|
||||||
e.addStats(parents);
|
e.addStats(self.dir);
|
||||||
break :blk e;
|
break :blk e;
|
||||||
};
|
};
|
||||||
var f = e.file().?;
|
var f = e.file().?;
|
||||||
switch (t) {
|
switch (t) {
|
||||||
.err => e.set_err(parents),
|
.err => e.setErr(self.dir),
|
||||||
.other_fs => f.other_fs = true,
|
.other_fs => f.other_fs = true,
|
||||||
.kernfs => f.kernfs = true,
|
.kernfs => f.kernfs = true,
|
||||||
.excluded => f.excluded = 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
|
const etype = if (stat.dir) model.EType.dir
|
||||||
else if (stat.hlinkc) model.EType.link
|
else if (stat.hlinkc) model.EType.link
|
||||||
else model.EType.file;
|
else model.EType.file;
|
||||||
|
|
@ -192,11 +197,11 @@ const ScanDir = struct {
|
||||||
if (e.etype == etype and samedev and sameino) {
|
if (e.etype == etype and samedev and sameino) {
|
||||||
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
|
_ = self.entries.removeAdapted(@as(?*model.Entry,null), HashContext{ .cmp = name });
|
||||||
break :blk e;
|
break :blk e;
|
||||||
} else e.delStatsRec(parents);
|
} else e.delStatsRec(self.dir);
|
||||||
}
|
}
|
||||||
var e = model.Entry.create(etype, main.config.extended, name);
|
var e = model.Entry.create(etype, main.config.extended, name);
|
||||||
e.next = parents.top().sub;
|
e.next = self.dir.sub;
|
||||||
parents.top().sub = e;
|
self.dir.sub = e;
|
||||||
break :blk e;
|
break :blk e;
|
||||||
};
|
};
|
||||||
// Ignore the new size/blocks field for directories, as we don't know
|
// 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
|
// sizes. The current approach may result in incorrect sizes after
|
||||||
// refresh, but I expect the difference to be fairly minor.
|
// 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)) {
|
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.blocks = stat.blocks;
|
||||||
e.size = stat.size;
|
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| {
|
if (e.file()) |f| {
|
||||||
f.resetFlags();
|
f.resetFlags();
|
||||||
f.notreg = !stat.dir and !stat.reg;
|
f.notreg = !stat.dir and !stat.reg;
|
||||||
|
|
@ -228,19 +236,19 @@ const ScanDir = struct {
|
||||||
|
|
||||||
// Assumption: l.link == 0 only happens on import, not refresh.
|
// Assumption: l.link == 0 only happens on import, not refresh.
|
||||||
if (if (e.link()) |l| l.nlink == 0 else false)
|
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
|
else
|
||||||
e.addStats(parents);
|
e.addStats(self.dir);
|
||||||
return e;
|
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
|
if (self.entries.count() == 0) // optimization for the common case
|
||||||
return;
|
return;
|
||||||
var it = &parents.top().sub;
|
var it = &self.dir.sub;
|
||||||
while (it.*) |e| {
|
while (it.*) |e| {
|
||||||
if (self.entries.contains(e)) {
|
if (self.entries.contains(e)) {
|
||||||
e.delStatsRec(parents);
|
e.delStatsRec(self.dir);
|
||||||
it.* = e.next;
|
it.* = e.next;
|
||||||
} else
|
} else
|
||||||
it = &e.next;
|
it = &e.next;
|
||||||
|
|
@ -264,8 +272,7 @@ const ScanDir = struct {
|
||||||
//
|
//
|
||||||
const Context = struct {
|
const Context = struct {
|
||||||
// When scanning to RAM
|
// When scanning to RAM
|
||||||
parents: ?model.Parents = null,
|
parents: ?std.ArrayList(ScanDir) = std.ArrayList(ScanDir).init(main.allocator),
|
||||||
parent_entries: std.ArrayList(ScanDir) = std.ArrayList(ScanDir).init(main.allocator),
|
|
||||||
// When scanning to a file
|
// When scanning to a file
|
||||||
wr: ?*Writer = null,
|
wr: ?*Writer = null,
|
||||||
|
|
||||||
|
|
@ -303,10 +310,10 @@ const Context = struct {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ownership of p is passed to the object, it will be deallocated on deinit().
|
fn initMem(dir: ?*model.Dir) *Self {
|
||||||
fn initMem(p: model.Parents) *Self {
|
|
||||||
var self = main.allocator.create(Self) catch unreachable;
|
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;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,10 +342,11 @@ const Context = struct {
|
||||||
|
|
||||||
if (self.stat.dir) {
|
if (self.stat.dir) {
|
||||||
if (self.parents) |*p| {
|
if (self.parents) |*p| {
|
||||||
var d = self.parent_entries.pop();
|
if (p.items.len > 0) {
|
||||||
d.final(p);
|
var d = p.pop();
|
||||||
d.deinit();
|
d.final();
|
||||||
if (!p.isRoot()) p.pop();
|
d.deinit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (self.wr) |w| w.writer().writeByte(']') catch |e| writeErr(e);
|
if (self.wr) |w| w.writer().writeByte(']') catch |e| writeErr(e);
|
||||||
} else
|
} 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.
|
// 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)
|
// (Such errors are silently ignored when exporting to a file, as the directory metadata has already been written)
|
||||||
fn setDirlistError(self: *Self) void {
|
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 };
|
const Special = enum { err, other_fs, kernfs, excluded };
|
||||||
|
|
@ -383,7 +391,7 @@ const Context = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.parents) |*p|
|
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|
|
else if (self.wr) |wr|
|
||||||
self.writeSpecial(wr.writer(), t) catch |e| writeErr(e);
|
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
|
// Insert current path as a counted file/dir/hardlink, with information from self.stat
|
||||||
fn addStat(self: *Self, dir_dev: u64) void {
|
fn addStat(self: *Self, dir_dev: u64) void {
|
||||||
if (self.parents) |*p| {
|
if (self.parents) |*p| {
|
||||||
var e = if (self.items_seen == 0) blk: {
|
var e = if (p.items.len == 0) blk: {
|
||||||
// Root entry
|
// Root entry
|
||||||
var e = model.Entry.create(.dir, main.config.extended, self.name);
|
var e = model.Entry.create(.dir, main.config.extended, self.name);
|
||||||
e.blocks = self.stat.blocks;
|
e.blocks = self.stat.blocks;
|
||||||
|
|
@ -420,12 +428,10 @@ const Context = struct {
|
||||||
model.root.dev = model.devices.getId(self.stat.dev);
|
model.root.dev = model.devices.getId(self.stat.dev);
|
||||||
break :blk e;
|
break :blk e;
|
||||||
} else
|
} 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 (e.dir()) |d| // Enter the directory
|
||||||
if (self.items_seen != 0) p.push(d);
|
p.append(ScanDir.init(d)) catch unreachable;
|
||||||
self.parent_entries.append(ScanDir.init(p)) catch unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (self.wr) |wr|
|
} else if (self.wr) |wr|
|
||||||
self.writeStat(wr.writer(), dir_dev) catch |e| writeErr(e);
|
self.writeStat(wr.writer(), dir_dev) catch |e| writeErr(e);
|
||||||
|
|
@ -435,11 +441,13 @@ const Context = struct {
|
||||||
|
|
||||||
fn deinit(self: *Self) void {
|
fn deinit(self: *Self) void {
|
||||||
if (self.last_error) |p| main.allocator.free(p);
|
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);
|
if (self.wr) |p| main.allocator.destroy(p);
|
||||||
self.path.deinit();
|
self.path.deinit();
|
||||||
self.path_indices.deinit();
|
self.path_indices.deinit();
|
||||||
self.parent_entries.deinit();
|
|
||||||
main.allocator.destroy(self);
|
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 {
|
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;
|
const full_path = std.fs.realpathAlloc(main.allocator, path) catch null;
|
||||||
defer if (full_path) |p| main.allocator.free(p);
|
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();
|
scan();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setupRefresh(parents: model.Parents) void {
|
pub fn setupRefresh(parent: *model.Dir) void {
|
||||||
active_context = Context.initMem(parents);
|
active_context = Context.initMem(parent);
|
||||||
var full_path = std.ArrayList(u8).init(main.allocator);
|
var full_path = std.ArrayList(u8).init(main.allocator);
|
||||||
defer full_path.deinit();
|
defer full_path.deinit();
|
||||||
parents.fmtPath(true, &full_path);
|
parent.fmtPath(true, &full_path);
|
||||||
active_context.pushPath(full_path.items);
|
active_context.pushPath(full_path.items);
|
||||||
active_context.parent_entries.append(ScanDir.init(&parents)) catch unreachable;
|
|
||||||
active_context.stat.dir = true;
|
active_context.stat.dir = true;
|
||||||
active_context.stat.dev = model.devices.getDev(parents.top().dev);
|
active_context.stat.dev = model.devices.getDev(parent.dev);
|
||||||
active_context.items_seen = 1; // The "root" item has already been added.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// To be called after setupRefresh() (or from scanRoot())
|
// 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)});
|
catch |e| ui.die("Error reading file: {s}.\n", .{ui.errorString(e)});
|
||||||
defer fd.close();
|
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 };
|
var imp = Import{ .ctx = active_context, .rd = fd };
|
||||||
defer imp.ctx.deinit();
|
defer imp.ctx.deinit();
|
||||||
imp.root();
|
imp.root();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue