Compare commits

..

No commits in common. "65b82fcd97f7d827fb2da23ab6f8c69a18eb07e1" and "3de17cb77e07456e7ffe2bb7292188e21ab1d6c7" have entirely different histories.

20 changed files with 1749 additions and 2070 deletions

66
Cargo.lock generated
View File

@ -579,18 +579,6 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.3.0"
@ -613,12 +601,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -758,18 +740,6 @@ name = "hashbrown"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.4",
]
[[package]]
name = "hdrhistogram"
@ -1092,15 +1062,6 @@ dependencies = [
"walkdir",
]
[[package]]
name = "lb_sqlite"
version = "0.0.1"
dependencies = [
"lb",
"luaffi",
"rusqlite",
]
[[package]]
name = "libc"
version = "0.2.174"
@ -1139,18 +1100,6 @@ dependencies = [
"libc",
]
[[package]]
name = "libsqlite3-sys"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91632f3b4fb6bd1d72aa3d78f41ffecfcf2b1a6648d8c241dbe7dbfaf4875e15"
dependencies = [
"bindgen",
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "libz-sys"
version = "1.1.22"
@ -1238,7 +1187,6 @@ dependencies = [
"clap",
"console-subscriber",
"lb",
"lb_sqlite",
"luajit",
"mimalloc",
"owo-colors",
@ -1606,20 +1554,6 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rusqlite"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3de23c3319433716cf134eed225fe9986bc24f63bed9be9f20c329029e672dc7"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustc-demangle"
version = "0.1.25"

View File

@ -2,7 +2,6 @@
resolver = "3"
members = [
"crates/lb",
"crates/lb_sqlite",
"crates/luaffi",
"crates/luaffi_impl",
"crates/luaify",
@ -36,19 +35,17 @@ name = "main"
harness = false
[features]
default = ["task", "time", "fs", "net", "sqlite"]
default = ["task", "time", "fs", "net"]
task = ["lb/task"]
time = ["lb/time"]
fs = ["lb/fs"]
net = ["lb/net"]
sqlite = ["dep:lb_sqlite"]
tokio-console = ["dep:console-subscriber"]
[dependencies]
clap = { version = "4.5.40", features = ["derive", "env"] }
console-subscriber = { version = "0.4.1", optional = true }
lb = { path = "crates/lb", features = ["runtime"] }
lb_sqlite = { path = "crates/lb_sqlite", optional = true }
luajit = { path = "crates/luajit", features = ["runtime"] }
mimalloc = "0.1.47"
owo-colors = "4.2.1"

645
crates/lb/src/fs.rs Normal file
View File

@ -0,0 +1,645 @@
//! Filesystem library.
//!
//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the
//! filesystem.
//!
//! ## Asynchronous by default
//!
//! Filesystem operations are blocking by nature; to provide asynchronicity, luby performs blocking
//! operations in a background thread pool by default. Synchronous complements to all asynchronous
//! functions are always provided.
//!
//! ## Exports
//!
//! See [`lb_fslib`] for items exported by this library.
use luaffi::{cdef, metatype};
use std::{
cell::{BorrowError, BorrowMutError, RefCell},
path::PathBuf,
time::SystemTime,
};
use thiserror::Error;
/// Errors that can be thrown by this library.
///
/// Functions which return this error will **throw**. The error message can be caught by using
/// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
#[derive(Debug, Error)]
pub enum Error {
/// Attempt to access an object while it is being modified.
#[error("cannot access object while it is being modified")]
Borrow(#[from] BorrowError),
/// Attempt to modify an object while it is in use.
#[error("cannot modify object while it is in use")]
BorrowMut(#[from] BorrowMutError),
/// I/O error.
#[error("{0}")]
Io(#[from] std::io::Error),
/// Walk directory error.
#[error("{0}")]
Walk(#[from] walkdir::Error),
/// Glob pattern error.
#[error("{0}")]
Glob(#[from] globset::Error),
}
type Result<T> = std::result::Result<T, Error>;
/// Items exported by the `lb:fs` library.
///
/// This library can be acquired by calling
/// [`require("lb:fs")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
///
/// ```lua
/// local fs = require("lb:fs");
/// ```
#[cdef(module = "lb:fs")]
pub struct lb_fslib;
#[metatype]
impl lb_fslib {
#[new]
extern "Lua-C" fn new() -> Self {
Self
}
/// Reads the entire contents of a file.
///
/// # Errors
///
/// This function may throw if the file does not exist or could not be read.
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
Ok(tokio::fs::read(path).await?)
}
/// Reads the entire contents of a file synchronously.
///
/// This is a synchronous complement to [`read`](Self::read).
///
/// # Errors
///
/// This function may throw if the file does not exist or could not be read.
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
Ok(std::fs::read(path)?)
}
/// Writes the given contents to a file, replacing its contents if it exists.
///
/// # Errors
///
/// This function may throw if the file could not be written.
pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> {
Ok(tokio::fs::write(path, contents).await?)
}
/// Writes the given contents to a file synchronously, replacing its contents if it exists.
///
/// This is a synchronous complement to [`write`](Self::write).
///
/// # Errors
///
/// This function may throw if the file could not be written.
pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> {
Ok(std::fs::write(path, contents)?)
}
/// Reads the entries in a directory.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
pub async extern "Lua-C" fn read_dir(path: &str) -> Result<lb_read_dir> {
Ok(lb_read_dir::new(tokio::fs::read_dir(path).await?))
}
/// Reads the entries in a directory synchronously.
///
/// This is a synchronous complement to [`read_dir`](Self::read_dir).
///
/// # Errors
///
/// This function may throw if the directory could not be read.
pub extern "Lua-C" fn read_dir_sync(path: &str) -> Result<lb_read_dir_sync> {
Ok(lb_read_dir_sync::new(std::fs::read_dir(path)?))
}
/// Recursively walks a directory, yielding all entries within it.
pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir {
lb_walk_dir::new(walkdir::WalkDir::new(path).into_iter())
}
/// Returns an iterator over all files matching a glob pattern in the current directory.
///
/// # Errors
///
/// This function may throw if the pattern is invalid.
pub extern "Lua" fn glob(pattern: &str) -> Result<lb_glob_dir> {
Self::glob_dir(".", pattern)
}
/// Returns an iterator over all files matching a glob pattern in the given directory.
///
/// # Errors
///
/// This function may throw if the pattern is invalid.
pub extern "Lua-C" fn glob_dir(path: &str, pattern: &str) -> Result<lb_glob_dir> {
let prefix = PathBuf::from(path);
let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter();
let matcher = globset::GlobSet::builder()
.add(globset::Glob::new(pattern)?)
.build()?;
Ok(lb_glob_dir::new(iter, matcher, prefix))
}
/// Creates a new temporary directory.
///
/// # Errors
///
/// This function may throw if the temporary directory could not be created.
pub extern "Lua-C" fn temp_dir() -> Result<lb_temp_dir> {
Ok(lb_temp_dir::new(tempfile::tempdir()?))
}
/// Creates a new temporary directory inside the specified path.
///
/// # Errors
///
/// This function may throw if the temporary directory could not be created.
pub extern "Lua-C" fn temp_dir_in(path: &str) -> Result<lb_temp_dir> {
Ok(lb_temp_dir::new(tempfile::tempdir_in(path)?))
}
}
/// Iterator over the entries in a directory.
#[derive(Debug)]
#[cdef]
pub struct lb_read_dir(#[opaque] RefCell<tokio::fs::ReadDir>);
#[metatype]
impl lb_read_dir {
fn new(iter: tokio::fs::ReadDir) -> Self {
Self(RefCell::new(iter))
}
/// Returns the next entry in the directory, or `nil` if there are no more entries.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
#[call]
pub async extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry>> {
Ok(self
.0
.try_borrow_mut()?
.next_entry()
.await?
.map(lb_dir_entry::new))
}
}
/// Synchronous version of [`lb_read_dir`].
#[derive(Debug)]
#[cdef]
pub struct lb_read_dir_sync(#[opaque] RefCell<std::fs::ReadDir>);
#[metatype]
impl lb_read_dir_sync {
fn new(iter: std::fs::ReadDir) -> Self {
Self(RefCell::new(iter))
}
/// Returns the next entry in the directory, or `nil` if there are no more entries.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
#[call]
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry_sync>> {
Ok(self
.0
.try_borrow_mut()?
.next()
.transpose()?
.map(lb_dir_entry_sync::new))
}
}
/// Entry inside of a directory on the filesystem.
#[derive(Debug)]
#[cdef]
pub struct lb_dir_entry(#[opaque] tokio::fs::DirEntry);
#[metatype]
impl lb_dir_entry {
fn new(entry: tokio::fs::DirEntry) -> Self {
Self(entry)
}
/// Returns the full path of this entry.
pub extern "Lua-C" fn path(&self) -> String {
self.0.path().to_string_lossy().into()
}
/// Returns the file name of this entry.
pub extern "Lua-C" fn name(&self) -> String {
self.0.file_name().to_string_lossy().into()
}
/// Returns the type of this entry.
///
/// # Errors
///
/// This function may throw if the file type could not be determined.
pub async extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
Ok(lb_file_type::new(self.0.file_type().await?))
}
/// Returns the metadata for this entry.
///
/// # Errors
///
/// This function may throw if the metadata could not be retrieved.
pub async extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
Ok(lb_file_meta::new(self.0.metadata().await?))
}
/// Returns the inode number for this entry.
#[cfg(unix)]
pub extern "Lua-C" fn ino(&self) -> u64 {
self.0.ino()
}
/// Returns the full path of this entry.
#[tostring]
pub extern "Lua" fn tostring(&self) -> String {
self.path()
}
}
/// Synchronous version of [`lb_dir_entry`].
#[derive(Debug)]
#[cdef]
pub struct lb_dir_entry_sync(#[opaque] std::fs::DirEntry);
#[metatype]
impl lb_dir_entry_sync {
fn new(entry: std::fs::DirEntry) -> Self {
Self(entry)
}
/// Returns the full path of this entry.
pub extern "Lua-C" fn path(&self) -> String {
self.0.path().to_string_lossy().into()
}
/// Returns the file name of this entry.
pub extern "Lua-C" fn name(&self) -> String {
self.0.file_name().to_string_lossy().into()
}
/// Returns the type of this entry.
///
/// # Errors
///
/// This function may throw if the file type could not be determined.
pub extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
Ok(lb_file_type::new(self.0.file_type()?))
}
/// Returns the metadata for this entry.
///
/// # Errors
///
/// This function may throw if the metadata could not be retrieved.
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
Ok(lb_file_meta::new(self.0.metadata()?))
}
/// Returns the inode number for this entry.
#[cfg(unix)]
pub extern "Lua-C" fn ino(&self) -> u64 {
use std::os::unix::fs::DirEntryExt;
self.0.ino()
}
/// Returns the full path of this entry.
#[tostring]
pub extern "Lua" fn tostring(&self) -> String {
self.path()
}
}
/// Structure representing the type of a file with accessors for each file type.
#[derive(Debug)]
#[cdef]
pub struct lb_file_type(#[opaque] std::fs::FileType);
#[metatype]
impl lb_file_type {
fn new(ty: std::fs::FileType) -> Self {
Self(ty)
}
/// Returns `true` if this file type is a directory.
pub extern "Lua-C" fn is_dir(&self) -> bool {
self.0.is_dir()
}
/// Returns `true` if this file type is a regular file.
pub extern "Lua-C" fn is_file(&self) -> bool {
self.0.is_file()
}
/// Returns `true` if this file type is a symbolic link.
pub extern "Lua-C" fn is_symlink(&self) -> bool {
self.0.is_file()
}
/// Returns the string `"file"` if this is a regular file, `"dir"` if this is a directory,
/// `"symlink"` if this is a symbolic link, or `"other"` if it is some other type of file.
#[tostring]
pub extern "Lua-C" fn tostring(&self) -> String {
if self.0.is_file() {
"file"
} else if self.0.is_dir() {
"dir"
} else if self.0.is_symlink() {
"symlink"
} else {
"other"
}
.into()
}
}
/// Metadata information about a file.
#[derive(Debug)]
#[cdef]
pub struct lb_file_meta(#[opaque] std::fs::Metadata);
#[metatype]
impl lb_file_meta {
fn new(meta: std::fs::Metadata) -> Self {
Self(meta)
}
/// Returns `true` if this file is a directory.
pub extern "Lua-C" fn is_dir(&self) -> bool {
self.0.is_dir()
}
/// Returns `true` if this file is a regular file.
pub extern "Lua-C" fn is_file(&self) -> bool {
self.0.is_file()
}
/// Returns `true` if this file is a symbolic link.
pub extern "Lua-C" fn is_symlink(&self) -> bool {
self.0.is_file()
}
/// Returns the type of this file.
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
lb_file_type::new(self.0.file_type())
}
/// Returns the size of this file in bytes.
pub extern "Lua-C" fn size(&self) -> u64 {
self.0.len()
}
/// Returns the permissions of this file.
pub extern "Lua-C" fn perms(&self) -> lb_file_perms {
lb_file_perms::new(self.0.permissions())
}
/// Returns the creation time of this file as seconds since the Unix epoch.
///
/// # Errors
///
/// This function may throw if the creation time could not be retrieved.
pub extern "Lua-C" fn created(&self) -> Result<f64> {
Ok(self
.0
.created()?
.duration_since(SystemTime::UNIX_EPOCH)
.map(|dur| dur.as_secs_f64())
.unwrap_or(0.))
}
/// Returns the modification time of this file as seconds since the Unix epoch.
///
/// # Errors
///
/// This function may throw if the modification time could not be retrieved.
pub extern "Lua-C" fn modified(&self) -> Result<f64> {
Ok(self
.0
.modified()?
.duration_since(SystemTime::UNIX_EPOCH)
.map(|dur| dur.as_secs_f64())
.unwrap_or(0.))
}
/// Returns the last access time of this file as seconds since the Unix epoch.
///
/// # Errors
///
/// This function may throw if the access time could not be retrieved.
pub extern "Lua-C" fn accessed(&self) -> Result<f64> {
Ok(self
.0
.accessed()?
.duration_since(SystemTime::UNIX_EPOCH)
.map(|dur| dur.as_secs_f64())
.unwrap_or(0.))
}
/// Returns a string representation of this file's metadata.
#[tostring]
pub extern "Lua-C" fn tostring(&self) -> String {
let ty = self.0.file_type();
if ty.is_file() {
format!("file {}", self.0.len())
} else if ty.is_dir() {
"dir".into()
} else if ty.is_symlink() {
"symlink".into()
} else {
"other".into()
}
}
}
/// Representation of the various permissions on a file.
#[derive(Debug)]
#[cdef]
pub struct lb_file_perms(#[opaque] RefCell<std::fs::Permissions>);
#[metatype]
impl lb_file_perms {
fn new(perms: std::fs::Permissions) -> Self {
Self(RefCell::new(perms))
}
/// Returns `true` if the readonly flag is set.
pub extern "Lua-C" fn readonly(&self) -> bool {
self.0.borrow().readonly()
}
/// Sets the readonly flag.
pub extern "Lua-C" fn set_readonly(&self, readonly: bool) {
self.0.borrow_mut().set_readonly(readonly);
}
}
/// Iterator for recursively descending into a directory.
#[derive(Debug)]
#[cdef]
pub struct lb_walk_dir(#[opaque] RefCell<walkdir::IntoIter>);
#[metatype]
impl lb_walk_dir {
fn new(iter: walkdir::IntoIter) -> Self {
Self(RefCell::new(iter))
}
/// Returns the next entry in the walk, or `nil` if there are no more entries.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
#[call]
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
Ok(self
.0
.try_borrow_mut()?
.next()
.transpose()?
.map(lb_walk_dir_entry::new))
}
}
/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`].
#[derive(Debug)]
#[cdef]
pub struct lb_walk_dir_entry(#[opaque] walkdir::DirEntry);
#[metatype]
impl lb_walk_dir_entry {
fn new(entry: walkdir::DirEntry) -> Self {
Self(entry)
}
/// Returns the full path of this entry.
pub extern "Lua-C" fn path(&self) -> String {
self.0.path().to_string_lossy().into()
}
/// Returns the file name of this entry.
pub extern "Lua-C" fn name(&self) -> String {
self.0.file_name().to_string_lossy().into()
}
/// Returns the type of this entry.
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
lb_file_type::new(self.0.file_type())
}
/// Returns the metadata for this entry.
///
/// # Errors
///
/// This function may throw if the metadata could not be retrieved.
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
Ok(lb_file_meta::new(self.0.metadata()?))
}
/// Returns `true` if this entry was created from a symbolic link.
pub extern "Lua-C" fn is_symlink(&self) -> bool {
self.0.path_is_symlink()
}
/// Returns the depth of this entry in the walk.
pub extern "Lua-C" fn depth(&self) -> u32 {
self.0.depth() as u32
}
/// Returns the inode number for this entry.
#[cfg(unix)]
pub extern "Lua-C" fn ino(&self) -> u64 {
use walkdir::DirEntryExt;
self.0.ino()
}
/// Returns the full path of this entry.
#[tostring]
pub extern "Lua" fn tostring(&self) -> String {
self.path()
}
}
/// Iterator that yields paths from the filesystem that match a particular pattern.
#[derive(Debug)]
#[cdef]
pub struct lb_glob_dir {
#[opaque]
iter: RefCell<walkdir::IntoIter>,
#[opaque]
matcher: globset::GlobSet,
#[opaque]
prefix: PathBuf,
}
#[metatype]
impl lb_glob_dir {
fn new(iter: walkdir::IntoIter, matcher: globset::GlobSet, prefix: PathBuf) -> Self {
Self {
iter: RefCell::new(iter),
matcher,
prefix,
}
}
/// Returns the next entry matching the glob pattern, or `nil` if there are no more entries.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
#[call]
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
while let Some(res) = self.iter.try_borrow_mut()?.next() {
let entry = res?;
let path = entry.path().strip_prefix(&self.prefix).unwrap();
if self.matcher.is_match(path) {
return Ok(Some(lb_walk_dir_entry::new(entry)));
}
}
Ok(None)
}
}
/// Directory in the filesystem that is automatically deleted when it is garbage-collected.
#[derive(Debug)]
#[cdef]
pub struct lb_temp_dir(#[opaque] tempfile::TempDir);
#[metatype]
impl lb_temp_dir {
fn new(dir: tempfile::TempDir) -> Self {
Self(dir)
}
/// Returns the full path of this temporary directory.
pub extern "Lua-C" fn path(&self) -> String {
self.0.path().to_string_lossy().into()
}
/// Returns the full path of this temporary directory.
#[tostring]
pub extern "Lua" fn tostring(&self) -> String {
self.path()
}
}

View File

@ -1,83 +0,0 @@
use super::*;
use luaffi::{cdef, metatype};
use std::cell::RefCell;
use tokio::fs::{DirEntry, ReadDir};
/// Iterator over the entries in a directory.
#[derive(Debug)]
#[cdef]
pub struct lb_read_dir(#[opaque] RefCell<ReadDir>);
#[metatype]
impl lb_read_dir {
pub(super) fn new(iter: ReadDir) -> Self {
Self(RefCell::new(iter))
}
/// Returns the next entry in the directory, or `nil` if there are no more entries.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
#[call]
pub async extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry>> {
Ok(self
.0
.try_borrow_mut()?
.next_entry()
.await?
.map(lb_dir_entry::new))
}
}
/// Entry inside of a directory on the filesystem.
#[derive(Debug)]
#[cdef]
pub struct lb_dir_entry(#[opaque] DirEntry);
#[metatype]
impl lb_dir_entry {
pub(super) fn new(entry: DirEntry) -> Self {
Self(entry)
}
/// Returns the full path of this entry.
pub extern "Lua-C" fn path(&self) -> String {
self.0.path().to_string_lossy().into()
}
/// Returns the file name of this entry.
pub extern "Lua-C" fn name(&self) -> String {
self.0.file_name().to_string_lossy().into()
}
/// Returns the type of this entry.
///
/// # Errors
///
/// This function may throw if the file type could not be determined.
pub async extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
Ok(lb_file_type::new(self.0.file_type().await?))
}
/// Returns the metadata for this entry.
///
/// # Errors
///
/// This function may throw if the metadata could not be retrieved.
pub async extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
Ok(lb_file_meta::new(self.0.metadata().await?))
}
/// Returns the inode number for this entry.
#[cfg(unix)]
pub extern "Lua-C" fn ino(&self) -> u64 {
self.0.ino()
}
/// Returns the full path of this entry.
#[tostring]
pub extern "Lua" fn tostring(&self) -> String {
self.path()
}
}

View File

@ -1,46 +0,0 @@
use super::*;
use globset::GlobSet;
use luaffi::{cdef, metatype};
use std::cell::RefCell;
use walkdir::IntoIter;
/// Iterator that yields paths from the filesystem that match a particular pattern.
#[derive(Debug)]
#[cdef]
pub struct lb_glob_dir {
#[opaque]
iter: RefCell<IntoIter>,
#[opaque]
matcher: GlobSet,
#[opaque]
prefix: PathBuf,
}
#[metatype]
impl lb_glob_dir {
pub(super) fn new(iter: IntoIter, matcher: GlobSet, prefix: PathBuf) -> Self {
Self {
iter: RefCell::new(iter),
matcher,
prefix,
}
}
/// Returns the next entry matching the glob pattern, or `nil` if there are no more entries.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
#[call]
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
while let Some(res) = self.iter.try_borrow_mut()?.next() {
let entry = res?;
let path = entry.path().strip_prefix(&self.prefix).unwrap();
if self.matcher.is_match(path) {
return Ok(Some(lb_walk_dir_entry::new(entry)));
}
}
Ok(None)
}
}

View File

@ -1,190 +0,0 @@
//! Filesystem library.
//!
//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the
//! filesystem.
//!
//! ## Asynchronous by default
//!
//! Filesystem operations are blocking by nature; to provide asynchronicity, luby performs blocking
//! operations in a background thread pool by default. Synchronous complements to all asynchronous
//! functions are always provided.
//!
//! ## Exports
//!
//! See [`lb_fslib`] for items exported by this library.
use luaffi::{cdef, metatype};
use std::{
cell::{BorrowError, BorrowMutError},
path::PathBuf,
};
use thiserror::Error;
mod r#async;
mod glob;
mod sync;
mod temp;
mod walk;
pub use r#async::*;
pub use glob::*;
pub use sync::*;
pub use temp::*;
pub use walk::*;
/// Errors that can be thrown by this library.
///
/// Functions which return this error will **throw**. The error message can be caught by using
/// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
#[derive(Debug, Error)]
pub enum Error {
/// Attempt to access an object while it is being modified.
#[error("cannot access object while it is being modified")]
Borrow(#[from] BorrowError),
/// Attempt to modify an object while it is in use.
#[error("cannot modify object while it is in use")]
BorrowMut(#[from] BorrowMutError),
/// I/O error.
#[error("{0}")]
Io(#[from] std::io::Error),
/// Walk directory error.
#[error("{0}")]
Walk(#[from] walkdir::Error),
/// Glob pattern error.
#[error("{0}")]
Glob(#[from] globset::Error),
}
type Result<T> = std::result::Result<T, Error>;
/// Items exported by the `lb:fs` library.
///
/// This library can be acquired by calling
/// [`require("lb:fs")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
///
/// ```lua
/// local fs = require("lb:fs")
/// ```
#[cdef(module = "lb:fs")]
pub struct lb_fslib;
#[metatype]
impl lb_fslib {
#[new]
extern "Lua-C" fn new() -> Self {
Self
}
/// Reads the entire contents of a file.
///
/// # Errors
///
/// This function may throw if the file does not exist or could not be read.
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
Ok(tokio::fs::read(path).await?)
}
/// Reads the entire contents of a file synchronously.
///
/// This is a synchronous complement to [`read`](Self::read).
///
/// # Errors
///
/// This function may throw if the file does not exist or could not be read.
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
Ok(std::fs::read(path)?)
}
/// Writes the given contents to a file, replacing its contents if it exists.
///
/// # Errors
///
/// This function may throw if the file could not be written.
pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> {
Ok(tokio::fs::write(path, contents).await?)
}
/// Writes the given contents to a file synchronously, replacing its contents if it exists.
///
/// This is a synchronous complement to [`write`](Self::write).
///
/// # Errors
///
/// This function may throw if the file could not be written.
pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> {
Ok(std::fs::write(path, contents)?)
}
/// Reads the entries in a directory.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
pub async extern "Lua-C" fn read_dir(path: &str) -> Result<lb_read_dir> {
Ok(lb_read_dir::new(tokio::fs::read_dir(path).await?))
}
/// Reads the entries in a directory synchronously.
///
/// This is a synchronous complement to [`read_dir`](Self::read_dir).
///
/// # Errors
///
/// This function may throw if the directory could not be read.
pub extern "Lua-C" fn read_dir_sync(path: &str) -> Result<lb_read_dir_sync> {
Ok(lb_read_dir_sync::new(std::fs::read_dir(path)?))
}
/// Recursively walks the current directory, yielding all entries within it.
pub extern "Lua" fn walk(pattern: &str) -> Result<lb_walk_dir> {
Self::walk_dir(".")
}
/// Recursively walks a directory, yielding all entries within it.
pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir {
lb_walk_dir::new(walkdir::WalkDir::new(path).into_iter())
}
/// Recursively walks the current directory, yielding all entries within it that match the given
/// glob pattern.
///
/// # Errors
///
/// This function may throw if the pattern is invalid.
pub extern "Lua" fn glob(pattern: &str) -> Result<lb_glob_dir> {
Self::glob_dir(".", pattern)
}
/// Recursively walks a directory, yielding all entries within it that match the given glob
/// pattern.
///
/// # Errors
///
/// This function may throw if the pattern is invalid.
pub extern "Lua-C" fn glob_dir(path: &str, pattern: &str) -> Result<lb_glob_dir> {
let prefix = PathBuf::from(path);
let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter();
let matcher = globset::GlobSet::builder()
.add(globset::Glob::new(pattern)?)
.build()?;
Ok(lb_glob_dir::new(iter, matcher, prefix))
}
/// Creates a new temporary directory.
///
/// # Errors
///
/// This function may throw if the temporary directory could not be created.
pub extern "Lua-C" fn temp_dir() -> Result<lb_temp_dir> {
Ok(lb_temp_dir::new(tempfile::tempdir()?))
}
/// Creates a new temporary directory inside the specified path.
///
/// # Errors
///
/// This function may throw if the temporary directory could not be created.
pub extern "Lua-C" fn temp_dir_in(path: &str) -> Result<lb_temp_dir> {
Ok(lb_temp_dir::new(tempfile::tempdir_in(path)?))
}
}

View File

@ -1,251 +0,0 @@
use super::*;
use luaffi::{cdef, metatype};
use std::{
cell::RefCell,
fs::{DirEntry, FileType, Metadata, Permissions, ReadDir},
time::SystemTime,
};
/// Structure representing the type of a file with accessors for each file type.
#[derive(Debug)]
#[cdef]
pub struct lb_file_type(#[opaque] FileType);
#[metatype]
impl lb_file_type {
pub(super) fn new(ty: FileType) -> Self {
Self(ty)
}
/// Returns `true` if this file type is a directory.
pub extern "Lua-C" fn is_dir(&self) -> bool {
self.0.is_dir()
}
/// Returns `true` if this file type is a regular file.
pub extern "Lua-C" fn is_file(&self) -> bool {
self.0.is_file()
}
/// Returns `true` if this file type is a symbolic link.
pub extern "Lua-C" fn is_symlink(&self) -> bool {
self.0.is_file()
}
/// Returns the string `"file"` if this is a regular file, `"dir"` if this is a directory,
/// `"symlink"` if this is a symbolic link, or `"other"` if it is some other type of file.
#[tostring]
pub extern "Lua-C" fn tostring(&self) -> String {
if self.0.is_file() {
"file"
} else if self.0.is_dir() {
"dir"
} else if self.0.is_symlink() {
"symlink"
} else {
"other"
}
.into()
}
}
/// Metadata information about a file.
#[derive(Debug)]
#[cdef]
pub struct lb_file_meta(#[opaque] Metadata);
#[metatype]
impl lb_file_meta {
pub(super) fn new(meta: Metadata) -> Self {
Self(meta)
}
/// Returns `true` if this file is a directory.
pub extern "Lua-C" fn is_dir(&self) -> bool {
self.0.is_dir()
}
/// Returns `true` if this file is a regular file.
pub extern "Lua-C" fn is_file(&self) -> bool {
self.0.is_file()
}
/// Returns `true` if this file is a symbolic link.
pub extern "Lua-C" fn is_symlink(&self) -> bool {
self.0.is_file()
}
/// Returns the type of this file.
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
lb_file_type::new(self.0.file_type())
}
/// Returns the size of this file in bytes.
pub extern "Lua-C" fn size(&self) -> u64 {
self.0.len()
}
/// Returns the permissions of this file.
pub extern "Lua-C" fn perms(&self) -> lb_file_perms {
lb_file_perms::new(self.0.permissions())
}
/// Returns the creation time of this file as seconds since the Unix epoch.
///
/// # Errors
///
/// This function may throw if the creation time could not be retrieved.
pub extern "Lua-C" fn created(&self) -> Result<f64> {
Ok(self
.0
.created()?
.duration_since(SystemTime::UNIX_EPOCH)
.map(|dur| dur.as_secs_f64())
.unwrap_or(0.))
}
/// Returns the modification time of this file as seconds since the Unix epoch.
///
/// # Errors
///
/// This function may throw if the modification time could not be retrieved.
pub extern "Lua-C" fn modified(&self) -> Result<f64> {
Ok(self
.0
.modified()?
.duration_since(SystemTime::UNIX_EPOCH)
.map(|dur| dur.as_secs_f64())
.unwrap_or(0.))
}
/// Returns the last access time of this file as seconds since the Unix epoch.
///
/// # Errors
///
/// This function may throw if the access time could not be retrieved.
pub extern "Lua-C" fn accessed(&self) -> Result<f64> {
Ok(self
.0
.accessed()?
.duration_since(SystemTime::UNIX_EPOCH)
.map(|dur| dur.as_secs_f64())
.unwrap_or(0.))
}
/// Returns a string representation of this file's metadata.
#[tostring]
pub extern "Lua-C" fn tostring(&self) -> String {
let ty = self.0.file_type();
if ty.is_file() {
format!("file {}", self.0.len())
} else if ty.is_dir() {
"dir".into()
} else if ty.is_symlink() {
"symlink".into()
} else {
"other".into()
}
}
}
/// Representation of the various permissions on a file.
#[derive(Debug)]
#[cdef]
pub struct lb_file_perms(#[opaque] RefCell<Permissions>);
#[metatype]
impl lb_file_perms {
pub(super) fn new(perms: Permissions) -> Self {
Self(RefCell::new(perms))
}
/// Returns `true` if the readonly flag is set.
pub extern "Lua-C" fn readonly(&self) -> bool {
self.0.borrow().readonly()
}
/// Sets the readonly flag.
pub extern "Lua-C" fn set_readonly(&self, readonly: bool) {
self.0.borrow_mut().set_readonly(readonly);
}
}
/// Synchronous version of [`lb_read_dir`].
#[derive(Debug)]
#[cdef]
pub struct lb_read_dir_sync(#[opaque] RefCell<ReadDir>);
#[metatype]
impl lb_read_dir_sync {
pub(super) fn new(iter: ReadDir) -> Self {
Self(RefCell::new(iter))
}
/// Returns the next entry in the directory, or `nil` if there are no more entries.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
#[call]
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry_sync>> {
Ok(self
.0
.try_borrow_mut()?
.next()
.transpose()?
.map(lb_dir_entry_sync::new))
}
}
/// Synchronous version of [`lb_dir_entry`].
#[derive(Debug)]
#[cdef]
pub struct lb_dir_entry_sync(#[opaque] DirEntry);
#[metatype]
impl lb_dir_entry_sync {
pub(super) fn new(entry: DirEntry) -> Self {
Self(entry)
}
/// Returns the full path of this entry.
pub extern "Lua-C" fn path(&self) -> String {
self.0.path().to_string_lossy().into()
}
/// Returns the file name of this entry.
pub extern "Lua-C" fn name(&self) -> String {
self.0.file_name().to_string_lossy().into()
}
/// Returns the type of this entry.
///
/// # Errors
///
/// This function may throw if the file type could not be determined.
pub extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
Ok(lb_file_type::new(self.0.file_type()?))
}
/// Returns the metadata for this entry.
///
/// # Errors
///
/// This function may throw if the metadata could not be retrieved.
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
Ok(lb_file_meta::new(self.0.metadata()?))
}
/// Returns the inode number for this entry.
#[cfg(unix)]
pub extern "Lua-C" fn ino(&self) -> u64 {
use std::os::unix::fs::DirEntryExt;
self.0.ino()
}
/// Returns the full path of this entry.
#[tostring]
pub extern "Lua" fn tostring(&self) -> String {
self.path()
}
}

View File

@ -1,25 +0,0 @@
use luaffi::{cdef, metatype};
use tempfile::TempDir;
/// Directory in the filesystem that is automatically deleted when it is garbage-collected.
#[derive(Debug)]
#[cdef]
pub struct lb_temp_dir(#[opaque] TempDir);
#[metatype]
impl lb_temp_dir {
pub(super) fn new(dir: TempDir) -> Self {
Self(dir)
}
/// Returns the full path of this temporary directory.
pub extern "Lua-C" fn path(&self) -> String {
self.0.path().to_string_lossy().into()
}
/// Returns the full path of this temporary directory.
#[tostring]
pub extern "Lua" fn tostring(&self) -> String {
self.path()
}
}

View File

@ -1,90 +0,0 @@
use super::*;
use luaffi::{cdef, metatype};
use std::cell::RefCell;
use walkdir::{DirEntry, IntoIter};
/// Iterator for recursively descending into a directory.
#[derive(Debug)]
#[cdef]
pub struct lb_walk_dir(#[opaque] RefCell<IntoIter>);
#[metatype]
impl lb_walk_dir {
pub(super) fn new(iter: IntoIter) -> Self {
Self(RefCell::new(iter))
}
/// Returns the next entry in the walk, or `nil` if there are no more entries.
///
/// # Errors
///
/// This function may throw if the directory could not be read.
#[call]
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
Ok(self
.0
.try_borrow_mut()?
.next()
.transpose()?
.map(lb_walk_dir_entry::new))
}
}
/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`].
#[derive(Debug)]
#[cdef]
pub struct lb_walk_dir_entry(#[opaque] DirEntry);
#[metatype]
impl lb_walk_dir_entry {
pub(super) fn new(entry: DirEntry) -> Self {
Self(entry)
}
/// Returns the full path of this entry.
pub extern "Lua-C" fn path(&self) -> String {
self.0.path().to_string_lossy().into()
}
/// Returns the file name of this entry.
pub extern "Lua-C" fn name(&self) -> String {
self.0.file_name().to_string_lossy().into()
}
/// Returns the type of this entry.
pub extern "Lua-C" fn r#type(&self) -> lb_file_type {
lb_file_type::new(self.0.file_type())
}
/// Returns the metadata for this entry.
///
/// # Errors
///
/// This function may throw if the metadata could not be retrieved.
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
Ok(lb_file_meta::new(self.0.metadata()?))
}
/// Returns `true` if this entry was created from a symbolic link.
pub extern "Lua-C" fn is_symlink(&self) -> bool {
self.0.path_is_symlink()
}
/// Returns the depth of this entry in the walk.
pub extern "Lua-C" fn depth(&self) -> u32 {
self.0.depth() as u32
}
/// Returns the inode number for this entry.
#[cfg(unix)]
pub extern "Lua-C" fn ino(&self) -> u64 {
use walkdir::DirEntryExt;
self.0.ino()
}
/// Returns the full path of this entry.
#[tostring]
pub extern "Lua" fn tostring(&self) -> String {
self.path()
}
}

1078
crates/lb/src/net.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,588 +0,0 @@
//! Network library.
//!
//! The `lb:net` library provides an asynchronous network API for woring with TCP, UDP and IPC
//! sockets.
//!
//! ## Exports
//!
//! See [`lb_netlib`] for items exported by this library.
use derive_more::{From, FromStr};
use luaffi::{cdef, marker::OneOf, metatype};
use std::{
cell::{BorrowError, BorrowMutError},
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
};
use thiserror::Error;
use tokio::net::TcpSocket;
mod tcp;
pub use tcp::*;
/// Errors that can be thrown by this library.
///
/// Functions which return this error will **throw**. The error message can be caught by using
/// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall).
#[derive(Debug, Error)]
pub enum Error {
/// Attempt to access an object while it is being modified.
#[error("cannot access object while it is being modified")]
Borrow(#[from] BorrowError),
/// Attempt to modify an object while it is in use.
#[error("cannot modify object while it is in use")]
BorrowMut(#[from] BorrowMutError),
/// I/O error.
#[error("{0}")]
Io(#[from] std::io::Error),
/// IP or socket address syntax error.
#[error("{0}")]
InvalidAddr(#[from] AddrParseError),
/// Socket was already converted and can no longer be used.
#[error("socket was already converted")]
SocketConsumed,
/// Socket was closed and can no longer be used.
#[error("socket was closed")]
SocketClosed,
/// Specified socket half was not `"read"` or `"write"`.
#[error("invalid socket half")]
InvalidSocketHalf,
}
type Result<T> = std::result::Result<T, Error>;
/// Items exported by the `lb:net` library.
///
/// This library can be acquired by calling
/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
///
/// ```lua
/// local net = require("lb:net")
/// ```
#[cdef(module = "lb:net")]
pub struct lb_netlib;
#[metatype]
impl lb_netlib {
#[new]
extern "Lua-C" fn new() -> Self {
Self
}
/// An IPv4 address representing localhost: `127.0.0.1`
pub extern "Lua-C" fn localhost() -> lb_ipaddr {
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
}
/// An IPv6 address representing localhost: `::1`
pub extern "Lua-C" fn localhost_v6() -> lb_ipaddr {
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
}
/// An IPv4 address representing an unspecified address: `0.0.0.0`
pub extern "Lua-C" fn unspecified() -> lb_ipaddr {
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
}
/// An IPv6 address representing an unspecified address: `::`
pub extern "Lua-C" fn unspecified_v6() -> lb_ipaddr {
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
}
/// An IPv4 address representing the broadcast address: `255.255.255.255`
pub extern "Lua-C" fn broadcast() -> lb_ipaddr {
lb_ipaddr(Ipv4Addr::BROADCAST.into())
}
/// Creates an IP address from the given input.
///
/// If `addr` is an [`lb_ipaddr`], a copy of that value is returned. If `addr` is an
/// [`lb_socketaddr`], the IP part of the socket address is returned. Otherwise, parses `addr`
/// as an IP address string. Both IPv4 and IPv6 addresses are accepted.
///
/// An address string may be something like `127.0.0.1`.
///
/// The type of the parsed address can be checked using [`is_v4`](lb_ipaddr::is_v4) or
/// [`is_v6`](lb_ipaddr::is_v6) functions on the returned [`lb_ipaddr`](lb_ipaddr) value.
///
/// # Errors
///
/// This function will throw an error if the input syntax is invalid.
///
/// # Example
///
/// ```lua
/// local net = require("lb:net")
/// local addr = net.ipaddr("192.168.1.1")
///
/// assert(addr:is_v4())
/// ```
pub extern "Lua" fn ipaddr(
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
) -> Result<lb_ipaddr> {
if __istype(__ct.lb_ipaddr, addr) {
__new(__ct.lb_ipaddr, addr) // copy constructor
} else if __istype(__ct.lb_socketaddr, addr) {
s.ip()
} else {
Self::__parse_ipaddr(addr)
}
}
extern "Lua-C" fn __parse_ipaddr(addr: &str) -> Result<lb_ipaddr> {
Ok(addr.parse()?)
}
/// Creates a socket address from the given input.
///
/// A socket address is an IP address with a prescribed port number.
///
/// If `addr` is an [`lb_socketaddr`], a copy of that value is returned. If `addr` is an
/// [`lb_ipaddr`], a socket address with that IP part is returned. Otherwise, parses `addr` as a
/// socket address string. Both IPv4 and IPv6 addresses are accepted.
///
/// If `port` is specified, it is always used as the port part of the returned socket address.
/// Otherwise, `0` is used as the default.
///
/// An address string may be something like `127.0.0.1:3000`.
///
/// # Errors
///
/// This function will throw an error if the input syntax is invalid.
///
/// # Example
///
/// ```lua
/// local net = require("lb:net")
/// local addr = net.socketaddr("::1", 8080)
///
/// assert(addr:ip():is_v6() and addr:ip():is_loopback())
/// assert(addr:port() == 8080)
/// ```
pub extern "Lua" fn socketaddr(
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
port: Option<u16>,
) -> Result<lb_socketaddr> {
if port != () {
Self::__new_skaddr(Self::ipaddr(addr), port)
} else {
if __istype(__ct.lb_socketaddr, addr) {
__new(__ct.lb_socketaddr, addr) // copy constructor
} else if __istype(__ct.lb_ipaddr, addr) {
Self::__new_skaddr(addr, 0) // default port 0
} else {
Self::__parse_skaddr(addr)
}
}
}
extern "Lua-C" fn __new_skaddr(ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
SocketAddr::new(ip.0, port).into()
}
extern "Lua-C" fn __parse_skaddr(addr: &str) -> Result<lb_socketaddr> {
Ok(if let Ok(addr) = addr.parse() {
SocketAddr::new(addr, 0).into() // default port 0
} else {
addr.parse::<SocketAddr>()?.into()
})
}
/// Creates a new TCP socket configured for IPv4.
///
/// This calls `socket(2)` with `AF_INET` and `SOCK_STREAM`.
///
/// # Errors
///
/// This function may throw an error if the socket could not be created.
pub extern "Lua-C" fn tcp() -> Result<lb_tcpsocket> {
Ok(lb_tcpsocket::new(TcpSocket::new_v4()?))
}
/// Creates a new TCP socket configured for IPv6.
///
/// This calls `socket(2)` with `AF_INET6` and `SOCK_STREAM`.
///
/// # Errors
///
/// This function may throw an error if the socket could not be created.
pub extern "Lua-C" fn tcp_v6() -> Result<lb_tcpsocket> {
Ok(lb_tcpsocket::new(TcpSocket::new_v6()?))
}
/// Creates a new TCP socket bound to the given address and port.
///
/// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a
/// new TCP socket configured to use either IPv4 or IPv6 depending on the type of the given
/// address, and binds it to that address.
///
/// # Errors
///
/// This function may throw an error if the socket could not be created or bound to the
/// specified address.
///
/// # Example
///
/// ```lua
/// local net = require("lb:net")
/// local socket = net.bind_tcp("127.0.0.1")
///
/// assert(socket:local_addr():ip() == net.ipaddr("127.0.0.1"))
/// socket:set_nodelay(true)
/// ```
pub extern "Lua" fn bind_tcp(
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
port: Option<u16>,
) -> Result<lb_tcpsocket> {
let addr = Self::socketaddr(addr, port);
let socket;
if addr.ip().is_v6() {
socket = Self::tcp_v6();
} else {
socket = Self::tcp();
}
socket.bind(addr);
socket
}
/// Creates a new TCP socket listening on the given address and port.
///
/// This is a convenience function that combines [`bind_tcp`](Self::bind_tcp) and
/// [`listen`](lb_tcpsocket::listen). It accepts the same arguments as
/// [`socketaddr`](Self::socketaddr).
///
/// # Errors
///
/// This function may throw an error if the socket could not be created, bound to the specified
/// address, or could not transition to the listening state.
///
/// # Example
///
/// ```lua,no_run
/// local net = require("lb:net")
/// local listener = net.listen_tcp("127.0.0.1")
///
/// assert(listener:local_addr():ip() == net.ipaddr("127.0.0.1"))
///
/// for stream in listener do
/// print("client connected: ", stream:peer_addr())
/// end
/// ```
pub extern "Lua" fn listen_tcp(
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
port: Option<u16>,
) -> Result<lb_tcplistener> {
Self::bind_tcp(addr, port).listen(1024)
}
/// Establishes a new TCP connection to the server at the given address and port.
///
/// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a
/// new TCP socket and connects it to the specified address.
///
/// # Errors
///
/// This function may throw an error if the socket could not be created or connected to the
/// specified address.
///
/// # Example
///
/// ```lua
/// local net = require("lb:net")
/// local listener = net.listen_tcp("127.0.0.1")
/// local stream = net.connect_tcp("127.0.0.1", listener:local_addr():port())
///
/// assert(stream:peer_addr():ip() == net.ipaddr("127.0.0.1"))
/// stream:write("Hello, server!\n")
/// ```
pub async extern "Lua" fn connect_tcp(
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
port: Option<u16>,
) -> Result<lb_tcpstream> {
let addr = Self::socketaddr(addr, port);
let socket;
if addr.ip().is_v6() {
socket = Self::tcp_v6();
} else {
socket = Self::tcp();
}
socket.connect(addr)
}
}
/// IP address, either IPv4 or IPv6.
///
/// # Example
///
/// This example creates an [`lb_ipaddr`] by parsing an IP address string.
///
/// ```lua
/// local net = require("lb:net")
/// local addr = net.ipaddr("127.0.0.1") -- ipv4 loopback address
///
/// assert(addr:is_v4() and addr:is_loopback())
/// assert(tostring(addr) == "127.0.0.1")
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
#[cdef]
pub struct lb_ipaddr(#[opaque] IpAddr);
#[metatype]
impl lb_ipaddr {
/// Returns `true` if this is an IPv4 address.
pub extern "Lua-C" fn is_v4(&self) -> bool {
self.0.is_ipv4()
}
/// Returns `true` if this is an IPv6 address.
pub extern "Lua-C" fn is_v6(&self) -> bool {
self.0.is_ipv6()
}
/// Returns the string `"v4"` if this is an IPv4 address, or `"v6"` if this is an IPv6 address.
pub extern "Lua" fn family(&self) -> String {
if self.is_v6() { "v6" } else { "v4" }
}
/// Returns `true` if this is a loopback address.
///
/// For IPv4, this is any address in the `127.0.0.0/8` range.
///
/// For IPv6, this is the address `::1`.
pub extern "Lua-C" fn is_loopback(&self) -> bool {
self.0.is_loopback()
}
/// Returns `true` if this address is unspecified.
///
/// For IPv4, this is the address `0.0.0.0`.
///
/// For IPv6, this is the address `::`.
pub extern "Lua-C" fn is_unspecified(&self) -> bool {
self.0.is_unspecified()
}
/// Returns `true` if this address is a multicast address.
///
/// For IPv4, this is any address in the `224.0.0.0/4` range.
///
/// For IPv6, this is any address in the `ff00::/8` range.
pub extern "Lua-C" fn is_multicast(&self) -> bool {
self.0.is_multicast()
}
/// Returns `true` if this is an IPv4 private address.
///
/// For IPv4, this is any address in one of these ranges:
///
/// - `10.0.0.0/8`
/// - `172.16.0.0/12`
/// - `192.168.0.0/16`
///
/// For IPv6, this always returns `false`.
pub extern "Lua-C" fn is_v4_private(&self) -> bool {
match self.0 {
IpAddr::V4(v4) => v4.is_private(),
IpAddr::V6(_) => false,
}
}
/// Returns `true` if this is an IPv4 link-local address.
///
/// For IPv4, this is any address in the `169.254.0.0/16` range.
///
/// For IPv6, this always returns `false`.
pub extern "Lua-C" fn is_v4_link_local(&self) -> bool {
match self.0 {
IpAddr::V4(v4) => v4.is_link_local(),
IpAddr::V6(_) => false,
}
}
/// Returns `true` if this is an IPv4 broadcast address.
///
/// For IPv4, this is the address `255.255.255.255`.
///
/// For IPv6, this always returns `false`.
pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool {
match self.0 {
IpAddr::V4(v4) => v4.is_broadcast(),
IpAddr::V6(_) => false,
}
}
/// Returns `true` if this is an IPv4 documentation address.
///
/// For IPv4, this is any address in one of these ranges:
///
/// - `192.0.2.0/24` (TEST-NET-1)
/// - `198.51.100.0/24` (TEST-NET-2)
/// - `203.0.113.0/24` (TEST-NET-3)
///
/// For IPv6, this always returns `false`.
pub extern "Lua-C" fn is_v4_documentation(&self) -> bool {
match self.0 {
IpAddr::V4(v4) => v4.is_documentation(),
IpAddr::V6(_) => false,
}
}
/// Returns `true` if this is an IPv6 unique local address.
///
/// For IPv4, this always returns `false`.
///
/// For IPv6, this is any address in the `fc00::/7` range.
pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool {
match self.0 {
IpAddr::V4(_) => false,
IpAddr::V6(v6) => v6.is_unique_local(),
}
}
/// Returns `true` if this is an IPv6 unicast address with link-local scope.
///
/// For IPv4, this always returns `false`.
///
/// For IPv6, this is any address in the `fe80::/10` range.
pub extern "Lua-C" fn is_v6_unicast_link_local(&self) -> bool {
match self.0 {
IpAddr::V4(_) => false,
IpAddr::V6(v6) => v6.is_unicast_link_local(),
}
}
/// Converts this address to IPv4.
///
/// For IPv4, this returns the address unchanged.
///
/// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible
/// IPv6 address. Otherwise, this returns `nil`.
pub extern "Lua-C" fn to_v4(&self) -> Option<Self> {
match self.0 {
IpAddr::V4(_) => Some(*self),
IpAddr::V6(v6) => v6.to_ipv4().map(|v| Self(v.into())),
}
}
/// Converts this address to IPv6.
///
/// For IPv4, this returns the IPv4-mapped IPv6 address as defined by
/// [`Ipv4Addr::to_ipv6_mapped`].
///
/// For IPv6, this returns the address unchanged.
pub extern "Lua-C" fn to_v6(&self) -> Self {
match self.0 {
IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()),
IpAddr::V6(_) => *self,
}
}
/// Returns the canonical form of this address.
///
/// For IPv4, this returns the address unchanged.
///
/// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible
/// IPv6 address. Otherwise, this returns the address unchanged.
pub extern "Lua-C" fn to_canonical(&self) -> Self {
match self.0 {
IpAddr::V4(_) => *self,
IpAddr::V6(v6) => v6.to_ipv4().map_or(*self, |v| Self(v.into())),
}
}
/// Returns `true` if the given addresses are equal.
///
/// Two addresses are considered equal if they are of the same family (IPv4 or IPv6) and
/// represent the same address in octets.
#[eq]
pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool {
left.0 == right.0
}
/// Returns `true` if the left address is less than the right address.
///
/// IPv4 addresses are always less than IPv6 addresses.
#[lt]
pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool {
left.0 < right.0
}
/// Returns `true` if the left address is less than or equal to the right address.
///
/// IPv4 addresses are always less than IPv6 addresses.
#[le]
pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool {
left.0 <= right.0
}
/// Returns the string representation of this address.
#[tostring]
pub extern "Lua-C" fn tostring(&self) -> String {
self.0.to_string()
}
}
/// Socket address, which is an IP address with a port number.
///
/// This represents an IP address with a prescribed port, such as `127.0.0.1:8080` or `[::1]:443`.
/// It is used to specify endpoints for network connections and listeners, and can be constructed by
/// [`socketaddr`](lb_netlib::socketaddr).
///
/// # Example
///
/// ```lua
/// local net = require("lb:net")
/// local addr = net.socketaddr("127.0.0.1:8080")
///
/// assert(addr:ip() == net.ipaddr("127.0.0.1") and addr:port() == 8080)
/// assert(tostring(addr) == "127.0.0.1:8080")
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
#[cdef]
pub struct lb_socketaddr(#[opaque] SocketAddr);
#[metatype]
impl lb_socketaddr {
/// The IP part of this address.
pub extern "Lua-C" fn ip(&self) -> lb_ipaddr {
self.0.ip().into()
}
/// The port part of this address.
pub extern "Lua-C" fn port(&self) -> u16 {
self.0.port()
}
/// Returns a new socket address with the given IP address and the same port.
pub extern "Lua-C" fn with_ip(&self, ip: &lb_ipaddr) -> Self {
SocketAddr::new(ip.0, self.port()).into()
}
/// Returns a new socket address with the given port and the same IP address.
pub extern "Lua-C" fn with_port(&self, port: u16) -> Self {
SocketAddr::new(self.ip().0, port).into()
}
/// Returns `true` if the given addresses are equal.
#[eq]
pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool {
left.0 == right.0
}
/// Returns `true` if the left address is less than the right address.
#[lt]
pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool {
left.0 < right.0
}
/// Returns `true` if the left address is less than or equal to the right address.
#[le]
pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool {
left.0 <= right.0
}
/// Returns the string representation of this address.
#[tostring]
pub extern "Lua-C" fn tostring(&self) -> String {
self.0.to_string()
}
}

View File

@ -1,622 +0,0 @@
use super::*;
use luaffi::{cdef, marker::fun, metatype};
use luajit::LUA_NOREF;
use std::io::ErrorKind;
use std::{
cell::{Ref, RefCell, RefMut},
ffi::c_int,
time::Duration,
};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt, Interest},
net::{TcpListener, TcpSocket, TcpStream},
};
/// TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
///
/// This type represents a TCP socket in its initial state, before it is connected or set to listen.
/// It can be configured (e.g., socket options, bind address) before being converted to an
/// [`lb_tcpstream`] (via [`connect`](lb_tcpsocket::connect)) or [`lb_tcplistener`] (via
/// [`listen`](lb_tcpsocket::listen)), after which it can no longer be used.
///
/// Methods on this type may fail if the operating system does not support the requested operation.
#[derive(Debug)]
#[cdef]
pub struct lb_tcpsocket(#[opaque] RefCell<Option<TcpSocket>>);
#[metatype]
impl lb_tcpsocket {
pub(super) fn new(socket: TcpSocket) -> Self {
Self(RefCell::new(Some(socket)))
}
fn socket<'s>(&'s self) -> Result<Ref<'s, TcpSocket>> {
let socket = self.0.borrow();
match *socket {
Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())),
None => Err(Error::SocketConsumed),
}
}
/// Gets the value of the `SO_KEEPALIVE` option on this socket.
pub extern "Lua-C" fn keepalive(&self) -> Result<bool> {
Ok(self.socket()?.keepalive()?)
}
/// Sets value for the `SO_KEEPALIVE` option on this socket.
///
/// This enables or disables periodic keepalive messages on the connection.
pub extern "Lua-C" fn set_keepalive(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_keepalive(enabled)?)
}
/// Gets the value of the `SO_REUSEADDR` option on this socket.
pub extern "Lua-C" fn reuseaddr(&self) -> Result<bool> {
Ok(self.socket()?.reuseaddr()?)
}
/// Sets value for the `SO_REUSEADDR` option on this socket.
///
/// This allows the socket to bind to an address that is already in use.
pub extern "Lua-C" fn set_reuseaddr(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_reuseaddr(enabled)?)
}
/// Gets the value of the `SO_REUSEPORT` option on this socket.
pub extern "Lua-C" fn reuseport(&self) -> Result<bool> {
Ok(self.socket()?.reuseport()?)
}
/// Sets value for the `SO_REUSEPORT` option on this socket.
///
/// This allows multiple sockets to bind to the same port.
pub extern "Lua-C" fn set_reuseport(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_reuseport(enabled)?)
}
/// Gets the value of the `SO_SNDBUF` option on this socket.
pub extern "Lua-C" fn sendbuf(&self) -> Result<u32> {
Ok(self.socket()?.send_buffer_size()?)
}
/// Sets value for the `SO_SNDBUF` option on this socket.
///
/// This sets the size of the send buffer in bytes.
pub extern "Lua-C" fn set_sendbuf(&self, size: u32) -> Result<()> {
Ok(self.socket()?.set_send_buffer_size(size)?)
}
/// Gets the value of the `SO_RCVBUF` option on this socket.
pub extern "Lua-C" fn recvbuf(&self) -> Result<u32> {
Ok(self.socket()?.recv_buffer_size()?)
}
/// Sets value for the `SO_RCVBUF` option on this socket.
///
/// This sets the size of the receive buffer in bytes.
pub extern "Lua-C" fn set_recvbuf(&self, size: u32) -> Result<()> {
Ok(self.socket()?.set_recv_buffer_size(size)?)
}
/// Gets the value of the `SO_LINGER` option on this socket, in seconds.
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
Ok(self
.socket()?
.linger()?
.map(|n| n.as_secs_f64())
.unwrap_or(0.))
}
/// Sets the value of the `SO_LINGER` option on this socket.
///
/// This controls how long the socket will remain open after close if unsent data is present.
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
let secs = secs.max(0.);
Ok(self
.socket()?
.set_linger((secs != 0.).then_some(Duration::from_secs_f64(secs)))?)
}
/// Gets the value of the `TCP_NODELAY` option on this socket.
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
Ok(self.socket()?.nodelay()?)
}
/// Sets the value of the `TCP_NODELAY` option on this socket.
///
/// This enables or disables Nagle's algorithm, which delays sending small packets.
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
Ok(self.socket()?.set_nodelay(enabled)?)
}
/// Gets the local address that this socket is bound to.
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
Ok(self.socket()?.local_addr()?.into())
}
/// Binds this socket to the given local address.
pub extern "Lua-C" fn bind(&self, addr: &lb_socketaddr) -> Result<()> {
Ok(self.socket()?.bind(addr.0)?)
}
/// Transitions this socket to the listening state.
///
/// This consumes the socket and returns a new [`lb_tcplistener`] that can accept incoming
/// connections. This socket object can no longer be used after this call.
pub extern "Lua-C" fn listen(&self, backlog: u32) -> Result<lb_tcplistener> {
let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?;
Ok(lb_tcplistener::new(socket.listen(backlog)?))
}
/// Connects this socket to the given remote socket address, transitioning it to an established
/// state.
///
/// This consumes the socket and returns a new [`lb_tcpstream`] that can be used to send and
/// receive data. This socket object can no longer be used after this call.
///
/// # Errors
///
/// This function may throw an error if connection could not be established to the given remote
/// address.
pub async extern "Lua-C" fn connect(&self, addr: &lb_socketaddr) -> Result<lb_tcpstream> {
let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?;
Ok(lb_tcpstream::new(socket.connect(addr.0).await?))
}
}
/// TCP connection between a local and a remote socket.
///
/// This represents an established TCP connection. It is created by connecting an [`lb_tcpsocket`]
/// to a remote socket (via [`connect`](lb_tcpsocket::connect)) or accepting a connection from an
/// [`lb_tcplistener`] (via [`accept`](lb_tcplistener::accept)). It provides methods for reading
/// from and writing to the stream asynchronously.
///
/// The stream supports reading and writing data in both directions concurrently. Typically you
/// would spawn one reader task and one writer task to handle incoming and outgoing data
/// respectively. Connection is closed when this object goes out of scope and gets garbage
/// collected, or when [`close`](Self::close) is explicitly called.
///
/// Methods on this type may fail if the operating system does not support the requested operation.
///
/// # Example
///
/// This examples spawns a reader task and a writer task to operate on the stream concurrently.
///
/// ```lua,no_run
/// local task = require("lb:task")
/// local net = require("lb:net")
/// local socket = net.connect_tcp("127.0.0.1:1234")
///
/// print("local address: ", socket:local_addr())
/// print("remote address: ", socket:peer_addr())
///
/// local reader = spawn(function()
/// for chunk in socket, 1024 do
/// print("received: ", chunk)
/// end
///
/// print("done reading")
/// end)
///
/// local writer = spawn(function()
/// for i = 1, 10 do
/// local msg = ("message %d"):format(i)
/// socket:write(msg)
/// print("sent: ", msg)
/// end
///
/// print("done writing")
/// end)
///
/// task.join(reader, writer)
/// ```
///
/// The above example uses the socket as an iterator in a generic `for` loop to read data in chunks
/// of up to 1024 bytes. It is equivalent to the following:
///
/// ```lua,no_run
/// local net = require("lb:net")
/// local socket = net.connect_tcp("127.0.0.1:1234")
///
/// while true do
/// local chunk = socket:read_partial(1024)
/// if chunk == nil then break end
/// print("received: ", chunk)
/// end
/// ```
#[derive(Debug)]
#[cdef]
pub struct lb_tcpstream {
#[opaque]
read: RefCell<Option<OwnedReadHalf>>,
#[opaque]
write: RefCell<Option<OwnedWriteHalf>>,
}
#[metatype]
impl lb_tcpstream {
pub(super) fn new(stream: TcpStream) -> Self {
let (read, write) = stream.into_split();
Self {
read: RefCell::new(Some(read)),
write: RefCell::new(Some(write)),
}
}
fn read_half<'s>(&'s self) -> Result<RefMut<'s, OwnedReadHalf>> {
let read = self.read.try_borrow_mut()?;
match *read {
Some(_) => Ok(RefMut::map(read, |s| s.as_mut().unwrap())),
None => Err(Error::SocketClosed),
}
}
fn write_half<'s>(&'s self) -> Result<RefMut<'s, OwnedWriteHalf>> {
let write = self.write.try_borrow_mut()?;
match *write {
Some(_) => Ok(RefMut::map(write, |s| s.as_mut().unwrap())),
None => Err(Error::SocketClosed),
}
}
/// The local socket address that this stream is bound to.
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
Ok(self.read_half()?.local_addr()?.into())
}
/// The remote socket address that this stream is connected to.
pub extern "Lua-C" fn peer_addr(&self) -> Result<lb_socketaddr> {
Ok(self.read_half()?.peer_addr()?.into())
}
/// Waits for this stream to be ready in the given half.
///
/// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half,
/// or `nil` for both.
pub async extern "Lua-C" fn ready(&self, half: Option<&str>) -> Result<()> {
self.read_half()?
.ready(match half {
Some("read") => Interest::READABLE,
Some("write") => Interest::WRITABLE,
None => Interest::READABLE | Interest::WRITABLE,
_ => Err(Error::InvalidSocketHalf)?,
})
.await?;
Ok(())
}
/// Closes this stream in the given half.
///
/// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half,
/// or `nil` for both.
///
/// Once the half is closed, it can no longer be used for reading or writing for that half. Once
/// both halves are closed, the stream is fully shut down.
pub extern "Lua-C" fn close(&self, half: Option<&str>) -> Result<()> {
Ok(match half {
Some("read") => drop(self.read.try_borrow_mut()?.take()),
Some("write") => drop(self.write.try_borrow_mut()?.take()),
None => drop((
self.read.try_borrow_mut()?.take(),
self.write.try_borrow_mut()?.take(),
)),
_ => Err(Error::InvalidSocketHalf)?,
})
}
fn is_disc(err: ErrorKind) -> bool {
matches!(
err,
ErrorKind::ConnectionReset // graceful shutdown
| ErrorKind::BrokenPipe // abrupt shutdown
| ErrorKind::UnexpectedEof // could not read requested amount of data
| ErrorKind::WriteZero // could not write requested amount of data
)
}
/// Reads exactly `len` bytes from this stream.
///
/// If the connection was closed, this returns `nil`.
pub async extern "Lua-C" fn read(&self, len: u32) -> Result<Option<Vec<u8>>> {
let mut buf = vec![0; len as usize];
Ok(match self.read_half()?.read_exact(&mut buf).await {
Ok(_) => Some(buf),
Err(err) if Self::is_disc(err.kind()) => None,
Err(err) => return Err(err.into()),
})
}
/// Reads up to `len` bytes from this stream.
///
/// The returned bytes may be less than `len` in length if the stream had less data available in
/// queue. If there was no data available or the connection was closed, this returns `nil`.
pub async extern "Lua-C" fn read_partial(&self, len: u32) -> Result<Option<Vec<u8>>> {
let mut buf = vec![0; len as usize];
Ok(match self.read_half()?.read(&mut buf).await {
Ok(0) => None,
Ok(n) => Some({
buf.truncate(n);
buf
}),
Err(err) if Self::is_disc(err.kind()) => None,
Err(err) => return Err(err.into()),
})
}
/// Attempts to read up to `len` bytes from this stream without waiting.
///
/// The returned bytes may be less than `len` in length if the stream had less data available in
/// queue. If there was no data available or the connection was closed, this returns `nil`.
pub extern "Lua-C" fn try_read(&self, len: u32) -> Result<Option<Vec<u8>>> {
let mut buf = vec![0; len as usize];
Ok(match self.read_half()?.try_read(&mut buf) {
Ok(0) => None,
Ok(n) => Some({
buf.truncate(n);
buf
}),
Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None,
Err(err) => return Err(err.into()),
})
}
/// Writes exactly the given bytes to this stream.
///
/// If the connection was closed, this returns `false`.
pub async extern "Lua-C" fn write(&self, buf: &[u8]) -> Result<bool> {
Ok(match self.write_half()?.write_all(buf).await {
Ok(()) => true,
Err(err) if Self::is_disc(err.kind()) => false,
Err(err) => return Err(err.into()),
})
}
/// Writes the given bytes to this stream, and returns the number of bytes successfully written.
///
/// The returned number may be less than the length of `buf` if there was not enough space in
/// queue. If the connection was closed, this returns `nil`.
pub async extern "Lua-C" fn write_partial(&self, buf: &[u8]) -> Result<Option<u32>> {
Ok(match self.write_half()?.write(buf).await {
Ok(n) => Some(n as u32),
Err(err) if Self::is_disc(err.kind()) => None,
Err(err) => return Err(err.into()),
})
}
/// Attempts to write the given bytes to this stream without waiting, and returns the number of
/// bytes successfully written.
///
/// The returned number may be less than the length of `buf` if there was not enough space in
/// queue. If the connection was closed, this returns `nil`.
pub extern "Lua-C" fn try_write(&self, buf: &[u8]) -> Result<Option<u32>> {
Ok(match self.write_half()?.try_write(buf) {
Ok(n) => Some(n as u32),
Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None,
Err(err) => return Err(err.into()),
})
}
/// Peeks up to `len` bytes at incoming data without consuming it.
///
/// Successive calls will return the same data until it is consumed by the [`read*`](Self::read)
/// family of functions.
pub async extern "Lua-C" fn peek(&self, len: u32) -> Result<Option<Vec<u8>>> {
let mut buf = vec![0; len as usize];
Ok(match self.read_half()?.peek(&mut buf).await {
Ok(0) => None,
Ok(n) => Some({
buf.truncate(n);
buf
}),
Err(err) if Self::is_disc(err.kind()) => None,
Err(err) => return Err(err.into()),
})
}
/// Alias for [`read_partial`](Self::read_partial).
#[call]
pub async extern "Lua" fn call(&self, len: u32) -> Result<Option<Vec<u8>>> {
self.read_partial(len)
}
}
/// TCP socket server, listening for connections.
///
/// This type represents a TCP server socket that can accept incoming connections. It is created by
/// transitioning an [`lb_tcpsocket`] to the listening state via [`listen`](lb_tcpsocket::listen).
///
/// Methods on this type may fail if the operating system does not support the requested operation.
///
/// # Example
///
/// The listener can be used as an iterator in a generic `for` loop to accept incoming connections:
///
/// ```lua,no_run
/// local net = require("lb:net")
/// local listener = net.listen_tcp("127.0.0.1")
///
/// print("listening on: ", listener:local_addr())
///
/// for stream in listener do
/// print("accepted connection from: ", stream:peer_addr())
/// print("local address: ", stream:local_addr())
///
/// spawn(function()
/// stream:write("hello from server\n")
/// stream:close()
/// end)
/// end
/// ```
#[derive(Debug)]
#[cdef]
pub struct lb_tcplistener {
#[opaque]
listener: TcpListener,
__on_accept_ref: c_int,
}
#[metatype]
impl lb_tcplistener {
pub(super) fn new(listener: TcpListener) -> Self {
Self {
listener,
__on_accept_ref: LUA_NOREF,
}
}
/// The local socket address that this listener is bound to.
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
Ok(self.listener.local_addr()?.into())
}
/// Registers a callback to be invoked with each new incoming connection before it is converted
/// to an [`lb_tcpstream`].
///
/// The callback receives a temporary [`lb_tcplistener_stream`] object, which can be used to log
/// incoming connections or configure socket options (such as
/// [`set_nodelay`](lb_tcplistener_stream), [`set_linger`](lb_tcplistener_stream), etc.) before
/// it is converted to an [`lb_tcpstream`]. The callback is called synchronously during
/// [`accept`](Self::accept) and should complete as quickly as possible. The provided
/// configurable object is only valid within the callback and is converted to an
/// [`lb_tcpstream`] as soon as it returns.
///
/// If a callback already exists, it is replaced with the new one.
///
/// # Example
///
/// ```lua
/// local net = require("lb:net")
/// local listener = net.listen_tcp("127.0.0.1")
///
/// listener:on_accept(function(stream)
/// print("accepted connection from: ", stream:peer_addr())
/// print("local address: ", stream:local_addr())
///
/// stream:set_nodelay(true)
/// end)
/// ```
pub extern "Lua" fn on_accept(&self, cb: fun<(&lb_tcplistener_stream,), ()>) {
assert(
rawequal(cb, ()) || r#type(cb) == "function",
concat!("function expected in argument 'cb', got ", r#type(cb)),
);
__unref(self.__on_accept_ref);
self.__on_accept_ref = __ref(cb);
}
/// Accepts a new incoming TCP connection.
///
/// If an [`on_accept`](Self::on_accept) callback is registered, it is invoked with a temporary
/// [`lb_tcplistener_stream`] object representing the new connection. This allows configuration
/// of socket options for this specific connection, before the stream is converted to an
/// [`lb_tcpstream`] and returned for the connection to be read from or written to.
pub async extern "Lua" fn accept(&self) -> Result<lb_tcpstream> {
let stream = self.__accept();
let on_accept = __registry[self.__on_accept_ref];
if !rawequal(on_accept, ()) {
on_accept(stream);
}
stream.__convert()
}
async extern "Lua-C" fn __accept(&self) -> Result<lb_tcplistener_stream> {
let (stream, _) = self.listener.accept().await?;
Ok(lb_tcplistener_stream::new(stream))
}
/// Alias for [`accept`](Self::accept).
#[call]
pub async extern "Lua" fn call(&self) -> Result<lb_tcpstream> {
self.accept()
}
#[gc]
extern "Lua" fn gc(&self) {
__unref(self.__on_accept_ref);
}
}
/// TCP connection that has just been accepted by [`lb_tcplistener`].
///
/// This type is passed to the [`on_accept`](lb_tcplistener::on_accept) callback on
/// [`lb_tcplistener`], allowing socket options to be set before the stream is converted to an
/// [`lb_tcpstream`]. After conversion, this object can no longer be used.
///
/// Methods on this type may fail if the operating system does not support the requested operation.
#[derive(Debug)]
#[cdef]
pub struct lb_tcplistener_stream(#[opaque] RefCell<Option<TcpStream>>);
#[metatype]
impl lb_tcplistener_stream {
fn new(stream: TcpStream) -> Self {
Self(RefCell::new(Some(stream)))
}
fn stream<'s>(&'s self) -> Result<Ref<'s, TcpStream>> {
let socket = self.0.borrow();
match *socket {
Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())),
None => Err(Error::SocketConsumed),
}
}
/// The local socket address that the listener is bound to.
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
Ok(self.stream()?.local_addr()?.into())
}
/// The remote socket address that this stream is connected to.
pub extern "Lua-C" fn peer_addr(&self) -> Result<lb_socketaddr> {
Ok(self.stream()?.peer_addr()?.into())
}
/// Gets the value of the `TCP_NODELAY` option on this stream.
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
Ok(self.stream()?.nodelay()?)
}
/// Sets the value of the `TCP_NODELAY` option on this stream.
///
/// This enables or disables Nagle's algorithm, which delays sending small packets.
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
Ok(self.stream()?.set_nodelay(enabled)?)
}
/// Gets the value of the `SO_LINGER` option on this stream, in seconds.
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
Ok(self
.stream()?
.linger()?
.map(|n| n.as_secs_f64())
.unwrap_or(0.))
}
/// Sets the value of the `SO_LINGER` option on this stream.
///
/// This controls how long the stream will remain open after close if unsent data is present.
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
let secs = secs.max(0.);
Ok(self
.stream()?
.set_linger((secs != 0.).then_some(std::time::Duration::from_secs_f64(secs)))?)
}
/// Gets the value of the `IP_TTL` option for this stream.
pub extern "Lua-C" fn ttl(&self) -> Result<u32> {
Ok(self.stream()?.ttl()?)
}
/// Sets the value for the `IP_TTL` option on this stream.
pub extern "Lua-C" fn set_ttl(&self, ttl: u32) -> Result<()> {
Ok(self.stream()?.set_ttl(ttl)?)
}
extern "Lua-C" fn __convert(&self) -> Result<lb_tcpstream> {
Ok(lb_tcpstream::new(
self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?,
))
}
}

