| 129 | } // namespace |
| 130 | |
| 131 | bool ToolboxRuntimeHost::onCreateParserIngest( |
| 132 | void* ctx, uint32_t data_source_id, PJ_data_source_runtime_host_t* out_host, PJ_error_t* out_error) noexcept { |
| 133 | auto* self = static_cast<ToolboxRuntimeHost*>(ctx); |
| 134 | if (self == nullptr || out_host == nullptr) { |
| 135 | return parserIngestFail(out_error, "invalid arguments"); |
| 136 | } |
| 137 | try { |
| 138 | if (self->parser_ingest_deps_.catalog == nullptr) { |
| 139 | return parserIngestFail(out_error, "parser ingest is not configured on this host"); |
| 140 | } |
| 141 | // Same validation the toolbox write host applies to object topics |
| 142 | // (plugin_data_host.cpp toolboxRegisterObjectTopic): the id must be a |
| 143 | // live dataset. |
| 144 | if (self->engine_.getDataset(static_cast<DatasetId>(data_source_id)) == nullptr) { |
| 145 | return parserIngestFail(out_error, "data source not found — call createDataSource first"); |
| 146 | } |
| 147 | std::lock_guard lock(self->parser_ingest_mu_); |
| 148 | // Construct first, insert only on success: a throwing constructor must not |
| 149 | // leave a null entry in the map (the release path dereferences entries). |
| 150 | auto it = self->parser_ingests_.find(data_source_id); |
| 151 | if (it == self->parser_ingests_.end()) { |
| 152 | auto host = std::make_unique<DataSourceRuntimeHost>( |
| 153 | self->engine_, *self->parser_ingest_deps_.catalog, static_cast<DatasetId>(data_source_id), |
| 154 | PJ_data_source_handle_t{data_source_id}, self->object_store_, |
| 155 | "toolbox-ingest-" + std::to_string(data_source_id), self->parser_ingest_deps_.register_object_parser, |
| 156 | // No secondary store/engine (toolbox ingest writes straight into the |
| 157 | // primary), and no plugin-DSO keepalive — the toolbox isn't a |
| 158 | // DataSource plugin; its render parsers come from the catalog, which |
| 159 | // owns their .so lifetime and outlives every ingest context. |
| 160 | /*secondary_object_store=*/nullptr, /*secondary_data_engine=*/nullptr, |
| 161 | /*library_keepalive=*/std::shared_ptr<void>{}); |
| 162 | it = self->parser_ingests_.emplace(data_source_id, std::move(host)).first; |
| 163 | } |
| 164 | // Remember the dataset for the next notify_data_changed: an ingest context |
| 165 | // marks a bulk import the host will want to focus playback on. |
| 166 | const auto ds_id = static_cast<DatasetId>(data_source_id); |
| 167 | auto& pending = self->pending_ingest_datasets_; |
| 168 | if (std::find(pending.begin(), pending.end(), ds_id) == pending.end()) { |
| 169 | pending.push_back(ds_id); |
| 170 | } |
| 171 | *out_host = it->second->hostHandle(); // out_host written ONLY on success (documented contract) |
| 172 | return true; |
| 173 | } catch (const std::exception& e) { |
| 174 | return parserIngestFail(out_error, e.what()); |
| 175 | } catch (...) { |
| 176 | return parserIngestFail(out_error, "unknown exception in create_parser_ingest"); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | bool ToolboxRuntimeHost::onReleaseParserIngest(void* ctx, uint32_t data_source_id, PJ_error_t* out_error) noexcept { |
| 181 | auto* self = static_cast<ToolboxRuntimeHost*>(ctx); |
nothing calls this directly
no test coverage detected