ncdu-zig/src/delete.zig
2022-10-26 13:34:06 +02:00

225 lines
6.5 KiB
Zig

// SPDX-FileCopyrightText: 2021-2022 Yoran Heling <projects@yorhel.nl>
// SPDX-License-Identifier: MIT
const std = @import("std");
const main = @import("main.zig");
const model = @import("model.zig");
const ui = @import("ui.zig");
const browser = @import("browser.zig");
const util = @import("util.zig");
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;
var confirm: enum { yes, no, ignore } = .no;
var error_option: enum { abort, ignore, all } = .abort;
var error_code: anyerror = undefined;
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;
confirm = .no;
}
// Returns true to abort scanning.
fn err(e: anyerror) bool {
if (main.config.ignore_delete_errors)
return false;
error_code = e;
state = .err;
while (main.state == .delete and state == .err)
main.handleEvent(true, false);
return main.state != .delete;
}
fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool {
entry = ptr.*.?;
main.handleEvent(false, false);
if (main.state != .delete)
return true;
if (entry.dir()) |d| {
var fd = dir.openDirZ(path, .{.no_follow = true}, false) catch |e| return err(e);
var it = &d.sub;
parent = d;
defer parent = parent.parent.?;
while (it.*) |n| {
if (deleteItem(fd, n.name(), it)) {
fd.close();
return true;
}
if (it.* == n) // item deletion failed, make sure to still advance to next
it = &n.next;
}
fd.close();
dir.deleteDirZ(path) catch |e|
return if (e != error.DirNotEmpty or d.sub == null) err(e) else false;
} else
dir.deleteFileZ(path) catch |e| return err(e);
ptr.*.?.delStats(parent);
ptr.* = ptr.*.?.next;
return false;
}
// Returns the item that should be selected in the browser.
pub fn delete() ?*model.Entry {
while (main.state == .delete and state == .confirm)
main.handleEvent(true, false);
if (main.state != .delete)
return entry;
// Find the pointer to this entry
const e = entry;
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();
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;
_ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it);
model.inodes.addAllStats();
return if (it.* == e) e else next_sel;
}
fn drawConfirm() void {
browser.draw();
const box = ui.Box.create(6, 60, "Confirm delete");
box.move(1, 2);
ui.addstr("Are you sure you want to delete \"");
ui.addstr(ui.shorten(ui.toUtf8(entry.name()), 21));
ui.addch('"');
if (entry.etype != .dir)
ui.addch('?')
else {
box.move(2, 18);
ui.addstr("and all of its contents?");
}
box.move(4, 15);
ui.style(if (confirm == .yes) .sel else .default);
ui.addstr("yes");
box.move(4, 25);
ui.style(if (confirm == .no) .sel else .default);
ui.addstr("no");
box.move(4, 31);
ui.style(if (confirm == .ignore) .sel else .default);
ui.addstr("don't ask me again");
}
fn drawProgress() void {
var path = std.ArrayList(u8).init(main.allocator);
defer path.deinit();
parent.fmtPath(false, &path);
path.append('/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable;
// TODO: Item counts and progress bar would be nice.
const box = ui.Box.create(6, 60, "Deleting...");
box.move(2, 2);
ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 56));
box.move(4, 41);
ui.addstr("Press ");
ui.style(.key);
ui.addch('q');
ui.style(.default);
ui.addstr(" to abort");
}
fn drawErr() void {
var path = std.ArrayList(u8).init(main.allocator);
defer path.deinit();
parent.fmtPath(false, &path);
path.append('/') catch unreachable;
path.appendSlice(entry.name()) catch unreachable;
const box = ui.Box.create(6, 60, "Error");
box.move(1, 2);
ui.addstr("Error deleting ");
ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 41));
box.move(2, 4);
ui.addstr(ui.errorString(error_code));
box.move(4, 14);
ui.style(if (error_option == .abort) .sel else .default);
ui.addstr("abort");
box.move(4, 23);
ui.style(if (error_option == .ignore) .sel else .default);
ui.addstr("ignore");
box.move(4, 33);
ui.style(if (error_option == .all) .sel else .default);
ui.addstr("ignore all");
}
pub fn draw() void {
switch (state) {
.confirm => drawConfirm(),
.busy => drawProgress(),
.err => drawErr(),
}
}
pub fn keyInput(ch: i32) void {
switch (state) {
.confirm => switch (ch) {
'h', ui.c.KEY_LEFT => confirm = switch (confirm) {
.ignore => .no,
else => .yes,
},
'l', ui.c.KEY_RIGHT => confirm = switch (confirm) {
.yes => .no,
else => .ignore,
},
'q' => main.state = .browse,
'\n' => switch (confirm) {
.yes => state = .busy,
.no => main.state = .browse,
.ignore => {
main.config.confirm_delete = false;
state = .busy;
},
},
else => {}
},
.busy => {
if (ch == 'q')
main.state = .browse;
},
.err => switch (ch) {
'h', ui.c.KEY_LEFT => error_option = switch (error_option) {
.all => .ignore,
else => .abort,
},
'l', ui.c.KEY_RIGHT => error_option = switch (error_option) {
.abort => .ignore,
else => .all,
},
'q' => main.state = .browse,
'\n' => switch (error_option) {
.abort => main.state = .browse,
.ignore => state = .busy,
.all => {
main.config.ignore_delete_errors = true;
state = .busy;
},
},
else => {}
},
}
}