blob: e18cd3365417bd928c03c21cdd396edfd04dc3be [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "vm_tools/garcon/app_search.h"
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/strings/string_split.h>
#include <base/strings/string_tokenizer.h>
#include <base/strings/string_util.h>
#include <algorithm>
namespace {
constexpr char kDevelFacetPrefix[] = "devel::";
constexpr char kGraphicalTag[] = "interface::graphical";
// TODO(danielng): Need UX spec.
constexpr double ResultThreshold = 0.6;
bool OrderBySecondDescending(const std::pair<std::string, float>& x,
const std::pair<std::string, float>& y) {
if (x.second == y.second) {
return x.first < y.first;
} else {
return x.second > y.second;
}
}
} // namespace
namespace vm_tools {
namespace garcon {
bool ParseDebtags(const std::string& file_name,
std::vector<std::string>* out_packages,
std::string* out_error) {
CHECK(out_packages);
CHECK(out_error);
base::FilePath file_path(file_name);
if (!base::PathExists(file_path)) {
out_error->assign("package-tags file '" + file_name + "' does not exist");
LOG(ERROR) << *out_error;
return false;
}
std::string file_contents;
if (!base::ReadFileToString(file_path, &file_contents)) {
out_error->assign("Failed reading in package-tags file '" + file_name +
"'");
PLOG(ERROR) << *out_error;
return false;
}
// See: https://sources.debian.org/src/debtags/2.1.5/debtags/#L601
std::vector<base::StringPiece> lines = base::SplitStringPiece(
file_contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& curr_line : lines) {
size_t pos = curr_line.find_first_of(":", 0);
base::StringPiece name = curr_line.substr(0, pos);
std::vector<base::StringPiece> tags = base::SplitStringPiece(
curr_line.substr(pos + 1), ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
// Track what tags a package has.
bool contains_devel = false;
bool contains_graphical = false;
for (const auto& tag : tags) {
if (!contains_devel && tag.starts_with(kDevelFacetPrefix))
contains_devel = true;
if (!contains_graphical && tag == kGraphicalTag)
contains_graphical = true;
if (contains_devel && contains_graphical) {
out_packages->push_back(name.as_string());
break;
}
}
}
return true;
}
std::vector<std::pair<std::string, float>> SearchPackages(
const std::vector<std::string>& package_list, const std::string& query) {
std::vector<std::pair<std::string, float>> results;
std::string query_lower_case = base::ToLowerASCII(query);
for (auto& package_name : package_list) {
if (package_name.find(query_lower_case) != base::StringPiece::npos) {
// TODO(danielng): expand logic for ranking search results,
// possibly look to incorporating popularity statistics
double score =
static_cast<float>(query_lower_case.size()) / package_name.size();
if (score >= ResultThreshold)
results.emplace_back(package_name, score);
}
}
std::sort(results.begin(), results.end(), OrderBySecondDescending);
return results;
}
} // namespace garcon
} // namespace vm_tools