1use super::*;
2#[cfg(test)]
3use serial_test::serial;
4
5pub struct RemoteStorage {
7 pub(crate) rs: *mut sys::ISteamRemoteStorage,
8 pub(crate) util: *mut sys::ISteamUtils,
9 pub(crate) inner: Arc<Inner>,
10}
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum PublishedFileVisibility {
14 Public,
15 FriendsOnly,
16 Private,
17 Unlisted,
18}
19
20impl From<sys::ERemoteStoragePublishedFileVisibility> for PublishedFileVisibility {
21 fn from(visibility: sys::ERemoteStoragePublishedFileVisibility) -> Self {
22 match visibility {
23 sys::ERemoteStoragePublishedFileVisibility::k_ERemoteStoragePublishedFileVisibilityPublic => PublishedFileVisibility::Public,
24 sys::ERemoteStoragePublishedFileVisibility::k_ERemoteStoragePublishedFileVisibilityFriendsOnly => PublishedFileVisibility::FriendsOnly,
25 sys::ERemoteStoragePublishedFileVisibility::k_ERemoteStoragePublishedFileVisibilityPrivate => PublishedFileVisibility::Private,
26 sys::ERemoteStoragePublishedFileVisibility::k_ERemoteStoragePublishedFileVisibilityUnlisted => PublishedFileVisibility::Unlisted,
27 _ => unreachable!(),
28 }
29 }
30}
31
32impl Into<sys::ERemoteStoragePublishedFileVisibility> for PublishedFileVisibility {
33 fn into(self) -> sys::ERemoteStoragePublishedFileVisibility {
34 match self {
35 PublishedFileVisibility::Public => sys::ERemoteStoragePublishedFileVisibility::k_ERemoteStoragePublishedFileVisibilityPublic,
36 PublishedFileVisibility::FriendsOnly => sys::ERemoteStoragePublishedFileVisibility::k_ERemoteStoragePublishedFileVisibilityFriendsOnly,
37 PublishedFileVisibility::Private => sys::ERemoteStoragePublishedFileVisibility::k_ERemoteStoragePublishedFileVisibilityPrivate,
38 PublishedFileVisibility::Unlisted => sys::ERemoteStoragePublishedFileVisibility::k_ERemoteStoragePublishedFileVisibilityUnlisted,
39 }
40 }
41}
42
43impl Clone for RemoteStorage {
44 fn clone(&self) -> Self {
45 RemoteStorage {
46 inner: self.inner.clone(),
47 rs: self.rs,
48 util: self.util,
49 }
50 }
51}
52
53impl RemoteStorage {
54 pub fn set_cloud_enabled_for_app(&self, enabled: bool) {
56 unsafe {
57 sys::SteamAPI_ISteamRemoteStorage_SetCloudEnabledForApp(self.rs, enabled);
58 }
59 }
60
61 pub fn is_cloud_enabled_for_app(&self) -> bool {
67 unsafe { sys::SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp(self.rs) }
68 }
69
70 pub fn is_cloud_enabled_for_account(&self) -> bool {
76 unsafe { sys::SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount(self.rs) }
77 }
78
79 pub fn files(&self) -> Vec<SteamFileInfo> {
81 unsafe {
82 let count = sys::SteamAPI_ISteamRemoteStorage_GetFileCount(self.rs);
83 if count == -1 {
84 return Vec::new();
85 }
86 let mut files = Vec::with_capacity(count as usize);
87 for idx in 0..count {
88 let mut size = 0;
89 let name = CStr::from_ptr(sys::SteamAPI_ISteamRemoteStorage_GetFileNameAndSize(
90 self.rs, idx, &mut size,
91 ));
92 files.push(SteamFileInfo {
93 name: name.to_string_lossy().into_owned(),
94 size: size as u64,
95 })
96 }
97
98 files
99 }
100 }
101
102 pub fn file(&self, name: &str) -> SteamFile {
106 SteamFile {
107 rs: self.rs,
108 util: self.util,
109 _inner: self.inner.clone(),
110 name: CString::new(name).unwrap(),
111 }
112 }
113}
114
115bitflags! {
116 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
119 #[repr(C)]
120 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
121 pub struct RemoteStoragePlatforms: u32 {
122 const WINDOWS = sys::ERemoteStoragePlatform::k_ERemoteStoragePlatformWindows.0 as _;
123 const MACOS = sys::ERemoteStoragePlatform::k_ERemoteStoragePlatformOSX.0 as _;
124 const PS3 = sys::ERemoteStoragePlatform::k_ERemoteStoragePlatformPS3.0 as _;
125 const LINUX = sys::ERemoteStoragePlatform::k_ERemoteStoragePlatformLinux.0 as _;
126 const SWITCH = sys::ERemoteStoragePlatform::k_ERemoteStoragePlatformSwitch.0 as _;
127 const ANDROID = sys::ERemoteStoragePlatform::k_ERemoteStoragePlatformAndroid.0 as _;
128 const IOS = sys::ERemoteStoragePlatform::k_ERemoteStoragePlatformIOS.0 as _;
129 }
130}
131
132impl From<RemoteStoragePlatforms> for sys::ERemoteStoragePlatform {
133 fn from(platforms: RemoteStoragePlatforms) -> Self {
134 sys::ERemoteStoragePlatform(platforms.bits() as _)
135 }
136}
137
138pub struct SteamFile {
140 pub(crate) rs: *mut sys::ISteamRemoteStorage,
141 pub(crate) util: *mut sys::ISteamUtils,
142 pub(crate) _inner: Arc<Inner>,
143 name: CString,
144}
145
146impl SteamFile {
147 pub fn delete(&self) -> bool {
151 unsafe { sys::SteamAPI_ISteamRemoteStorage_FileDelete(self.rs, self.name.as_ptr()) }
152 }
153 pub fn forget(&self) -> bool {
157 unsafe { sys::SteamAPI_ISteamRemoteStorage_FileForget(self.rs, self.name.as_ptr()) }
158 }
159
160 pub fn exists(&self) -> bool {
162 unsafe { sys::SteamAPI_ISteamRemoteStorage_FileExists(self.rs, self.name.as_ptr()) }
163 }
164
165 pub fn is_persisted(&self) -> bool {
167 unsafe { sys::SteamAPI_ISteamRemoteStorage_FilePersisted(self.rs, self.name.as_ptr()) }
168 }
169
170 pub fn timestamp(&self) -> i64 {
172 unsafe { sys::SteamAPI_ISteamRemoteStorage_GetFileTimestamp(self.rs, self.name.as_ptr()) }
173 }
174
175 pub fn set_sync_platforms(&self, platforms: RemoteStoragePlatforms) {
177 unsafe {
178 sys::SteamAPI_ISteamRemoteStorage_SetSyncPlatforms(
179 self.rs,
180 self.name.as_ptr(),
181 platforms.into(),
182 );
183 }
184 }
185
186 pub fn get_sync_platforms(&self) -> RemoteStoragePlatforms {
188 let bits = unsafe {
189 sys::SteamAPI_ISteamRemoteStorage_GetSyncPlatforms(self.rs, self.name.as_ptr())
190 };
191 RemoteStoragePlatforms::from_bits_truncate(bits.0 as _)
192 }
193
194 pub fn write(self) -> SteamFileWriter {
195 unsafe {
196 let handle =
197 sys::SteamAPI_ISteamRemoteStorage_FileWriteStreamOpen(self.rs, self.name.as_ptr());
198 SteamFileWriter { file: self, handle }
199 }
200 }
201
202 pub fn read(self) -> SteamFileReader {
203 unsafe {
204 SteamFileReader {
205 offset: 0,
206 size: sys::SteamAPI_ISteamRemoteStorage_GetFileSize(self.rs, self.name.as_ptr())
207 as usize,
208 file: self,
209 }
210 }
211 }
212
213 pub fn share(&self, cb: impl FnOnce(Result<u64, SteamError>) + 'static + Send) {
214 let api_call =
215 unsafe { sys::SteamAPI_ISteamRemoteStorage_FileShare(self.rs, self.name.as_ptr()) };
216 unsafe {
217 register_call_result::<sys::RemoteStorageFileShareResult_t, _>(
218 &self._inner,
219 api_call,
220 move |v, io_error| {
221 if io_error {
222 cb(Err(SteamError::IOFailure));
223 return;
224 }
225 if v.m_eResult != sys::EResult::k_EResultOK {
226 cb(Err(v.m_eResult.into()));
227 return;
228 }
229
230 cb(Ok(v.m_hFile))
231 },
232 )
233 }
234 }
235}
236
237pub struct SteamFileWriter {
239 file: SteamFile,
240 handle: sys::UGCFileWriteStreamHandle_t,
241}
242
243impl std::io::Write for SteamFileWriter {
244 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
245 unsafe {
246 if sys::SteamAPI_ISteamRemoteStorage_FileWriteStreamWriteChunk(
247 self.file.rs,
248 self.handle,
249 buf.as_ptr().cast(),
250 buf.len() as _,
251 ) {
252 Ok(buf.len())
253 } else {
254 Err(std::io::ErrorKind::Other.into())
255 }
256 }
257 }
258
259 fn flush(&mut self) -> std::io::Result<()> {
260 Ok(())
261 }
262}
263
264impl Drop for SteamFileWriter {
265 fn drop(&mut self) {
266 unsafe {
267 sys::SteamAPI_ISteamRemoteStorage_FileWriteStreamClose(self.file.rs, self.handle);
268 }
269 }
270}
271
272pub struct SteamFileReader {
274 file: SteamFile,
275 offset: usize,
276 size: usize,
277}
278
279impl std::io::Read for SteamFileReader {
280 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
281 use std::cmp::min;
282 if buf.is_empty() || self.size - self.offset == 0 {
283 return Ok(0);
284 }
285 let len = min(buf.len(), self.size - self.offset);
286 unsafe {
287 let api_call = sys::SteamAPI_ISteamRemoteStorage_FileReadAsync(
288 self.file.rs,
289 self.file.name.as_ptr(),
290 self.offset as _,
291 len as _,
292 );
293
294 let mut failed = false;
295 while !sys::SteamAPI_ISteamUtils_IsAPICallCompleted(
296 self.file.util,
297 api_call,
298 &mut failed,
299 ) {
300 std::thread::yield_now();
301 }
302 if failed {
303 return Err(std::io::ErrorKind::Other.into());
304 }
305 let mut callback: sys::RemoteStorageFileReadAsyncComplete_t = std::mem::zeroed();
306 sys::SteamAPI_ISteamUtils_GetAPICallResult(
307 self.file.util,
308 api_call,
309 (&mut callback) as *mut _ as *mut _,
310 std::mem::size_of::<sys::RemoteStorageFileReadAsyncComplete_t>() as _,
311 1332,
312 &mut failed,
313 );
314
315 if callback.m_eResult != sys::EResult::k_EResultOK {
316 return Err(std::io::ErrorKind::Other.into());
317 }
318 let size = callback.m_cubRead as usize;
319 sys::SteamAPI_ISteamRemoteStorage_FileReadAsyncComplete(
320 self.file.rs,
321 callback.m_hFileReadAsync,
322 buf.as_mut_ptr().cast(),
323 callback.m_cubRead,
324 );
325
326 self.offset += size;
327 Ok(size)
328 }
329 }
330}
331
332impl std::io::Seek for SteamFileReader {
333 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
334 match pos {
335 std::io::SeekFrom::Current(o) => {
336 if self.offset as isize + o as isize >= self.size as isize {
337 return Err(std::io::ErrorKind::InvalidInput.into());
338 }
339 self.offset = (self.offset as isize + o as isize) as usize;
340 }
341 std::io::SeekFrom::End(o) => {
342 if o as isize >= self.size as isize {
343 return Err(std::io::ErrorKind::InvalidInput.into());
344 }
345 self.offset = (self.size as isize - 1 - o as isize) as usize;
346 }
347 std::io::SeekFrom::Start(o) => {
348 if o as usize >= self.size {
349 return Err(std::io::ErrorKind::InvalidInput.into());
350 }
351 self.offset = o as usize;
352 }
353 }
354 Ok(self.offset as u64)
355 }
356}
357
358#[derive(Clone, Debug)]
360#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
361pub struct SteamFileInfo {
362 pub name: String,
364 pub size: u64,
366}
367
368#[test]
369#[serial]
370fn test_cloud() {
371 use std::io::{Read, Write};
372 let client = Client::init().unwrap();
373
374 let rs = client.remote_storage();
375 println!("Listing files:");
376 for f in rs.files() {
377 println!("{:?}", f);
378 }
379
380 {
381 let test = rs.file("test.txt");
382 let mut w = test.write();
383 write!(w, "Testing").unwrap();
384 }
385
386 println!("Listing files:");
387 for f in rs.files() {
388 println!("{:?}", f);
389 }
390
391 let mut output = String::new();
392 let test = rs.file("test.txt");
393 test.read().read_to_string(&mut output).unwrap();
394 println!("Got: {:?}", output);
395
396 assert_eq!(output, "Testing");
397}