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}
213pub struct SteamFileWriter {
215 file: SteamFile,
216 handle: sys::UGCFileWriteStreamHandle_t,
217}
218
219impl std::io::Write for SteamFileWriter {
220 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
221 unsafe {
222 if sys::SteamAPI_ISteamRemoteStorage_FileWriteStreamWriteChunk(
223 self.file.rs,
224 self.handle,
225 buf.as_ptr().cast(),
226 buf.len() as _,
227 ) {
228 Ok(buf.len())
229 } else {
230 Err(std::io::ErrorKind::Other.into())
231 }
232 }
233 }
234
235 fn flush(&mut self) -> std::io::Result<()> {
236 Ok(())
237 }
238}
239
240impl Drop for SteamFileWriter {
241 fn drop(&mut self) {
242 unsafe {
243 sys::SteamAPI_ISteamRemoteStorage_FileWriteStreamClose(self.file.rs, self.handle);
244 }
245 }
246}
247
248pub struct SteamFileReader {
250 file: SteamFile,
251 offset: usize,
252 size: usize,
253}
254
255impl std::io::Read for SteamFileReader {
256 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
257 use std::cmp::min;
258 if buf.is_empty() || self.size - self.offset == 0 {
259 return Ok(0);
260 }
261 let len = min(buf.len(), self.size - self.offset);
262 unsafe {
263 let api_call = sys::SteamAPI_ISteamRemoteStorage_FileReadAsync(
264 self.file.rs,
265 self.file.name.as_ptr(),
266 self.offset as _,
267 len as _,
268 );
269
270 let mut failed = false;
271 while !sys::SteamAPI_ISteamUtils_IsAPICallCompleted(
272 self.file.util,
273 api_call,
274 &mut failed,
275 ) {
276 std::thread::yield_now();
277 }
278 if failed {
279 return Err(std::io::ErrorKind::Other.into());
280 }
281 let mut callback: sys::RemoteStorageFileReadAsyncComplete_t = std::mem::zeroed();
282 sys::SteamAPI_ISteamUtils_GetAPICallResult(
283 self.file.util,
284 api_call,
285 (&mut callback) as *mut _ as *mut _,
286 std::mem::size_of::<sys::RemoteStorageFileReadAsyncComplete_t>() as _,
287 1332,
288 &mut failed,
289 );
290
291 if callback.m_eResult != sys::EResult::k_EResultOK {
292 return Err(std::io::ErrorKind::Other.into());
293 }
294 let size = callback.m_cubRead as usize;
295 sys::SteamAPI_ISteamRemoteStorage_FileReadAsyncComplete(
296 self.file.rs,
297 callback.m_hFileReadAsync,
298 buf.as_mut_ptr().cast(),
299 callback.m_cubRead,
300 );
301
302 self.offset += size;
303 Ok(size)
304 }
305 }
306}
307
308impl std::io::Seek for SteamFileReader {
309 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
310 match pos {
311 std::io::SeekFrom::Current(o) => {
312 if self.offset as isize + o as isize >= self.size as isize {
313 return Err(std::io::ErrorKind::InvalidInput.into());
314 }
315 self.offset = (self.offset as isize + o as isize) as usize;
316 }
317 std::io::SeekFrom::End(o) => {
318 if o as isize >= self.size as isize {
319 return Err(std::io::ErrorKind::InvalidInput.into());
320 }
321 self.offset = (self.size as isize - 1 - o as isize) as usize;
322 }
323 std::io::SeekFrom::Start(o) => {
324 if o as usize >= self.size {
325 return Err(std::io::ErrorKind::InvalidInput.into());
326 }
327 self.offset = o as usize;
328 }
329 }
330 Ok(self.offset as u64)
331 }
332}
333
334#[derive(Clone, Debug)]
336#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
337pub struct SteamFileInfo {
338 pub name: String,
340 pub size: u64,
342}
343
344#[test]
345#[serial]
346fn test_cloud() {
347 use std::io::{Read, Write};
348 let client = Client::init().unwrap();
349
350 let rs = client.remote_storage();
351 println!("Listing files:");
352 for f in rs.files() {
353 println!("{:?}", f);
354 }
355
356 {
357 let test = rs.file("test.txt");
358 let mut w = test.write();
359 write!(w, "Testing").unwrap();
360 }
361
362 println!("Listing files:");
363 for f in rs.files() {
364 println!("{:?}", f);
365 }
366
367 let mut output = String::new();
368 let test = rs.file("test.txt");
369 test.read().read_to_string(&mut output).unwrap();
370 println!("Got: {:?}", output);
371
372 assert_eq!(output, "Testing");
373}