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}