Commit 5edc7a5e authored by Laurent Wouters's avatar Laurent Wouters
Browse files

[fix] Use equivalent URIs for aliases lookup

parent 8593bcc88597
......@@ -20,12 +20,12 @@
import { definition } from "@logilab/libview";
import {
DataSourceLinked,
DocumentSourceLinked,
tryNegotiateData,
ObservedResourceRegistry,
resolveObservationsForTab,
hasDetectedData,
ResourceObservedData,
DocumentObservations,
detectTopicOnlinks,
detectDataOnLinks,
fetchObservableAt,
......@@ -147,7 +147,7 @@ function onHeadersReceived(
// if there are still nothing, try to probe with HTTP content negotiation
if (!hasDetectedData(observation)) {
tryNegotiateData(details.url)
.then((source: DataSourceLinked) => {
.then((source: DocumentSourceLinked) => {
observation.sources.push(source);
onObservedTabUpdated(details.tabId);
})
......@@ -169,7 +169,7 @@ function onBeforeNavigate(
details: chrome.webNavigation.WebNavigationFramedCallbackDetails
): void {
if (details.tabId == -1 || details.frameId != 0) return;
let observation = new ResourceObservedData();
let observation = new DocumentObservations();
observation.url = details.url;
allObservations[details.tabId] = observation;
chrome.pageAction.hide(details.tabId);
......
......@@ -159,10 +159,10 @@ export function detectTopicOnlinks(links: Link[]): string {
* Detects linked data in HTTP headers
* @param links The links in the header
*/
export function detectDataOnLinks(links: Link[]): DataSourceLinked[] {
export function detectDataOnLinks(links: Link[]): DocumentSourceLinked[] {
return links
.filter((link: Link) => refersToData(link))
.map((link: Link) => new DataSourceLinked(link));
.map((link: Link) => new DocumentSourceLinked(link));
}
/**
......@@ -313,9 +313,9 @@ export const MIME: { [mime: string]: MimeInfo } = {
};
/**
* A source of data
* A source for a document
*/
export interface DataSource {
export interface DocumentSource {
/**
* A descriptor of the type of source
*/
......@@ -341,9 +341,9 @@ export interface DataSource {
/**
* No data source
*/
export class DataSourceNone implements DataSource {
export class DocumentSourceNone implements DocumentSource {
constructor() {
this.sourceType = "DataSourceNone";
this.sourceType = "DocumentSourceNone";
this.name = "No data";
this.url = "";
this.contentType = "";
......@@ -359,9 +359,9 @@ export class DataSourceNone implements DataSource {
/**
* A data source with inline content
*/
export class DataSourceInline implements DataSource {
export class DocumentSourceInline implements DocumentSource {
constructor(url: string, mime: MimeInfo, content: string) {
this.sourceType = "DataSourceInline";
this.sourceType = "DocumentSourceInline";
this.name = "Inline content";
this.url = url;
this.contentType = mime.mime;
......@@ -382,10 +382,10 @@ export class DataSourceInline implements DataSource {
/**
* The data about a page
*/
export class DataSourcePage implements DataSource {
export class DocumentSourcePage implements DocumentSource {
constructor(url: string, contentType: string) {
let mime = MIME[contentType];
this.sourceType = "DataSourcePage";
this.sourceType = "DocumentSourcePage";
this.name =
"Page's content" +
(mime != undefined && mime != null ? " (" + mime.name + ")" : "");
......@@ -403,7 +403,7 @@ export class DataSourcePage implements DataSource {
/**
* Get a promise to fetch the content of the current document
*/
function doFetchSourcePage(page: DataSourcePage): Promise<RawContent> {
function doFetchDocumentPage(page: DocumentSourcePage): Promise<RawContent> {
return new Promise(function(resolve, reject) {
for (var i = 0; i != document.body.children.length; i++) {
let child = document.body.children[i];
......@@ -428,12 +428,12 @@ function doFetchSourcePage(page: DataSourcePage): Promise<RawContent> {
/**
* A link for a related resource
*/
export class DataSourceLinked implements DataSource {
export class DocumentSourceLinked implements DocumentSource {
constructor(link: Link) {
let mime = MIME[link.tags.type];
if (mime == null || mime == undefined) throw "Unrecognized link type";
this.tags = link.tags;
this.sourceType = "DataSourceLinked";
this.sourceType = "DocumentSourceLinked";
this.name =
"Linked content" +
(mime != undefined && mime != null ? " (" + mime.name + ")" : "") +
......@@ -458,7 +458,7 @@ export class DataSourceLinked implements DataSource {
/**
* Get a promise to fetch content at a URI
*/
function doFetchSourceLink(link: DataSourceLinked): Promise<RawContent> {
function doFetchDcoumentLink(link: DocumentSourceLinked): Promise<RawContent> {
return new Promise(function(resolve, reject) {
let xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
......@@ -482,20 +482,20 @@ function doFetchSourceLink(link: DataSourceLinked): Promise<RawContent> {
/**
* Fetches this data
*/
export function fetchSource(source: DataSource): Promise<RawContent> {
export function fetchDocument(source: DocumentSource): Promise<RawContent> {
if (source == null || source == undefined) {
return new Promise((resolve, reject) => reject("No data"));
} else if (source.sourceType == "DataSourceNone") {
} else if (source.sourceType == "DocumentSourceNone") {
return new Promise((resolve, reject) => reject("No data"));
} else if (source.sourceType == "DataSourceInline") {
let inline = source as DataSourceInline;
} else if (source.sourceType == "DocumentSourceInline") {
let inline = source as DocumentSourceInline;
return new Promise((resolve, reject) =>
resolve({ contentType: inline.contentType, content: inline.content })
);
} else if (source.sourceType == "DataSourcePage") {
return doFetchSourcePage(source as DataSourcePage);
} else if (source.sourceType == "DataSourceLinked") {
return doFetchSourceLink(source as DataSourceLinked);
} else if (source.sourceType == "DocumentSourcePage") {
return doFetchDocumentPage(source as DocumentSourcePage);
} else if (source.sourceType == "DocumentSourceLinked") {
return doFetchDcoumentLink(source as DocumentSourceLinked);
}
return new Promise((resolve, reject) => reject("Unknown data source"));
}
......@@ -505,32 +505,32 @@ export function fetchSource(source: DataSource): Promise<RawContent> {
* @param x The first data source
* @param y The second data source
*/
function compareSources(x: DataSource, y: DataSource): number {
function compareSources(x: DocumentSource, y: DocumentSource): number {
return x.priority - y.priority;
}
/**
* Constant when no data are available
*/
export const NO_DATA = new DataSourceNone();
export const NO_DATA = new DocumentSourceNone();
/**
* The observed data about a web resource
* The observations made for a specific documents
*/
export class ResourceObservedData {
export class DocumentObservations {
constructor() {
this.url = "";
this.sources = [];
this.primaryTopic = "";
}
/**
* The resource's url
* The document's url
*/
url: string;
/**
* All the alternative data sources
* The possible sources of datasets
*/
sources: DataSource[];
sources: DocumentSource[];
/**
* The detected primary topic if any
*/
......@@ -543,25 +543,25 @@ export class ResourceObservedData {
*/
export function observeContent(
observable: ObservableContent
): ResourceObservedData {
): DocumentObservations {
if (MIME.hasOwnProperty(observable.contentType)) {
// Linked-data content at this URI
let links = parseLinks(observable.linkHeader);
let primary = detectTopicOnlinks(links);
let source =
observable.content != null
? new DataSourceInline(
? new DocumentSourceInline(
observable.url,
MIME[observable.contentType],
observable.content
)
: new DataSourceLinked(
: new DocumentSourceLinked(
new Link(observable.url, [
{ name: "rel", value: "alternate" },
{ name: "type", value: observable.contentType }
])
);
let observations: ResourceObservedData = {
let observations: DocumentObservations = {
primaryTopic: primary == null ? "" : primary,
url: observable.url,
sources: [source]
......@@ -588,7 +588,7 @@ export function observeContent(
primary = detectTopicOnlinks(links);
}
let sources = detectDataOnLinks(links);
let observations: ResourceObservedData = {
let observations: DocumentObservations = {
primaryTopic: primary == null || primary == undefined ? "" : primary,
url: observable.url,
sources: sources
......@@ -601,9 +601,9 @@ export function observeContent(
* Gets whether data has been detected
* @param observation The current observation
*/
export function hasDetectedData(observation: ResourceObservedData): boolean {
export function hasDetectedData(observation: DocumentObservations): boolean {
let main = observation.sources.find(
(source: DataSource) => source != NO_DATA
(source: DocumentSource) => source != NO_DATA
);
return main != undefined && main != null && main != NO_DATA;
}
......@@ -614,7 +614,6 @@ export function hasDetectedData(observation: ResourceObservedData): boolean {
export class ResourceUserCommand {
constructor() {
this.isAutomatic = true;
this.selectedSource = "";
this.selectedTopic = "";
this.selectedView = "";
this.selectedLanguage = application.NO_LANGUAGE;
......@@ -624,10 +623,6 @@ export class ResourceUserCommand {
* Whether the command use the defaults
*/
isAutomatic: boolean;
/**
* The identifier of the user-selected data source
*/
selectedSource: string;
/**
* The URI of the user-selected topic, if any
*/
......@@ -647,76 +642,64 @@ export class ResourceUserCommand {
*/
export class ResourceData {
/**
* The observed data
* The associated resource
*/
observations: ResourceObservedData;
resource: application.Resource;
/**
* The observations for the associated documents
*/
observations: { [url: string]: DocumentObservations };
/**
* The associated user command
*/
command: ResourceUserCommand;
/**
* The URI of supplementary resources that have been merged into this one
* The supplementary resources that have been merged into this one
*/
supplements: string[];
supplements: application.Resource[];
}
/**
* Selects the main resource among the provided one
* @param resource The current data about a resource
* Gets the sources of data for the specified resource
* @param data The current data about a resource
*/
export function selectDataSources(resource: ResourceData): DataSource[] {
if (
resource.command.isAutomatic ||
resource.command.selectedSource == null ||
resource.command.selectedSource == ""
) {
return selectDataSourcesAuto(resource);
} else {
let source = resource.observations.sources.find(
(source: DataSource) => source.url == resource.command.selectedSource
);
if (source != null) return [source];
return selectDataSourcesAuto(resource);
}
}
/**
* Automatically selects the main resource among the provided one
* @param resource The current data about a resource
*/
function selectDataSourcesAuto(resource: ResourceData): DataSource[] {
return resource.observations.sources
.filter((source: DataSource) => {
return source != NO_DATA;
})
.sort(compareSources);
export function getSourcesFor(data: ResourceData): DocumentSource[][] {
return Object.keys(data.observations).map((url: string) => {
let observations = data.observations[url];
return observations.sources
.filter((source: DocumentSource) => source != NO_DATA)
.sort(compareSources);
});
}
/**
* Selects the relevant primay topic
* @param resource The current data about a resource
* @param data The current data about a resource
*/
export function selectPrimaryTopic(resource: ResourceData): string {
export function selectPrimaryTopic(data: ResourceData): string {
if (
resource.command.isAutomatic ||
resource.command.selectedTopic == null ||
resource.command.selectedTopic == ""
data.command.isAutomatic ||
data.command.selectedTopic == null ||
data.command.selectedTopic == ""
) {
if (
resource.observations.primaryTopic == null ||
resource.observations.primaryTopic.length == 0
)
return resource.observations.url;
return resource.observations.primaryTopic;
let url = Object.keys(data.observations).find(
(url: string) =>
data.observations[url].primaryTopic != null &&
data.observations[url].primaryTopic.length > 0
);
if (url == undefined) return data.resource.uri;
return data.observations[url].primaryTopic;
} else {
return resource.command.selectedTopic;
return data.command.selectedTopic;
}
}
/**
* Tries to negotiate data content at the specified URL
*/
export function tryNegotiateData(target: string): Promise<DataSourceLinked> {
export function tryNegotiateData(
target: string
): Promise<DocumentSourceLinked> {
let accept = Object.keys(MIME)
.map(key => {
return MIME[key];
......@@ -743,7 +726,7 @@ export function tryNegotiateData(target: string): Promise<DataSourceLinked> {
{ name: "type", value: contentType },
{ name: "rel", value: "alternate" }
]);
resolve(new DataSourceLinked(link));
resolve(new DocumentSourceLinked(link));
}
};
xmlHttp.open("HEAD", target, true);
......@@ -756,7 +739,7 @@ export function tryNegotiateData(target: string): Promise<DataSourceLinked> {
* A registry for observed data
*/
export interface ObservedResourceRegistry {
[index: number]: ResourceObservedData;
[index: number]: DocumentObservations;
}
/**
......@@ -767,9 +750,9 @@ export interface ObservedResourceRegistry {
export function resolveObservationsForTab(
registry: ObservedResourceRegistry,
id: number
): ResourceObservedData {
): DocumentObservations {
if (!registry.hasOwnProperty(id)) {
registry[id] = new ResourceObservedData();
registry[id] = new DocumentObservations();
}
return registry[id];
}
......@@ -19,7 +19,7 @@
******************************************************************************/
import {
ResourceObservedData,
DocumentObservations,
ObservableContent,
ResourceData,
ResourceUserCommand
......@@ -182,10 +182,10 @@ export function getMyTabId(): Promise<number> {
*/
export function getObservationsFor(
tabId: number
): Promise<ResourceObservedData> {
return new Promise<ResourceObservedData>(
): Promise<DocumentObservations> {
return new Promise<DocumentObservations>(
(
resolve: (data: ResourceObservedData) => void,
resolve: (data: DocumentObservations) => void,
reject: (reason: any) => void
) => {
chrome.runtime.sendMessage(
......@@ -193,7 +193,7 @@ export function getObservationsFor(
requestType: "GetObservations",
payload: tabId
},
(data: ResourceObservedData) => {
(data: DocumentObservations) => {
resolve(data);
}
);
......
......@@ -18,7 +18,7 @@
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import { ResourceObservedData, findLinksInDocument } from "../common/data";
import { DocumentObservations, findLinksInDocument } from "../common/data";
import { getObservationsFor } from "../common/messages";
import "chrome";
......@@ -26,7 +26,7 @@ import "chrome";
* Sends the links in the HTML head to the background
* @param observation The current observations about this tab
*/
function sendHead(observation: ResourceObservedData): void {
function sendHead(observation: DocumentObservations): void {
let links = findLinksInDocument(
document,
document.URL.toString(),
......@@ -42,6 +42,6 @@ function sendHead(observation: ResourceObservedData): void {
/**
* Gets data about this tab from the background and reply with the HTML head data
*/
getObservationsFor(null).then((observation: ResourceObservedData) => {
getObservationsFor(null).then((observation: DocumentObservations) => {
sendHead(observation);
});
......@@ -24,14 +24,15 @@ import { application, definition, implementation } from "@logilab/libview";
import * as LANGUAGES from "../common/data.iso639.json";
import {
ResourceData,
fetchSource,
selectDataSources,
fetchDocument,
getSourcesFor,
RawContent,
selectPrimaryTopic,
ObservableContent,
observeContent,
ResourceUserCommand,
DataSource
DocumentSource,
DocumentObservations
} from "../common/data";
import {
getResourceContent,
......@@ -131,7 +132,7 @@ class LDBrowserImpl implements LDBrowser {
constructor(handler: LDBrowserEventHandler) {
this.handler = handler;
this.renderer = implementation.newRenderer(getResourceContent);
this.currentResource = null;
this.currentData = null;
this.cacheResources = {};
this.cacheStores = {};
this.onRequestNavigateTo = this.onRequestNavigateTo.bind(this);
......@@ -149,43 +150,65 @@ class LDBrowserImpl implements LDBrowser {
/**
* The history of resources
*/
private currentResource: ResourceData;
private currentData: ResourceData;
/**
* The data for each known resource
*/
private cacheResources: { [url: string]: ResourceData };
private cacheResources: { [uri: string]: ResourceData };
/**
* The cache of loaded RDF data
*/
private cacheStores: { [url: string]: $rdf.Formula };
private cacheStores: { [uri: string]: $rdf.Formula };
/**
* Resolves an uri into a resource
* @param uri The URI
*/
private resolveResource(uri: string): application.Resource {
return new application.SimpleResource(...application.uriEquivalents(uri));
}
/**
* Resolves the data about a resource
* @param uri The resource's URI
* @param resource The resource
*/
private resolveResourceData(uri: string): Promise<ResourceData> {
private resolveResourceData(
resource: application.Resource
): Promise<ResourceData> {
let self = this;
return new Promise<ResourceData>(
(
resolve: (resource: ResourceData) => void,
reject: (reason: any) => void
) => {
let resource = self.cacheResources[uri];
if (resource != null && resource != undefined) return resolve(resource);
fetchObservableAt(uri)
.then((observable: ObservableContent) => {
let observations = observeContent(observable);
let resource: ResourceData = {
observations: observations,
command: new ResourceUserCommand(),
supplements: []
};
self.cacheResources[uri] = resource;
resolve(resource);
})
.catch((reason: any) => {
reject(reason);
});
let data: ResourceData = self.cacheResources[resource.uri];
if (data != null && data != undefined) return resolve(data);
data = {
resource: resource,
observations: {},
command: new ResourceUserCommand(),
supplements: []
};
let count = resource.uris.length;
let onObservations = (observations: DocumentObservations) => {
if (observations != null)
data.observations[observations.url] = observations;
count--;
if (count == 0) resolve(data);
};
resource.uris.forEach((uri: string) => {
self.cacheResources[uri] = data;
fetchObservableAt(uri)
.then((observable: ObservableContent) => {
onObservations(observeContent(observable));
})
.catch((reason: any) => {
console.log(reason);
onObservations(null);
});
});
}
);
}
......@@ -193,31 +216,64 @@ class LDBrowserImpl implements LDBrowser {
/**
* Gets the RDF store for a resource
* Successively try the sources until one succeed
* @param resource The resource
* @param data The resource data
*/
private resolveRdfStore(resource: ResourceData): Promise<$rdf.Formula> {
private resolveRdfStore(data: ResourceData): Promise<$rdf.Formula> {
let self = this;
return new Promise(
(
resolve: (store: $rdf.Formula) => void,
reject: (reason: any) => void
) => {
let cached = self.cacheStores[resource.observations.url];
let cached = self.cacheStores[data.resource.uri];
if (cached != null && cached != undefined) return resolve(cached);
let sources = selectDataSources(resource);
let store = $rdf.graph();
data.resource.uris.forEach(
(uri: string) => (self.cacheStores[uri] = store)
);
let sources = getSourcesFor(data);
let count = sources.length;
let onFinished = () => {
count--;
if (count == 0) resolve(store);
};
sources.forEach((series: DocumentSource[]) => {
self
.loadRdfStoreFromSources(series, store)
.then(onFinished)
.catch(onFinished);
<