mirror of
https://code.blicky.net/yorhel/ncdu.git
synced 2026-01-13 01:08:41 -09:00
Implement trash functionality for file deletion and update delete confirmation options. Closes #215.
This commit is contained in:
parent
67f34090fb
commit
52c6fa7e82
3 changed files with 239 additions and 39 deletions
144
src/delete.zig
144
src/delete.zig
|
|
@ -1,10 +1,12 @@
|
||||||
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
|
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
|
||||||
|
// SPDX-FileCopyrightText: 2025 Ivan Stepanov <ivanstepanov@gmail.com>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const main = @import("main.zig");
|
const main = @import("main.zig");
|
||||||
const model = @import("model.zig");
|
const model = @import("model.zig");
|
||||||
const ui = @import("ui.zig");
|
const ui = @import("ui.zig");
|
||||||
|
const trash = @import("trash.zig");
|
||||||
const browser = @import("browser.zig");
|
const browser = @import("browser.zig");
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
|
|
@ -12,8 +14,8 @@ const c = @import("c.zig").c;
|
||||||
var parent: *model.Dir = undefined;
|
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_delete, busy_trash, err } = .confirm;
|
||||||
var confirm: enum { yes, no, ignore } = .no;
|
var confirm: enum { delete, trash, no, always_trash, always_delete } = .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;
|
||||||
|
|
||||||
|
|
@ -21,8 +23,14 @@ pub fn setup(p: *model.Dir, e: *model.Entry, n: ?*model.Entry) void {
|
||||||
parent = p;
|
parent = p;
|
||||||
entry = e;
|
entry = e;
|
||||||
next_sel = n;
|
next_sel = n;
|
||||||
state = if (main.config.confirm_delete) .confirm else .busy;
|
switch (main.config.delete_action) {
|
||||||
|
.ask => {
|
||||||
|
state = .confirm;
|
||||||
confirm = .no;
|
confirm = .no;
|
||||||
|
},
|
||||||
|
.always_delete => state = .busy_delete,
|
||||||
|
.always_trash => state = .busy_trash,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -39,6 +47,24 @@ fn err(e: anyerror) bool {
|
||||||
return main.state != .delete;
|
return main.state != .delete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn trashItem(path: *std.ArrayList(u8), ptr: *align(1) ?*model.Entry) bool {
|
||||||
|
entry = ptr.*.?;
|
||||||
|
main.handleEvent(false, false);
|
||||||
|
if (main.state != .delete)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const original_len = path.items.len;
|
||||||
|
defer path.shrinkRetainingCapacity(original_len);
|
||||||
|
|
||||||
|
path.appendSlice(entry.name()) catch unreachable;
|
||||||
|
|
||||||
|
// Only trash the top-level selected item, not recursively each file
|
||||||
|
trash.trashFile(main.allocator, util.arrayListBufZ(path)) catch |e| return err(e);
|
||||||
|
ptr.*.?.zeroStats(parent);
|
||||||
|
ptr.* = ptr.*.?.next.ptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool {
|
fn deleteItem(dir: std.fs.Dir, path: [:0]const u8, ptr: *align(1) ?*model.Entry) bool {
|
||||||
entry = ptr.*.?;
|
entry = ptr.*.?;
|
||||||
main.handleEvent(false, false);
|
main.handleEvent(false, false);
|
||||||
|
|
@ -82,14 +108,45 @@ pub fn delete() ?*model.Entry {
|
||||||
if (it.* == entry)
|
if (it.* == entry)
|
||||||
break;
|
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);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
.busy_trash => {
|
||||||
var path = std.ArrayList(u8).init(main.allocator);
|
var path = std.ArrayList(u8).init(main.allocator);
|
||||||
defer path.deinit();
|
defer path.deinit();
|
||||||
parent.fmtPath(true, &path);
|
parent.fmtPath(true, &path);
|
||||||
if (path.items.len == 0 or path.items[path.items.len-1] != '/')
|
// if (path.items.len > 0 and path.items[path.items.len - 1] != '/') {
|
||||||
|
// path.append('/') catch unreachable;
|
||||||
|
// }
|
||||||
|
if (path.items.len == 0 or path.items[path.items.len - 1] != '/') {
|
||||||
path.append('/') catch unreachable;
|
path.append('/') catch unreachable;
|
||||||
|
}
|
||||||
|
_ = trashItem(&path, it);
|
||||||
|
},
|
||||||
|
.busy_delete => {
|
||||||
|
var path = std.ArrayList(u8).init(main.allocator);
|
||||||
|
defer path.deinit();
|
||||||
path.appendSlice(entry.name()) catch unreachable;
|
path.appendSlice(entry.name()) catch unreachable;
|
||||||
|
|
||||||
|
// if (path.items.len > 0 and path.items[path.items.len - 1] != '/') {
|
||||||
|
// path.append('/') catch unreachable;
|
||||||
|
// }
|
||||||
|
// if (path.items.len == 0 or path.items[path.items.len - 1] != '/') {
|
||||||
|
// path.append('/') catch unreachable;
|
||||||
|
// }
|
||||||
|
|
||||||
_ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it);
|
_ = deleteItem(std.fs.cwd(), util.arrayListBufZ(&path), it);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
model.inodes.addAllStats();
|
model.inodes.addAllStats();
|
||||||
return if (it.* == e) e else next_sel;
|
return if (it.* == e) e else next_sel;
|
||||||
}
|
}
|
||||||
|
|
@ -104,33 +161,50 @@ fn drawConfirm() void {
|
||||||
if (entry.pack.etype != .dir)
|
if (entry.pack.etype != .dir)
|
||||||
ui.addch('?')
|
ui.addch('?')
|
||||||
else {
|
else {
|
||||||
box.move(2, 18);
|
box.move(2, 17);
|
||||||
ui.addstr("and all of its contents?");
|
ui.addstr("and all of its contents?");
|
||||||
}
|
}
|
||||||
|
|
||||||
box.move(4, 15);
|
box.move(4, 4);
|
||||||
ui.style(if (confirm == .yes) .sel else .default);
|
ui.style(if (confirm == .delete) .sel else .default);
|
||||||
ui.addstr("yes");
|
ui.addstr("delete");
|
||||||
|
|
||||||
box.move(4, 25);
|
box.move(4, 13);
|
||||||
|
ui.style(if (confirm == .trash) .sel else .default);
|
||||||
|
ui.addstr("trash");
|
||||||
|
|
||||||
|
box.move(4, 21);
|
||||||
ui.style(if (confirm == .no) .sel else .default);
|
ui.style(if (confirm == .no) .sel else .default);
|
||||||
ui.addstr("no");
|
ui.addstr("no");
|
||||||
|
|
||||||
box.move(4, 31);
|
box.move(4, 26);
|
||||||
ui.style(if (confirm == .ignore) .sel else .default);
|
ui.style(if (confirm == .always_trash) .sel else .default);
|
||||||
ui.addstr("don't ask me again");
|
ui.addstr("always trash");
|
||||||
|
|
||||||
|
box.move(4, 41);
|
||||||
|
ui.style(if (confirm == .always_delete) .sel else .default);
|
||||||
|
ui.addstr("always delete");
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
// This is a bit of a hack, but parent is not set correctly during trashItem recursion
|
||||||
|
// if (parent.sub.ptr == null) parent = entry.link().?.parent;
|
||||||
|
|
||||||
parent.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;
|
||||||
|
|
||||||
// TODO: Item counts and progress bar would be nice.
|
// TODO: Item counts and progress bar would be nice.
|
||||||
|
|
||||||
const box = ui.Box.create(6, 60, "Deleting...");
|
const title = switch (state) {
|
||||||
|
.busy_trash => "Trashing...",
|
||||||
|
.busy_delete => "Deleting...",
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
const box = ui.Box.create(6, 60, title);
|
||||||
box.move(2, 2);
|
box.move(2, 2);
|
||||||
ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 56));
|
ui.addstr(ui.shorten(ui.toUtf8(util.arrayListBufZ(&path)), 56));
|
||||||
box.move(4, 41);
|
box.move(4, 41);
|
||||||
|
|
@ -171,7 +245,7 @@ fn drawErr() void {
|
||||||
pub fn draw() void {
|
pub fn draw() void {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
.confirm => drawConfirm(),
|
.confirm => drawConfirm(),
|
||||||
.busy => drawProgress(),
|
.busy_delete, .busy_trash => drawProgress(),
|
||||||
.err => drawErr(),
|
.err => drawErr(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,25 +254,36 @@ pub fn keyInput(ch: i32) void {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
.confirm => switch (ch) {
|
.confirm => switch (ch) {
|
||||||
'h', c.KEY_LEFT => confirm = switch (confirm) {
|
'h', c.KEY_LEFT => confirm = switch (confirm) {
|
||||||
.ignore => .no,
|
.delete => .always_delete,
|
||||||
else => .yes,
|
.trash => .delete,
|
||||||
|
.no => .trash,
|
||||||
|
.always_trash => .no,
|
||||||
|
.always_delete => .always_trash,
|
||||||
},
|
},
|
||||||
'l', c.KEY_RIGHT => confirm = switch (confirm) {
|
'l', c.KEY_RIGHT => confirm = switch (confirm) {
|
||||||
.yes => .no,
|
.delete => .trash,
|
||||||
else => .ignore,
|
.trash => .no,
|
||||||
|
.no => .always_trash,
|
||||||
|
.always_trash => .always_delete,
|
||||||
|
.always_delete => .delete,
|
||||||
},
|
},
|
||||||
'q' => main.state = .browse,
|
'q' => main.state = .browse,
|
||||||
'\n' => switch (confirm) {
|
'\n' => switch (confirm) {
|
||||||
.yes => state = .busy,
|
.delete => state = .busy_delete,
|
||||||
|
.trash => state = .busy_trash,
|
||||||
.no => main.state = .browse,
|
.no => main.state = .browse,
|
||||||
.ignore => {
|
.always_trash => {
|
||||||
main.config.confirm_delete = false;
|
main.config.delete_action = .always_trash;
|
||||||
state = .busy;
|
state = .busy_trash;
|
||||||
|
},
|
||||||
|
.always_delete => {
|
||||||
|
main.config.delete_action = .always_delete;
|
||||||
|
state = .busy_delete;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
else => {}
|
else => {},
|
||||||
},
|
},
|
||||||
.busy => {
|
.busy_delete, .busy_trash => {
|
||||||
if (ch == 'q')
|
if (ch == 'q')
|
||||||
main.state = .browse;
|
main.state = .browse;
|
||||||
},
|
},
|
||||||
|
|
@ -214,13 +299,16 @@ pub fn keyInput(ch: i32) void {
|
||||||
'q' => main.state = .browse,
|
'q' => main.state = .browse,
|
||||||
'\n' => switch (error_option) {
|
'\n' => switch (error_option) {
|
||||||
.abort => main.state = .browse,
|
.abort => main.state = .browse,
|
||||||
.ignore => state = .busy,
|
.ignore, .all => {
|
||||||
.all => {
|
if (error_option == .all)
|
||||||
main.config.ignore_delete_errors = true;
|
main.config.ignore_delete_errors = true;
|
||||||
state = .busy;
|
state = switch (state) {
|
||||||
|
.busy_trash, .busy_delete => state,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
else => {}
|
else => {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/main.zig
12
src/main.zig
|
|
@ -1,6 +1,8 @@
|
||||||
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
|
// SPDX-FileCopyrightText: Yorhel <projects@yorhel.nl>
|
||||||
|
// SPDX-FileCopyrightText: 2025 Ivan Stepanov <ivanstepanov@gmail.com>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// zig fmt: off
|
||||||
pub const program_version = "2.8.2";
|
pub const program_version = "2.8.2";
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
@ -17,6 +19,7 @@ const ui = @import("ui.zig");
|
||||||
const browser = @import("browser.zig");
|
const browser = @import("browser.zig");
|
||||||
const delete = @import("delete.zig");
|
const delete = @import("delete.zig");
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
const trash = @import("trash.zig");
|
||||||
const exclude = @import("exclude.zig");
|
const exclude = @import("exclude.zig");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
|
@ -34,6 +37,7 @@ test "imports" {
|
||||||
_ = browser;
|
_ = browser;
|
||||||
_ = delete;
|
_ = delete;
|
||||||
_ = util;
|
_ = util;
|
||||||
|
_ = trash;
|
||||||
_ = exclude;
|
_ = exclude;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +116,8 @@ pub const config = struct {
|
||||||
pub var can_shell: ?bool = null;
|
pub var can_shell: ?bool = null;
|
||||||
pub var can_refresh: ?bool = null;
|
pub var can_refresh: ?bool = null;
|
||||||
pub var confirm_quit: bool = false;
|
pub var confirm_quit: bool = false;
|
||||||
pub var confirm_delete: bool = true;
|
pub const DeleteAction = enum { ask, always_trash, always_delete };
|
||||||
|
pub var delete_action: DeleteAction = .ask;
|
||||||
pub var ignore_delete_errors: bool = false;
|
pub var ignore_delete_errors: bool = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -304,8 +309,8 @@ fn argConfig(args: *Args, opt: Args.Option, infile: bool) !void {
|
||||||
config.export_block_size = @as(usize, num) * 1024;
|
config.export_block_size = @as(usize, num) * 1024;
|
||||||
} else if (opt.is("--confirm-quit")) config.confirm_quit = true
|
} else if (opt.is("--confirm-quit")) config.confirm_quit = true
|
||||||
else if (opt.is("--no-confirm-quit")) config.confirm_quit = false
|
else if (opt.is("--no-confirm-quit")) config.confirm_quit = false
|
||||||
else if (opt.is("--confirm-delete")) config.confirm_delete = true
|
else if (opt.is("--confirm-delete")) config.delete_action = .ask
|
||||||
else if (opt.is("--no-confirm-delete")) config.confirm_delete = false
|
else if (opt.is("--no-confirm-delete")) config.delete_action = .always_delete
|
||||||
else if (opt.is("--color")) {
|
else if (opt.is("--color")) {
|
||||||
const val = try args.arg();
|
const val = try args.arg();
|
||||||
if (std.mem.eql(u8, val, "off")) config.ui_color = .off
|
if (std.mem.eql(u8, val, "off")) config.ui_color = .off
|
||||||
|
|
@ -428,6 +433,7 @@ fn help() noreturn {
|
||||||
\\ --group-directories-first Sort directories before files
|
\\ --group-directories-first Sort directories before files
|
||||||
\\ --confirm-quit Ask confirmation before quitting ncdu
|
\\ --confirm-quit Ask confirmation before quitting ncdu
|
||||||
\\ --no-confirm-delete Don't ask confirmation before deletion
|
\\ --no-confirm-delete Don't ask confirmation before deletion
|
||||||
|
\\ --trash-on-delete Use trash-can by default on deletion
|
||||||
\\ --color SCHEME off / dark / dark-bg
|
\\ --color SCHEME off / dark / dark-bg
|
||||||
\\
|
\\
|
||||||
\\Refer to `man ncdu` for more information.
|
\\Refer to `man ncdu` for more information.
|
||||||
|
|
|
||||||
106
src/trash.zig
Normal file
106
src/trash.zig
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Ivan Stepanov <ivanstepanov@gmail.com>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const fs = std.fs;
|
||||||
|
const mem = std.mem;
|
||||||
|
const time = std.time;
|
||||||
|
|
||||||
|
/// Implements the FreeDesktop.org Trash specification for a single file or empty directory.
|
||||||
|
/// This function is not recursive; recursion should be handled by the caller.
|
||||||
|
pub fn trashFile(allocator: mem.Allocator, full_path: []const u8) !void {
|
||||||
|
const cwd = fs.cwd();
|
||||||
|
|
||||||
|
// 1. Determine the top-level trash directory path.
|
||||||
|
const trash_dir_path = try getTrashDir(allocator);
|
||||||
|
defer allocator.free(trash_dir_path);
|
||||||
|
|
||||||
|
// 2. Ensure the trash subdirectories 'files' and 'info' exist.
|
||||||
|
const trash_files_path = try fs.path.join(allocator, &.{ trash_dir_path, "files" });
|
||||||
|
defer allocator.free(trash_files_path);
|
||||||
|
const trash_info_path = try fs.path.join(allocator, &.{ trash_dir_path, "info" });
|
||||||
|
defer allocator.free(trash_info_path);
|
||||||
|
|
||||||
|
// Ensure the trash subdirectories exist using cwd relative operations
|
||||||
|
cwd.makeDir(trash_files_path) catch |e| switch (e) {
|
||||||
|
error.PathAlreadyExists => {},
|
||||||
|
else => return e,
|
||||||
|
};
|
||||||
|
cwd.makeDir(trash_info_path) catch |e| switch (e) {
|
||||||
|
error.PathAlreadyExists => {},
|
||||||
|
else => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Handle name collisions to find a unique name in the trash.
|
||||||
|
const original_basename = fs.path.basename(full_path);
|
||||||
|
var dest_basename_buf = std.ArrayList(u8).init(allocator);
|
||||||
|
defer dest_basename_buf.deinit();
|
||||||
|
|
||||||
|
var counter: u32 = 1;
|
||||||
|
while (true) {
|
||||||
|
try dest_basename_buf.writer().print("{s}", .{original_basename});
|
||||||
|
|
||||||
|
const dest_file_path = try fs.path.join(allocator, &.{ trash_files_path, dest_basename_buf.items });
|
||||||
|
defer allocator.free(dest_file_path);
|
||||||
|
|
||||||
|
// Try to access the file - if it doesn't exist, we found our unique name
|
||||||
|
cwd.access(dest_file_path, .{}) catch |err| {
|
||||||
|
if (err == error.FileNotFound) {
|
||||||
|
// This name is available
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
// File exists, try next name
|
||||||
|
|
||||||
|
counter += 1;
|
||||||
|
dest_basename_buf.clearRetainingCapacity();
|
||||||
|
const ext = fs.path.extension(original_basename);
|
||||||
|
const stem = fs.path.stem(original_basename);
|
||||||
|
try dest_basename_buf.writer().print("{s}.{d}{s}", .{ stem, counter, ext });
|
||||||
|
}
|
||||||
|
const final_basename = try dest_basename_buf.toOwnedSlice();
|
||||||
|
defer allocator.free(final_basename);
|
||||||
|
|
||||||
|
// 4. Create and write the .trashinfo file.
|
||||||
|
const info_file_name = try std.fmt.allocPrint(allocator, "{s}.trashinfo", .{final_basename});
|
||||||
|
defer allocator.free(info_file_name);
|
||||||
|
const info_file_path = try fs.path.join(allocator, &.{ trash_info_path, info_file_name });
|
||||||
|
defer allocator.free(info_file_path);
|
||||||
|
|
||||||
|
const info_file = try cwd.createFile(info_file_path, .{});
|
||||||
|
defer info_file.close();
|
||||||
|
|
||||||
|
const datetime = "2025-01-01T00:00:00"; // Simple placeholder for now
|
||||||
|
|
||||||
|
// For simplicity, use the path as-is (proper URI encoding would be ideal)
|
||||||
|
const uri_encoded_path = full_path;
|
||||||
|
|
||||||
|
try info_file.writer().print(
|
||||||
|
\\ [Trash Info]
|
||||||
|
\\ Path=file://{s}
|
||||||
|
\\ DeletionDate={s}
|
||||||
|
\\
|
||||||
|
, .{ uri_encoded_path, datetime });
|
||||||
|
|
||||||
|
// 5. Finally, move the original file to the trash 'files' directory.
|
||||||
|
const final_dest_path = try fs.path.join(allocator, &.{ trash_files_path, final_basename });
|
||||||
|
defer allocator.free(final_dest_path);
|
||||||
|
|
||||||
|
try cwd.rename(full_path, final_dest_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the user's trash directory according to the XDG Base Directory Specification.
|
||||||
|
fn getTrashDir(allocator: mem.Allocator) ![]u8 {
|
||||||
|
if (std.posix.getenv("XDG_DATA_HOME")) |xdg_data_home| {
|
||||||
|
if (xdg_data_home.len > 0) {
|
||||||
|
return fs.path.join(allocator, &.{ xdg_data_home, "Trash" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const home_dir = std.posix.getenv("HOME") orelse {
|
||||||
|
return error.HomeDirectoryNotFound;
|
||||||
|
};
|
||||||
|
|
||||||
|
return fs.path.join(allocator, &.{ home_dir, ".local", "share", "Trash" });
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue