Providers.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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/FuzzyMatch.h>
  8. #include <AK/LexicalPath.h>
  9. #include <AK/URL.h>
  10. #include <LibCore/DirIterator.h>
  11. #include <LibCore/ElapsedTimer.h>
  12. #include <LibCore/Process.h>
  13. #include <LibCore/StandardPaths.h>
  14. #include <LibDesktop/Launcher.h>
  15. #include <LibGUI/Clipboard.h>
  16. #include <LibGUI/FileIconProvider.h>
  17. #include <LibJS/Interpreter.h>
  18. #include <LibJS/Runtime/GlobalObject.h>
  19. #include <LibJS/Script.h>
  20. #include <errno.h>
  21. #include <fcntl.h>
  22. #include <serenity.h>
  23. #include <spawn.h>
  24. #include <sys/stat.h>
  25. #include <unistd.h>
  26. namespace Assistant {
  27. void AppResult::activate() const
  28. {
  29. if (chdir(Core::StandardPaths::home_directory().characters()) < 0) {
  30. perror("chdir");
  31. exit(1);
  32. }
  33. auto arguments_list = m_arguments.split_view(' ');
  34. m_app_file->spawn(arguments_list.span());
  35. }
  36. void CalculatorResult::activate() const
  37. {
  38. GUI::Clipboard::the().set_plain_text(title());
  39. }
  40. void FileResult::activate() const
  41. {
  42. Desktop::Launcher::open(URL::create_with_file_scheme(title()));
  43. }
  44. void TerminalResult::activate() const
  45. {
  46. // FIXME: This should be a GUI::Process::spawn_or_show_error(), however this is a
  47. // Assistant::Result object, which does not have access to the application's GUI::Window* pointer
  48. // (which spawn_or_show_error() needs in case it has to open a error message box).
  49. (void)Core::Process::spawn("/bin/Terminal"sv, Array { "-k", "-e", title().characters() });
  50. }
  51. void URLResult::activate() const
  52. {
  53. Desktop::Launcher::open(URL::create_with_url_or_path(title()));
  54. }
  55. void AppProvider::query(DeprecatedString const& query, Function<void(NonnullRefPtrVector<Result>)> on_complete)
  56. {
  57. if (query.starts_with('=') || query.starts_with('$'))
  58. return;
  59. NonnullRefPtrVector<Result> results;
  60. Desktop::AppFile::for_each([&](NonnullRefPtr<Desktop::AppFile> app_file) {
  61. auto query_and_arguments = query.split_limit(' ', 2);
  62. auto app_name = query_and_arguments.is_empty() ? query : query_and_arguments[0];
  63. auto arguments = query_and_arguments.size() < 2 ? DeprecatedString::empty() : query_and_arguments[1];
  64. auto match_result = fuzzy_match(app_name, app_file->name());
  65. if (!match_result.matched)
  66. return;
  67. auto icon = GUI::FileIconProvider::icon_for_executable(app_file->executable());
  68. results.append(adopt_ref(*new AppResult(icon.bitmap_for_size(16), app_file->name(), {}, app_file, arguments, match_result.score)));
  69. });
  70. on_complete(move(results));
  71. }
  72. void CalculatorProvider::query(DeprecatedString const& query, Function<void(NonnullRefPtrVector<Result>)> on_complete)
  73. {
  74. if (!query.starts_with('='))
  75. return;
  76. auto vm = JS::VM::create();
  77. auto interpreter = JS::Interpreter::create<JS::GlobalObject>(*vm);
  78. auto source_code = query.substring(1);
  79. auto parse_result = JS::Script::parse(source_code, interpreter->realm());
  80. if (parse_result.is_error())
  81. return;
  82. auto completion = interpreter->run(parse_result.value());
  83. if (completion.is_error())
  84. return;
  85. auto result = completion.release_value();
  86. DeprecatedString calculation;
  87. if (!result.is_number()) {
  88. calculation = "0";
  89. } else {
  90. calculation = result.to_string_without_side_effects().release_value_but_fixme_should_propagate_errors().to_deprecated_string();
  91. }
  92. NonnullRefPtrVector<Result> results;
  93. results.append(adopt_ref(*new CalculatorResult(calculation)));
  94. on_complete(move(results));
  95. }
  96. Gfx::Bitmap const* FileResult::bitmap() const
  97. {
  98. return GUI::FileIconProvider::icon_for_path(title()).bitmap_for_size(16);
  99. }
  100. FileProvider::FileProvider()
  101. {
  102. build_filesystem_cache();
  103. }
  104. void FileProvider::query(DeprecatedString const& query, Function<void(NonnullRefPtrVector<Result>)> on_complete)
  105. {
  106. build_filesystem_cache();
  107. if (m_fuzzy_match_work)
  108. m_fuzzy_match_work->cancel();
  109. m_fuzzy_match_work = Threading::BackgroundAction<Optional<NonnullRefPtrVector<Result>>>::construct(
  110. [this, query](auto& task) -> Optional<NonnullRefPtrVector<Result>> {
  111. NonnullRefPtrVector<Result> results;
  112. for (auto& path : m_full_path_cache) {
  113. if (task.is_cancelled())
  114. return {};
  115. auto match_result = fuzzy_match(query, path);
  116. if (!match_result.matched)
  117. continue;
  118. if (match_result.score < 0)
  119. continue;
  120. results.append(adopt_ref(*new FileResult(path, match_result.score)));
  121. }
  122. return results;
  123. },
  124. [on_complete = move(on_complete)](auto results) -> ErrorOr<void> {
  125. if (results.has_value())
  126. on_complete(move(results.value()));
  127. return {};
  128. });
  129. }
  130. void FileProvider::build_filesystem_cache()
  131. {
  132. if (m_full_path_cache.size() > 0 || m_building_cache)
  133. return;
  134. m_building_cache = true;
  135. m_work_queue.enqueue("/");
  136. (void)Threading::BackgroundAction<int>::construct(
  137. [this, strong_ref = NonnullRefPtr(*this)](auto&) {
  138. DeprecatedString slash = "/";
  139. auto timer = Core::ElapsedTimer::start_new();
  140. while (!m_work_queue.is_empty()) {
  141. auto base_directory = m_work_queue.dequeue();
  142. if (base_directory.template is_one_of("/dev"sv, "/proc"sv, "/sys"sv))
  143. continue;
  144. Core::DirIterator di(base_directory, Core::DirIterator::SkipDots);
  145. while (di.has_next()) {
  146. auto path = di.next_path();
  147. struct stat st = {};
  148. if (fstatat(di.fd(), path.characters(), &st, AT_SYMLINK_NOFOLLOW) < 0) {
  149. perror("fstatat");
  150. continue;
  151. }
  152. if (S_ISLNK(st.st_mode))
  153. continue;
  154. auto full_path = LexicalPath::join(slash, base_directory, path).string();
  155. m_full_path_cache.append(full_path);
  156. if (S_ISDIR(st.st_mode)) {
  157. m_work_queue.enqueue(full_path);
  158. }
  159. }
  160. }
  161. dbgln("Built cache in {} ms", timer.elapsed());
  162. return 0;
  163. },
  164. [this](auto) -> ErrorOr<void> {
  165. m_building_cache = false;
  166. return {};
  167. });
  168. }
  169. void TerminalProvider::query(DeprecatedString const& query, Function<void(NonnullRefPtrVector<Result>)> on_complete)
  170. {
  171. if (!query.starts_with('$'))
  172. return;
  173. auto command = query.substring(1).trim_whitespace();
  174. NonnullRefPtrVector<Result> results;
  175. results.append(adopt_ref(*new TerminalResult(move(command))));
  176. on_complete(move(results));
  177. }
  178. void URLProvider::query(DeprecatedString const& query, Function<void(NonnullRefPtrVector<Result>)> on_complete)
  179. {
  180. if (query.is_empty() || query.starts_with('=') || query.starts_with('$'))
  181. return;
  182. URL url = URL(query);
  183. if (url.scheme().is_empty())
  184. url.set_scheme("http");
  185. if (url.host().is_empty())
  186. url.set_host(query);
  187. if (url.paths().is_empty())
  188. url.set_paths({ "" });
  189. if (!url.is_valid())
  190. return;
  191. NonnullRefPtrVector<Result> results;
  192. results.append(adopt_ref(*new URLResult(url)));
  193. on_complete(results);
  194. }
  195. }