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