1use std::sync::Arc;
4
5use crate::builders::{Flag, Query};
6use crate::error::Result;
7use crate::http::{HttpRequestSender, RequestSender};
8use crate::models::{CardId, Deck, Note, NoteId};
9
10use super::request::{
11 self, AddNoteOptions, AddNoteParams, CardsReordering, DuplicateScopeDto, FindCardsParams,
12 GuiBrowseParams, Media, NoteDto,
13};
14
15pub struct CardClient {
17 sender: Arc<HttpRequestSender>,
18}
19
20impl CardClient {
21 pub(crate) fn new(sender: Arc<HttpRequestSender>) -> Self {
23 Self { sender }
24 }
25
26 pub(crate) fn get_version(&self) -> Result<u16> {
28 self.sender.send::<(), u16>("version", None)
29 }
30
31 pub fn add_note(
47 &self,
48 deck: &Deck,
49 note: Note,
50 allow_duplicate: bool,
51 duplicate_scope: Option<DuplicateScope>,
52 ) -> Result<NoteId> {
53 let note_dto = self.prepare_note_dto(deck, ¬e, allow_duplicate, duplicate_scope);
56
57 let params = AddNoteParams { note: note_dto };
59 let note_id = self.sender.send("addNote", Some(params))?;
60
61 Ok(NoteId(note_id))
62 }
63
64 pub fn find(&self, query: &Query) -> Result<Vec<CardId>> {
74 let params = FindCardsParams {
75 query: query.as_str(),
76 };
77 let ids = self.sender.send::<_, Vec<u64>>("findCards", Some(params))?;
78 Ok(ids.into_iter().map(CardId).collect())
79 }
80
81 pub fn browse(&self, query: &str) -> Result<Vec<CardId>> {
91 let params = GuiBrowseParams {
92 query: query.to_string(),
93 reorder_cards: None,
94 };
95 let ids = self.sender.send::<_, Vec<u64>>("guiBrowse", Some(params))?;
96 Ok(ids.into_iter().map(CardId).collect())
97 }
98
99 pub fn browse_sorted(
111 &self,
112 query: &str,
113 column: SortColumn,
114 sort_direction: SortDirection,
115 ) -> Result<Vec<CardId>> {
116 let params = GuiBrowseParams {
117 query: query.to_string(),
118 reorder_cards: Some(CardsReordering {
119 order: sort_direction.into(),
120 column_id: column.into(),
121 }),
122 };
123
124 let ids = self.sender.send::<_, Vec<u64>>("guiBrowse", Some(params))?;
125 Ok(ids.into_iter().map(CardId).collect())
126 }
127
128 pub fn delete_notes(&self, note_ids: &[NoteId]) -> Result<()> {
134 let ids: Vec<u64> = note_ids.iter().map(|id| id.0).collect();
135 let params = request::DeleteNotesParams { notes: ids };
136 self.sender.send::<_, ()>("deleteNotes", Some(params))
137 }
138
139 pub fn suspend_cards(&self, card_ids: &[CardId]) -> Result<()> {
145 let ids: Vec<u64> = card_ids.iter().map(|id| id.0).collect();
146 let params = request::CardIdsParams { cards: ids };
147 self.sender.send::<_, ()>("suspend", Some(params))
148 }
149
150 pub fn unsuspend_cards(&self, card_ids: &[CardId]) -> Result<()> {
156 let ids: Vec<u64> = card_ids.iter().map(|id| id.0).collect();
157 let params = request::CardIdsParams { cards: ids };
158 self.sender.send::<_, ()>("unsuspend", Some(params))
159 }
160
161 pub fn set_flag(&self, card_ids: &[CardId], flag: Flag) -> Result<()> {
168 let ids: Vec<u64> = card_ids.iter().map(|id| id.0).collect();
169 let params = request::SetFlagParams {
170 cards: ids,
171 flag: flag as u8,
172 };
173
174 self.sender.send::<_, ()>("setFlag", Some(params))
175 }
176
177 pub fn get_note_info(&self, note_id: NoteId) -> Result<request::NoteInfo> {
187 let params = request::NoteIdParam { note: note_id.0 };
188 self.sender.send("noteInfo", Some(params))
189 }
190
191 fn prepare_note_dto(
193 &self,
194 deck: &Deck,
195 note: &Note,
196 allow_duplicate: bool,
197 duplicate_scope: Option<DuplicateScope>,
198 ) -> NoteDto {
199 let mut audio = Vec::new();
201 let mut video = Vec::new();
202 let mut picture = Vec::new();
203
204 for field_media in note.media() {
205 let media = Media {
206 path: field_media.media.source().path().map(|p| p.to_path_buf()),
207 url: field_media.media.source().url().map(|u| u.to_string()),
208 data: field_media.media.source().data().map(|d| d.to_string()),
209 filename: field_media.media.filename().to_string(),
210 fields: vec![field_media.field.clone()],
211 };
212
213 match field_media.media.media_type() {
214 crate::models::MediaType::Audio => audio.push(media),
215 crate::models::MediaType::Video => video.push(media),
216 crate::models::MediaType::Image => picture.push(media),
217 }
218 }
219
220 let duplicate_scope_options = if let Some(_scope) = &duplicate_scope {
222 None
224 } else {
225 None
226 };
227
228 NoteDto {
230 deck_name: deck.name().to_string(),
231 model_name: note.model().name().to_string(),
232 fields: note.field_values().clone(),
233 options: AddNoteOptions {
234 allow_duplicate,
235 duplicate_scope: duplicate_scope.map(|ds| ds.into()),
236 duplicate_scope_options,
237 },
238 tags: note.tags().iter().cloned().collect(),
239 audio,
240 video,
241 picture,
242 }
243 }
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum DuplicateScope {
249 Deck,
251
252 Collection,
254}
255
256impl From<DuplicateScope> for DuplicateScopeDto {
257 fn from(value: DuplicateScope) -> Self {
258 match value {
259 DuplicateScope::Deck => Self::Deck,
260 DuplicateScope::Collection => Self::Collection,
261 }
262 }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum SortColumn {
268 Answer,
269 CardModified,
270 Cards,
271 Deck,
272 Due,
273 Ease,
274 Lapses,
275 Interval,
276 NoteCreation,
277 NoteMod,
278 NoteType,
279 OriginalPosition,
280 Question,
281 Reps,
282 SortField,
283 Tags,
284 Stability,
285 Difficulty,
286 Retrievability,
287}
288
289impl From<SortColumn> for request::ColumnIdentifier {
290 fn from(value: SortColumn) -> Self {
291 match value {
292 SortColumn::Answer => Self::Answer,
293 SortColumn::CardModified => Self::CardMod,
294 SortColumn::Cards => Self::Cards,
295 SortColumn::Deck => Self::Deck,
296 SortColumn::Due => Self::Due,
297 SortColumn::Ease => Self::Ease,
298 SortColumn::Lapses => Self::Lapses,
299 SortColumn::Interval => Self::Interval,
300 SortColumn::NoteCreation => Self::NoteCreation,
301 SortColumn::NoteMod => Self::NoteMod,
302 SortColumn::NoteType => Self::Notetype,
303 SortColumn::OriginalPosition => Self::OriginalPosition,
304 SortColumn::Question => Self::Question,
305 SortColumn::Reps => Self::Reps,
306 SortColumn::SortField => Self::SortField,
307 SortColumn::Tags => Self::Tags,
308 SortColumn::Stability => Self::Stability,
309 SortColumn::Difficulty => Self::Difficulty,
310 SortColumn::Retrievability => Self::Retrievability,
311 }
312 }
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub enum SortDirection {
318 Ascending,
319 Descending,
320}
321
322impl From<SortDirection> for request::SortOrder {
323 fn from(value: SortDirection) -> Self {
324 match value {
325 SortDirection::Ascending => Self::Ascending,
326 SortDirection::Descending => Self::Descending,
327 }
328 }
329}