2024-10-09 16:47:24 -06:00
|
|
|
use fs;
|
|
|
|
use fmt;
|
|
|
|
use io;
|
2024-10-09 17:41:42 -06:00
|
|
|
use path;
|
2024-10-09 16:47:24 -06:00
|
|
|
use os;
|
2024-10-09 17:41:42 -06:00
|
|
|
use strings;
|
2024-10-09 16:47:24 -06:00
|
|
|
|
2024-10-09 17:41:42 -06:00
|
|
|
let bulb_path: []str = [];
|
|
|
|
@init fn bulb_path() void = bulb_path = strings::split(os::tryenv("BULBPATH", "/var/bulb"), ":");
|
|
|
|
@fini fn bulb_path() void = free(bulb_path);
|
2024-10-09 16:47:24 -06:00
|
|
|
|
2024-10-09 17:41:42 -06:00
|
|
|
export type no_such_board = !void;
|
|
|
|
export type error = (io::error | fs::error | no_such_board);
|
|
|
|
|
|
|
|
export fn strerror(err: error) str = match(err) {
|
2024-10-09 16:47:24 -06:00
|
|
|
case no_such_board => return "No such board";
|
|
|
|
case let err: fs::error => return fs::strerror(err);
|
|
|
|
case let err: io::error => return io::strerror(err);
|
|
|
|
};
|
|
|
|
|
2024-10-09 17:41:42 -06:00
|
|
|
export fn read(board: str, number: int) (void | error) = {
|
2024-10-09 16:47:24 -06:00
|
|
|
let location = look_up_board(board)?;
|
|
|
|
let file = os::open(location)?;
|
|
|
|
defer io::close(file)!;
|
|
|
|
seek_to_nth_last_line(file, number)?;
|
|
|
|
io::copy(os::stdout, file)?;
|
|
|
|
};
|
|
|
|
|
2024-10-09 17:41:42 -06:00
|
|
|
export fn post(board: str, user_name: (str | void), message: str) (void | error) = {
|
2024-10-09 16:47:24 -06:00
|
|
|
let location = look_up_board(board)?;
|
|
|
|
let file = os::open(location, fs::flag::APPEND | fs::flag::WRONLY)?;
|
|
|
|
defer io::close(file)!;
|
|
|
|
|
|
|
|
let user = match(user_name) {
|
|
|
|
case let user: str => yield(user);
|
|
|
|
case void => yield "anonymous";
|
|
|
|
};
|
|
|
|
fmt::fprintf(file, "{}: {}\n", user, message)?;
|
|
|
|
};
|
|
|
|
|
2024-10-09 17:41:42 -06:00
|
|
|
export fn list() (void | error) = {
|
|
|
|
for (let directory .. bulb_path) list_boards_in(directory)?;
|
|
|
|
};
|
|
|
|
|
|
|
|
export fn look_up_board(board: str) (str | no_such_board) = {
|
|
|
|
static let buf = path::buffer { ... };
|
|
|
|
for (let directory .. bulb_path) {
|
|
|
|
let location = path::set(&buf, directory, board)!;
|
|
|
|
match (os::stat(location)) {
|
|
|
|
case fs::filestat => return location;
|
|
|
|
case => void;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
return no_such_board;
|
|
|
|
};
|
|
|
|
|
|
|
|
fn list_boards_in(directory: str) (void | fs::error) = {
|
|
|
|
let entries = os::readdir(directory)?;
|
|
|
|
defer fs::dirents_free(entries);
|
|
|
|
for (let entry .. entries) {
|
|
|
|
fmt::println(entry.name)!;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2024-10-09 16:47:24 -06:00
|
|
|
fn seek_to_nth_last_line(file: io::handle, number: int) (void | io::error) = {
|
|
|
|
let buffer: [256]u8 = [0...];
|
|
|
|
let end = io::seek(file, 0, io::whence::END)?;
|
|
|
|
let step = len(buffer): io::off;
|
|
|
|
let current = end;
|
|
|
|
|
|
|
|
for (true) {
|
|
|
|
// read a chunk in reverse, or stop if there is nothing left
|
|
|
|
let buffer = buffer[..];
|
|
|
|
if (current - step > 0) {
|
|
|
|
current -= step;
|
|
|
|
} else {
|
|
|
|
buffer = buffer[..current];
|
|
|
|
current = 0;
|
|
|
|
};
|
|
|
|
io::seek(file, current, io::whence::SET)?;
|
|
|
|
let length = match (io::read(file, buffer)?) {
|
|
|
|
case let length: size => yield length;
|
|
|
|
case io::EOF => break;
|
|
|
|
};
|
|
|
|
io::seek(file, current, io::whence::SET)?;
|
|
|
|
|
|
|
|
// look for line breaks in that chunk
|
|
|
|
let buffer = buffer[..length];
|
|
|
|
for (let index = len(buffer): int - 1; index >= 0; index -= 1) {
|
|
|
|
if (buffer[index: size] == '\n') number -= 1;
|
|
|
|
if (number <= 0) {
|
|
|
|
io::seek(file, current + index: io::off, io::whence::SET)?;
|
|
|
|
return void;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
return void;
|
|
|
|
};
|