1use core::ffi::c_void;
2use core::ptr;
3use std::ptr::NonNull;
4use std::time::Duration;
5
6use serde::Deserialize;
7
8use crate::error::StoreKitError;
9use crate::ffi;
10use crate::private::{
11 duration_to_timeout_ms, error_from_status, parse_json_ptr, parse_optional_json_ptr,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Storefront {
17 pub country_code: String,
19 pub id: String,
21 pub currency_code: Option<String>,
23}
24
25impl Storefront {
26 pub fn current() -> Result<Option<Self>, StoreKitError> {
28 let mut storefront_json = ptr::null_mut();
29 let mut error_message = ptr::null_mut();
30 let status =
31 unsafe { ffi::sk_storefront_current_json(&mut storefront_json, &mut error_message) };
32 if status != ffi::status::OK {
33 return Err(unsafe { error_from_status(status, error_message) });
34 }
35 unsafe { parse_optional_json_ptr::<StorefrontPayload>(storefront_json, "storefront") }
36 .map(|payload| payload.map(StorefrontPayload::into_storefront))
37 }
38
39 pub fn updates() -> Result<StorefrontStream, StoreKitError> {
41 StorefrontStream::new()
42 }
43}
44
45#[derive(Debug)]
46pub struct StorefrontStream {
48 handle: NonNull<c_void>,
49 finished: bool,
50}
51
52impl Drop for StorefrontStream {
53 fn drop(&mut self) {
54 unsafe { ffi::sk_storefront_stream_release(self.handle.as_ptr()) };
55 }
56}
57
58impl StorefrontStream {
59 fn new() -> Result<Self, StoreKitError> {
60 let mut error_message = ptr::null_mut();
61 let handle = unsafe { ffi::sk_storefront_stream_create(&mut error_message) };
62 let handle = NonNull::new(handle)
63 .ok_or_else(|| unsafe { error_from_status(ffi::status::UNKNOWN, error_message) })?;
64 Ok(Self {
65 handle,
66 finished: false,
67 })
68 }
69
70 pub const fn is_finished(&self) -> bool {
72 self.finished
73 }
74
75 #[allow(clippy::should_implement_trait)]
76 pub fn next(&mut self) -> Result<Option<Storefront>, StoreKitError> {
78 self.next_timeout(Duration::from_secs(30))
79 }
80
81 pub fn next_timeout(&mut self, timeout: Duration) -> Result<Option<Storefront>, StoreKitError> {
83 let mut storefront_json = ptr::null_mut();
84 let mut error_message = ptr::null_mut();
85 let status = unsafe {
86 ffi::sk_storefront_stream_next(
87 self.handle.as_ptr(),
88 duration_to_timeout_ms(timeout),
89 &mut storefront_json,
90 &mut error_message,
91 )
92 };
93
94 match status {
95 ffi::status::OK => {
96 let payload =
97 unsafe { parse_json_ptr::<StorefrontPayload>(storefront_json, "storefront") }?;
98 Ok(Some(payload.into_storefront()))
99 }
100 ffi::status::END_OF_STREAM => {
101 self.finished = true;
102 Ok(None)
103 }
104 ffi::status::TIMED_OUT => Ok(None),
105 _ => Err(unsafe { error_from_status(status, error_message) }),
106 }
107 }
108}
109
110#[derive(Debug, Deserialize)]
111pub(crate) struct StorefrontPayload {
112 #[serde(rename = "countryCode")]
113 country_code: String,
114 id: String,
115 #[serde(rename = "currencyCode")]
116 currency_code: Option<String>,
117}
118
119impl StorefrontPayload {
120 pub(crate) fn into_storefront(self) -> Storefront {
121 Storefront {
122 country_code: self.country_code,
123 id: self.id,
124 currency_code: self.currency_code,
125 }
126 }
127}