1use std::{
53 mem,
54 sync::{atomic, mpsc},
55 time::Duration,
56};
57
58use bon::bon;
59use tracing::{debug, error, instrument, trace, warn};
60use windows::Win32::{
61 Foundation::{HWND, LPARAM, LRESULT, WPARAM},
62 System::DataExchange::COPYDATASTRUCT,
63 UI::{
64 Controls::WC_STATIC,
65 WindowsAndMessaging::{
66 CreateWindowExW, DefWindowProcW, DispatchMessageW, GWL_USERDATA, GWLP_WNDPROC,
67 GetMessageW, GetWindowLongPtrW, MSG, PostMessageW, ReplyMessage, SendMessageW,
68 SetWindowLongPtrW, WINDOW_EX_STYLE, WINDOW_STYLE, WM_APP, WM_COPYDATA, WM_QUIT,
69 },
70 },
71};
72
73use crate::IpcWindow;
74
75mod types;
76pub use types::*;
77mod ext;
78
79#[derive(Debug, thiserror::Error)]
81pub enum IpcError {
82 #[error("IPC window not found")]
84 NoIpcWindow,
85
86 #[error("failed to create reply window")]
88 CreateReplyWindow,
89
90 #[error("failed to send query to Everything")]
92 Send,
93
94 #[error("query timed out")]
96 Timeout,
97
98 #[error("query: {0}")]
99 Query(&'static str),
100}
101
102#[derive(Debug)]
143struct ReplyWindow {
144 hwnd: HWND,
145 _thread: mem::MaybeUninit<std::thread::JoinHandle<()>>,
148}
149
150unsafe impl Send for ReplyWindow {}
156unsafe impl Sync for ReplyWindow {}
159
160#[derive(Debug)]
162struct MessageLoopResult {
163 hwnd_usize: usize,
164}
165
166impl ReplyWindow {
167 pub fn new(inner: Box<ClientInner>) -> Result<Self, IpcError> {
169 let (tx, rx) = mpsc::channel::<MessageLoopResult>();
177
178 let thread = std::thread::spawn(move || {
184 let hwnd = unsafe {
186 CreateWindowExW(
187 WINDOW_EX_STYLE(0),
188 WC_STATIC,
190 None,
191 WINDOW_STYLE(0),
192 0,
193 0,
194 0,
195 0,
196 None,
197 None,
198 None,
200 None,
201 )
202 };
203
204 let hwnd = match hwnd.ok() {
205 Some(h) => h,
206 None => {
207 debug!("Failed to create window in message loop thread");
208 let _ = tx.send(MessageLoopResult { hwnd_usize: 0 });
209 return;
210 }
211 };
212
213 if let Err(_) = tx.send(MessageLoopResult {
215 hwnd_usize: hwnd.0 as usize,
216 }) {
217 return;
219 }
220
221 debug!(?hwnd, "Created reply window");
222
223 let inner_ptr = Box::into_raw(inner);
225 unsafe { SetWindowLongPtrW(hwnd, GWL_USERDATA, inner_ptr as isize) };
226
227 unsafe {
228 SetWindowLongPtrW(
229 hwnd,
230 GWLP_WNDPROC,
231 reply_window_wndproc as *const () as isize,
232 )
233 };
234
235 run_message_loop(hwnd);
237 });
238
239 let result = rx.recv().map_err(|_| IpcError::CreateReplyWindow)?;
241 let MessageLoopResult { hwnd_usize } = result;
242
243 let hwnd = HWND(hwnd_usize as *mut _);
244 if hwnd.is_invalid() {
245 return Err(IpcError::CreateReplyWindow);
246 }
247
248 Ok(Self {
249 hwnd,
250 _thread: mem::MaybeUninit::new(thread),
251 })
252 }
253
254 pub fn hwnd(&self) -> HWND {
256 self.hwnd
257 }
258
259 pub fn post_message(
261 &self,
262 msg: u32,
263 w_param: WPARAM,
264 l_param: LPARAM,
265 ) -> Result<(), windows::core::Error> {
266 unsafe { PostMessageW(Some(self.hwnd), msg, w_param, l_param) }
267 }
268
269 pub fn quit(&self) {
273 let _ = self.post_message(WM_QUIT, WPARAM(0), LPARAM(0));
274 }
275}
276
277impl Drop for ReplyWindow {
278 fn drop(&mut self) {
279 self.quit();
282
283 let _thread = unsafe { self._thread.assume_init_read() };
284 #[cfg(feature = "drop-join-thread")]
289 let _ = _thread.join();
290 }
291}
292
293#[derive(Debug)]
295pub struct QueryResponse {
296 pub id: u32,
297 pub data: Vec<u8>,
298}
299
300#[instrument(skip_all, fields(hwnd))]
302unsafe extern "system" fn reply_window_wndproc(
303 hwnd: HWND,
304 msg: u32,
305 w_param: WPARAM,
306 l_param: LPARAM,
307) -> LRESULT {
308 match msg {
310 WM_APP => {
312 let request_ptr = w_param.0 as *mut Vec<u8>;
315 let request = unsafe { Box::from_raw(request_ptr) };
316
317 let inner_ptr = unsafe { GetWindowLongPtrW(hwnd, GWL_USERDATA) };
319 if inner_ptr != 0 {
320 let inner = unsafe { &*(inner_ptr as *const ClientInner) };
321 let ipc_hwnd = inner.ipc_window.hwnd();
322
323 let cds = COPYDATASTRUCT {
325 dwData: EVERYTHING_IPC_COPYDATA_QUERY2W as usize,
326 cbData: request.len() as u32,
327 lpData: request.as_ptr() as *mut _,
328 };
329
330 let cds_ptr = &cds as *const COPYDATASTRUCT;
332
333 let r = unsafe {
335 SendMessageW(
336 ipc_hwnd,
337 WM_COPYDATA,
338 Some(WPARAM(hwnd.0 as usize)),
339 Some(LPARAM(cds_ptr as isize)),
340 )
341 };
342 if r.0 == 1 {
343 trace!(?ipc_hwnd, ?r);
344 } else {
345 warn!(?ipc_hwnd, ?r);
346 drop(inner.take_current_query_sender());
349 }
350 }
351
352 LRESULT(0)
355 }
356 WM_COPYDATA => {
358 let copydata = unsafe { &*(l_param.0 as *const COPYDATASTRUCT) };
359 let id = copydata.dwData as u32;
362
363 let inner_ptr = unsafe { GetWindowLongPtrW(hwnd, GWL_USERDATA) } as *const ClientInner;
365 if inner_ptr.is_null() {
366 error!("No object found");
367 return LRESULT(0);
368 }
369
370 let inner = unsafe { &*inner_ptr };
372 if let Some(sender) = inner.take_current_query_sender() {
373 if match &sender {
374 QuerySender::Sync(_sender) => {
375 false
382 }
383 #[cfg(feature = "tokio")]
384 QuerySender::Tokio(sender) => sender.is_closed(),
385 } {
386 return LRESULT(1);
387 }
388
389 let data = unsafe {
392 std::slice::from_raw_parts(
393 copydata.lpData as *const u8,
394 copydata.cbData as usize,
395 )
396 }
397 .into();
398 _ = unsafe { ReplyMessage(LRESULT(1)) };
400 trace!(id, cbData = copydata.cbData, "WM_COPYDATA received");
401
402 let results = QueryList::new(id, data);
403 if match sender {
404 QuerySender::Sync(sender) => sender.send(results).is_ok(),
405 #[cfg(feature = "tokio")]
406 QuerySender::Tokio(sender) => sender.send(results).is_ok(),
407 } {
408 debug!(id, "Sent query response");
409 } else {
410 warn!(id, "Failed to send query response");
411 }
412 } else {
413 warn!(id, "No pending query");
414 }
415
416 LRESULT(1)
417 }
418 _ => unsafe { DefWindowProcW(hwnd, msg, w_param, l_param) },
419 }
420}
421
422fn run_message_loop(hwnd: HWND) {
425 unsafe {
426 let mut msg: MSG = mem::zeroed();
427 let mut ret;
428 loop {
429 ret = GetMessageW(&mut msg, Some(hwnd), 0, 0);
430 if ret.0 <= 0 {
431 break;
432 }
433 DispatchMessageW(&mut msg);
434 }
435 }
436
437 let inner_ptr = unsafe { GetWindowLongPtrW(hwnd, GWL_USERDATA) };
439 if inner_ptr != 0 {
440 drop(unsafe { Box::from_raw(inner_ptr as *mut ClientInner) });
441 }
442}
443
444enum QuerySender {
446 Sync(mpsc::Sender<QueryList>),
447 #[cfg(feature = "tokio")]
448 Tokio(tokio::sync::oneshot::Sender<QueryList>),
449}
450
451struct ClientInner {
453 ipc_window: IpcWindow,
454 current_query_sender: std::sync::Mutex<Option<QuerySender>>,
457}
458
459impl ClientInner {
460 pub fn take_current_query_sender(&self) -> Option<QuerySender> {
462 match self.current_query_sender.lock() {
463 Ok(mut sender) => sender.take(),
464 #[cfg(debug_assertions)]
465 Err(e) => Err(e).unwrap(),
466 #[cfg(not(debug_assertions))]
467 Err(e) => {
468 error!("poison");
469 None
472 }
473 }
474 }
475}
476
477pub struct EverythingClient {
483 inner: &'static ClientInner,
485 reply_window: ReplyWindow,
486}
487
488impl IpcWindow {
489 pub fn wm_client(&self) -> Result<EverythingClient, IpcError> {
490 let inner = Box::new(ClientInner {
492 ipc_window: self.clone(),
493 current_query_sender: std::sync::Mutex::new(None),
494 });
495 let inner_ref: &ClientInner = inner.as_ref();
496 let inner_ref: &'static ClientInner = unsafe { mem::transmute(inner_ref) };
497
498 let reply_window = ReplyWindow::new(inner)?;
501
502 let inner = inner_ref;
503 Ok(EverythingClient {
504 inner,
505 reply_window,
506 })
507 }
508}
509
510impl std::ops::Deref for EverythingClient {
511 type Target = IpcWindow;
512
513 fn deref(&self) -> &Self::Target {
514 self.ipc_window()
515 }
516}
517
518impl EverythingClient {
519 pub fn new() -> Result<Self, IpcError> {
521 IpcWindow::new().ok_or(IpcError::NoIpcWindow)?.wm_client()
522 }
523
524 pub fn with_instance(instance_name: Option<&str>) -> Result<Self, IpcError> {
526 IpcWindow::with_instance(instance_name)
527 .ok_or(IpcError::NoIpcWindow)?
528 .wm_client()
529 }
530
531 fn ipc_window(&self) -> &IpcWindow {
533 &self.inner.ipc_window
534 }
535
536 fn next_id(&self) -> u32 {
538 static NEXT_ID: atomic::AtomicU32 = atomic::AtomicU32::new(0);
539 NEXT_ID.fetch_add(1, atomic::Ordering::SeqCst)
540 }
541
542 fn query_send(
544 &self,
545 search: &str,
546 search_flags: SearchFlags,
547 request_flags: RequestFlags,
548 sort: Sort,
549 id: u32,
550 offset: u32,
551 max_results: Option<u32>,
552 ) -> bool {
553 let msg_hwnd = self.reply_window.hwnd();
554
555 let request = EverythingIpcQuery2::create(
557 msg_hwnd.0 as u32,
558 id,
559 search_flags.bits(),
560 offset,
561 max_results.unwrap_or(u32::MAX),
562 request_flags.bits(),
563 sort as u32,
564 search,
565 );
566
567 let request_box = Box::new(request);
569 let request_ptr = Box::into_raw(request_box);
570
571 match self
579 .reply_window
580 .post_message(WM_APP, WPARAM(request_ptr as usize), LPARAM(0))
581 {
582 Ok(_) => true,
583 Err(_) => {
584 let _ = unsafe { Box::from_raw(request_ptr) };
586 false
587 }
588 }
589 }
590}
591
592#[bon]
593impl EverythingClient {
594 #[instrument(skip_all)]
627 #[builder]
628 pub fn query(
629 &self,
630 #[builder(start_fn)] search: &str,
631 #[builder(default)] search_flags: SearchFlags,
632 request_flags: RequestFlags,
633 #[builder(default)] sort: Sort,
634 #[builder(default)] offset: u32,
635 max_results: Option<u32>,
636 ) -> Result<mpsc::Receiver<QueryList>, IpcError> {
637 let id = self.next_id();
638 debug!("generating query ID {}", id);
639
640 let (sender, receiver) = mpsc::channel::<QueryList>();
642
643 let sent = self.query_send(
645 search,
646 search_flags,
647 request_flags,
648 sort,
649 id,
650 offset,
651 max_results,
652 );
653
654 if !sent {
655 warn!("failed to send query ID {}", id);
656 return Err(IpcError::Send);
657 }
658 debug!("query ID {} sent successfully", id);
659
660 let old_sender = self
663 .inner
664 .current_query_sender
665 .lock()
666 .unwrap()
667 .replace(QuerySender::Sync(sender));
668 drop(old_sender);
670
671 Ok(receiver)
672 }
673
674 #[instrument(skip_all)]
679 #[builder]
680 pub fn query_wait(
681 &self,
682 #[builder(start_fn)] search: &str,
683 #[builder(default)] search_flags: SearchFlags,
684 request_flags: RequestFlags,
685 #[builder(default)] sort: Sort,
686 #[builder(default)] offset: u32,
687 max_results: Option<u32>,
688 #[builder(default = Duration::from_millis(3000))] timeout: Duration,
689 ) -> Result<QueryList, IpcError> {
690 let receiver = self
692 .query(search)
693 .search_flags(search_flags)
694 .request_flags(request_flags)
695 .sort(sort)
696 .offset(offset)
697 .maybe_max_results(max_results)
698 .call()?;
699
700 match receiver.recv_timeout(timeout) {
702 Ok(results) => Ok(results),
703 Err(_) => {
704 warn!("query timed out");
705 Err(IpcError::Timeout)
706 }
707 }
708 }
709}
710
711#[cfg(feature = "tokio")]
712#[bon]
713impl EverythingClient {
714 #[instrument(skip_all)]
747 #[builder]
748 pub fn query_tokio(
749 &self,
750 #[builder(start_fn)] search: &str,
751 #[builder(default)] search_flags: SearchFlags,
752 request_flags: RequestFlags,
753 #[builder(default)] sort: Sort,
754 #[builder(default)] offset: u32,
755 max_results: Option<u32>,
756 ) -> Result<tokio::sync::oneshot::Receiver<QueryList>, IpcError> {
757 let id = self.next_id();
758 debug!("generating query ID {}", id);
759
760 let (sender, receiver) = tokio::sync::oneshot::channel::<QueryList>();
762
763 let sent = self.query_send(
765 search,
766 search_flags,
767 request_flags,
768 sort,
769 id,
770 offset,
771 max_results,
772 );
773
774 if !sent {
775 warn!("failed to send query ID {}", id);
776 return Err(IpcError::Send);
777 }
778 debug!("query ID {} sent successfully", id);
779
780 let old_sender = self
783 .inner
784 .current_query_sender
785 .lock()
786 .unwrap()
787 .replace(QuerySender::Tokio(sender));
788 drop(old_sender);
790
791 Ok(receiver)
792 }
793
794 #[instrument(skip_all)]
799 #[builder]
800 pub async fn query_wait_tokio(
801 &self,
802 #[builder(start_fn)] search: &str,
803 #[builder(default)] search_flags: SearchFlags,
804 request_flags: RequestFlags,
805 #[builder(default)] sort: Sort,
806 #[builder(default)] offset: u32,
807 max_results: Option<u32>,
808 #[builder(default = Duration::from_millis(3000))] timeout: Duration,
809 ) -> Result<QueryList, IpcError> {
810 let receiver = self
812 .query_tokio(search)
813 .search_flags(search_flags)
814 .request_flags(request_flags)
815 .sort(sort)
816 .offset(offset)
817 .maybe_max_results(max_results)
818 .call()?;
819
820 match tokio::time::timeout(timeout, receiver).await {
822 Ok(Ok(results)) => Ok(results),
823 Ok(Err(_)) => {
824 warn!("query receiver error");
825 Err(IpcError::Send)
826 }
827 Err(_) => {
828 warn!("query timed out");
829 Err(IpcError::Timeout)
830 }
831 }
832 }
833}
834
835#[cfg(test)]
836mod tests {
837 use super::*;
838
839 #[test_log::test]
840 #[test_log(default_log_filter = "trace")]
841 fn doc() {
842 let everything = EverythingClient::new().expect("not available");
843
844 let list = everything
845 .query_wait(r"C:\Windows\ *.exe")
846 .request_flags(RequestFlags::FileName | RequestFlags::Size | RequestFlags::Path)
847 .sort(Sort::SizeDescending)
848 .max_results(5)
849 .call()
850 .expect("query");
851
852 println!("Found {} items:", list.len());
853 println!("{:<25} {:>10} {}", "Filename", "Size", "Path");
854 for item in list.iter() {
855 let filename = item.get_string(RequestFlags::FileName).unwrap();
857 let path = item.get_str(RequestFlags::Path).unwrap().display();
858 let size = item.get_size(RequestFlags::Size).unwrap();
859 println!("{:<25} {:>10} {}", filename, size, path);
860 }
861 println!("Total: {} items", list.total_len());
862 }
863
864 #[test_log::test]
865 #[test_log(default_log_filter = "trace")]
866 fn query_empty_search() {
867 let everything = EverythingClient::new().unwrap();
868 let search = "";
869 let search_flags = SearchFlags::MatchCase;
870 let request_flags = RequestFlags::FileName | RequestFlags::Path;
871 let sort = Sort::NameAscending;
872
873 let result =
875 everything.query_send(search, search_flags, request_flags, sort, 1000, 0, Some(5));
876
877 assert!(result, "Query should be sent successfully");
878 }
879
880 #[test_log::test]
881 #[test_log(default_log_filter = "trace")]
882 fn query_with_pattern() {
883 let everything = EverythingClient::new().unwrap();
884 let search = "test";
885 let search_flags = SearchFlags::MatchCase;
886 let request_flags = RequestFlags::FileName;
887 let sort = Sort::NameAscending;
888
889 let result =
890 everything.query_send(search, search_flags, request_flags, sort, 1001, 0, Some(10));
891
892 assert!(result, "Query should be sent successfully");
893 }
894
895 #[test]
896 fn query_with_full_path() {
897 let everything = EverythingClient::new().unwrap();
898 let search = "";
899 let search_flags = SearchFlags::MatchCase;
900 let request_flags =
901 RequestFlags::FullPathAndFileName | RequestFlags::Size | RequestFlags::DateModified;
902 let sort = Sort::NameAscending;
903
904 let result =
905 everything.query_send(search, search_flags, request_flags, sort, 1002, 0, Some(3));
906
907 assert!(result, "Query should be sent successfully");
908 }
909
910 #[test]
911 fn query_sort_by_size() {
912 let everything = EverythingClient::new().unwrap();
913 let search = "";
914 let search_flags = SearchFlags::MatchCase;
915 let request_flags = RequestFlags::FileName | RequestFlags::Size;
916 let sort = Sort::SizeAscending;
917
918 let result =
919 everything.query_send(search, search_flags, request_flags, sort, 1003, 0, Some(5));
920
921 assert!(result, "Query should be sent successfully");
922 }
923
924 #[test]
925 fn query_with_offset() {
926 let everything = EverythingClient::new().unwrap();
927 let search = "";
928 let search_flags = SearchFlags::MatchCase;
929 let request_flags = RequestFlags::FileName;
930 let sort = Sort::NameAscending;
931
932 let result1 =
934 everything.query_send(search, search_flags, request_flags, sort, 1005, 0, Some(2));
935
936 assert!(result1, "First query should be sent successfully");
937
938 let result2 =
940 everything.query_send(search, search_flags, request_flags, sort, 1006, 2, Some(2));
941
942 assert!(
943 result2,
944 "Second query with offset should be sent successfully"
945 );
946 }
947
948 #[test]
949 fn query_everything() {
950 let everything = EverythingClient::new().unwrap();
951 let search = "test";
952 let search_flags = SearchFlags::MatchCase;
953 let request_flags = RequestFlags::FileName;
954 let sort = Sort::NameAscending;
955
956 let result = everything.query_send(
957 search,
958 search_flags,
959 request_flags,
960 sort,
961 everything.next_id(),
962 0,
963 Some(5),
964 );
965
966 assert!(result, "Query should be sent successfully");
967 }
968
969 #[test]
970 fn query_multiple_requests() {
971 let everything = EverythingClient::new().unwrap();
972 let search = "";
973 let search_flags = SearchFlags::MatchCase;
974 let request_flags = RequestFlags::FileName
975 | RequestFlags::Path
976 | RequestFlags::Size
977 | RequestFlags::DateModified
978 | RequestFlags::DateCreated;
979 let sort = Sort::NameAscending;
980
981 let result =
982 everything.query_send(search, search_flags, request_flags, sort, 1004, 0, Some(5));
983
984 assert!(result, "Query should be sent successfully");
985 }
986
987 #[test]
988 fn query_wait_empty() {
989 let everything = EverythingClient::new().unwrap();
990 let search = "";
991 let search_flags = SearchFlags::MatchCase;
992 let request_flags = RequestFlags::FileName;
993 let sort = Sort::NameAscending;
994
995 assert!(everything.is_ipc_available(), "IPC should be available");
997
998 let result = everything
999 .query_wait(search)
1000 .search_flags(search_flags)
1001 .request_flags(request_flags)
1002 .sort(sort)
1003 .offset(0)
1004 .max_results(10)
1005 .call();
1006 assert!(
1007 result.is_ok(),
1008 "query_wait should return Ok when Everything is available"
1009 );
1010 }
1011
1012 #[test_log::test]
1013 #[test_log(default_log_filter = "trace")]
1014 fn query_wait() {
1015 let everything = EverythingClient::new().unwrap();
1016 let search = "test";
1019 let search_flags = SearchFlags::MatchCase;
1021 let request_flags = RequestFlags::FileName;
1022 let sort = Sort::NameAscending;
1023
1024 assert!(everything.is_ipc_available(), "IPC should be available");
1026
1027 let result = everything
1028 .query_wait(search)
1029 .search_flags(search_flags)
1030 .request_flags(request_flags)
1031 .sort(sort)
1032 .offset(0)
1033 .max_results(10)
1034 .call();
1035 dbg!(&result);
1036 assert!(
1037 result.is_ok(),
1038 "query_wait should return Ok when Everything is available"
1039 );
1040 assert!(
1041 result.as_ref().is_ok_and(|r| r.total_len() > 0),
1042 "Expected found_num > 0, got: {:?}",
1043 result
1044 );
1045 }
1046
1047 #[test_log::test]
1048 #[test_log(default_log_filter = "trace")]
1049 fn query_wait_cancel() {
1050 let everything = EverythingClient::new().unwrap();
1051
1052 assert!(everything.is_ipc_available(), "IPC should be available");
1054
1055 let searches = ["", "test", "rust"];
1059 let mut receivers = Vec::new();
1060
1061 for search in &searches {
1062 let search_flags = SearchFlags::MatchCase;
1063 let request_flags = RequestFlags::FileName;
1064 let sort = Sort::NameAscending;
1065 let receiver = everything
1066 .query(search)
1067 .search_flags(search_flags)
1068 .request_flags(request_flags)
1069 .sort(sort)
1070 .offset(0)
1071 .max_results(10)
1072 .call()
1073 .expect("query should succeed");
1074 receivers.push(receiver);
1075 }
1076
1077 let result = receivers[0].recv_timeout(std::time::Duration::from_millis(3000));
1081 assert!(
1082 result.is_err(),
1083 "Query 0 should fail because sender was replaced (got: {:?})",
1084 result
1085 );
1086
1087 let result = receivers[1].recv_timeout(std::time::Duration::from_millis(3000));
1089 assert!(
1090 result.is_err(),
1091 "Query 1 should fail because sender was replaced (got: {:?})",
1092 result
1093 );
1094
1095 let result = receivers[2].recv_timeout(std::time::Duration::from_millis(3000));
1097 let result = result.expect("Last query should succeed");
1098 dbg!(&result);
1099 assert!(
1100 result.total_len() > 0,
1101 "Last query should return valid results"
1102 );
1103 }
1104
1105 #[test_log::test]
1106 #[test_log(default_log_filter = "trace")]
1107 fn query_wait_parallel() {
1108 let everything1 = EverythingClient::new().unwrap();
1110 let everything2 = EverythingClient::new().unwrap();
1111 let everything3 = EverythingClient::new().unwrap();
1112
1113 assert!(everything1.is_ipc_available(), "IPC should be available");
1114
1115 let receiver1 = everything1
1118 .query("")
1119 .search_flags(SearchFlags::MatchCase)
1120 .request_flags(RequestFlags::FileName)
1121 .sort(Sort::NameAscending)
1122 .offset(0)
1123 .max_results(10)
1124 .call()
1125 .expect("query should succeed");
1126 let receiver2 = everything2
1127 .query("test")
1128 .search_flags(SearchFlags::MatchCase)
1129 .request_flags(RequestFlags::FileName)
1130 .sort(Sort::NameAscending)
1131 .offset(0)
1132 .max_results(10)
1133 .call()
1134 .expect("query should succeed");
1135 let receiver3 = everything3
1136 .query("rust")
1137 .search_flags(SearchFlags::MatchCase)
1138 .request_flags(RequestFlags::FileName)
1139 .sort(Sort::NameAscending)
1140 .offset(0)
1141 .max_results(10)
1142 .call()
1143 .expect("query should succeed");
1144
1145 for (i, receiver) in [receiver1, receiver2, receiver3].into_iter().enumerate() {
1147 let result = receiver.recv_timeout(std::time::Duration::from_millis(5000));
1148 let result = result.expect(&format!("Query {} timed out", i));
1149 dbg!(&result);
1150 assert!(result.len() > 0, "Query {} should return valid results", i);
1151 }
1152 }
1153}