Skip to main content

java_manager/
search.rs

1//! Functions for locating Java installations on the system.
2
3use crate::{JavaError, JavaInfo};
4use std::env;
5
6/// Quickly searches for Java installations by looking for `java` in every directory
7/// listed in the `PATH` environment variable.
8///
9/// This function iterates over all paths in `PATH`, appends the platform‑specific
10/// executable name (`java.exe` on Windows, `java` elsewhere), and attempts to
11/// create a `JavaInfo` for each existing file. Entries that fail to produce a
12/// valid `JavaInfo` (e.g., because the `release` file is missing) are silently
13/// skipped.
14///
15/// # Returns
16///
17/// A `Vec<JavaInfo>` containing all valid Java installations found in `PATH`.
18/// The vector may be empty if none are found.
19///
20/// # Examples
21///
22/// ```no_run
23/// use java_manager::quick_search;
24///
25/// let javas = quick_search()?;
26/// for java in javas {
27///     println!("Found Java at {} (version {})", java.path.display(), java.version);
28/// }
29/// # Ok::<_, java_manager::JavaError>(())
30/// ```
31pub fn quick_search() -> Result<Vec<JavaInfo>, JavaError> {
32    let mut results: Vec<JavaInfo> = Vec::new();
33
34    // Search for 'java' in all directories listed in PATH
35    if let Ok(paths) = env::var("PATH") {
36        for path in env::split_paths(&paths) {
37            let java_exe = if cfg!(windows) { "java.exe" } else { "java" };
38            let java_path = path.join(java_exe);
39
40            if java_path.is_file() {
41                // Attempt to build JavaInfo; skip if it fails (e.g., missing release file)
42                if let Ok(info) = JavaInfo::new(java_path.to_string_lossy().to_string()) {
43                    results.push(info);
44                }
45            }
46        }
47    }
48
49    // Additional common locations can be added here if desired
50
51    Ok(results)
52}
53
54/// Performs a deep search for Java installations using platform‑specific tools.
55///
56/// On Windows, this function uses the [Everything SDK](https://www.voidtools.com/)
57/// to search for `java.exe` files (excluding the `C:\Windows` directory). It requires
58/// the Everything search engine to be installed and running.
59///
60/// On Linux, it walks through common installation directories (`/usr/lib/jvm`,
61/// `/usr/java`, `/opt`, `/usr/local`) and looks for executable files named `java`.
62/// Symbolic links are followed.
63///
64/// # Errors
65///
66/// On Windows, returns `JavaError::ExecuteError` if the Everything database is not
67/// loaded or if Everything is not running.
68///
69/// On Linux, this function does not produce errors from the search itself, but
70/// individual `JavaInfo::new` calls may fail; such failures are ignored.
71///
72/// # Returns
73///
74/// A `Vec<JavaInfo>` containing all valid Java installations found.
75///
76/// # Examples
77///
78/// ```no_run
79/// use java_manager::deep_search;
80///
81/// let javas = deep_search()?;
82/// for java in javas {
83///     println!("Deep search found Java at {}", java.path.display());
84/// }
85/// # Ok::<_, java_manager::JavaError>(())
86/// ```
87pub fn deep_search() -> Result<Vec<JavaInfo>, JavaError> {
88    let mut results: Vec<JavaInfo> = Vec::new();
89
90    // Windows
91    #[cfg(target_os = "windows")]
92    {
93        use everything_sdk::*;
94
95        // Acquire lock on Everything global state
96        let mut everything = global().try_lock().map_err(|_| {
97            JavaError::RuntimeError("Failed to lock Everything global state".to_string())
98        })?;
99
100        // Check if database is loaded
101        match everything.is_db_loaded() {
102            Ok(false) => {
103                return Err(JavaError::ExecuteError(
104                    "Everything database is not fully loaded".to_string(),
105                ));
106            }
107            Err(EverythingError::Ipc) => {
108                return Err(JavaError::ExecuteError(
109                    "Everything is not running in the background. Please start Everything.exe"
110                        .to_string(),
111                ));
112            }
113            _ => {}
114        }
115
116        // Create searcher and set search criteria
117        let mut searcher = everything.searcher();
118        searcher.set_search("\"java.exe\" !C:\\Windows\\");
119        searcher.set_request_flags(
120            RequestFlags::EVERYTHING_REQUEST_FILE_NAME
121                | RequestFlags::EVERYTHING_REQUEST_PATH
122                | RequestFlags::EVERYTHING_REQUEST_SIZE,
123        );
124        searcher.set_sort(SortType::EVERYTHING_SORT_NAME_ASCENDING);
125
126        // Ensure case-insensitive matching (default)
127        assert_eq!(searcher.get_match_case(), false);
128
129        // Execute query
130        let query_results = searcher.query();
131
132        // Collect results
133        
134        for item in query_results.iter() {
135            if let Ok(path) = item.filepath() {
136                if let Ok(info) = JavaInfo::new(path.to_string_lossy().to_string()) {
137                    results.push(info);
138                }
139            }
140        }
141    }
142
143    #[cfg(target_os = "linux")]
144    {
145        use walkdir::WalkDir;
146
147        // Standard locations where Java is commonly installed on Linux
148        let search_dirs = vec![
149            "/usr/lib/jvm",
150            "/usr/java",
151            "/opt",
152            "/usr/local",
153        ];
154
155        for dir in search_dirs {
156            let path = std::path::Path::new(dir);
157            if !path.exists() {
158                continue;
159            }
160
161            for entry in WalkDir::new(path)
162                .follow_links(true)
163                .into_iter()
164                .filter_map(|e| e.ok())
165            {
166                let entry_path = entry.path();
167                // Look for files exactly named "java"
168                if entry_path.file_name() != Some(std::ffi::OsStr::new("java")) {
169                    continue;
170                }
171                if !entry_path.is_file() {
172                    continue;
173                }
174
175                // On Unix, also verify the file is executable
176                #[cfg(unix)]
177                {
178                    use std::os::unix::fs::PermissionsExt;
179                    if let Ok(metadata) = entry_path.metadata() {
180                        let permissions = metadata.permissions();
181                        if permissions.mode() & 0o111 != 0 {
182                            if let Ok(info) = JavaInfo::new(entry_path.to_string_lossy().to_string()) {
183                                results.push(info);
184                            }
185                        }
186                    }
187                }
188                #[cfg(not(unix))]
189                {
190                    if let Ok(info) = JavaInfo::new(entry_path.to_string_lossy().to_string()) {
191                        results.push(info);
192                    }
193                }
194            }
195        }
196    }
197
198    Ok(results)
199}