.
This commit is contained in:
Generated
+280
-247
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -19,12 +19,14 @@ iced = { git = "https://github.com/iced-rs/iced.git", branch = "master", feature
|
|||||||
oxigraph = { version = "0.5", features = ["rdf-12"] }
|
oxigraph = { version = "0.5", features = ["rdf-12"] }
|
||||||
oxilangtag = "0.1"
|
oxilangtag = "0.1"
|
||||||
parse_link_header = "0.4"
|
parse_link_header = "0.4"
|
||||||
|
pin-project-lite = "0.2"
|
||||||
rfd = "0.17"
|
rfd = "0.17"
|
||||||
slotmap = "1.1"
|
slotmap = "1.1"
|
||||||
tantivy = "0.26"
|
tantivy = "0.26"
|
||||||
tar = "0.4"
|
tar = "0.4"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tokio = { version = "1.52", features = ["rt", "rt-multi-thread", "macros", "fs"] }
|
tokio = { version = "1.53", features = ["rt", "rt-multi-thread", "macros", "fs"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-appender = "0.2"
|
tracing-appender = "0.2"
|
||||||
|
tracing-futures = "0.2"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
+47
-36
@@ -17,6 +17,9 @@
|
|||||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||||
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
||||||
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
|
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
|
||||||
|
@prefix premis: <http://www.loc.gov/premis/rdf/v1#> .
|
||||||
|
@prefix ebucore: <http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#> .
|
||||||
|
@prefix premis3: <http://www.loc.gov/premis/rdf/v3/> .
|
||||||
@base <https://graphofliberty.org/2026/04/ont/> .
|
@base <https://graphofliberty.org/2026/04/ont/> .
|
||||||
|
|
||||||
<https://graphofliberty.org/2026/04/ont> rdf:type owl:Ontology .
|
<https://graphofliberty.org/2026/04/ont> rdf:type owl:Ontology .
|
||||||
@@ -84,10 +87,6 @@ xsd:unsignedShort rdf:type rdfs:Datatype ;
|
|||||||
# Object Properties
|
# Object Properties
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
### http://fedora.info/definitions/v4/repository#hasParent
|
|
||||||
fedora:hasParent rdf:type owl:ObjectProperty .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/associatedProperty
|
### https://graphofliberty.org/2026/04/ont/associatedProperty
|
||||||
:associatedProperty rdf:type owl:ObjectProperty ;
|
:associatedProperty rdf:type owl:ObjectProperty ;
|
||||||
rdfs:label "associated property"@en .
|
rdfs:label "associated property"@en .
|
||||||
@@ -110,35 +109,12 @@ fedora:hasParent rdf:type owl:ObjectProperty .
|
|||||||
# Data properties
|
# Data properties
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
### http://fedora.info/definitions/v4/repository#created
|
### https://graphofliberty.org/2026/04/ont/categoryId
|
||||||
fedora:created rdf:type owl:DatatypeProperty ;
|
:categoryId rdf:type owl:DatatypeProperty ;
|
||||||
rdfs:subPropertyOf owl:topDataProperty .
|
rdfs:domain :SearchableClass ;
|
||||||
|
rdfs:range xsd:nonNegativeInteger ;
|
||||||
|
rdfs:comment "An integer associated with the class for fast lookup in a database."@en ;
|
||||||
### http://fedora.info/definitions/v4/repository#createdBy
|
rdfs:label "category id" .
|
||||||
fedora:createdBy rdf:type owl:DatatypeProperty ;
|
|
||||||
rdfs:subPropertyOf owl:topDataProperty .
|
|
||||||
|
|
||||||
|
|
||||||
### http://fedora.info/definitions/v4/repository#lastModified
|
|
||||||
fedora:lastModified rdf:type owl:DatatypeProperty .
|
|
||||||
|
|
||||||
|
|
||||||
### http://fedora.info/definitions/v4/repository#lastModifiedBy
|
|
||||||
fedora:lastModifiedBy rdf:type owl:DatatypeProperty .
|
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/ns/ldp#contains
|
|
||||||
ldp:contains rdf:type owl:DatatypeProperty ;
|
|
||||||
rdfs:subPropertyOf owl:topDataProperty .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/catalogId
|
|
||||||
:catalogId rdf:type owl:DatatypeProperty ;
|
|
||||||
rdfs:domain :SearchableClass ;
|
|
||||||
rdfs:range xsd:nonNegativeInteger ;
|
|
||||||
rdfs:comment "An integer associated with the class for fast lookup in a database."@en ;
|
|
||||||
rdfs:label "catalog id" .
|
|
||||||
|
|
||||||
|
|
||||||
### https://graphofliberty.org/2026/04/ont/fieldLabel
|
### https://graphofliberty.org/2026/04/ont/fieldLabel
|
||||||
@@ -191,6 +167,11 @@ ldp:contains rdf:type owl:DatatypeProperty ;
|
|||||||
# Individuals
|
# Individuals
|
||||||
#################################################################
|
#################################################################
|
||||||
|
|
||||||
|
### http://fedora.info/definitions/v4/repository#Binary
|
||||||
|
fedora:Binary rdf:type owl:NamedIndividual ;
|
||||||
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
### http://fedora.info/definitions/v4/repository#Container
|
### http://fedora.info/definitions/v4/repository#Container
|
||||||
fedora:Container rdf:type owl:NamedIndividual ;
|
fedora:Container rdf:type owl:NamedIndividual ;
|
||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
@@ -211,6 +192,11 @@ fedora:createdBy rdf:type owl:NamedIndividual ;
|
|||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
|
### http://fedora.info/definitions/v4/repository#hasFixityService
|
||||||
|
fedora:hasFixityService rdf:type owl:NamedIndividual ;
|
||||||
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
### http://fedora.info/definitions/v4/repository#hasParent
|
### http://fedora.info/definitions/v4/repository#hasParent
|
||||||
fedora:hasParent rdf:type owl:NamedIndividual ;
|
fedora:hasParent rdf:type owl:NamedIndividual ;
|
||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
@@ -241,7 +227,27 @@ rdac:C10004 rdf:type owl:NamedIndividual ,
|
|||||||
:SearchableClass ;
|
:SearchableClass ;
|
||||||
:associatedProperty <http://rdaregistry.info/Elements/a/datatype/P50291> ,
|
:associatedProperty <http://rdaregistry.info/Elements/a/datatype/P50291> ,
|
||||||
<http://rdaregistry.info/Elements/a/datatype/P50292> ;
|
<http://rdaregistry.info/Elements/a/datatype/P50292> ;
|
||||||
:catalogId "2"^^xsd:nonNegativeInteger .
|
:categoryId "2"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
|
### http://www.loc.gov/premis/rdf/v1#hasMessageDigest
|
||||||
|
premis:hasMessageDigest rdf:type owl:NamedIndividual ;
|
||||||
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
|
### http://www.loc.gov/premis/rdf/v1#hasSize
|
||||||
|
premis:hasSize rdf:type owl:NamedIndividual ;
|
||||||
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
|
### http://www.loc.gov/premis/rdf/v3/File
|
||||||
|
premis3:File rdf:type owl:NamedIndividual ;
|
||||||
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
|
### http://www.loc.gov/premis/rdf/v3/size
|
||||||
|
premis3:size rdf:type owl:NamedIndividual ;
|
||||||
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
|
### http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
|
||||||
@@ -250,7 +256,7 @@ rdf:Property rdf:type owl:NamedIndividual ,
|
|||||||
:associatedProperty rdfs:comment ,
|
:associatedProperty rdfs:comment ,
|
||||||
rdfs:label ,
|
rdfs:label ,
|
||||||
skos:definition ;
|
skos:definition ;
|
||||||
:catalogId "0"^^xsd:nonNegativeInteger .
|
:categoryId "0"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/2000/01/rdf-schema#Class
|
### http://www.w3.org/2000/01/rdf-schema#Class
|
||||||
@@ -259,7 +265,7 @@ rdfs:Class rdf:type owl:NamedIndividual ,
|
|||||||
:associatedProperty rdfs:comment ,
|
:associatedProperty rdfs:comment ,
|
||||||
rdfs:label ,
|
rdfs:label ,
|
||||||
skos:definition ;
|
skos:definition ;
|
||||||
:catalogId "1"^^xsd:nonNegativeInteger .
|
:categoryId "1"^^xsd:nonNegativeInteger .
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/2000/01/rdf-schema#comment
|
### http://www.w3.org/2000/01/rdf-schema#comment
|
||||||
@@ -287,6 +293,11 @@ ldp:Container rdf:type owl:NamedIndividual ;
|
|||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
|
### http://www.w3.org/ns/ldp#NonRDFSource
|
||||||
|
ldp:NonRDFSource rdf:type owl:NamedIndividual ;
|
||||||
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|
||||||
|
|
||||||
### http://www.w3.org/ns/ldp#RDFSource
|
### http://www.w3.org/ns/ldp#RDFSource
|
||||||
ldp:RDFSource rdf:type owl:NamedIndividual ;
|
ldp:RDFSource rdf:type owl:NamedIndividual ;
|
||||||
:readOnly "true"^^xsd:boolean .
|
:readOnly "true"^^xsd:boolean .
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ thiserror.workspace = true
|
|||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-appender.workspace = true
|
tracing-appender.workspace = true
|
||||||
|
tracing-futures.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
+59
-47
@@ -20,7 +20,8 @@ use oxigraph::io::RdfFormat;
|
|||||||
use oxigraph::model::vocab::{rdf, rdfs};
|
use oxigraph::model::vocab::{rdf, rdfs};
|
||||||
use oxigraph::model::{BaseDirection, Dataset, NamedNode, Quad, Term};
|
use oxigraph::model::{BaseDirection, Dataset, NamedNode, Quad, Term};
|
||||||
use tracing::{debug, debug_span, error, trace};
|
use tracing::{debug, debug_span, error, trace};
|
||||||
use tracing::span::EnteredSpan;
|
use tracing::span::Span;
|
||||||
|
use tracing_futures::Instrument;
|
||||||
use crate::navigator::Navigator;
|
use crate::navigator::Navigator;
|
||||||
use crate::rdf::curie::CurieHelper;
|
use crate::rdf::curie::CurieHelper;
|
||||||
use crate::rdf::language;
|
use crate::rdf::language;
|
||||||
@@ -36,6 +37,7 @@ pub(crate) enum Message {
|
|||||||
AddRdfSource(RdfSource<Dataset>),
|
AddRdfSource(RdfSource<Dataset>),
|
||||||
ConcludeTraversal,
|
ConcludeTraversal,
|
||||||
IndexQueryResults(HashMap<NamedNode, IndexEntry>),
|
IndexQueryResults(HashMap<NamedNode, IndexEntry>),
|
||||||
|
CommitIndex,
|
||||||
WindowClosed(window::Id),
|
WindowClosed(window::Id),
|
||||||
URLInputChanged(String),
|
URLInputChanged(String),
|
||||||
URLInputSubmitted,
|
URLInputSubmitted,
|
||||||
@@ -105,7 +107,7 @@ pub(crate) struct Publisher {
|
|||||||
hovered_row: Option<QuadKey>,
|
hovered_row: Option<QuadKey>,
|
||||||
search_state: Option<SearchState>,
|
search_state: Option<SearchState>,
|
||||||
index: SearchIndex,
|
index: SearchIndex,
|
||||||
traversal: Option<(Dataset, EnteredSpan)>,
|
traversal: Option<Dataset>,
|
||||||
show_overwrite_confirmation: bool,
|
show_overwrite_confirmation: bool,
|
||||||
modified: bool,
|
modified: bool,
|
||||||
show_new_document_buttons: bool,
|
show_new_document_buttons: bool,
|
||||||
@@ -177,20 +179,24 @@ impl Publisher {
|
|||||||
|
|
||||||
match message {
|
match message {
|
||||||
Message::RebuildIndex => {
|
Message::RebuildIndex => {
|
||||||
self.index
|
if let Ok(writer) = self.index.writer() {
|
||||||
.writer()
|
writer.remove_all().expect("Unable to clear index");
|
||||||
.expect("Unable to create writer")
|
|
||||||
.remove_all()
|
|
||||||
.expect("Unable to clear index");
|
|
||||||
|
|
||||||
let query = Ontology::index_query(&*language::ENGLISH_OR_UNTAGGED);
|
let index_future = self.ontology
|
||||||
let query_results = self.ontology.execute_query(query).expect("Unable to generate index of entities");
|
.index(&*language::ENGLISH_OR_UNTAGGED, None);
|
||||||
let results = Ontology::transform_index_results(query_results);
|
let index_task = Task::perform(index_future, |task_result| {
|
||||||
task = Task::done(Message::IndexQueryResults(results));
|
match task_result {
|
||||||
|
Ok(results) => Message::IndexQueryResults(results),
|
||||||
|
Err(err) => Message::ShowError(err.to_string()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
task = Task::batch([
|
||||||
|
index_task,
|
||||||
|
Task::done(Message::Traverse),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::Traverse => {
|
Message::Traverse => {
|
||||||
self.traversal = Some((self.ontology.to_dataset(), debug_span!("Repository Traversal").entered()));
|
|
||||||
|
|
||||||
let client = self.http_client.clone();
|
let client = self.http_client.clone();
|
||||||
let root = Url::parse(&self.url_input).expect("Invalid URL");
|
let root = Url::parse(&self.url_input).expect("Invalid URL");
|
||||||
let stream = Traverse::new(client, root, None);
|
let stream = Traverse::new(client, root, None);
|
||||||
@@ -199,33 +205,36 @@ impl Publisher {
|
|||||||
Ok(rdf_source) => Message::AddRdfSource(rdf_source),
|
Ok(rdf_source) => Message::AddRdfSource(rdf_source),
|
||||||
Err(err) => Message::ShowError(format!("Unable to fetch RDF Source: {err}")),
|
Err(err) => Message::ShowError(format!("Unable to fetch RDF Source: {err}")),
|
||||||
}).chain(Task::done(Message::ConcludeTraversal));
|
}).chain(Task::done(Message::ConcludeTraversal));
|
||||||
|
|
||||||
|
self.traversal = Some(self.ontology.to_dataset());
|
||||||
}
|
}
|
||||||
Message::AddRdfSource(rdf_source) => {
|
Message::AddRdfSource(rdf_source) => {
|
||||||
if let Some((traversal, _)) = &mut self.traversal {
|
if let Some(traversal) = &mut self.traversal {
|
||||||
traversal.extend(rdf_source.dataset());
|
traversal.extend(rdf_source.dataset());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ConcludeTraversal => {
|
Message::ConcludeTraversal => {
|
||||||
if let Some((traversal, _)) = &self.traversal {
|
if let Some(traversal) = self.traversal.take() {
|
||||||
let query = Ontology::index_query(&*language::ENGLISH_OR_UNTAGGED);
|
let index_task = self.ontology
|
||||||
let query_results = query.on_queryable_dataset(traversal)
|
.index(&*language::ENGLISH_OR_UNTAGGED, Some(traversal));
|
||||||
.execute()
|
task = Task::perform(index_task, |task_result| {
|
||||||
.expect("Unable to generate index of entities");
|
match task_result {
|
||||||
let results = Ontology::transform_index_results(query_results);
|
Ok(results) => Message::IndexQueryResults(results),
|
||||||
task = Task::done(Message::IndexQueryResults(results));
|
Err(err) => Message::ShowError(err.to_string()),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
self.traversal = None;
|
|
||||||
}
|
}
|
||||||
Message::IndexQueryResults(results) => {
|
Message::IndexQueryResults(results) => {
|
||||||
let mut writer = self.index.writer().expect("Failed to build index writer");
|
let mut index = self.index.clone();
|
||||||
let curie_helper = self.curie_helper.clone();
|
let curie_helper = self.curie_helper.clone();
|
||||||
let index_task = tokio::task::spawn_blocking(move || {
|
task = Task::perform(tokio::task::spawn_blocking(move || {
|
||||||
debug_span!("Indexing").in_scope(|| {
|
let _span = debug_span!("Indexing").entered();
|
||||||
|
if let Ok(writer) = index.writer() {
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
for (individual, entry) in results {
|
for (individual, entry) in results {
|
||||||
debug!(%individual, ?entry);
|
|
||||||
let mut document = doc!(
|
let mut document = doc!(
|
||||||
Schema::type_field() => entry.catalog_id,
|
Schema::type_field() => entry.category_id,
|
||||||
Schema::iri_field() => individual.as_str(),
|
Schema::iri_field() => individual.as_str(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -239,18 +248,19 @@ impl Publisher {
|
|||||||
writer.add(document).expect("Failed to add document to search index");
|
writer.add(document).expect("Failed to add document to search index");
|
||||||
counter += 1;
|
counter += 1;
|
||||||
}
|
}
|
||||||
writer.commit().expect("Failed to commit changes to search index");
|
|
||||||
debug!("Added {counter} documents to search index");
|
debug!("Added {counter} documents to search index");
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
task = Task::future(async {
|
|
||||||
match index_task.await {
|
|
||||||
Ok(()) => Message::None,
|
|
||||||
Err(err) => Message::ShowError(err.to_string()),
|
|
||||||
}
|
}
|
||||||
|
}), |result| if let Err(err) = result {
|
||||||
|
Message::ShowError(err.to_string())
|
||||||
|
} else {
|
||||||
|
Message::CommitIndex
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Message::CommitIndex => {
|
||||||
|
if let Err(err) = self.index.commit() {
|
||||||
|
task = Task::done(Message::ShowError(err.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::WindowClosed(id) => {
|
Message::WindowClosed(id) => {
|
||||||
if self.window_id == id {
|
if self.window_id == id {
|
||||||
task = iced::exit();
|
task = iced::exit();
|
||||||
@@ -263,7 +273,9 @@ impl Publisher {
|
|||||||
}
|
}
|
||||||
Message::URLInputSubmitted => {
|
Message::URLInputSubmitted => {
|
||||||
let url = Url::parse(&self.url_input).expect("Invalid URL");
|
let url = Url::parse(&self.url_input).expect("Invalid URL");
|
||||||
self.navigator.goto(url.clone());
|
if &url != self.navigator.current() {
|
||||||
|
self.navigator.goto(url.clone());
|
||||||
|
}
|
||||||
task = Task::done(Message::NavigateTo(url));
|
task = Task::done(Message::NavigateTo(url));
|
||||||
}
|
}
|
||||||
Message::NavigateTo(url) => {
|
Message::NavigateTo(url) => {
|
||||||
@@ -397,24 +409,27 @@ impl Publisher {
|
|||||||
}
|
}
|
||||||
Message::QueryUpdated(new_query) => {
|
Message::QueryUpdated(new_query) => {
|
||||||
if let Some(search_state) = &mut self.search_state {
|
if let Some(search_state) = &mut self.search_state {
|
||||||
let catalog_id = search_state.entity_selection
|
let category_id = search_state.entity_selection
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|info| self.ontology.catalog_id(&info.iri));
|
.and_then(|info| self.ontology.category_id(&info.iri));
|
||||||
|
|
||||||
let index = self.index.clone();
|
|
||||||
let query = new_query.clone();
|
let query = new_query.clone();
|
||||||
let search_task = tokio::task::spawn_blocking(move || {
|
/*let search_task = tokio::task::spawn_blocking(move || {
|
||||||
index.query(catalog_id, query.as_str(), Schema::all_fields(), 25)
|
index.query(category_id, query.as_str(), Schema::all_fields(), 25)
|
||||||
});
|
});*/
|
||||||
task = Task::future(async {
|
/*task = Task::future(async {
|
||||||
match search_task.await {
|
match search_task.await {
|
||||||
Ok(Ok(results)) => Message::SetSearchResults(results),
|
Ok(Ok(results)) => Message::SetSearchResults(results),
|
||||||
Ok(Err(err)) => Message::ShowError(err.to_string()),
|
Ok(Err(err)) => Message::ShowError(err.to_string()),
|
||||||
Err(err) => Message::ShowError(err.to_string()),
|
Err(err) => Message::ShowError(err.to_string()),
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
|
|
||||||
search_state.query = new_query;
|
search_state.query = new_query;
|
||||||
|
task = Task::done(Message::SetSearchResults(
|
||||||
|
self.index.query(category_id, query.as_str(), Schema::all_fields(), 25)
|
||||||
|
.expect("Unable to complete search")
|
||||||
|
));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Message::SetSearchResults(results) => {
|
Message::SetSearchResults(results) => {
|
||||||
@@ -829,8 +844,6 @@ impl Publisher {
|
|||||||
|
|
||||||
let reindex_ontology_button = button("Rebuild index").on_press(Message::RebuildIndex);
|
let reindex_ontology_button = button("Rebuild index").on_press(Message::RebuildIndex);
|
||||||
|
|
||||||
let traverse_button = button("Traverse").on_press(Message::Traverse);
|
|
||||||
|
|
||||||
let address_input = iri_input(&self.curie_helper, "URL", &self.url_input)
|
let address_input = iri_input(&self.curie_helper, "URL", &self.url_input)
|
||||||
.on_input(Message::URLInputChanged)
|
.on_input(Message::URLInputChanged)
|
||||||
.on_submit(Message::URLInputSubmitted)
|
.on_submit(Message::URLInputSubmitted)
|
||||||
@@ -865,7 +878,6 @@ impl Publisher {
|
|||||||
forward_button,
|
forward_button,
|
||||||
add_row_button,
|
add_row_button,
|
||||||
reindex_ontology_button,
|
reindex_ontology_button,
|
||||||
traverse_button,
|
|
||||||
address_input,
|
address_input,
|
||||||
save_button,
|
save_button,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -30,4 +30,7 @@ pub(crate) enum Error {
|
|||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SparqlSyntax(#[from] oxigraph::sparql::SparqlSyntaxError),
|
SparqlSyntax(#[from] oxigraph::sparql::SparqlSyntaxError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Join(#[from] tokio::task::JoinError),
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-30
@@ -2,11 +2,12 @@ use crate::error;
|
|||||||
use crate::rdf::vocab::gl;
|
use crate::rdf::vocab::gl;
|
||||||
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
use oxigraph::model::vocab::{rdf, rdfs, xsd};
|
||||||
use oxigraph::model::{Dataset, Graph, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef};
|
use oxigraph::model::{Dataset, Graph, GraphNameRef, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term, TermRef, Triple, TripleRef};
|
||||||
use oxigraph::sparql::{PreparedSparqlQuery, QueryResults, SparqlEvaluator};
|
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
use iced::futures::TryFutureExt;
|
||||||
use oxigraph::store::Store;
|
use oxigraph::store::Store;
|
||||||
use tracing::debug_span;
|
use tracing::debug_span;
|
||||||
use crate::rdf::{conversion, materialize};
|
use crate::rdf::{conversion, materialize};
|
||||||
@@ -46,7 +47,7 @@ static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
|||||||
("rdax", "http://rdaregistry.info/Elements/x/"),
|
("rdax", "http://rdaregistry.info/Elements/x/"),
|
||||||
("schema", "https://schema.org/"),
|
("schema", "https://schema.org/"),
|
||||||
("quill", "http://fedora.quill.lan/rest/"),
|
("quill", "http://fedora.quill.lan/rest/"),
|
||||||
("gl", "ONTOLOGY_PREFIX"),
|
("gl", ONTOLOGY_PREFIX),
|
||||||
].map(|(k, v)| (k.to_string(), v.to_string())))
|
].map(|(k, v)| (k.to_string(), v.to_string())))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ impl OntologyBuilder {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct IndexEntry {
|
pub struct IndexEntry {
|
||||||
pub catalog_id: u64,
|
pub category_id: u64,
|
||||||
pub fields: HashMap<String, String>,
|
pub fields: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,12 +173,12 @@ impl Ontology {
|
|||||||
.collect::<Dataset>()
|
.collect::<Dataset>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index_query(language: &LanguageCondition) -> PreparedSparqlQuery {
|
pub fn index(&self, language: &LanguageCondition, source: Option<Dataset>) -> impl Future<Output = error::Result<HashMap<NamedNode, IndexEntry>>> + 'static {
|
||||||
let language_filter = language.to_filter_expression("fieldValue");
|
let language_filter = language.to_filter_expression("fieldValue");
|
||||||
let query = format!(r#"SELECT ?individual ?catalogId ?fieldName ?fieldValue
|
let query = format!(r#"SELECT ?individual ?categoryId ?fieldName ?fieldValue
|
||||||
WHERE {{
|
WHERE {{
|
||||||
?class a gl:SearchableClass ;
|
?class a gl:SearchableClass ;
|
||||||
gl:catalogId ?catalogId ;
|
gl:categoryId ?categoryId ;
|
||||||
gl:associatedProperty ?property .
|
gl:associatedProperty ?property .
|
||||||
|
|
||||||
?property gl:indexedByField/gl:fieldName ?fieldName .
|
?property gl:indexedByField/gl:fieldName ?fieldName .
|
||||||
@@ -190,39 +191,44 @@ WHERE {{
|
|||||||
let mut sparql = SparqlEvaluator::new()
|
let mut sparql = SparqlEvaluator::new()
|
||||||
.with_prefix("gl", ONTOLOGY_PREFIX).unwrap()
|
.with_prefix("gl", ONTOLOGY_PREFIX).unwrap()
|
||||||
.parse_query(&query)
|
.parse_query(&query)
|
||||||
.unwrap();
|
.expect("Unable to parse query");
|
||||||
sparql.dataset_mut().set_default_graph_as_union();
|
sparql.dataset_mut().set_default_graph_as_union();
|
||||||
sparql
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn execute_query(&self, query: PreparedSparqlQuery) -> error::Result<QueryResults<'_>> {
|
let store = self.store.clone();
|
||||||
Ok(query.on_store(&self.store).execute()?)
|
tokio::task::spawn_blocking(move || {
|
||||||
}
|
let _span = debug_span!("Index Query").entered();
|
||||||
|
let query_results = if let Some(source) = &source {
|
||||||
|
sparql.on_queryable_dataset(source).execute()
|
||||||
|
} else {
|
||||||
|
sparql.on_store(&store).execute()
|
||||||
|
}.expect("Unable to execute indexing query");
|
||||||
|
|
||||||
pub fn transform_index_results(query_results: QueryResults) -> HashMap<NamedNode, IndexEntry> {
|
let mut results = HashMap::new();
|
||||||
let mut results = HashMap::new();
|
if let QueryResults::Solutions(solutions) = query_results {
|
||||||
if let QueryResults::Solutions(solutions) = query_results {
|
for solution in solutions.filter_map(Result::ok) {
|
||||||
for solution in solutions.filter_map(Result::ok) {
|
let individual = solution.get("individual").and_then(conversion::term_to_named_node);
|
||||||
let individual = solution.get("individual").and_then(conversion::term_to_named_node);
|
let catalog_id = solution.get("categoryId").and_then(conversion::term_to_u64);
|
||||||
let catalog_id = solution.get("catalogId").and_then(conversion::term_to_u64);
|
let field_name = solution.get("fieldName").and_then(conversion::term_as_str);
|
||||||
let field_name = solution.get("fieldName").and_then(conversion::term_as_str);
|
let field_value = solution.get("fieldValue").and_then(conversion::term_as_str);
|
||||||
let field_value = solution.get("fieldValue").and_then(conversion::term_as_str);
|
if let (Some(individual), Some(catalog_id), Some(field_name), Some(field_value)) = (individual, catalog_id, field_name, field_value) {
|
||||||
if let (Some(individual), Some(catalog_id), Some(field_name), Some(field_value)) = (individual, catalog_id, field_name, field_value) {
|
results.entry(individual.to_owned())
|
||||||
results.entry(individual.to_owned())
|
.and_modify(|entry: &mut IndexEntry| {
|
||||||
.and_modify(|entry: &mut IndexEntry| {
|
entry.fields.insert(field_name.to_owned(), field_value.to_owned());
|
||||||
entry.fields.insert(field_name.to_owned(), field_value.to_owned());
|
}).or_insert(IndexEntry {
|
||||||
}).or_insert(IndexEntry {
|
category_id: catalog_id,
|
||||||
catalog_id,
|
|
||||||
fields: HashMap::from_iter([(field_name.to_owned(), field_value.to_owned())]),
|
fields: HashMap::from_iter([(field_name.to_owned(), field_value.to_owned())]),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
results
|
||||||
results
|
}).map_err(error::Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn catalog_id(&self, class: &NamedNode) -> Option<u64> {
|
pub fn category_id(&self, class: &NamedNode) -> Option<u64> {
|
||||||
self.store.quads_for_pattern(Some(class.into()), Some(gl::CATALOG_ID), None, None)
|
self.store.quads_for_pattern(Some(class.into()), Some(gl::CATEGORY_ID), None, None)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.map(conversion::quad_into_term)
|
.map(conversion::quad_into_term)
|
||||||
.filter_map(conversion::term_into_u64)
|
.filter_map(conversion::term_into_u64)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ pub mod gl {
|
|||||||
pub const INDEXED_BY_FIELD: NamedNodeRef =
|
pub const INDEXED_BY_FIELD: NamedNodeRef =
|
||||||
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/indexedByField");
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/indexedByField");
|
||||||
|
|
||||||
pub const CATALOG_ID: NamedNodeRef =
|
pub const CATEGORY_ID: NamedNodeRef =
|
||||||
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/catalogId");
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/categoryId");
|
||||||
|
|
||||||
pub const SEARCHABLE_CLASS: NamedNodeRef =
|
pub const SEARCHABLE_CLASS: NamedNodeRef =
|
||||||
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/SearchableClass");
|
NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont/SearchableClass");
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use iced::advanced::{Layout, Widget};
|
|||||||
use iced::advanced::widget::{tree, Tree};
|
use iced::advanced::widget::{tree, Tree};
|
||||||
use iced::mouse::{Cursor, Interaction};
|
use iced::mouse::{Cursor, Interaction};
|
||||||
use iced::widget::text_input::Catalog;
|
use iced::widget::text_input::Catalog;
|
||||||
use tracing::debug;
|
|
||||||
use crate::rdf::curie::CurieHelper;
|
use crate::rdf::curie::CurieHelper;
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ edition = "2024"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
futures.workspace = true
|
||||||
tantivy.workspace = true
|
tantivy.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
|||||||
+22
-5
@@ -1,7 +1,8 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use crate::{error, update};
|
use crate::{error, IndexWriter};
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
use tantivy::collector::TopDocs;
|
use tantivy::collector::TopDocs;
|
||||||
use tantivy::directory::{ManagedDirectory, MmapDirectory};
|
use tantivy::directory::{ManagedDirectory, MmapDirectory};
|
||||||
use tantivy::query::{BooleanQuery, Occur, QueryParser, TermQuery};
|
use tantivy::query::{BooleanQuery, Occur, QueryParser, TermQuery};
|
||||||
@@ -51,6 +52,7 @@ impl SearchIndexBuilder {
|
|||||||
Ok(SearchIndex {
|
Ok(SearchIndex {
|
||||||
index,
|
index,
|
||||||
reader,
|
reader,
|
||||||
|
writer: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +61,7 @@ impl SearchIndexBuilder {
|
|||||||
pub struct SearchIndex {
|
pub struct SearchIndex {
|
||||||
index: Index,
|
index: Index,
|
||||||
reader: IndexReader,
|
reader: IndexReader,
|
||||||
|
writer: Option<Arc<IndexWriter>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchIndex {
|
impl SearchIndex {
|
||||||
@@ -66,9 +69,23 @@ impl SearchIndex {
|
|||||||
SearchIndexBuilder::default()
|
SearchIndexBuilder::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writer(&self) -> crate::Result<update::IndexWriter> {
|
pub fn writer(&mut self) -> crate::Result<&IndexWriter> {
|
||||||
let inner = self.index.writer(128 * 1024usize.pow(2))?;
|
if self.writer.is_none() {
|
||||||
Ok(crate::IndexWriter { inner })
|
let inner = self.index.writer(128 * 1024 * 1024)?;
|
||||||
|
self.writer = Some(Arc::new(IndexWriter { inner }));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.writer.as_ref().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(&mut self) -> crate::Result<()> {
|
||||||
|
if let Some(writer) = self.writer.take() {
|
||||||
|
if let Some(mut writer) = Arc::into_inner(writer) {
|
||||||
|
writer.commit()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query(
|
pub fn query(
|
||||||
@@ -78,7 +95,7 @@ impl SearchIndex {
|
|||||||
default_fields: Vec<Field>,
|
default_fields: Vec<Field>,
|
||||||
limit: usize,
|
limit: usize,
|
||||||
) -> error::Result<Vec<TantivyDocument>> {
|
) -> error::Result<Vec<TantivyDocument>> {
|
||||||
let _ = debug_span!("Search Query").entered();
|
let _span = debug_span!("Search Query").entered();
|
||||||
|
|
||||||
let parser = QueryParser::for_index(&self.index, default_fields);
|
let parser = QueryParser::for_index(&self.index, default_fields);
|
||||||
let (user_query, _) = parser.parse_query_lenient(user_query);
|
let (user_query, _) = parser.parse_query_lenient(user_query);
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ impl IndexWriter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_all_of_type(&mut self, type_: u64) -> crate::Result<()> {
|
pub fn remove_all_of_type(&self, type_: u64) -> crate::Result<()> {
|
||||||
self.inner.delete_term(Term::from_field_u64(Schema::type_field(), type_));
|
self.inner.delete_term(Term::from_field_u64(Schema::type_field(), type_));
|
||||||
self.inner.commit()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_all(&mut self) -> crate::Result<()> {
|
pub fn remove_all(&self) -> crate::Result<()> {
|
||||||
self.inner.delete_all_documents()?;
|
self.inner.delete_all_documents()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user