AvailablePort.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /*
  2. * Copyright (c) 2023, Liav A. <liavalb@hotmail.co.il>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Optional.h>
  7. #include <AK/Stream.h>
  8. #include <LibCore/ArgsParser.h>
  9. #include <LibCore/EventLoop.h>
  10. #include <LibCore/File.h>
  11. #include <LibCore/Proxy.h>
  12. #include <LibCore/System.h>
  13. #include <LibFileSystem/FileSystem.h>
  14. #include <LibHTTP/HttpResponse.h>
  15. #include <LibMain/Main.h>
  16. #include <LibMarkdown/Document.h>
  17. #include <LibMarkdown/Table.h>
  18. #include <LibProtocol/Request.h>
  19. #include <LibProtocol/RequestClient.h>
  20. #include "AvailablePort.h"
  21. #include "MarkdownTableFinder.h"
  22. #include <Shell/AST.h>
  23. #include <Shell/Formatter.h>
  24. #include <Shell/NodeVisitor.h>
  25. #include <Shell/PosixParser.h>
  26. #include <Shell/Shell.h>
  27. void AvailablePort::query_details_for_package(HashMap<String, AvailablePort>& available_ports, HashMap<String, InstalledPort> const& installed_ports, StringView package_name, bool verbose)
  28. {
  29. auto possible_available_port = available_ports.find(package_name);
  30. if (possible_available_port == available_ports.end()) {
  31. outln("pkg: No match for queried name \"{}\"", package_name);
  32. return;
  33. }
  34. auto& available_port = possible_available_port->value;
  35. outln("{}: {}, {}", available_port.name(), available_port.version_string(), available_port.website());
  36. if (verbose) {
  37. out("Installed: ");
  38. auto installed_port = installed_ports.find(package_name);
  39. if (installed_port != installed_ports.end()) {
  40. outln("Yes");
  41. out("Update Status: ");
  42. auto error_or_available_version = available_port.version_semver();
  43. auto error_or_installed_version = installed_port->value.version_semver();
  44. if (error_or_available_version.is_error() || error_or_installed_version.is_error()) {
  45. auto ip_version = installed_port->value.version_string();
  46. auto ap_version = available_port.version_string();
  47. if (ip_version == ap_version) {
  48. outln("Already on latest version");
  49. } else {
  50. outln("Update to {} available", ap_version);
  51. }
  52. return;
  53. }
  54. auto available_version = error_or_available_version.value();
  55. auto installed_version = error_or_installed_version.value();
  56. if (available_version.is_same(installed_version, SemVer::CompareType::Patch)) {
  57. outln("Already on latest version");
  58. } else if (available_version.is_greater_than(installed_version)) {
  59. outln("Update to {} available", available_port.version_string());
  60. }
  61. } else {
  62. outln("No");
  63. }
  64. }
  65. }
  66. static Optional<Markdown::Table::Column const&> get_column_in_table(Markdown::Table const& ports_table, StringView column_name)
  67. {
  68. for (auto const& column : ports_table.columns()) {
  69. if (column_name == column.header.render_for_terminal())
  70. return column;
  71. }
  72. return {};
  73. }
  74. ErrorOr<int> AvailablePort::update_available_ports_list_file()
  75. {
  76. if (auto error_or_void = Core::System::mkdir("/usr/Ports/"sv, 0655); error_or_void.is_error()) {
  77. if (error_or_void.error().code() != EEXIST)
  78. return error_or_void.release_error();
  79. }
  80. if (!Core::System::access("/usr/Ports/AvailablePorts.md"sv, R_OK).is_error() && FileSystem::remove("/usr/Ports/AvailablePorts.md"sv, FileSystem::RecursionMode::Disallowed).is_error()) {
  81. outln("pkg: /usr/Ports/AvailablePorts.md exists, but can't delete it before updating it!");
  82. return 0;
  83. }
  84. RefPtr<Protocol::Request> request;
  85. auto protocol_client = TRY(Protocol::RequestClient::try_create());
  86. HashMap<ByteString, ByteString, CaseInsensitiveStringTraits> request_headers;
  87. Core::ProxyData proxy_data {};
  88. auto output_stream = TRY(Core::File::open("/usr/Ports/AvailablePorts.md"sv, Core::File::OpenMode::ReadWrite, 0644));
  89. Core::EventLoop loop;
  90. URL::URL url("https://raw.githubusercontent.com/SerenityOS/serenity/master/Ports/AvailablePorts.md");
  91. ByteString method = "GET";
  92. outln("pkg: Syncing packages database...");
  93. request = protocol_client->start_request(method, url, request_headers, ReadonlyBytes {}, proxy_data);
  94. auto on_data_received = [&](auto data) {
  95. output_stream->write_until_depleted(data).release_value_but_fixme_should_propagate_errors();
  96. };
  97. auto on_finished = [&](bool success, auto) {
  98. if (!success)
  99. outln("pkg: Syncing packages database failed.");
  100. else
  101. outln("pkg: Syncing packages database done.");
  102. loop.quit(success ? 0 : 1);
  103. };
  104. request->set_unbuffered_request_callbacks({}, move(on_data_received), move(on_finished));
  105. return loop.exec();
  106. }
  107. static ErrorOr<String> extract_port_name_from_column(Markdown::Table::Column const& column, size_t row_index)
  108. {
  109. struct : public Markdown::Visitor {
  110. virtual RecursionDecision visit(Markdown::Text::LinkNode const& node) override
  111. {
  112. text_node = node.text.ptr();
  113. return RecursionDecision::Break;
  114. }
  115. public:
  116. Markdown::Text::Node* text_node;
  117. } text_node_find_visitor;
  118. column.rows[row_index].walk(text_node_find_visitor);
  119. VERIFY(text_node_find_visitor.text_node);
  120. StringBuilder string_builder;
  121. text_node_find_visitor.text_node->render_for_raw_print(string_builder);
  122. return string_builder.to_string();
  123. }
  124. ErrorOr<HashMap<String, AvailablePort>> AvailablePort::read_available_ports_list()
  125. {
  126. if (Core::System::access("/usr/Ports/AvailablePorts.md"sv, R_OK).is_error()) {
  127. warnln("pkg: /usr/Ports/AvailablePorts.md doesn't exist, did you run pkg -u first?");
  128. return Error::from_errno(ENOENT);
  129. }
  130. auto available_ports_file = TRY(Core::File::open("/usr/Ports/AvailablePorts.md"sv, Core::File::OpenMode::Read, 0600));
  131. auto content_buffer = TRY(available_ports_file->read_until_eof());
  132. auto content = StringView(content_buffer);
  133. auto document = Markdown::Document::parse(content);
  134. auto finder = MarkdownTableFinder::analyze(*document);
  135. if (finder.table_count() != 1)
  136. return Error::from_string_literal("Invalid tables count in /usr/Ports/AvailablePorts.md");
  137. VERIFY(finder.tables()[0]);
  138. auto possible_port_name_column = get_column_in_table(*finder.tables()[0], "Port"sv);
  139. auto possible_port_version_column = get_column_in_table(*finder.tables()[0], "Version"sv);
  140. auto possible_port_website_column = get_column_in_table(*finder.tables()[0], "Website"sv);
  141. if (!possible_port_name_column.has_value())
  142. return Error::from_string_literal("pkg: Port column not found /usr/Ports/AvailablePorts.md");
  143. if (!possible_port_version_column.has_value())
  144. return Error::from_string_literal("pkg: Version column not found /usr/Ports/AvailablePorts.md");
  145. if (!possible_port_website_column.has_value())
  146. return Error::from_string_literal("pkg: Website column not found /usr/Ports/AvailablePorts.md");
  147. auto const& port_name_column = possible_port_name_column.release_value();
  148. auto const& port_version_column = possible_port_version_column.release_value();
  149. auto const& port_website_column = possible_port_website_column.release_value();
  150. VERIFY(port_name_column.rows.size() == port_version_column.rows.size());
  151. VERIFY(port_version_column.rows.size() == port_website_column.rows.size());
  152. HashMap<String, AvailablePort> available_ports;
  153. for (size_t port_index = 0; port_index < port_name_column.rows.size(); port_index++) {
  154. auto name = TRY(extract_port_name_from_column(port_name_column, port_index));
  155. auto website = TRY(String::from_byte_string(port_website_column.rows[port_index].render_for_terminal()));
  156. if (website.is_empty())
  157. website = "n/a"_string;
  158. auto version = TRY(String::from_byte_string(port_version_column.rows[port_index].render_for_terminal()));
  159. if (version.is_empty())
  160. version = "n/a"_string;
  161. TRY(available_ports.try_set(name, AvailablePort { name, version, website }));
  162. }
  163. return available_ports;
  164. }