aboutsummaryrefslogtreecommitdiff
path: root/mod/mod-packages.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'mod/mod-packages.cxx')
-rw-r--r--mod/mod-packages.cxx195
1 files changed, 195 insertions, 0 deletions
diff --git a/mod/mod-packages.cxx b/mod/mod-packages.cxx
new file mode 100644
index 0000000..1157378
--- /dev/null
+++ b/mod/mod-packages.cxx
@@ -0,0 +1,195 @@
+// file : mod/mod-packages.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <mod/mod-packages.hxx>
+
+#include <libstudxml/serializer.hxx>
+
+#include <odb/session.hxx>
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <web/xhtml.hxx>
+#include <web/module.hxx>
+#include <web/mime-url-encoding.hxx>
+
+#include <libbrep/package.hxx>
+#include <libbrep/package-odb.hxx>
+
+#include <mod/page.hxx>
+#include <mod/options.hxx>
+
+using namespace odb::core;
+using namespace brep::cli;
+
+// While currently the user-defined copy constructor is not required (we don't
+// need to deep copy nullptr's), it is a good idea to keep the placeholder
+// ready for less trivial cases.
+//
+brep::packages::
+packages (const packages& r)
+ : database_module (r),
+ options_ (r.initialized_ ? r.options_ : nullptr)
+{
+}
+
+void brep::packages::
+init (scanner& s)
+{
+ HANDLER_DIAG;
+
+ options_ = make_shared<options::packages> (
+ s, unknown_mode::fail, unknown_mode::fail);
+
+ database_module::init (*options_, options_->package_db_retry ());
+
+ if (options_->root ().empty ())
+ options_->root (dir_path ("/"));
+
+ // Check that the database 'package' schema matches the current one. It's
+ // enough to perform the check in just a single module implementation (and we
+ // don't do in the dispatcher because it doesn't use the database).
+ //
+ // Note that the failure can be reported by each web server worker process.
+ // While it could be tempting to move the check to the
+ // repository_root::version() function, it would be wrong. The function can
+ // be called by a different process (usually the web server root one) not
+ // having the proper permissions to access the database.
+ //
+ const string ds ("package");
+ if (schema_catalog::current_version (*package_db_, ds) !=
+ package_db_->schema_version (ds))
+ fail << "database 'package' schema differs from the current one (module "
+ << BREP_VERSION_ID << ")";
+}
+
+template <typename T>
+static inline query<T>
+search_param (const brep::string& q, const brep::string& t)
+{
+ using query = query<T>;
+ return "(" +
+ (q.empty ()
+ ? query ("NULL")
+ : "plainto_tsquery (" + query::_val (q) + ")") +
+ "," +
+ query::_val (t) +
+ ")";
+}
+
+bool brep::packages::
+handle (request& rq, response& rs)
+{
+ using namespace web::xhtml;
+
+ HANDLER_DIAG;
+
+ const size_t res_page (options_->search_results ());
+ const dir_path& root (options_->root ());
+ const string& title (options_->search_title ());
+
+ params::packages params;
+
+ try
+ {
+ name_value_scanner s (rq.parameters (8 * 1024));
+ params = params::packages (s, unknown_mode::fail, unknown_mode::fail);
+ }
+ catch (const cli::exception& e)
+ {
+ throw invalid_request (400, e.what ());
+ }
+
+ size_t page (params.page ());
+ const string& squery (params.q ());
+ string equery (web::mime_url_encode (squery));
+
+ xml::serializer s (rs.content (), title);
+
+ s << HTML
+ << HEAD
+ << TITLE
+ << title;
+
+ if (!squery.empty ())
+ s << " " << squery;
+
+ s << ~TITLE
+ << CSS_LINKS (path ("package-search.css"), root)
+ //
+ // This hack is required to avoid the "flash of unstyled content", which
+ // happens due to the presence of the autofocus attribute in the input
+ // element of the search form. The problem appears in Firefox and has a
+ // (4-year old, at the time of this writing) bug report:
+ //
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=712130
+ //
+ // @@ An update: claimed to be fixed in Firefox 60 that is released in
+ // May 2018. Is it time to cleanup? Remember to cleanup in all places.
+ //
+ << SCRIPT << " " << ~SCRIPT
+ << ~HEAD
+ << BODY
+ << DIV_HEADER (options_->logo (), options_->menu (), root, tenant)
+ << DIV(ID="content");
+
+ session sn;
+ transaction t (package_db_->begin ());
+
+ auto pkg_count (
+ package_db_->query_value<latest_package_count> (
+ search_param<latest_package_count> (squery, tenant)));
+
+ s << FORM_SEARCH (squery, "packages")
+ << DIV_COUNTER (pkg_count, "Package", "Packages");
+
+ // Enclose the subsequent tables to be able to use nth-child CSS selector.
+ //
+ // @@ TENANT: use tenant for sorting when add support for global view.
+ //
+ s << DIV;
+ for (const auto& pr:
+ package_db_->query<latest_package_search_rank> (
+ search_param<latest_package_search_rank> (squery, tenant) +
+ "ORDER BY rank DESC, name" +
+ "OFFSET" + to_string (page * res_page) +
+ "LIMIT" + to_string (res_page)))
+ {
+ shared_ptr<package> p (package_db_->load<package> (pr.id));
+
+ s << TABLE(CLASS="proplist package")
+ << TBODY
+ << TR_NAME (p->name, equery, root, tenant)
+ << TR_SUMMARY (p->summary)
+ << TR_LICENSE (p->license_alternatives)
+ << TR_TAGS (p->project, p->tags, root, tenant)
+ << TR_DEPENDS (p->dependencies, root, tenant)
+ << TR_REQUIRES (p->requirements)
+ << ~TBODY
+ << ~TABLE;
+ }
+ s << ~DIV;
+
+ t.commit ();
+
+ string url (tenant_dir (root, tenant).string () + "?packages");
+
+ if (!equery.empty ())
+ {
+ url += '=';
+ url += equery;
+ }
+
+ s << DIV_PAGER (page,
+ pkg_count,
+ res_page,
+ options_->search_pages (),
+ url)
+ << ~DIV
+ << ~BODY
+ << ~HTML;
+
+ return true;
+}