flow/src/buffer/Manager.zig

262 lines
8.5 KiB
Zig

const std = @import("std");
const cbor = @import("cbor");
const tp = @import("thespian");
const Buffer = @import("Buffer.zig");
const Self = @This();
allocator: std.mem.Allocator,
buffers: std.StringHashMapUnmanaged(*Buffer),
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.allocator = allocator,
.buffers = .{},
};
}
pub fn deinit(self: *Self) void {
var i = self.buffers.iterator();
while (i.next()) |p| {
self.allocator.free(p.key_ptr.*);
p.value_ptr.*.deinit();
}
self.buffers.deinit(self.allocator);
}
fn get_buffer(self: *const Self, file_path: []const u8) ?*Buffer {
return self.buffers.get(file_path);
}
fn add_buffer(self: *Self, buffer: *Buffer) error{OutOfMemory}!void {
try self.buffers.put(self.allocator, try self.allocator.dupe(u8, buffer.get_file_path()), buffer);
}
pub fn delete_buffer(self: *Self, buffer_: *Buffer) void {
const buffer = self.buffer_from_ref(self.buffer_to_ref(buffer_)) orelse return; // check buffer is valid
if (self.buffers.fetchRemove(buffer.get_file_path())) |kv| {
self.allocator.free(kv.key);
kv.value.deinit();
} else buffer.deinit();
}
pub fn open_file(self: *Self, file_path: []const u8) Buffer.LoadFromFileError!*Buffer {
const buffer = if (self.get_buffer(file_path)) |buffer| blk: {
if (!buffer.ephemeral and buffer.hidden)
try buffer.refresh_from_file();
break :blk buffer;
} else blk: {
var buffer = try Buffer.create(self.allocator);
errdefer buffer.deinit();
try buffer.load_from_file_and_update(file_path);
try self.add_buffer(buffer);
break :blk buffer;
};
buffer.update_last_used_time();
buffer.hidden = false;
return buffer;
}
pub fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) Buffer.LoadError!*Buffer {
const buffer = if (self.buffers.get(file_path)) |buffer| buffer else blk: {
var buffer = try Buffer.create(self.allocator);
errdefer buffer.deinit();
try buffer.load_from_string_and_update(file_path, content);
buffer.file_exists = true;
try self.add_buffer(buffer);
break :blk buffer;
};
buffer.update_last_used_time();
buffer.hidden = false;
buffer.ephemeral = true;
return buffer;
}
pub fn write_state(self: *const Self, writer: *std.Io.Writer) error{ Stop, OutOfMemory, WriteFailed }!void {
const buffers = self.list_unordered(self.allocator) catch return;
defer self.allocator.free(buffers);
try cbor.writeArrayHeader(writer, buffers.len);
for (buffers) |buffer| {
tp.trace(tp.channel.debug, .{ @typeName(Self), "write_state", buffer.get_file_path(), buffer.file_type_name });
buffer.write_state(writer) catch |e| {
tp.trace(tp.channel.debug, .{ @typeName(Self), "write_state", "failed", e });
return;
};
}
}
pub fn extract_state(self: *Self, iter: *[]const u8) !void {
var len = try cbor.decodeArrayHeader(iter);
tp.trace(tp.channel.debug, .{ @typeName(Self), "extract_state", len });
while (len > 0) : (len -= 1) {
var buffer = try Buffer.create(self.allocator);
errdefer |e| {
tp.trace(tp.channel.debug, .{ "buffer", "extract", "failed", buffer.get_file_path(), e });
buffer.deinit();
}
try buffer.extract_state(iter);
try self.add_buffer(buffer);
tp.trace(tp.channel.debug, .{ "buffer", "extract", buffer.get_file_path(), buffer.file_type_name });
}
}
pub fn get_buffer_for_file(self: *const Self, file_path: []const u8) ?*Buffer {
return self.get_buffer(file_path);
}
pub fn retire(_: *Self, buffer: *Buffer, meta: ?[]const u8) void {
if (meta) |buf| buffer.set_meta(buf) catch {};
tp.trace(tp.channel.debug, .{ "buffer", "retire", buffer.get_file_path(), "hidden", buffer.hidden, "ephemeral", buffer.ephemeral });
if (meta) |buf| tp.trace(tp.channel.debug, tp.message{ .buf = buf });
}
pub fn close_buffer(self: *Self, buffer: *Buffer) void {
buffer.hidden = true;
tp.trace(tp.channel.debug, .{ "buffer", "close", buffer.get_file_path(), "hidden", buffer.hidden, "ephemeral", buffer.ephemeral });
if (buffer.is_ephemeral())
self.delete_buffer(buffer);
}
pub fn list_most_recently_used(self: *Self, allocator: std.mem.Allocator) error{OutOfMemory}![]*Buffer {
const result = try self.list_unordered(allocator);
std.mem.sort(*Buffer, result, {}, struct {
fn less_fn(_: void, lhs: *Buffer, rhs: *Buffer) bool {
return lhs.utime > rhs.utime;
}
}.less_fn);
return result;
}
pub fn list_unordered(self: *const Self, allocator: std.mem.Allocator) error{OutOfMemory}![]*Buffer {
var buffers = try std.ArrayListUnmanaged(*Buffer).initCapacity(allocator, self.buffers.size);
var i = self.buffers.iterator();
while (i.next()) |kv|
(try buffers.addOne(allocator)).* = kv.value_ptr.*;
return buffers.toOwnedSlice(allocator);
}
pub fn is_dirty(self: *const Self) bool {
var i = self.buffers.iterator();
while (i.next()) |kv|
if (kv.value_ptr.*.is_dirty())
return true;
return false;
}
pub fn count_dirty_buffers(self: *const Self) usize {
var count: usize = 0;
var i = self.buffers.iterator();
while (i.next()) |p| {
const buffer = p.value_ptr.*;
if (!buffer.is_ephemeral() and buffer.is_dirty()) {
count += 1;
}
}
return count;
}
pub fn is_buffer_dirty(self: *const Self, file_path: []const u8) bool {
return if (self.get_buffer(file_path)) |buffer| buffer.is_dirty() else false;
}
pub fn save_all(self: *const Self) Buffer.StoreToFileError!void {
var i = self.buffers.iterator();
while (i.next()) |kv| {
const buffer = kv.value_ptr.*;
if (buffer.is_ephemeral())
buffer.mark_clean()
else
try buffer.store_to_file_and_clean(buffer.get_file_path());
}
}
pub fn reload_all(self: *const Self) Buffer.LoadFromFileError!void {
var i = self.buffers.iterator();
while (i.next()) |kv| {
const buffer = kv.value_ptr.*;
if (buffer.is_ephemeral())
buffer.mark_clean()
else
try buffer.refresh_from_file();
}
}
pub fn delete_all(self: *Self) void {
var i = self.buffers.iterator();
while (i.next()) |p| {
self.allocator.free(p.key_ptr.*);
p.value_ptr.*.deinit();
}
self.buffers.clearRetainingCapacity();
}
pub fn delete_others(self: *Self, protected: *Buffer) error{OutOfMemory}!void {
var to_delete = try std.ArrayList(*Buffer).initCapacity(self.allocator, self.buffers.size);
defer to_delete.deinit(self.allocator);
var it = self.buffers.iterator();
while (it.next()) |p| {
const buffer = p.value_ptr.*;
if (buffer != protected)
to_delete.appendAssumeCapacity(buffer);
}
for (to_delete.items) |buffer|
_ = self.delete_buffer(buffer);
}
pub fn close_others(self: *Self, protected: *Buffer) error{OutOfMemory}!usize {
var remaining: usize = 0;
var to_delete = try std.ArrayList(*Buffer).initCapacity(self.allocator, self.buffers.size);
defer to_delete.deinit(self.allocator);
var it = self.buffers.iterator();
while (it.next()) |p| {
const buffer = p.value_ptr.*;
if (buffer != protected) {
if (buffer.is_ephemeral() or !buffer.is_dirty()) {
to_delete.appendAssumeCapacity(buffer);
} else {
remaining += 1;
}
}
}
for (to_delete.items) |buffer|
self.delete_buffer(buffer);
return remaining;
}
pub fn buffer_from_ref(self: *Self, buffer_ref: usize) ?*Buffer {
var i = self.buffers.iterator();
while (i.next()) |p|
if (@intFromPtr(p.value_ptr.*) == buffer_ref)
return p.value_ptr.*;
tp.trace(tp.channel.debug, .{ "buffer_from_ref", "failed", buffer_ref });
return null;
}
pub fn buffer_to_ref(_: *Self, buffer: *Buffer) usize {
return @intFromPtr(buffer);
}
const watcher = @import("watcher");
pub fn watch_all_buffers(self: *Self) watcher.Error!void {
var debouncer: watcher.Debouncer = .{};
var buffers: std.ArrayList([]const u8) = .empty;
var iter = self.buffers.keyIterator();
while (iter.next()) |key| {
(try buffers.addOne(self.allocator)).* = key.*;
}
const project = tp.env.get().str("project");
var watcher_: watcher.Watcher = try .init(
self.allocator,
&debouncer,
&.{project},
);
try watcher_.start();
}