Skip to main content

steamkit/steam/handlers/steam_cloud/
steam_cloud.rs

1//! Remote storage and user generated content, through the `Cloud` unified
2//! service.
3
4use std::fmt;
5use std::sync::{Arc, OnceLock, Weak};
6
7use async_trait::async_trait;
8use steamkit_protos::steam::{
9    CCloudGetFileDetailsRequest, CCloudGetFileDetailsResponse, CCloudGetSingleFileInfoRequest,
10    CCloudGetSingleFileInfoResponse, CCloudShareFileRequest, CCloudShareFileResponse,
11};
12
13use crate::base::packet_base::IPacketMsg;
14use crate::error::{SteamError, SteamResult};
15use crate::steam::handlers::client_msg_handler::ClientMsgHandler;
16use crate::steam::handlers::steam_unified_messages::steam_unified_messages::{
17    SteamUnifiedMessages, UnifiedService,
18};
19use crate::steam::steam_client::steam_client::SteamClient;
20use crate::types::global_id::UGCHandle;
21
22use super::callbacks::{ShareFileCallback, SingleFileInfoCallback, UGCDetailsCallback};
23
24/// The unified service this handler uses.
25const SERVICE_NAME: &str = "Cloud";
26
27/// Interacts with remote storage and user generated content.
28pub struct SteamCloud {
29    client: Weak<SteamClient>,
30    service: OnceLock<Arc<UnifiedService>>,
31}
32
33impl fmt::Debug for SteamCloud {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.debug_struct("SteamCloud").finish_non_exhaustive()
36    }
37}
38
39impl SteamCloud {
40    /// Creates the handler and stores a weak back-reference to the client.
41    pub fn new(client: &Arc<SteamClient>) -> Self {
42        Self {
43            client: Arc::downgrade(client),
44            service: OnceLock::new(),
45        }
46    }
47
48    /// Upgrades the weak client reference.
49    fn client(&self) -> SteamResult<Arc<SteamClient>> {
50        self.client
51            .upgrade()
52            .ok_or_else(|| SteamError::Connection("SteamClient has been dropped".to_string()))
53    }
54
55    /// The `Cloud` unified service.
56    fn service(&self) -> SteamResult<Arc<UnifiedService>> {
57        if let Some(service) = self.service.get() {
58            return Ok(Arc::clone(service));
59        }
60
61        let client = self.client()?;
62        let unified_messages = client.get_required_handler::<SteamUnifiedMessages>()?;
63        let service = unified_messages.create_service(SERVICE_NAME)?;
64
65        service.set_response_type::<CCloudGetFileDetailsResponse>("GetFileDetails");
66        service.set_response_type::<CCloudGetSingleFileInfoResponse>("GetSingleFileInfo");
67        service.set_response_type::<CCloudShareFileResponse>("ShareFile");
68
69        let _ = self.service.set(Arc::clone(&service));
70
71        Ok(service)
72    }
73
74    /// Requests the details of a piece of user generated content.
75    pub async fn request_ugc_details(&self, ugc_id: UGCHandle) -> SteamResult<UGCDetailsCallback> {
76        let service = self.service()?;
77
78        let request = CCloudGetFileDetailsRequest {
79            ugcid: Some(ugc_id.value()),
80            ..Default::default()
81        };
82
83        let response = service
84            .send_message::<CCloudGetFileDetailsRequest, CCloudGetFileDetailsResponse>(
85                "GetFileDetails",
86                request,
87            )
88            .await?;
89
90        Ok(UGCDetailsCallback::from_response(
91            response.result(),
92            response.body().clone(),
93        ))
94    }
95
96    /// Requests the details of a file in the user's cloud storage.
97    pub async fn get_single_file_info(
98        &self,
99        app_id: u32,
100        filename: &str,
101    ) -> SteamResult<SingleFileInfoCallback> {
102        let service = self.service()?;
103
104        let request = CCloudGetSingleFileInfoRequest {
105            app_id: Some(app_id),
106            file_name: Some(filename.to_string()),
107            ..Default::default()
108        };
109
110        let response = service
111            .send_message::<CCloudGetSingleFileInfoRequest, CCloudGetSingleFileInfoResponse>(
112                "GetSingleFileInfo",
113                request,
114            )
115            .await?;
116
117        Ok(SingleFileInfoCallback::from_response(
118            response.result(),
119            response.body().clone(),
120        ))
121    }
122
123    /// Commits the cloud file at `filename` so its UGC handle becomes public.
124    pub async fn share_file(&self, app_id: u32, filename: &str) -> SteamResult<ShareFileCallback> {
125        let service = self.service()?;
126
127        let request = CCloudShareFileRequest {
128            app_id: Some(app_id),
129            file_name: Some(filename.to_string()),
130            ..Default::default()
131        };
132
133        let response = service
134            .send_message::<CCloudShareFileRequest, CCloudShareFileResponse>("ShareFile", request)
135            .await?;
136
137        Ok(ShareFileCallback::from_response(
138            response.result(),
139            response.body().clone(),
140        ))
141    }
142}
143
144#[async_trait]
145impl ClientMsgHandler for SteamCloud {
146    async fn handle_msg(
147        &self,
148        _client: &SteamClient,
149        _packet_msg: &dyn IPacketMsg,
150    ) -> SteamResult<()> {
151        // Nothing to handle; all responses come through unified messages.
152        Ok(())
153    }
154}