172 lines
3.7 KiB
C++
172 lines
3.7 KiB
C++
|
|
#include <selectorResolve.h>
|
||
|
|
#include <algorithm>
|
||
|
|
#include <cctype>
|
||
|
|
#include <optional>
|
||
|
|
#include <sstream>
|
||
|
|
#include <stdexcept>
|
||
|
|
|
||
|
|
namespace lcamera_dev {
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
std::string toLowerAscii(const std::string& text)
|
||
|
|
{
|
||
|
|
std::string lowered = text;
|
||
|
|
for (char& ch : lowered) {
|
||
|
|
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||
|
|
}
|
||
|
|
return lowered;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool recordMatchesCriterion(
|
||
|
|
const CameraIdentityRecord& record, const SelectorCriterion& criterion)
|
||
|
|
{
|
||
|
|
switch (criterion.kind)
|
||
|
|
{
|
||
|
|
case SelectorCriterionKind::LibcameraId:
|
||
|
|
return record.id == criterion.value;
|
||
|
|
|
||
|
|
case SelectorCriterionKind::Index:
|
||
|
|
return false;
|
||
|
|
|
||
|
|
case SelectorCriterionKind::Model:
|
||
|
|
return record.model == criterion.value;
|
||
|
|
|
||
|
|
case SelectorCriterionKind::ModelSubstr:
|
||
|
|
return record.model.find(criterion.value) != std::string::npos;
|
||
|
|
|
||
|
|
case SelectorCriterionKind::Location:
|
||
|
|
return toLowerAscii(record.locationLabel)
|
||
|
|
== toLowerAscii(criterion.value);
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
int parseIndexCriterion(const SelectorCriterion& criterion)
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
return std::stoi(criterion.value);
|
||
|
|
}
|
||
|
|
catch (const std::exception&) {
|
||
|
|
throw std::runtime_error("Invalid index: value in deviceSelector");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
std::string formatCameraListForDiagnostics(
|
||
|
|
const std::vector<CameraIdentityRecord>& records)
|
||
|
|
{
|
||
|
|
std::ostringstream result;
|
||
|
|
result << "Known cameras:\n";
|
||
|
|
|
||
|
|
for (size_t i = 0; i < records.size(); ++i)
|
||
|
|
{
|
||
|
|
const CameraIdentityRecord& record = records[i];
|
||
|
|
result << " [" << i << "] id=" << record.id;
|
||
|
|
if (!record.model.empty()) {
|
||
|
|
result << " model=" << record.model;
|
||
|
|
}
|
||
|
|
if (!record.locationLabel.empty()) {
|
||
|
|
result << " location=" << record.locationLabel;
|
||
|
|
}
|
||
|
|
result << '\n';
|
||
|
|
}
|
||
|
|
|
||
|
|
return result.str();
|
||
|
|
}
|
||
|
|
|
||
|
|
CameraIdentityRecord resolveSelectorAgainstRecords(
|
||
|
|
const std::vector<SelectorCriterion>& criteria,
|
||
|
|
const std::vector<CameraIdentityRecord>& records)
|
||
|
|
{
|
||
|
|
std::optional<int> indexCriterion;
|
||
|
|
for (const SelectorCriterion& criterion : criteria)
|
||
|
|
{
|
||
|
|
if (criterion.kind != SelectorCriterionKind::Index) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const int index = parseIndexCriterion(criterion);
|
||
|
|
if (index < 0
|
||
|
|
|| static_cast<size_t>(index) >= records.size())
|
||
|
|
{
|
||
|
|
throw std::runtime_error("index: selector out of range");
|
||
|
|
}
|
||
|
|
|
||
|
|
indexCriterion = index;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<const CameraIdentityRecord*> matches;
|
||
|
|
matches.reserve(records.size());
|
||
|
|
|
||
|
|
for (const CameraIdentityRecord& record : records)
|
||
|
|
{
|
||
|
|
bool matchesAll = true;
|
||
|
|
for (const SelectorCriterion& criterion : criteria)
|
||
|
|
{
|
||
|
|
if (criterion.kind == SelectorCriterionKind::Index) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!recordMatchesCriterion(record, criterion))
|
||
|
|
{
|
||
|
|
matchesAll = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!matchesAll) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
matches.push_back(&record);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (indexCriterion.has_value())
|
||
|
|
{
|
||
|
|
const CameraIdentityRecord& indexedRecord =
|
||
|
|
records.at(static_cast<size_t>(*indexCriterion));
|
||
|
|
|
||
|
|
auto it = std::find_if(
|
||
|
|
matches.begin(), matches.end(),
|
||
|
|
[&indexedRecord](const CameraIdentityRecord* candidate) {
|
||
|
|
return candidate->id == indexedRecord.id;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (it == matches.end())
|
||
|
|
{
|
||
|
|
throw std::runtime_error(
|
||
|
|
"index: criterion conflicts with other selector clauses");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (matches.size() > 1)
|
||
|
|
{
|
||
|
|
throw std::runtime_error(
|
||
|
|
"Ambiguous deviceSelector: multiple cameras match\n"
|
||
|
|
+ formatCameraListForDiagnostics(records));
|
||
|
|
}
|
||
|
|
|
||
|
|
return indexedRecord;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (matches.empty())
|
||
|
|
{
|
||
|
|
throw std::runtime_error(
|
||
|
|
"No camera matches deviceSelector\n"
|
||
|
|
+ formatCameraListForDiagnostics(records));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (matches.size() > 1)
|
||
|
|
{
|
||
|
|
throw std::runtime_error(
|
||
|
|
"Ambiguous deviceSelector: multiple cameras match\n"
|
||
|
|
+ formatCameraListForDiagnostics(records));
|
||
|
|
}
|
||
|
|
|
||
|
|
return *matches.front();
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace lcamera_dev
|