#include #include #include #include #include #include namespace lcamera_dev { namespace { std::string toLowerAscii(const std::string& text) { std::string lowered = text; for (char& ch : lowered) { ch = static_cast(std::tolower(static_cast(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& 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& criteria, const std::vector& records) { std::optional indexCriterion; for (const SelectorCriterion& criterion : criteria) { if (criterion.kind != SelectorCriterionKind::Index) { continue; } const int index = parseIndexCriterion(criterion); if (index < 0 || static_cast(index) >= records.size()) { throw std::runtime_error("index: selector out of range"); } indexCriterion = index; } std::vector 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(*indexCriterion)); bool hasNonIndexCriteria = false; for (const SelectorCriterion& criterion : criteria) { if (criterion.kind != SelectorCriterionKind::Index) { hasNonIndexCriteria = true; break; } } if (!hasNonIndexCriteria) { return indexedRecord; } 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"); } 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(); } CameraIdentityRecord resolveDeviceSelectorAgainstRecords( const std::string& deviceSelector, const std::vector& records) { const std::vector criteria = parseDeviceSelector(deviceSelector); return resolveSelectorAgainstRecords(criteria, records); } } // namespace lcamera_dev