View File

@ -21,7 +21,7 @@ use tokio::{task::JoinHandle, time::sleep};
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
///
/// ```lua
/// local task = require("lb:task")
/// local task = require("lb:task");
/// ```
#[cdef(module = "lb:task")]
pub struct lb_tasklib;

View File

@ -14,7 +14,7 @@ use luaffi::{cdef, metatype};
/// [`require("lb:time")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
///
/// ```lua
/// local time = require("lb:time")
/// local time = require("lb:time");
/// ```
#[cdef(module = "lb:time")]
pub struct lb_timelib;

View File

@ -90,7 +90,7 @@ describe("tcp", function()
spawn(function()
assert(not pcall(client.read, client, 1)) -- this should fail, since the first task is still reading
end):await()
server:close()
server:shutdown()
reader:await()
end)
@ -104,7 +104,7 @@ describe("tcp", function()
spawn(function()
client:write("hello") -- should be able to write while the first task is reading
end):await()
server:close()
server:shutdown()
reader:await()
end)
@ -123,7 +123,9 @@ describe("tcp", function()
assert(server:write("ping") == true)
end
sleep(100)
server:close()
server:shutdown()
server = nil
collectgarbage()
reader:await()
end)
@ -142,7 +144,9 @@ describe("tcp", function()
assert(server:read(4) == "pong")
end
sleep(100)
server:close()
server:shutdown()
server = nil
collectgarbage()
writer:await()
end)
end)
@ -176,9 +180,9 @@ describe("tcp", function()
local buf = client_stream:read(5)
assert(buf ~= nil and #buf == 5)
assert(buf == "hello")
-- close
server_stream:close()
client_stream:close()
-- shutdown
server_stream:shutdown()
client_stream:shutdown()
end)
end)
end)

