|
| 1 | +//! Tools for Text-based response |
| 2 | +
|
| 3 | +pub mod error; |
| 4 | +pub use error::Error; |
| 5 | + |
| 6 | +// Local dependencies |
| 7 | +use gio::{ |
| 8 | + prelude::{IOStreamExt, InputStreamExt}, |
| 9 | + Cancellable, SocketConnection, |
| 10 | +}; |
| 11 | +use glib::{GString, Priority}; |
| 12 | + |
| 13 | +// Default limits |
| 14 | +pub const BUFFER_CAPACITY: usize = 0x400; // 1024 |
| 15 | +pub const BUFFER_MAX_SIZE: usize = 0xfffff; // 1M |
| 16 | + |
| 17 | +/// Container for text-based response data |
| 18 | +pub struct Text { |
| 19 | + data: GString, |
| 20 | +} |
| 21 | + |
| 22 | +impl Text { |
| 23 | + // Constructors |
| 24 | + |
| 25 | + /// Create new `Self` with options |
| 26 | + pub fn new(data: GString) -> Self { |
| 27 | + Self { data } |
| 28 | + } |
| 29 | + |
| 30 | + /// Create new `Self` from UTF-8 buffer |
| 31 | + pub fn from_utf8(buffer: &[u8]) -> Result<Self, (Error, Option<&str>)> { |
| 32 | + match GString::from_utf8(buffer.into()) { |
| 33 | + Ok(data) => Ok(Self::new(data)), |
| 34 | + Err(_) => Err((Error::Decode, None)), |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | + /// Asynchronously create new `Self` from [InputStream](https://docs.gtk.org/gio/class.InputStream.html) |
| 39 | + /// for given [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html) |
| 40 | + pub fn from_socket_connection_async( |
| 41 | + socket_connection: SocketConnection, |
| 42 | + priority: Option<Priority>, |
| 43 | + cancellable: Option<Cancellable>, |
| 44 | + on_complete: impl FnOnce(Result<Self, (Error, Option<&str>)>) + 'static, |
| 45 | + ) { |
| 46 | + read_all_from_socket_connection_async( |
| 47 | + Vec::with_capacity(BUFFER_CAPACITY), |
| 48 | + socket_connection, |
| 49 | + match cancellable { |
| 50 | + Some(value) => Some(value), |
| 51 | + None => None::<Cancellable>, |
| 52 | + }, |
| 53 | + match priority { |
| 54 | + Some(value) => value, |
| 55 | + None => Priority::DEFAULT, |
| 56 | + }, |
| 57 | + |result| match result { |
| 58 | + Ok(buffer) => on_complete(Self::from_utf8(&buffer)), |
| 59 | + Err(reason) => on_complete(Err(reason)), |
| 60 | + }, |
| 61 | + ); |
| 62 | + } |
| 63 | + |
| 64 | + // Getters |
| 65 | + |
| 66 | + /// Get reference to `Self` data |
| 67 | + pub fn data(&self) -> &GString { |
| 68 | + &self.data |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +// Tools |
| 73 | + |
| 74 | +/// Asynchronously read all bytes from [InputStream](https://docs.gtk.org/gio/class.InputStream.html) |
| 75 | +/// for given [SocketConnection](https://docs.gtk.org/gio/class.SocketConnection.html) |
| 76 | +/// |
| 77 | +/// Return UTF-8 buffer collected. |
| 78 | +/// |
| 79 | +/// * this function implements low-level helper for `Text::from_socket_connection_async`, also provides public API for external integrations |
| 80 | +/// * requires `SocketConnection` instead of `InputStream` to keep connection alive (by increasing reference count in async context) @TODO |
| 81 | +pub fn read_all_from_socket_connection_async( |
| 82 | + mut buffer: Vec<u8>, |
| 83 | + socket_connection: SocketConnection, |
| 84 | + cancelable: Option<Cancellable>, |
| 85 | + priority: Priority, |
| 86 | + callback: impl FnOnce(Result<Vec<u8>, (Error, Option<&str>)>) + 'static, |
| 87 | +) { |
| 88 | + socket_connection.input_stream().read_bytes_async( |
| 89 | + BUFFER_CAPACITY, |
| 90 | + priority, |
| 91 | + cancelable.clone().as_ref(), |
| 92 | + move |result| match result { |
| 93 | + Ok(bytes) => { |
| 94 | + // No bytes were read, end of stream |
| 95 | + if bytes.len() == 0 { |
| 96 | + return callback(Ok(buffer)); |
| 97 | + } |
| 98 | + |
| 99 | + // Validate overflow |
| 100 | + if buffer.len() + bytes.len() > BUFFER_MAX_SIZE { |
| 101 | + return callback(Err((Error::BufferOverflow, None))); |
| 102 | + } |
| 103 | + |
| 104 | + // Save chunks to buffer |
| 105 | + for &byte in bytes.iter() { |
| 106 | + buffer.push(byte); |
| 107 | + } |
| 108 | + |
| 109 | + // Continue bytes reading |
| 110 | + read_all_from_socket_connection_async( |
| 111 | + buffer, |
| 112 | + socket_connection, |
| 113 | + cancelable, |
| 114 | + priority, |
| 115 | + callback, |
| 116 | + ); |
| 117 | + } |
| 118 | + Err(reason) => callback(Err((Error::InputStream, Some(reason.message())))), |
| 119 | + }, |
| 120 | + ); |
| 121 | +} |
0 commit comments