Providers.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*
  2. * Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "Providers.h"
  7. #include <AK/BinaryHeap.h>
  8. #include <AK/FuzzyMatch.h>
  9. #include <AK/LexicalPath.h>
  10. #include <LibCore/Directory.h>
  11. #include <LibCore/ElapsedTimer.h>
  12. #include <LibCore/StandardPaths.h>
  13. #include <LibDesktop/Launcher.h>
  14. #include <LibGUI/Clipboard.h>
  15. #include <LibGUI/FileIconProvider.h>
  16. #include <LibGUI/Process.h>
  17. #include <LibJS/Bytecode/Interpreter.h>
  18. #include <LibJS/Runtime/GlobalObject.h>
  19. #include <LibJS/Runtime/ValueInlines.h>
  20. #include <LibJS/Script.h>
  21. #include <LibURL/URL.h>
  22. #include <errno.h>
  23. #include <fcntl.h>
  24. #include <serenity.h>
  25. #include <spawn.h>
  26. #include <sys/stat.h>
  27. #include <unistd.h>
  28. namespace Assistant {
  29. void AppResult::activate(GUI::Window& window) const
  30. {
  31. if (chdir(Core::StandardPaths::home_directory().characters()) < 0) {
  32. perror("chdir");
  33. exit(1);
  34. }
  35. auto arguments_list = m_arguments.split_view(' ');
  36. m_app_file->spawn_with_escalation_or_show_error(window, arguments_list.span());
  37. }
  38. void CalculatorResult::activate(GUI::Window& window) const
  39. {
  40. (void)window;
  41. GUI::Clipboard::the().set_plain_text(title());
  42. }
  43. void FileResult::activate(GUI::Window& window) const
  44. {
  45. (void)window;
  46. Desktop::Launcher::open(URL::create_with_file_scheme(title()));
  47. }
  48. void TerminalResult::activate(GUI::Window& window) const
  49. {
  50. GUI::Process::spawn_or_show_error(&window, "/bin/Terminal"sv, Array { "-k", "-e", title().characters() });
  51. }
  52. void URLResult::activate(GUI::Window& window) const
  53. {
  54. (void)window;
  55. Desktop::Launcher::open(URL::create_with_url_or_path(title()));
  56. }
  57. AppProvider::AppProvider()
  58. {
  59. Desktop::AppFile::for_each([this](NonnullRefPtr<Desktop::AppFile> app_file) {
  60. m_app_file_cache.append(move(app_file));
  61. });
  62. }
  63. void AppProvider::query(ByteString const& query, Function<void(Vector<NonnullRefPtr<Result>>)> on_complete)
  64. {
  65. if (query.starts_with('=') || query.starts_with('$'))
  66. return;
  67. Vector<NonnullRefPtr<Result>> results;
  68. for (auto const& app_file : m_app_file_cache) {
  69. auto query_and_arguments = query.split_limit(' ', 2);
  70. auto app_name = query_and_arguments.is_empty() ? query : query_and_arguments[0];
  71. auto arguments = query_and_arguments.size() < 2 ? ByteString::empty() : query_and_arguments[1];
  72. auto score = 0;
  73. if (app_name.equals_ignoring_ascii_case(app_file->name()))
  74. score = NumericLimits<int>::max();
  75. else {
  76. auto match_result = fuzzy_match(app_name, app_file->name());
  77. if (!match_result.matched)
  78. continue;
  79. score = match_result.score;
  80. }
  81. auto icon = GUI::FileIconProvider::icon_for_executable(app_file->executable());
  82. results.append(make_ref_counted<AppResult>(icon.bitmap_for_size(16), app_file->name(), String(), app_file, arguments, score));
  83. };
  84. on_complete(move(results));
  85. }
  86. void CalculatorProvider::query(ByteString const& query, Function<void(Vector<NonnullRefPtr<Result>>)> on_complete)
  87. {
  88. if (!query.starts_with('='))
  89. return;
  90. auto vm = JS::VM::create().release_value_but_fixme_should_propagate_errors();
  91. auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
  92. auto source_code = query.substring(1);
  93. auto parse_result = JS::Script::parse(source_code, *root_execution_context->realm);
  94. if (parse_result.is_error())
  95. return;
  96. auto completion = vm->bytecode_interpreter().run(parse_result.value());
  97. if (completion.is_error())
  98. return;
  99. auto result = completion.release_value();
  100. ByteString calculation;
  101. if (!result.is_number()) {
  102. calculation = "0";
  103. } else {
  104. calculation = result.to_string_without_side_effects().to_byte_string();
  105. }
  106. Vector<NonnullRefPtr<Result>> results;
  107. results.append(make_ref_counted<CalculatorResult>(calculation));
  108. on_complete(move(results));
  109. }
  110. Gfx::Bitmap const* FileResult::bitmap() const
  111. {
  112. return GUI::FileIconProvider::icon_for_path(title()).bitmap_for_size(16);
  113. }
  114. FileProvider::FileProvider()
  115. {
  116. build_filesystem_cache();
  117. }
  118. void FileProvider::query(ByteString const& query, Function<void(Vector<NonnullRefPtr<Result>>)> on_complete)
  119. {
  120. build_filesystem_cache();
  121. if (m_fuzzy_match_work)
  122. m_fuzzy_match_work->cancel();
  123. m_fuzzy_match_work = Threading::BackgroundAction<Optional<Vector<NonnullRefPtr<Result>>>>::construct(
  124. [this, query](auto& task) -> Optional<Vector<NonnullRefPtr<Result>>> {
  125. BinaryHeap<int, ByteString, MAX_SEARCH_RESULTS> sorted_results;
  126. for (auto& path : m_full_path_cache) {
  127. if (task.is_canceled())
  128. return {};
  129. auto score = 0;
  130. if (query.equals_ignoring_ascii_case(path)) {
  131. score = NumericLimits<int>::max();
  132. } else {
  133. auto match_result = fuzzy_match(query, path);
  134. if (!match_result.matched)
  135. continue;
  136. if (match_result.score < 0)
  137. continue;
  138. score = match_result.score;
  139. }
  140. if (sorted_results.size() < MAX_SEARCH_RESULTS || score > sorted_results.peek_min_key()) {
  141. if (sorted_results.size() == MAX_SEARCH_RESULTS)
  142. sorted_results.pop_min();
  143. sorted_results.insert(score, path);
  144. }
  145. }
  146. Vector<NonnullRefPtr<Result>> results;
  147. results.ensure_capacity(sorted_results.size());
  148. while (!sorted_results.is_empty()) {
  149. auto score = sorted_results.peek_min_key();
  150. auto path = sorted_results.pop_min();
  151. results.append(make_ref_counted<FileResult>(path, score));
  152. }
  153. return results;
  154. },
  155. [on_complete = move(on_complete)](auto results) -> ErrorOr<void> {
  156. if (results.has_value())
  157. on_complete(move(results.value()));
  158. return {};
  159. },
  160. [](auto) {
  161. // Ignore cancellation errors.
  162. });
  163. }
  164. void FileProvider::build_filesystem_cache()
  165. {
  166. if (m_full_path_cache.size() > 0 || m_building_cache)
  167. return;
  168. m_building_cache = true;
  169. m_work_queue.enqueue("/");
  170. (void)Threading::BackgroundAction<int>::construct(
  171. [this, strong_ref = NonnullRefPtr(*this)](auto&) {
  172. ByteString slash = "/";
  173. auto timer = Core::ElapsedTimer::start_new();
  174. while (!m_work_queue.is_empty()) {
  175. auto base_directory = m_work_queue.dequeue();
  176. if (base_directory.template is_one_of("/dev"sv, "/proc"sv, "/sys"sv))
  177. continue;
  178. // FIXME: Propagate errors.
  179. (void)Core::Directory::for_each_entry(base_directory, Core::DirIterator::SkipDots, [&](auto const& entry, auto const& directory) -> ErrorOr<IterationDecision> {
  180. struct stat st = {};
  181. if (fstatat(directory.fd(), entry.name.characters(), &st, AT_SYMLINK_NOFOLLOW) < 0) {
  182. perror("fstatat");
  183. return IterationDecision::Continue;
  184. }
  185. if (S_ISLNK(st.st_mode))
  186. return IterationDecision::Continue;
  187. auto full_path = LexicalPath::join(directory.path().string(), entry.name).string();
  188. if (access(full_path.characters(), R_OK) != 0)
  189. return IterationDecision::Continue;
  190. m_full_path_cache.append(full_path);
  191. if (S_ISDIR(st.st_mode)) {
  192. m_work_queue.enqueue(full_path);
  193. }
  194. return IterationDecision::Continue;
  195. });
  196. }
  197. dbgln("Built cache in {} ms", timer.elapsed());
  198. return 0;
  199. },
  200. [this](auto) -> ErrorOr<void> {
  201. m_building_cache = false;
  202. return {};
  203. });
  204. }
  205. void TerminalProvider::query(ByteString const& query, Function<void(Vector<NonnullRefPtr<Result>>)> on_complete)
  206. {
  207. if (!query.starts_with('$'))
  208. return;
  209. auto command = query.substring(1).trim_whitespace();
  210. Vector<NonnullRefPtr<Result>> results;
  211. results.append(make_ref_counted<TerminalResult>(move(command)));
  212. on_complete(move(results));
  213. }
  214. void URLProvider::query(ByteString const& query, Function<void(Vector<NonnullRefPtr<Result>>)> on_complete)
  215. {
  216. if (query.is_empty() || query.starts_with('=') || query.starts_with('$'))
  217. return;
  218. URL::URL url = URL::URL(query);
  219. if (url.scheme().is_empty())
  220. url.set_scheme("http"_string);
  221. if (url.host().has<Empty>() || url.host() == String {})
  222. url.set_host(String::from_byte_string(query).release_value_but_fixme_should_propagate_errors());
  223. if (url.path_segment_count() == 0)
  224. url.set_paths({ "" });
  225. if (!url.is_valid())
  226. return;
  227. Vector<NonnullRefPtr<Result>> results;
  228. results.append(make_ref_counted<URLResult>(url));
  229. on_complete(results);
  230. }
  231. }