aidoku/imports/
std.rs

1//! Module for standard Aidoku source library functions.
2use super::{FFIResult, Ptr, Rid};
3use crate::{
4	AidokuError,
5	alloc::{String, Vec},
6};
7use core::ptr::null;
8use serde::{Serialize, de::DeserializeOwned};
9
10#[link(wasm_import_module = "std")]
11unsafe extern "C" {
12	pub(crate) fn destroy(rid: Rid);
13
14	pub(crate) fn buffer_len(rid: Rid) -> FFIResult;
15
16	#[link_name = "read_buffer"]
17	fn _read_buffer(rid: Rid, buf: *mut u8, len: usize) -> FFIResult;
18
19	#[link_name = "current_date"]
20	fn _current_date() -> f64;
21
22	fn utc_offset() -> i64;
23
24	#[link_name = "parse_date"]
25	fn _parse_date(
26		string_ptr: *const u8,
27		string_len: usize,
28		format_ptr: *const u8,
29		format_len: usize,
30		locale_ptr: *const u8,
31		locale_len: usize,
32		timezone_ptr: *const u8,
33		timezone_len: usize,
34	) -> f64;
35}
36
37// env module
38#[link(wasm_import_module = "env")]
39unsafe extern "C" {
40	// #[link_name = "abort"]
41	// fn _abort();
42
43	#[link_name = "print"]
44	fn _print(string: *const u8, size: usize);
45
46	#[link_name = "sleep"]
47	fn _sleep(seconds: i32);
48
49	#[link_name = "send_partial_result"]
50	fn _send_partial_result(value: Ptr);
51}
52
53/// Error type for std functions.
54#[derive(PartialEq, Eq, Debug, Clone, Copy)]
55pub enum StdError {
56	InvalidDescriptor,
57	InvalidBufferSize,
58	FailedMemoryWrite,
59	InvalidString,
60	InvalidDateString,
61}
62
63impl StdError {
64	fn from(value: i32) -> Option<Self> {
65		match value {
66			-1 => Some(Self::InvalidDescriptor),
67			-2 => Some(Self::InvalidBufferSize),
68			-3 => Some(Self::FailedMemoryWrite),
69			-4 => Some(Self::InvalidString),
70			-5 => Some(Self::InvalidDateString),
71			_ => None,
72		}
73	}
74}
75
76/// Prints a message to the Aidoku logs.
77pub fn print<T: AsRef<str>>(string: T) {
78	let string = string.as_ref();
79	unsafe {
80		_print(string.as_ptr(), string.len());
81	}
82}
83
84/// Blocks the current thread for the specified number of seconds.
85pub fn sleep(seconds: i32) {
86	unsafe {
87		_sleep(seconds);
88	}
89}
90
91/// Encodes a value into a byte array and returns a pointer to it.
92///
93/// Used for sending results back to Aidoku. The encoded data is prefixed with its length.
94/// Note that the byte vector is forgotten after encoding, and must be manually freed (with [free_result]).
95///
96/// # Safety
97/// The returned pointer is forgotten, and must be manually freed with [free_result].
98pub(crate) unsafe fn encode<T: Serialize>(result: &T) -> Ptr {
99	let mut bytes = ::postcard::to_allocvec(result).unwrap();
100	bytes.splice(0..0, [0, 0, 0, 0, 0, 0, 0, 0]);
101	let len_bytes = (bytes.len() as i32).to_le_bytes();
102	bytes[0..4].copy_from_slice(&len_bytes);
103	let cap_bytes = (bytes.capacity() as i32).to_le_bytes();
104	bytes[4..8].copy_from_slice(&cap_bytes);
105	let ptr = bytes.as_ptr() as Ptr;
106	::core::mem::forget(bytes);
107	ptr
108}
109
110/// Frees a byte array pointer returned by `encode`.
111///
112/// This function is exposed for the functions that the [register_source](crate::register_source)
113/// macro generates and should not be used directly.
114///
115/// # Safety
116/// The pointer must be a valid pointer to a byte array returned by `encode`.
117pub unsafe fn free_result(ptr: Ptr) {
118	let ptr = ptr as *const u8;
119	let (len, cap) = unsafe {
120		let cap_and_len = ::core::slice::from_raw_parts(ptr, 8);
121		let len = i32::from_le_bytes([
122			cap_and_len[0],
123			cap_and_len[1],
124			cap_and_len[2],
125			cap_and_len[3],
126		]);
127		let cap = i32::from_le_bytes([
128			cap_and_len[4],
129			cap_and_len[5],
130			cap_and_len[6],
131			cap_and_len[7],
132		]);
133		if len == -1 {
134			let real_len_slice = ::core::slice::from_raw_parts(ptr.offset(8), 4);
135			let real_len = i32::from_le_bytes([
136				real_len_slice[0],
137				real_len_slice[1],
138				real_len_slice[2],
139				real_len_slice[3],
140			]);
141			(real_len, cap)
142		} else {
143			(len, cap)
144		}
145	};
146	let original_vec: Vec<u8> =
147		unsafe { Vec::from_raw_parts(ptr as *mut u8, len as usize, cap as usize) };
148	drop(original_vec);
149}
150
151/// Sends a partial result to the source runner.
152///
153/// This function is used to send partial home layours and manga results
154/// back to the runner for faster loading.
155///
156/// Only [HomePartialResult](crate::HomePartialResult) and [Manga](crate::Manga)
157/// structs should be passed as arguments.
158pub fn send_partial_result<T: Serialize>(value: &T) {
159	let value_ptr = unsafe { encode(value) };
160	unsafe {
161		_send_partial_result(value_ptr);
162	}
163	unsafe { free_result(value_ptr) };
164}
165
166/// Gets the current time as a Unix timestamp.
167pub fn current_date() -> i64 {
168	unsafe { _current_date() as i64 }
169}
170
171/// Reads an object from a descriptor.
172///
173/// This function is exposed for the functions that the [register_source](crate::register_source)
174/// macro generates and should not be used directly.
175pub fn read<T: DeserializeOwned>(rid: Rid) -> Result<T, AidokuError> {
176	read_buffer(rid)
177		.and_then(|buffer| postcard::from_bytes(&buffer).ok())
178		.ok_or(AidokuError::DeserializeError)
179}
180
181/// Reads a string from a descriptor.
182///
183/// This function is exposed for the functions that the [register_source](crate::register_source)
184/// macro generates and should not be used directly.
185pub fn read_string(rid: Rid) -> Option<String> {
186	let buffer = read_buffer(rid)?;
187	let string = String::from_utf8(buffer).unwrap_or_default();
188	if string.is_empty() {
189		None
190	} else {
191		Some(string)
192	}
193}
194
195pub(crate) fn read_string_and_destroy(rid: Rid) -> Option<String> {
196	let buffer = read_buffer(rid);
197	unsafe { destroy(rid) };
198	let buffer = buffer?;
199	let string = String::from_utf8(buffer).unwrap_or_default();
200	if string.is_empty() {
201		None
202	} else {
203		Some(string)
204	}
205}
206
207/// Reads a buffer from a descriptor.
208pub(crate) fn read_buffer(rid: Rid) -> Option<Vec<u8>> {
209	let len = unsafe { buffer_len(rid) };
210	if len < 0 {
211		return None;
212	}
213	let len = len as usize;
214	let mut buffer = Vec::with_capacity(len);
215	let error = unsafe { _read_buffer(rid, buffer.as_mut_ptr(), len) };
216	// the values read are externally managed by the source runner, so we don't need to free them
217	// unsafe { destroy(rid) };
218	if error != 0 {
219		return None;
220	}
221	unsafe { buffer.set_len(len) };
222	Some(buffer)
223}
224
225pub fn get_utc_offset() -> i64 {
226	unsafe { utc_offset() }
227}
228
229/// Parses a date string into a Unix timestamp (seconds since epoch) using the specified format.
230///
231/// The result will be in UTC. The format string should be valid for Swift's DateFormatter.
232///
233/// # Examples
234///
235/// ```ignore
236/// use aidoku::imports::std::parse_date;
237/// let timestamp = parse_date("07-01-2025 13:00", "MM-dd-yyyy HH:mm");
238/// assert_eq!(timestamp, Some(1751374800));
239/// ```
240pub fn parse_date<T: AsRef<str>, U: AsRef<str>>(date_str: T, format: U) -> Option<i64> {
241	let string = date_str.as_ref();
242	let format = format.as_ref();
243	let timezone = "UTC";
244	let result = unsafe {
245		_parse_date(
246			string.as_ptr(),
247			string.len(),
248			format.as_ptr(),
249			format.len(),
250			null(),
251			0,
252			timezone.as_ptr(),
253			timezone.len(),
254		)
255	};
256	if StdError::from(result as i32).is_some() {
257		return None;
258	}
259	Some(result as i64)
260}
261
262/// Parses a date string into a Unix timestamp using the specified format and the user's local timezone.
263///
264/// The format string should be valid for Swift's DateFormatter.
265pub fn parse_local_date<T: AsRef<str>, U: AsRef<str>>(date_str: T, format: U) -> Option<i64> {
266	let string = date_str.as_ref();
267	let format = format.as_ref();
268	let timezone = "current";
269	let result = unsafe {
270		_parse_date(
271			string.as_ptr(),
272			string.len(),
273			format.as_ptr(),
274			format.len(),
275			null(),
276			0,
277			timezone.as_ptr(),
278			timezone.len(),
279		)
280	};
281	if StdError::from(result as i32).is_some() {
282		return None;
283	}
284	Some(result as i64)
285}
286
287/// Parses a date string into a Unix timestamp using the specified format, locale, and timezone.
288///
289/// The format string should be valid for Swift's DateFormatter. The locale and timezone should be
290/// identifiers accepted by Swift's Locale and TimeZone.
291pub fn parse_date_with_options<T: AsRef<str>, U: AsRef<str>, V: AsRef<str>, W: AsRef<str>>(
292	date_str: T,
293	format: U,
294	locale: V,
295	timezone: W,
296) -> Option<i64> {
297	let string = date_str.as_ref();
298	let format = format.as_ref();
299	let locale = locale.as_ref();
300	let timezone = timezone.as_ref();
301	let result = unsafe {
302		_parse_date(
303			string.as_ptr(),
304			string.len(),
305			format.as_ptr(),
306			format.len(),
307			locale.as_ptr(),
308			locale.len(),
309			timezone.as_ptr(),
310			timezone.len(),
311		)
312	};
313	if StdError::from(result as i32).is_some() {
314		return None;
315	}
316	Some(result as i64)
317}