everything_rs/
lib.rs

1//! # Everything  
2//! This crate provides a safe wrapper around the `everything-sys-bindgen`.  
3//! `everything-sys-bindgen` is a rust binding to the [Everything SDK](https://www.voidtools.com/support/everything/sdk/) that allow IPC communication to the everything service.  
4//! The Everything service indexes files on windows and provides a expressive query syntax to search for files.  
5//! See the [Everything SDK documentation](https://www.voidtools.com/support/everything/sdk/) for more information.  
6//!
7//! # Example
8//! ```rust
9//! use everything_rs::{Everything, EverythingRequestFlags, EverythingSort, EverythingError};
10//!
11//! fn main() -> Result<(), EverythingError> {
12//!    let mut everything = Everything::new();
13//!
14//!    everything.set_search("test");
15//!
16//!    everything.set_request_flags(
17//!        EverythingRequestFlags::FullPathAndFileName
18//!        | EverythingRequestFlags::Size
19//!        | EverythingRequestFlags::DateCreated
20//!    );
21//!
22//!    everything.set_sort(EverythingSort::DateCreatedDescending);
23//!
24//!    everything.query()?;
25//!
26//!    let num_results = everything.get_num_results();
27//!
28//!    assert!(num_results > 0);
29//!
30//!    for (i, path) in everything.full_path_iter().flatten().enumerate() {
31//!        let size = everything.get_result_size(i as u32)?;
32//!        let date_created = everything.get_result_created_date(i as u32)?;
33//!        println!("{}: {} {} {}", i, path, size, date_created);
34//!    }
35//!
36//!    Ok(())
37//! }
38//! ```
39
40#![allow(non_upper_case_globals)]
41#![allow(non_camel_case_types)]
42#![allow(non_snake_case)]
43
44mod error;
45mod sort;
46
47#[cfg(target_os = "windows")]
48extern crate everything_sys_bindgen;
49
50use bitflags::bitflags;
51pub use error::{EverythingError, EverythingResult, EverythingSDKError};
52use everything_sys_bindgen::*;
53pub use sort::EverythingSort;
54use std::time::Duration;
55use widestring::{U16CStr, U16CString};
56
57bitflags! {
58    /// Input to the Everything.set_request_flags() function.
59    /// Defines what file information is loaded into memory by everything when query is called.
60    /// See <https://www.voidtools.com/support/everything/sdk/everything_getrequestflags/> for more information.
61    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
62    pub struct EverythingRequestFlags: u32 {
63        const FileName = EVERYTHING_REQUEST_FILE_NAME;
64        const Path = EVERYTHING_REQUEST_PATH;
65        const FullPathAndFileName = EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME;
66        const Extension = EVERYTHING_REQUEST_EXTENSION;
67        const Size = EVERYTHING_REQUEST_SIZE;
68        const DateCreated = EVERYTHING_REQUEST_DATE_CREATED;
69        const DateModified = EVERYTHING_REQUEST_DATE_MODIFIED;
70        const DateAccessed = EVERYTHING_REQUEST_DATE_ACCESSED;
71        const Attributes = EVERYTHING_REQUEST_ATTRIBUTES;
72        const FileListFileName = EVERYTHING_REQUEST_FILE_LIST_FILE_NAME;
73        const RunCount = EVERYTHING_REQUEST_RUN_COUNT;
74        const DateRun = EVERYTHING_REQUEST_DATE_RUN;
75        const DateRecentlyChanged = EVERYTHING_REQUEST_DATE_RECENTLY_CHANGED;
76        const HighlightedFileName = EVERYTHING_REQUEST_HIGHLIGHTED_FILE_NAME;
77        const HighlightedPath = EVERYTHING_REQUEST_HIGHLIGHTED_PATH;
78        const HighlightedFullPathAndFileName = EVERYTHING_REQUEST_HIGHLIGHTED_FULL_PATH_AND_FILE_NAME;
79    }
80}
81
82trait U64Able {
83    fn as_u64(&self) -> u64;
84}
85
86impl U64Able for FILETIME {
87    fn as_u64(&self) -> u64 {
88        ((self.dwHighDateTime as u64) << 32) | (self.dwLowDateTime as u64)
89    }
90}
91
92/// Checks for a null pointer and gets the last everything error if there is one.   
93/// Otherwise, iterate until null is reached and return a string.  
94/// # Arguments  
95///
96/// * `ptr` - A pointer to a u16 string returned by the Everything API.  
97fn parse_string_ptr(ptr: *const u16) -> EverythingResult<String> {
98    if ptr.is_null() {
99        let error_code = Everything::get_last_error();
100        panic!("Error code: {:?}", error_code);
101    }
102
103    Ok(unsafe { U16CStr::from_ptr_str(ptr).to_string_lossy() })
104}
105
106/// A wrapper around the Everything API.  
107/// Calls cleanup on drop.  
108#[derive(Debug)]
109pub struct Everything;
110
111impl Everything {
112    /// See <https://www.voidtools.com/support/everything/sdk/everything_getlasterror/>  
113    pub fn get_last_error() -> EverythingResult<()> {
114        let error_code = unsafe { Everything_GetLastError() };
115        match error_code.try_into().unwrap() {
116            EverythingSDKError::Ok => Ok(()),
117            err => Err(EverythingError::SDKError(err)),
118        }
119    }
120
121    /// Sleep the current thread until the Everything database is loaded.  
122    /// See <https://www.voidtools.com/support/everything/sdk/everything_isdbloaded/>  
123    pub fn wait_db_loaded(timeout: Duration) -> EverythingResult<()> {
124        let sleep_duration: u64 = 300;
125        let mut wait_time: u64 = 0;
126
127        unsafe {
128            while Everything_IsDBLoaded() == 0 {
129                if wait_time >= timeout.as_millis() as u64 {
130                    return Err(EverythingError::DatabaseTimeout);
131                }
132                Everything::get_last_error()?;
133                std::thread::sleep(std::time::Duration::from_millis(sleep_duration));
134                wait_time += sleep_duration;
135            }
136        }
137
138        Ok(())
139    }
140
141    /// Set the query to be used by the next call to query.  
142    /// See <https://www.voidtools.com/support/everything/sdk/everything_setsearch/>  
143    pub fn set_search(&self, search: &str) {
144        let wide_search = U16CString::from_str(search).expect("Failed to convert search string");
145        unsafe {
146            Everything_SetSearchW(wide_search.as_ptr());
147        }
148    }
149
150    /// Get the current query.  
151    /// See <https://www.voidtools.com/support/everything/sdk/everything_getsearch/>  
152    ///
153    /// Search Syntax reference can be seen here   
154    /// <https://www.voidtools.com/support/everything/searching/>   
155    /// More search functions can be found on the forums.   
156    /// <https://www.voidtools.com/forum/viewtopic.php?t=10091>  
157    /// <https://www.voidtools.com/forum/viewtopic.php?t=10176>  
158    /// <https://www.voidtools.com/forum/viewtopic.php?t=10099>  
159    /// <https://www.voidtools.com/forum/viewtopic.php?t=10860>  
160    pub fn get_search(&self) -> EverythingResult<String> {
161        let search_ptr = unsafe { Everything_GetSearchW() };
162        parse_string_ptr(search_ptr)
163    }
164
165    /// Set the sorting to be used by the next call to query.  
166    /// See <https://www.voidtools.com/support/everything/sdk/everything_setsort/>  
167    pub fn set_sort(&self, sort: EverythingSort) {
168        unsafe {
169            Everything_SetSort(sort.into());
170        }
171    }
172
173    /// Get the current sorting.  
174    /// See <https://www.voidtools.com/support/everything/sdk/everything_getsort/>  
175    pub fn get_sort(&self) -> Option<EverythingSort> {
176        let sort = unsafe { Everything_GetSort() };
177        if let Ok(eve_sort) = sort.try_into() {
178            Some(eve_sort)
179        } else {
180            None
181        }
182    }
183
184    /// Check if the sort type is indexed.  
185    /// See <https://www.voidtools.com/support/everything/sdk/everything_isfastsort/>  
186    pub fn is_fast_sort(&self, sort: EverythingSort) -> bool {
187        unsafe { Everything_IsFastSort(sort.into()) != 0 }
188    }
189
190    /// See <https://www.voidtools.com/support/everything/sdk/everything_isfileresult/>  
191    pub fn is_result_file(&self, index: DWORD) -> bool {
192        unsafe { Everything_IsFileResult(index) != 0 }
193    }
194
195    /// See <https://www.voidtools.com/support/everything/sdk/everything_isfolderresult/>  
196    pub fn is_result_folder(&self, index: DWORD) -> bool {
197        unsafe { Everything_IsFolderResult(index) != 0 }
198    }
199
200    /// See <https://www.voidtools.com/support/everything/sdk/everything_isvolumeresult/>  
201    pub fn is_result_volume(&self, index: DWORD) -> bool {
202        unsafe { Everything_IsVolumeResult(index) != 0 }
203    }
204
205    /// Returns the number of visible results in everything's memory.  
206    /// Will be 0 until the first call to query.  
207    /// Then will equal the number of max results specified by set_max_results.  
208    /// See <https://www.voidtools.com/support/everything/sdk/everything_getnumresults/>  
209    pub fn get_num_results(&self) -> DWORD {
210        unsafe { Everything_GetNumResults() }
211    }
212
213    /// Returns the number of results returned by the query.  
214    /// See <https://www.voidtools.com/support/everything/sdk/everything_gettotresults/>  
215    pub fn get_total_results(&self) -> DWORD {
216        unsafe { Everything_GetTotResults() }
217    }
218
219    /// Limit's the number of results returned by the everything.  
220    /// See <https://www.voidtools.com/support/everything/sdk/everything_setmax/>  
221    pub fn set_max_results(&self, max_results: DWORD) {
222        unsafe {
223            Everything_SetMax(max_results);
224        }
225    }
226
227    /// Returns the current maximum number of results.  
228    /// See <https://www.voidtools.com/support/everything/sdk/everything_getmax/>  
229    pub fn get_max_results(&self) -> DWORD {
230        unsafe { Everything_GetMax() }
231    }
232
233    /// Set the index offset that everything will start its result window from.  
234    /// See <https://www.voidtools.com/support/everything/sdk/everything_setoffset/>  
235    pub fn set_result_offset(&self, offset_results: DWORD) {
236        unsafe {
237            Everything_SetOffset(offset_results);
238        }
239    }
240
241    /// Returns the current result offset.  
242    /// See <https://www.voidtools.com/support/everything/sdk/everything_getoffset/>  
243    pub fn get_result_offset(&self) -> DWORD {
244        unsafe { Everything_GetOffset() }
245    }
246
247    /// Set the type of data that the everything service will load.  
248    /// See <https://www.voidtools.com/support/everything/sdk/everything_setreplywindow/>  
249    pub fn set_request_flags(&self, request_flags: EverythingRequestFlags) {
250        unsafe {
251            Everything_SetRequestFlags(request_flags.bits());
252        }
253    }
254
255    /// Returns the current request flags.  
256    /// See <https://www.voidtools.com/support/everything/sdk/everything_getrequestflags/>  
257    pub fn get_request_flags(&self) -> EverythingRequestFlags {
258        let request_flags = unsafe { Everything_GetRequestFlags() };
259        EverythingRequestFlags::from_bits_truncate(request_flags)
260    }
261
262    pub fn query(&self) -> EverythingResult<()> {
263        let result = unsafe { Everything_QueryW(1) };
264        if result == 0 {
265            Everything::get_last_error()
266        } else {
267            Ok(())
268        }
269    }
270
271    /// Reset the query state.  
272    /// See <https://www.voidtools.com/support/everything/sdk/everything_reset/>  
273    pub fn reset(&self) {
274        unsafe {
275            Everything_Reset();
276        }
277    }
278
279    /// Returns the total number of indexes in the everything result window.  
280    /// See <https://www.voidtools.com/support/everything/sdk/everything_getnumfileresults/>  
281    pub fn get_result_count(&self) -> u32 {
282        unsafe { Everything_GetNumResults() }
283    }
284
285    /// Creates a owned string from full path slice for a result at an index.  
286    /// See <https://www.voidtools.com/support/everything/sdk/everything_getresultfullpathname/>  
287    pub fn get_result_full_path(&self, index: u32) -> EverythingResult<String> {
288        let path_length =
289            unsafe { Everything_GetResultFullPathNameW(index, std::ptr::null_mut(), 0) };
290        if path_length == 0 {
291            Everything::get_last_error()?;
292        }
293
294        // Length does not include null terminator
295        let mut path_buffer = Vec::with_capacity(path_length as usize + 1);
296        unsafe {
297            let count_copied = Everything_GetResultFullPathNameW(
298                index,
299                path_buffer.as_mut_ptr(),
300                path_buffer.len() as u32,
301            );
302            Ok(
303                U16CStr::from_ptr(path_buffer.as_ptr(), count_copied as usize)
304                    .unwrap()
305                    .to_string_lossy(),
306            )
307        }
308    }
309
310    /// Returns an iterator over the full paths of the results.  
311    /// See <https://www.voidtools.com/support/everything/sdk/everything_getresultfullpathname/>  
312    pub fn full_path_iter(&self) -> impl Iterator<Item = EverythingResult<String>> + '_ {
313        let num_results = self.get_result_count();
314        (0..num_results).map(|index| self.get_result_full_path(index))
315    }
316
317    /// Iterates from the pointer to find a null terminator returning an owned string.  
318    /// See <https://www.voidtools.com/support/everything/sdk/everything_getresultfilename/>  
319    pub fn get_result_file_name(&self, index: u32) -> EverythingResult<String> {
320        let result_ptr = unsafe { Everything_GetResultFileNameW(index) };
321
322        if result_ptr.is_null() {
323            Everything::get_last_error()?;
324        }
325
326        parse_string_ptr(result_ptr)
327    }
328
329    /// Returns an iterator over the file names of the results.  
330    /// See <https://www.voidtools.com/support/everything/sdk/everything_getresultfilename/>  
331    pub fn name_iter(&self) -> impl Iterator<Item = EverythingResult<String>> + '_ {
332        let num_results = self.get_result_count();
333        (0..num_results).map(|index| self.get_result_file_name(index))
334    }
335
336    /// Returns the created date of the result at the index.  
337    /// See <https://www.voidtools.com/support/everything/sdk/everything_getresultdatecreated/>  
338    pub fn get_result_created_date(&self, index: u32) -> EverythingResult<u64> {
339        let mut file_time: FILETIME = FILETIME {
340            dwLowDateTime: 0,
341            dwHighDateTime: 0,
342        };
343
344        let success = unsafe { Everything_GetResultDateCreated(index, &mut file_time) };
345
346        if success == 0 {
347            Everything::get_last_error()?;
348        }
349
350        Ok(file_time.as_u64())
351    }
352
353    /// Returns the modified date of the result at the index.  
354    /// See <https://www.voidtools.com/support/everything/sdk/everything_getresultdatemodified/>  
355    pub fn get_result_count_modified_date(&self, index: u32) -> EverythingResult<u64> {
356        let mut file_time: FILETIME = FILETIME {
357            dwLowDateTime: 0,
358            dwHighDateTime: 0,
359        };
360
361        let success = unsafe { Everything_GetResultDateModified(index, &mut file_time) };
362
363        if success == 0 {
364            Everything::get_last_error()?;
365        }
366
367        Ok(file_time.as_u64())
368    }
369
370    /// Returns the last accessed date of the result at the index.  
371    /// See <https://www.voidtools.com/support/everything/sdk/everything_getresultdateaccessed/>  
372    pub fn get_result_size(&self, index: u32) -> EverythingResult<u64> {
373        let mut size: LARGE_INTEGER = LARGE_INTEGER { QuadPart: 0 };
374
375        let success = unsafe { Everything_GetResultSize(index, &mut size) };
376
377        if success == 0 {
378            Everything::get_last_error()?;
379        }
380
381        Ok(unsafe { size.QuadPart as u64 })
382    }
383
384    /// Returns the extension of the result at the index.  
385    /// iterates from a string pointer to find a null terminator returning an owned string.  
386    /// See <https://www.voidtools.com/support/everything/sdk/everything_getresultextension/>  
387    pub fn get_result_extension(&self, index: u32) -> EverythingResult<String> {
388        let result_ptr = unsafe { Everything_GetResultExtensionW(index) };
389
390        if result_ptr.is_null() {
391            Everything::get_last_error()?;
392        }
393
394        parse_string_ptr(result_ptr)
395    }
396
397    /// Waits for the Everything database to be fully loaded before returning an instance.
398    pub fn new() -> Everything {
399        Everything::wait_db_loaded(Duration::from_secs(5)).expect("Everything database not loaded");
400        Everything
401    }
402
403    pub fn version() -> String {
404        unsafe {
405            let major = Everything_GetMajorVersion();
406            let minor = Everything_GetMinorVersion();
407            let revision = Everything_GetRevision();
408            let build = Everything_GetBuildNumber();
409
410            format!("{}.{}.{}.{}", major, minor, revision, build)
411        }
412    }
413}
414
415impl Drop for Everything {
416    fn drop(&mut self) {
417        unsafe {
418            Everything_CleanUp();
419        }
420    }
421}
422
423impl Default for Everything {
424    fn default() -> Self {
425        Everything::new()
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432    use lazy_static::lazy_static;
433    use std::path::Path;
434    use std::sync::Mutex;
435
436    lazy_static! {
437        static ref TEST_EVERYTHING: Mutex<Everything> = Mutex::new(Everything::new());
438    }
439
440    fn setup() -> std::sync::MutexGuard<'static, Everything> {
441        let everything = TEST_EVERYTHING.lock().unwrap();
442        everything.reset();
443        everything
444    }
445
446    #[test]
447    fn parses_string_ptr() {
448        let test_string = "test\0";
449        let test_string_ptr = test_string.encode_utf16().collect::<Vec<u16>>();
450        let test_string_ptr = test_string_ptr.as_ptr();
451        let parsed_string = parse_string_ptr(test_string_ptr).unwrap();
452        assert_eq!(parsed_string, test_string[0..4]);
453    }
454
455    #[test]
456    fn parses_full_path() {
457        let test_dir_path = Path::canonicalize(Path::new("../test")).unwrap();
458        let test_dir_path = test_dir_path.to_str().unwrap();
459        let test_dir_path = test_dir_path.trim_start_matches(r"\\?\");
460
461        println!("{}", test_dir_path);
462
463        let evthing = setup();
464
465        evthing.set_search(test_dir_path);
466        evthing.set_request_flags(EverythingRequestFlags::FullPathAndFileName);
467
468        evthing.query().unwrap();
469
470        let num_results = evthing.get_result_count();
471
472        assert!(num_results > 0);
473
474        for path in evthing.full_path_iter().flatten() {
475            println!("{}", path);
476            assert!(path.contains(test_dir_path));
477        }
478    }
479
480    #[test]
481    fn searches() {
482        let everything = setup();
483
484        everything.set_search("test");
485        let search = everything.get_search().unwrap();
486        assert_eq!(search, "test");
487
488        everything.set_max_results(10);
489        let max_results = everything.get_max_results();
490        assert_eq!(max_results, 10);
491
492        everything.set_result_offset(10);
493        let offset = everything.get_result_offset();
494        assert_eq!(offset, 10);
495
496        let flag = EverythingRequestFlags::FullPathAndFileName
497            | EverythingRequestFlags::DateCreated
498            | EverythingRequestFlags::DateModified
499            | EverythingRequestFlags::Size
500            | EverythingRequestFlags::Extension;
501        everything.set_request_flags(flag);
502
503        let flags = everything.get_request_flags();
504        assert_eq!(flags, flag);
505
506        everything.set_sort(EverythingSort::DateCreatedDescending);
507
508        everything.query().unwrap();
509
510        let num_results = everything.get_result_count();
511        assert_eq!(num_results, 10);
512
513        let full_path_results: Vec<EverythingResult<String>> =
514            everything.full_path_iter().collect();
515
516        assert_eq!(full_path_results.len(), 10);
517
518        let mut last_date_created = everything.get_result_created_date(0).unwrap();
519        for idx in 0..num_results {
520            let result = everything.get_result_full_path(idx).unwrap();
521            let iter_result = full_path_results[idx as usize].as_ref().unwrap();
522            assert_eq!(result, *iter_result);
523            println!("{}", result);
524
525            let size = everything.get_result_size(idx).unwrap();
526            assert!(size > 0);
527
528            let created_date = everything.get_result_created_date(idx).unwrap();
529            assert!(created_date > 0);
530            assert!(created_date <= last_date_created);
531            last_date_created = created_date;
532
533            let modified_date = everything.get_result_count_modified_date(idx).unwrap();
534            assert!(modified_date > 0);
535        }
536    }
537}