1use std::io;
2
3use serde::{Deserialize, Serialize};
4use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
5use uuid::Uuid;
6
7use crate::model::account::Account;
8use crate::model::api::{
9 AccountFilter, AccountIdentifier, AdminSetUserTierInfo, ServerIndex, StripeAccountTier,
10};
11use crate::model::file::ShareMode;
12use crate::model::file_metadata::{DocumentHmac, FileType};
13use crate::model::path_ops::Filter;
14use crate::service::activity::RankingWeights;
15use crate::service::events::Event;
16#[cfg(not(target_family = "wasm"))]
17use crate::subscribers::search::SearchConfig;
18
19#[derive(Debug, Serialize, Deserialize)]
20pub enum Frame {
21 Request { seq: u64, body: Request },
22 Response { seq: u64, output: Vec<u8> },
23 Event { stream_seq: u64, body: Event },
24 EventEnd { stream_seq: u64 },
25}
26
27impl Frame {
28 pub async fn read<R: AsyncRead + Unpin>(r: &mut R) -> io::Result<Self> {
29 let mut len_buf = [0u8; 4];
30 r.read_exact(&mut len_buf).await?;
31 let len = u32::from_le_bytes(len_buf) as usize;
32 let mut buf = vec![0u8; len];
33 r.read_exact(&mut buf).await?;
34 bincode::deserialize(&buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
35 }
36
37 pub async fn write<W: AsyncWrite + Unpin>(&self, w: &mut W) -> io::Result<()> {
38 let bytes =
39 bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
40 let len: u32 = bytes.len().try_into().map_err(|_| {
41 io::Error::new(
42 io::ErrorKind::InvalidData,
43 format!("frame {} bytes does not fit in a u32 length prefix", bytes.len()),
44 )
45 })?;
46 w.write_all(&len.to_le_bytes()).await?;
47 w.write_all(&bytes).await?;
48 w.flush().await
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub enum Request {
54 CreateAccount {
55 username: String,
56 api_url: String,
57 welcome_doc: bool,
58 },
59 ImportAccount {
60 key: String,
61 api_url: Option<String>,
62 },
63 ImportAccountPrivateKeyV1 {
64 account: Account,
65 },
66 ImportAccountPhrase {
67 phrase: Vec<String>,
68 api_url: String,
69 },
70 DeleteAccount,
71 GetAccount,
72
73 SuggestedDocs {
74 settings: RankingWeights,
75 },
76 ClearSuggested,
77 ClearSuggestedId {
78 id: Uuid,
79 },
80 AppForegrounded,
81
82 DisappearAccount {
83 username: String,
84 },
85 DisappearFile {
86 id: Uuid,
87 },
88 ListUsers {
89 filter: Option<AccountFilter>,
90 },
91 GetAccountInfo {
92 identifier: AccountIdentifier,
93 },
94 AdminValidateAccount {
95 username: String,
96 },
97 AdminValidateServer,
98 AdminFileInfo {
99 id: Uuid,
100 },
101 RebuildIndex {
102 index: ServerIndex,
103 },
104 SetUserTier {
105 username: String,
106 info: AdminSetUserTierInfo,
107 },
108
109 UpgradeAccountStripe {
110 account_tier: StripeAccountTier,
111 },
112 UpgradeAccountGooglePlay {
113 purchase_token: String,
114 account_id: String,
115 },
116 UpgradeAccountAppStore {
117 original_transaction_id: String,
118 app_account_token: String,
119 },
120 CancelSubscription,
121 GetSubscriptionInfo,
122
123 #[cfg(not(target_family = "wasm"))]
124 RecentPanic,
125 #[cfg(not(target_family = "wasm"))]
126 WritePanicToFile {
127 error_header: String,
128 bt: String,
129 },
130 #[cfg(not(target_family = "wasm"))]
131 DebugInfo {
132 os_info: String,
133 check_docs: bool,
134 },
135
136 ReadDocument {
137 id: Uuid,
138 user_activity: bool,
139 },
140 WriteDocument {
141 id: Uuid,
142 content: Vec<u8>,
143 },
144 ReadDocumentWithHmac {
145 id: Uuid,
146 user_activity: bool,
147 },
148 SafeWrite {
149 id: Uuid,
150 old_hmac: Option<DocumentHmac>,
151 content: Vec<u8>,
152 },
153
154 CreateFile {
155 name: String,
156 parent: Uuid,
157 file_type: FileType,
158 },
159 RenameFile {
160 id: Uuid,
161 new_name: String,
162 },
163 MoveFile {
164 id: Uuid,
165 new_parent: Uuid,
166 },
167 Delete {
168 id: Uuid,
169 },
170 Root,
171 ListMetadatas,
172 GetChildren {
173 id: Uuid,
174 },
175 GetAndGetChildrenRecursively {
176 id: Uuid,
177 },
178 GetFileById {
179 id: Uuid,
180 },
181 GetFileLinkUrl {
182 id: Uuid,
183 },
184 LocalChanges,
185
186 TestRepoIntegrity {
187 check_docs: bool,
188 },
189
190 CreateLinkAtPath {
191 path: String,
192 target_id: Uuid,
193 },
194 CreateAtPath {
195 path: String,
196 },
197 GetByPath {
198 path: String,
199 },
200 GetPathById {
201 id: Uuid,
202 },
203 ListPaths {
204 filter: Option<Filter>,
205 },
206 ListPathsWithIds {
207 filter: Option<Filter>,
208 },
209
210 ShareFile {
211 id: Uuid,
212 username: String,
213 mode: ShareMode,
214 },
215 GetPendingShares,
216 GetPendingShareFiles,
217 KnownUsernames,
218 RejectShare {
219 id: Uuid,
220 },
221
222 PinFile {
223 id: Uuid,
224 },
225 UnpinFile {
226 id: Uuid,
227 },
228 ListPinned,
229
230 GetUsage,
231
232 Sync,
233 Status,
234 GetLastSynced,
235 GetLastSyncedHuman,
236 Subscribe,
237 #[cfg(not(target_family = "wasm"))]
238 BuildIndex,
239 #[cfg(not(target_family = "wasm"))]
240 ReloadSearchIndex,
241 #[cfg(not(target_family = "wasm"))]
242 Search {
243 input: String,
244 cfg: SearchConfig,
245 },
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use tokio::io::duplex;
252
253 #[tokio::test]
254 async fn frame_round_trip() {
255 let (mut a, mut b) = duplex(64 * 1024);
256 let frame = Frame::Request { seq: 7, body: Request::Sync };
257 frame.write(&mut a).await.unwrap();
258 let got = Frame::read(&mut b).await.unwrap();
259 assert!(matches!(got, Frame::Request { seq: 7, body: Request::Sync }));
260 }
261}