View File

@ -1,13 +0,0 @@
[package]
name = "lb_sqlite"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
[dependencies]
lb = { path = "../lb", features = ["time"] }
luaffi = { version = "0.0.1", path = "../luaffi" }
rusqlite = { version = "0.36.0", features = ["bundled", "load_extension", "backup", "blob", "limits", "window", "series", "session", "collation", "serialize"] }

View File

@ -1,3 +0,0 @@
//! luby SQLite library.
#[path = "mod.rs"]
pub mod sqlite;

View File

@ -1,27 +0,0 @@
//! SQLite library.
//!
//! The `lb:sqlite` library provides an interface to SQLite databases.
//!
//! ## Exports
//!
//! See [`lb_sqlitelib`] for items exported by this library.
use luaffi::{cdef, metatype};
/// Items exported by the `lb:sqlite` library.
///
/// This library can be acquired by calling
/// [`require("lb:sqlite")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
///
/// ```lua
/// local sqlite = require("lb:sqlite")
/// ```
#[cdef(module = "lb:sqlite")]
pub struct lb_sqlitelib;
#[metatype]
impl lb_sqlitelib {
#[new]
extern "Lua-C" fn new() -> Self {
Self
}
}

View File

@ -8,9 +8,6 @@ pub use lb::task;
#[cfg(feature = "time")]
pub use lb::time;
#[cfg(feature = "sqlite")]
pub use lb_sqlite::sqlite;
#[doc(hidden)]
pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
#[cfg(feature = "task")]
@ -21,7 +18,4 @@ pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
rt.module::<fs::lb_fslib>();
#[cfg(feature = "net")]
rt.module::<net::lb_netlib>();
#[cfg(feature = "sqlite")]
rt.module::<sqlite::lb_sqlitelib>();
}

