senpy_ffi/
lib.rs

1// This file is part of senpy-ffi <https://github.com/senpy-club/senpy-ffi>.
2// Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful, but
9// WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11// General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15//
16// Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
17// SPDX-License-Identifier: GPL-3.0-only
18
19//! FFI bindings for [`senpy-rs`](https://github.com/senpy-club/senpy-rs)
20
21#![deny(
22  warnings,
23  nonstandard_style,
24  unused,
25  future_incompatible,
26  rust_2018_idioms
27)]
28#![deny(clippy::all, clippy::nursery, clippy::pedantic)]
29#![recursion_limit = "128"]
30#![doc(
31  html_logo_url = "https://senpy.club/favicon.png",
32  html_favicon_url = "https://senpy.club/favicon.png"
33)]
34
35use std::ffi::{CStr, CString};
36
37use libc::c_char;
38
39/// The base URL to The Senpy Club API
40///
41/// FFI binding to `senpy::SENPY_CLUB_API_BASE_URL`
42#[no_mangle]
43pub static SENPY_CLUB_API_BASE_URL: &str = senpy::SENPY_CLUB_API_BASE_URL;
44/// The current API version of The Senpy Club API
45///
46/// FFI binding to `senpy::SENPY_CLUB_API_CURRENT_VERSION`
47#[no_mangle]
48pub static SENPY_CLUB_API_CURRENT_VERSION: u32 =
49  senpy::SENPY_CLUB_API_CURRENT_VERSION;
50/// The API URL to The Senpy Club API
51///
52/// FFI binding to `senpy::SENPY_CLUB_API_URL`
53#[no_mangle]
54pub static SENPY_CLUB_API_URL: &str = senpy::SENPY_CLUB_API_URL;
55
56/// The response of the <https://api.senpy.club/v2/random> route
57///
58/// Part of the FFI binding to `senpy::random`
59#[repr(C)]
60#[derive(Default)]
61pub struct Random {
62  language: String,
63  image:    String,
64}
65impl Random {
66  /// Initializes a new `Random`
67  ///
68  /// Part of the FFI binding to `senpy::random`
69  #[must_use]
70  pub fn new() -> Self { Self::default() }
71
72  /// Populates a `Random` from a `senpy::random` call
73  ///
74  /// Part of the FFI binding to `senpy::random`
75  pub fn populate(&mut self) {
76    if let Ok(image) = senpy::random() {
77      self.language = image.language;
78      self.image = image.image;
79    } else {
80      self.language = "".to_string();
81      self.image = "".to_string();
82    }
83  }
84
85  /// Frees a `Random`
86  ///
87  /// Part of the FFI binding to `senpy::random`
88  #[must_use]
89  pub fn get(&self, key: &str) -> String {
90    match key {
91      "language" => self.language.clone(),
92      "image" => self.image.clone(),
93      _ => "".to_string(),
94    }
95  }
96}
97
98/// Returns an array where the first element is the size of the array and the
99/// remaining elements are the images.
100///
101/// If the first element (size) is `-1`; the
102/// request failed for any reason.
103///
104/// # Safety
105/// This is an *unsafe* FFI binding to `senpy::language`.
106///
107/// # Panics
108/// if a `String` cannot be converted into a `CString`
109#[no_mangle]
110pub unsafe extern "C" fn language(language: *const c_char) -> *mut *mut c_char {
111  match senpy::language(CStr::from_ptr(language).to_str().unwrap()) {
112    Ok(images) => {
113      let mut images_c =
114        vec![CString::new(images.len().to_string()).unwrap().into_raw()];
115
116      for image in images {
117        images_c.push(CString::new(image).unwrap().into_raw());
118      }
119
120      images_c.as_mut_ptr()
121    }
122    Err(_) => vec![CString::new("-1").unwrap().into_raw()].as_mut_ptr(),
123  }
124}
125
126/// Returns an array where the first element is the size of the array and the
127/// remaining elements are the languages.
128///
129/// If the first element (size) is `-1`; the request failed for
130/// any reason.
131///
132/// FFI binding to `senpy::languages`
133///
134/// # Panics
135/// if a `String` cannot be converted into a `CString`
136#[no_mangle]
137pub extern "C" fn languages() -> *mut *mut c_char {
138  match senpy::languages() {
139    Ok(languages) => {
140      let mut languages_c = vec![CString::new(languages.len().to_string())
141        .unwrap()
142        .into_raw()];
143
144      for language in languages {
145        languages_c.push(CString::new(language).unwrap().into_raw());
146      }
147
148      languages_c.as_mut_ptr()
149    }
150    Err(_) => vec![CString::new("-1").unwrap().into_raw()].as_mut_ptr(),
151  }
152}
153
154/// Initializes a new `Random`
155///
156/// Part of the FFI binding to `senpy::random`
157#[no_mangle]
158pub extern "C" fn random_new() -> *mut Random {
159  Box::into_raw(Box::new(Random::new()))
160}
161
162/// Populates a `Random` from a `senpy::random` call
163///
164/// # Safety
165/// This is part of an *unsafe* FFI binding to `senpy::random`.
166#[no_mangle]
167pub unsafe extern "C" fn random_populate(random: *mut Random) {
168  (&mut *random).populate();
169}
170
171/// Gets a member from a `Random`, valid members are `language` and `image`.
172///
173/// # Safety
174/// This is part of an *unsafe* FFI binding to `senpy::random`.
175///
176/// # Panics
177/// if the `key` cannot be wrapped as a safe `CStr`
178#[no_mangle]
179pub unsafe extern "C" fn random_get(
180  random: *const Random,
181  key: *const c_char,
182) -> *mut c_char {
183  CString::new((&*random).get(CStr::from_ptr(key).to_str().unwrap()))
184    .unwrap()
185    .into_raw()
186}
187
188/// Frees a `Random`
189///
190/// # Safety
191/// This is part of an *unsafe* FFI binding to `senpy::random`.
192#[no_mangle]
193pub unsafe extern "C" fn random_free(random: *mut Random) {
194  if random.is_null() {
195    return;
196  }
197
198  drop(Box::from_raw(random));
199}
200
201/// Returns `1` if up, returns `0` if down, and returns `-1` if the request
202/// failed for any reason.
203///
204/// FFI binding to `senpy::status`
205#[no_mangle]
206pub extern "C" fn status() -> i32 {
207  match senpy::status() {
208    Ok(status) =>
209      if status {
210        1
211      } else {
212        0
213      },
214    Err(_) => -1,
215  }
216}