Skip to content

Commit

Permalink
update api version with new implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
yggverse committed Oct 30, 2024
1 parent 47f58f8 commit 9398509
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 178 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ggemini"
version = "0.5.1"
version = "0.6.0"
edition = "2021"
license = "MIT"
readme = "README.md"
Expand Down
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,13 @@ cargo add ggemini

## Usage

* [Documentation](https://docs.rs/ggemini/latest/ggemini/)

_todo_

### `client`

[Gio](https://docs.gtk.org/gio/) API already provides powerful [SocketClient](https://docs.gtk.org/gio/class.SocketClient.html)\
`client` collection just extends some features wanted for Gemini Protocol interaction.

#### `client::response`
#### `client::response::header`
#### `client::response::body`

### `gio`

#### `gio::memory_input_stream`

## See also

* [ggemtext](https://github.com/YGGverse/ggemtext) - Glib-oriented Gemtext API
4 changes: 2 additions & 2 deletions src/client/response.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod body;
pub mod header;
pub mod meta;

pub use body::Body;
pub use header::Header;
pub use meta::Meta;
151 changes: 0 additions & 151 deletions src/client/response/header.rs

This file was deleted.

191 changes: 191 additions & 0 deletions src/client/response/meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
pub mod data;
pub mod error;
pub mod mime;
pub mod status;

pub use data::Data;
pub use error::Error;
pub use mime::Mime;
pub use status::Status;

use gio::{
prelude::{IOStreamExt, InputStreamExt},
Cancellable, SocketConnection,
};
use glib::{Bytes, Priority};

pub const MAX_LEN: usize = 0x400; // 1024

pub struct Meta {
data: Data,
mime: Mime,
status: Status,
// @TODO
// charset: Charset,
// language: Language,
}

impl Meta {
// Constructors

/// Create new `Self` from UTF-8 buffer
pub fn from_utf8(buffer: &[u8]) -> Result<Self, (Error, Option<&str>)> {
let len = buffer.len();

match buffer.get(..if len > MAX_LEN { MAX_LEN } else { len }) {
Some(slice) => {
// Parse data
let data = Data::from_utf8(&slice);

if let Err(reason) = data {
return Err((
match reason {
data::Error::Decode => Error::DataDecode,
data::Error::Protocol => Error::DataProtocol,
},
None,
));
}

// MIME

let mime = Mime::from_utf8(&slice);

if let Err(reason) = mime {
return Err((
match reason {
mime::Error::Decode => Error::MimeDecode,
mime::Error::Protocol => Error::MimeProtocol,
mime::Error::Undefined => Error::MimeUndefined,
},
None,
));
}

// Status

let status = Status::from_utf8(&slice);

if let Err(reason) = status {
return Err((
match reason {
status::Error::Decode => Error::StatusDecode,
status::Error::Protocol => Error::StatusProtocol,
status::Error::Undefined => Error::StatusUndefined,
},
None,
));
}

Ok(Self {
data: data.unwrap(),
mime: mime.unwrap(),
status: status.unwrap(),
})
}
None => Err((Error::Protocol, None)),
}
}

/// Asynchronously create new `Self` from [InputStream](https://docs.gtk.org/gio/class.InputStream.html)
/// for given [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html)
pub fn from_socket_connection_async(
socket_connection: SocketConnection,
priority: Option<Priority>,
cancellable: Option<Cancellable>,
on_complete: impl FnOnce(Result<Self, (Error, Option<&str>)>) + 'static,
) {
read_from_socket_connection_async(
Vec::with_capacity(MAX_LEN),
socket_connection,
match cancellable {
Some(value) => Some(value),
None => None::<Cancellable>,
},
match priority {
Some(value) => value,
None => Priority::DEFAULT,
},
|result| match result {
Ok(buffer) => on_complete(Self::from_utf8(&buffer)),
Err(reason) => on_complete(Err(reason)),
},
);
}

// Getters

pub fn status(&self) -> &Status {
&self.status
}

pub fn data(&self) -> &Data {
&self.data
}

pub fn mime(&self) -> &Mime {
&self.mime
}
}

// Tools

/// Asynchronously take meta bytes from [InputStream](https://docs.gtk.org/gio/class.InputStream.html)
/// for given [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html)
///
/// * this function implements low-level helper for `Meta::from_socket_connection_async`, also provides public API for external integrations
/// * requires entire `SocketConnection` instead of `InputStream` to keep connection alive in async context
pub fn read_from_socket_connection_async(
mut buffer: Vec<Bytes>,
connection: SocketConnection,
cancellable: Option<Cancellable>,
priority: Priority,
on_complete: impl FnOnce(Result<Vec<u8>, (Error, Option<&str>)>) + 'static,
) {
connection.input_stream().read_bytes_async(
1, // do not change!
priority,
cancellable.clone().as_ref(),
move |result| match result {
Ok(bytes) => {
// Expect valid header length
if bytes.len() == 0 || buffer.len() >= MAX_LEN {
return on_complete(Err((Error::Protocol, None)));
}

// Read next byte without buffer record
if bytes.contains(&b'\r') {
return read_from_socket_connection_async(
buffer,
connection,
cancellable,
priority,
on_complete,
);
}

// Complete without buffer record
if bytes.contains(&b'\n') {
return on_complete(Ok(buffer
.iter()
.flat_map(|byte| byte.iter())
.cloned()
.collect())); // convert to UTF-8
}

// Record
buffer.push(bytes);

// Continue
read_from_socket_connection_async(
buffer,
connection,
cancellable,
priority,
on_complete,
);
}
Err(reason) => on_complete(Err((Error::InputStream, Some(reason.message())))),
},
);
}
File renamed without changes.
Loading

0 comments on commit 9398509

Please sign in to comment.