Skip to content

Commit 9398509

Browse files
author
yggverse
committed
update api version with new implementation
1 parent 47f58f8 commit 9398509

File tree

14 files changed

+221
-178
lines changed

14 files changed

+221
-178
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ggemini"
3-
version = "0.5.1"
3+
version = "0.6.0"
44
edition = "2021"
55
license = "MIT"
66
readme = "README.md"

README.md

+2-10
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,13 @@ cargo add ggemini
1717

1818
## Usage
1919

20+
* [Documentation](https://docs.rs/ggemini/latest/ggemini/)
21+
2022
_todo_
2123

2224
### `client`
23-
24-
[Gio](https://docs.gtk.org/gio/) API already provides powerful [SocketClient](https://docs.gtk.org/gio/class.SocketClient.html)\
25-
`client` collection just extends some features wanted for Gemini Protocol interaction.
26-
27-
#### `client::response`
28-
#### `client::response::header`
29-
#### `client::response::body`
30-
3125
### `gio`
3226

33-
#### `gio::memory_input_stream`
34-
3527
## See also
3628

3729
* [ggemtext](https://github.com/YGGverse/ggemtext) - Glib-oriented Gemtext API

src/client/response.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pub mod body;
2-
pub mod header;
2+
pub mod meta;
33

44
pub use body::Body;
5-
pub use header::Header;
5+
pub use meta::Meta;

src/client/response/header.rs

-151
This file was deleted.

src/client/response/meta.rs

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
pub mod data;
2+
pub mod error;
3+
pub mod mime;
4+
pub mod status;
5+
6+
pub use data::Data;
7+
pub use error::Error;
8+
pub use mime::Mime;
9+
pub use status::Status;
10+
11+
use gio::{
12+
prelude::{IOStreamExt, InputStreamExt},
13+
Cancellable, SocketConnection,
14+
};
15+
use glib::{Bytes, Priority};
16+
17+
pub const MAX_LEN: usize = 0x400; // 1024
18+
19+
pub struct Meta {
20+
data: Data,
21+
mime: Mime,
22+
status: Status,
23+
// @TODO
24+
// charset: Charset,
25+
// language: Language,
26+
}
27+
28+
impl Meta {
29+
// Constructors
30+
31+
/// Create new `Self` from UTF-8 buffer
32+
pub fn from_utf8(buffer: &[u8]) -> Result<Self, (Error, Option<&str>)> {
33+
let len = buffer.len();
34+
35+
match buffer.get(..if len > MAX_LEN { MAX_LEN } else { len }) {
36+
Some(slice) => {
37+
// Parse data
38+
let data = Data::from_utf8(&slice);
39+
40+
if let Err(reason) = data {
41+
return Err((
42+
match reason {
43+
data::Error::Decode => Error::DataDecode,
44+
data::Error::Protocol => Error::DataProtocol,
45+
},
46+
None,
47+
));
48+
}
49+
50+
// MIME
51+
52+
let mime = Mime::from_utf8(&slice);
53+
54+
if let Err(reason) = mime {
55+
return Err((
56+
match reason {
57+
mime::Error::Decode => Error::MimeDecode,
58+
mime::Error::Protocol => Error::MimeProtocol,
59+
mime::Error::Undefined => Error::MimeUndefined,
60+
},
61+
None,
62+
));
63+
}
64+
65+
// Status
66+
67+
let status = Status::from_utf8(&slice);
68+
69+
if let Err(reason) = status {
70+
return Err((
71+
match reason {
72+
status::Error::Decode => Error::StatusDecode,
73+
status::Error::Protocol => Error::StatusProtocol,
74+
status::Error::Undefined => Error::StatusUndefined,
75+
},
76+
None,
77+
));
78+
}
79+
80+
Ok(Self {
81+
data: data.unwrap(),
82+
mime: mime.unwrap(),
83+
status: status.unwrap(),
84+
})
85+
}
86+
None => Err((Error::Protocol, None)),
87+
}
88+
}
89+
90+
/// Asynchronously create new `Self` from [InputStream](https://docs.gtk.org/gio/class.InputStream.html)
91+
/// for given [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html)
92+
pub fn from_socket_connection_async(
93+
socket_connection: SocketConnection,
94+
priority: Option<Priority>,
95+
cancellable: Option<Cancellable>,
96+
on_complete: impl FnOnce(Result<Self, (Error, Option<&str>)>) + 'static,
97+
) {
98+
read_from_socket_connection_async(
99+
Vec::with_capacity(MAX_LEN),
100+
socket_connection,
101+
match cancellable {
102+
Some(value) => Some(value),
103+
None => None::<Cancellable>,
104+
},
105+
match priority {
106+
Some(value) => value,
107+
None => Priority::DEFAULT,
108+
},
109+
|result| match result {
110+
Ok(buffer) => on_complete(Self::from_utf8(&buffer)),
111+
Err(reason) => on_complete(Err(reason)),
112+
},
113+
);
114+
}
115+
116+
// Getters
117+
118+
pub fn status(&self) -> &Status {
119+
&self.status
120+
}
121+
122+
pub fn data(&self) -> &Data {
123+
&self.data
124+
}
125+
126+
pub fn mime(&self) -> &Mime {
127+
&self.mime
128+
}
129+
}
130+
131+
// Tools
132+
133+
/// Asynchronously take meta bytes from [InputStream](https://docs.gtk.org/gio/class.InputStream.html)
134+
/// for given [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html)
135+
///
136+
/// * this function implements low-level helper for `Meta::from_socket_connection_async`, also provides public API for external integrations
137+
/// * requires entire `SocketConnection` instead of `InputStream` to keep connection alive in async context
138+
pub fn read_from_socket_connection_async(
139+
mut buffer: Vec<Bytes>,
140+
connection: SocketConnection,
141+
cancellable: Option<Cancellable>,
142+
priority: Priority,
143+
on_complete: impl FnOnce(Result<Vec<u8>, (Error, Option<&str>)>) + 'static,
144+
) {
145+
connection.input_stream().read_bytes_async(
146+
1, // do not change!
147+
priority,
148+
cancellable.clone().as_ref(),
149+
move |result| match result {
150+
Ok(bytes) => {
151+
// Expect valid header length
152+
if bytes.len() == 0 || buffer.len() >= MAX_LEN {
153+
return on_complete(Err((Error::Protocol, None)));
154+
}
155+
156+
// Read next byte without buffer record
157+
if bytes.contains(&b'\r') {
158+
return read_from_socket_connection_async(
159+
buffer,
160+
connection,
161+
cancellable,
162+
priority,
163+
on_complete,
164+
);
165+
}
166+
167+
// Complete without buffer record
168+
if bytes.contains(&b'\n') {
169+
return on_complete(Ok(buffer
170+
.iter()
171+
.flat_map(|byte| byte.iter())
172+
.cloned()
173+
.collect())); // convert to UTF-8
174+
}
175+
176+
// Record
177+
buffer.push(bytes);
178+
179+
// Continue
180+
read_from_socket_connection_async(
181+
buffer,
182+
connection,
183+
cancellable,
184+
priority,
185+
on_complete,
186+
);
187+
}
188+
Err(reason) => on_complete(Err((Error::InputStream, Some(reason.message())))),
189+
},
190+
);
191+
}

0 commit comments

Comments
 (0)