View File

@ -12,7 +12,6 @@ local color = {
reset = "\x1b[0m",
pass = "\x1b[32;1m", -- green
fail = "\x1b[31;1m", -- red
faint = "\x1b[2;39;49m", -- faint
}
local icon = {
@ -25,12 +24,6 @@ local function style(name, s)
return ("%s%s%s"):format(color[name], s, color.reset)
end
local function rjust(s, w)
if w == nil then w = 12 end
if #s >= w then return s end
return (" "):rep(w - #s) .. s
end
local function create_test(name, f, group)
local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
local fenv = setmetatable({}, { __index = global, __newindex = global })
@ -74,13 +67,13 @@ local function trace(msg)
end
local function run_test(test)
local ok, trace = xpcall(test.f, trace, test)
local ok, res = xpcall(test.f, trace, test)
if ok then
test.state = "pass"
print(("%s %s"):format(style("pass", rjust("PASS")), name_test(test)))
print("", ("%s %s"):format(style("pass", "PASS"), name_test(test)))
else
test.state = "fail"
print(("%s %s\n\n%s\n"):format(style("fail", rjust("!!! FAIL")), name_test(test), trace))
print("", ("%s %s\n\n%s\n"):format(style("fail", "FAIL"), name_test(test), res))
end
collectgarbage() -- gc after each test to test destructors
return test
@ -125,28 +118,28 @@ local function main(item)
end
end
local elapsed = time:elapsed_secs()
local retcode
local code = 1
if fail == 0 then
print(style("pass", ("\t%s %d tests passed"):format(icon.check, pass)))
retcode = 0
print("", style("pass", ("%s %d tests passed"):format(icon.check, pass)))
code = 0
else
print(
("\t%s, %s"):format(
"",
("%s, %s"):format(
style("pass", ("%s %d tests passed"):format(icon.check, pass)),
style("fail", ("%s %d tests failed"):format(icon.cross, fail))
)
)
retcode = 1
end
if elapsed < 1000 then
print(style("faint", ("\t%s completed in %.2f ms"):format(icon.chevron, elapsed * 1000)))
print("", ("%s completed in %.2f ms"):format(icon.chevron, elapsed * 1000))
else
print(style("faint", ("\t%s completed in %.2f s"):format(icon.chevron, elapsed)))
print("", ("%s completed in %.2f s"):format(icon.chevron, elapsed))
end
cx = nil
collectgarbage()
check_refs() -- check that all refs were properly unref'ed in destructors
return retcode -- report error to cargo
check_refs()
return code -- report error to cargo
end
return main(create_group("", function()
@ -159,34 +152,6 @@ return main(create_group("", function()
end
end
local function include_doctest(path, pat)
for entry in fs.glob_dir(path, pat) do
local line, doctest = 0, nil
for s in fs.read(entry:path()):gmatch("([^\n]*)\n?") do
line = line + 1
local prefix = s:match("^%s*///")
s = prefix and s:sub(#prefix + 1)
if s and not s:match("^%s*```%s*$") then
if s:match("^%s*```lua$") then
doctest = { line = line, col = #prefix + 2 }
elseif doctest then
table.insert(doctest, (" "):rep(#prefix) .. s)
end
else
if doctest then
local name = ("%s:%d:%d"):format(entry:path(), doctest.line, doctest.col)
local f, err = loadstring(table.concat(doctest, "\n"), "@" .. name)
if not f then error(err) end
test(("%s %s"):format(name, style("faint", "(doctest)")), f)
end
doctest = nil
end
end
end
end
include("tests", "**/*.lua")
include("crates", "*/tests/**/*.lua")
include_doctest("src", "**/*.rs")
include_doctest("crates", "*/src/**/*.rs")
end))