// file : mod/mod-packages.cxx -*- C++ -*- // 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/server/module.hxx> #include <web/server/mime-url-encoding.hxx> #include <web/xhtml/serialization.hxx> #include <libbrep/package.hxx> #include <libbrep/package-odb.hxx> #include <mod/page.hxx> #include <mod/module-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::optional<brep::string>& t) { using query = query<T>; return "(" + (q.empty () ? query ("NULL") : "plainto_tsquery (" + query::_val (q) + ")") + "," + (!t ? query ("NULL") : query (query::_val (*t))) + ")"; } bool brep::packages:: handle (request& rq, response& rs) { using namespace web::xhtml; HANDLER_DIAG; const size_t res_page (options_->search_page_entries ()); const dir_path& root (options_->root ()); const string& title (options_->search_title ()); const string& tenant_name (options_->tenant_name ()); 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 ("packages.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"); // On the first page print the search page description, if specified. // if (page == 0) { const web::xhtml::fragment& desc (options_->search_description ()); if (!desc.empty ()) s << DIV(ID="search-description") << desc << ~DIV; } // If the tenant is empty then we are in the global view and will display // packages from all the public tenants. // optional<string> tn; if (!tenant.empty ()) tn = tenant; session sn; transaction t (package_db_->begin ()); size_t pkg_count ( package_db_->query_value<latest_package_count> ( search_param<latest_package_count> (squery, tn))); s << FORM_SEARCH (squery, "packages") << DIV_COUNTER (pkg_count, "Package", "Packages"); // Enclose the subsequent tables to be able to use nth-child CSS selector. // s << DIV; for (const auto& pr: package_db_->query<latest_package_search_rank> ( search_param<latest_package_search_rank> (squery, tn) + "ORDER BY rank DESC, name, tenant" + "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, root, p->tenant) << TR_SUMMARY (p->summary) << TR_LICENSE (p->license_alternatives) << TR_DEPENDS (p->dependencies, root, p->tenant); // In the global view mode add the tenant packages link. Note that the // global view (and the link) makes sense only in the multi-tenant mode. // if (!tn && !p->tenant.empty ()) s << TR_TENANT (tenant_name, "packages", root, p->tenant); s << ~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; }