~leon_plickat/nfm

nfm: [WIP] Implements inotify to watch files v1 PROPOSED

Hugo Machet: 1
 [WIP] Implements inotify to watch files

 2 files changed, 106 insertions(+), 5 deletions(-)
#782734 alpine.yml success
#782735 freebsd.yml failed
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~leon_plickat/nfm/patches/33111/mbox | git am -3
Learn more about email & git

[RFC PATCH nfm] [WIP] Implements inotify to watch files Export this patch

---
 src/DirMap.zig | 66 ++++++++++++++++++++++++++++++++++++++++++++++++--
 src/nfm.zig    | 45 +++++++++++++++++++++++++++++++---
 2 files changed, 106 insertions(+), 5 deletions(-)

diff --git a/src/DirMap.zig b/src/DirMap.zig
index 3552f0d0b855..8f581292831f 100644
--- a/src/DirMap.zig
+++ b/src/DirMap.zig
@@ -26,6 +26,8 @@ const os = std.os;
const unicode = std.unicode;
const sort = std.sort;

const context = &@import("nfm.zig").context;

const Self = @This();

pub const VisibilityConfig = struct {
@@ -121,14 +123,36 @@ const Dir = struct {
    last_selected_id: usize,
    last_scroll_offset: usize,

    inotify_wd: i32,

    fn init(self: *Dir, map: *Self, alloc: mem.Allocator, dir: fs.Dir, name: []const u8) !void {
        self.files = std.ArrayList(File).init(alloc);
        self.name = name;
        self.last_selected_id = math.maxInt(usize);
        self.last_scroll_offset = 0;

        // Get Directories.
        // TODO should be moved to helper function for more flexibility
        self.inotify_wd = try os.inotify_add_watch(
            context.inotify_fd,
            name,
            os.linux.IN.CREATE | os.linux.IN.DELETE | os.linux.IN.DELETE_SELF |
                os.linux.IN.MODIFY | os.linux.IN.MOVE_SELF | os.linux.IN.MOVE,
        );

        try self.getEntries(map, dir);
    }

    fn refresh(self: *Dir, map: *Self) !void {
        self.files.clearRetainingCapacity();
        var dir = try fs.cwd().openDir(self.name, .{ .iterate = true });
        defer dir.close();

        try self.getEntries(map, dir);
        try self.files.resize(self.files.items.len);
    }

    fn getEntries(self: *Dir, map: *Self, dir: fs.Dir) !void {
        const alloc = map.arena.allocator();

        var it = dir.iterate();
        while (try it.next()) |entry| {
            const file = try self.files.addOne();
@@ -174,9 +198,47 @@ pub fn new(alloc: mem.Allocator, visconf: VisibilityConfig) !Self {
}

pub fn deinit(self: *Self) void {
    var it = self.dirs.valueIterator();
    while (it.next()) |dir| {
        os.inotify_rm_watch(context.inotify_fd, dir.*.inotify_wd);
    }

    self.arena.deinit();
}

pub fn handleInotifyEvent(self: *Self, visconf: VisibilityConfig) !void {
    var buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
    const read = try os.read(context.inotify_fd, &buf);

    if (read == 0) return;

    var offset: usize = 0;
    while (offset < read) {
        const ev = @ptrCast(
            *os.linux.inotify_event,
            @alignCast(@alignOf(os.linux.inotify_event), &buf),
        );

        var it = self.dirs.valueIterator();
        while (it.next()) |dir| {
            if (dir.*.inotify_wd == ev.wd) {
                try dir.*.refresh(self);
                try self.updateVisibleFiles(visconf);
                self.setCursor(self.cursor);
                break;
            }
        } else {
            return error.UnknownInotifyWatchDescriptor;
        }

        offset += @sizeOf(std.os.linux.inotify_event) + ev.len;
    }

    // TODO: Avoid clearing the term?
    context.ui.title_dirty = true;
    context.ui.list_dirty = true;
}

/// Add or find a dir for a given relative path
pub fn setCwdByRelativePath(self: *Self, _relpath: []const u8, visconf: VisibilityConfig) !void {
    if (_relpath.len == 1 and _relpath[0] == '.') return;
diff --git a/src/nfm.zig b/src/nfm.zig
index ca4ee4d07714..9b9606587e93 100644
--- a/src/nfm.zig
+++ b/src/nfm.zig
@@ -44,12 +44,14 @@ const util = @import("util.zig");
pub const Context = struct {
    pub const PollIndex = enum(u32) {
        ui = 1,
        dirmap = 2,
    };

    // TODO multiple view buffers?

    epoll_fd: i32 = undefined,
    fds: [1]os.pollfd = undefined,
    fds: [2]os.pollfd = undefined,
    inotify_fd: os.fd_t = undefined,

    config: Config = .{},
    ui: UserInterface = undefined,
@@ -367,6 +369,32 @@ fn dumpUsage() !void {
fn nfm() !void {
    if (builtin.os.tag == .linux) {
        context.epoll_fd = try os.epoll_create1(0);
        context.inotify_fd = try os.inotify_init1(0);
    }
    defer {
        if (builtin.os.tag == .linux) {
            os.close(context.inotify_fd);
        }
    }

    // TODO: Move this somewhere else?
    if (builtin.os.tag == .linux) {
        var inotify_ev: os.linux.epoll_event = .{
            .events = os.linux.EPOLL.IN,
            .data = .{ .@"u32" = @enumToInt(Context.PollIndex.dirmap) },
        };
        try os.epoll_ctl(
            context.epoll_fd,
            os.linux.EPOLL.CTL_ADD,
            context.inotify_fd,
            &inotify_ev,
        );
    } else {
        context.fds[@enumToInt(Context.PollIndex.dirmap)] = .{
            .fd = context.inotify_fd,
            .events = os.POLL.IN,
            .revents = 0,
        };
    }

    defer context.view.deinit();
@@ -394,9 +422,11 @@ fn nfm() !void {

    var input_buf: [32]u8 = undefined;
    while (context.loop) {
        // TODO: number of events?
        var epoll_ev: [8]os.linux.epoll_event = undefined;
        var epoll_ev_count: usize = 0;
        if (builtin.os.tag == .linux) {
            var ev: [4]os.linux.epoll_event = undefined;
            _ = os.linux.epoll_wait(context.epoll_fd, &ev, 4, -1);
            epoll_ev_count = os.epoll_wait(context.epoll_fd, &epoll_ev, -1);
        } else {
            _ = try os.poll(&context.fds, -1);
        }
@@ -417,6 +447,15 @@ fn nfm() !void {
            }
        }

        if (builtin.os.tag == .linux) {
            for (epoll_ev[0..epoll_ev_count]) |ev| {
                switch (@intToEnum(Context.PollIndex, ev.data.@"u32")) {
                    .ui => {},
                    .dirmap => try context.dirmap.handleInotifyEvent(context.config.visconf),
                }
            }
        }

        if (context.ui.title_dirty or context.ui.list_dirty) {
            try context.ui.term.updateContent();
        }
-- 
2.36.1
nfm/patches: FAILED in 46s

[[WIP] Implements inotify to watch files][0] from [Hugo Machet][1]

[0]: https://lists.sr.ht/~leon_plickat/nfm/patches/33111
[1]: mailto:mail@hmachet.com

✓ #782734 SUCCESS nfm/patches/alpine.yml  https://builds.sr.ht/~leon_plickat/job/782734
✗ #782735 FAILED  nfm/patches/freebsd.yml https://builds.sr.ht/~leon_plickat/job/782735