You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

370 lines
13 KiB

import { create_photo, create_album, create_tag } from './media.js';
export function escape_regex(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
};
export function do_image_query(query, database, collection_path, collection_thumbs_path) {
return new Promise(function (resolve, reject) {
var queries = [];
queries.push(query);
database.queries_async(queries).then(res => {
var photos = [];
if (res && Array.isArray(res)) {
res.forEach(row => {
var imagepath = process.env.PUBLIC_URL + collection_path + "/" + row["relativePath"] + "/" + row["name"];
var thumbpath = process.env.PUBLIC_URL + collection_thumbs_path + "/" + row["uniqueHash"] + ".jpg";
photos.push(create_photo(row["id"], row["name"], imagepath, thumbpath));
});
}
resolve(photos);
});
});
}
export function do_album_query(query, database) {
return new Promise(function (resolve, reject) {
var queries = [];
queries.push(query);
database.queries_async(queries).then(res => {
var albums = [];
if (res && Array.isArray(res)) {
res.forEach(row => {
var album_name = row["relativePath"].substring(row["relativePath"].lastIndexOf('/') + 1);
albums.push(create_album(row["id"], album_name, row["relativePath"]));
});
}
resolve(albums);
});
});
}
export function do_tag_query(query, database) {
return new Promise(function (resolve, reject) {
var queries = [];
queries.push(query);
database.queries_async(queries).then(res => {
var tags = [];
if (res && Array.isArray(res)) {
res.forEach(row => {
tags.push(create_tag(row["id"], row["name"]));
});
}
resolve(tags);
});
});
}
export const MatchTypeEnum = {
MATCH_IMAGE_NAME_EQUALS: 1,
MATCH_IMAGE_NAME_NATURAL: 2,
MATCH_ALBUM_EQUALS: 3, // Match on the full name (relative path) of the relevant album, if any
MATCH_ALBUM_EQUALS_OR_CHILD: 4, // Match on the full name (relative path) of the relevant album, if any, or any of its children
MATCH_ALBUM_NATURAL: 5,
MATCH_ALBUM_NAME_EQUALS: 6, // Match on the local album name (excluding parents)
MATCH_TAG_EQUALS: 7, // Match on the name of the relevant tag(s), if any
}
export const ResultTypeEnum = {
IMAGE: 1,
ALBUM: 2,
TAG: 3,
}
export class ResultFilter {
constructor(type) { this.result_type = type; }
result_type = ResultTypeEnum.IMAGE;
to_sql_where() { return "(1=1)"; }
is_true() { return false; }
is_false() { return false; }
is_negation() { return false; }
simplify() { return this; }
}
export class ConstFilter extends ResultFilter {
constructor(type, val) { super(type); this.constval = val; }
constval = true; // True lets everything through, false rejects everything
to_sql_where() {
return this.constval ? "(1=1)" : "(1=0)";
}
is_true() { return this.constval; }
is_false() { return !this.constval; }
simplify() { return this; }
}
export class NegationFilter extends ResultFilter {
constructor(rtype, body) {
super(rtype);
this.body = body;
}
body = new ConstFilter(this.return_type, false);
to_sql_where() {
return "NOT (" + this.body.to_sql_where() + ")";
}
is_negation() { return true; }
simplify() {
var f = this.body.simplify();
console.log("NOT body:", f);
if (f.is_true()) {
return new ConstFilter(this.result_type, false);
}
if (f.is_false()) {
return new ConstFilter(this.result_type, true);
}
if (f.is_negation()) {
return f.body;
}
return this;
}
}
export class MatchingFilter extends ResultFilter {
constructor(rtype, from, mtype) {
super(rtype);
this.match_from = from;
this.match_type = mtype;
}
// optional string used in the filtering
match_from = "";
// What and how to match
match_type = MatchTypeEnum.MATCH_IMAGE_NAME_EQUALS;
to_sql_where() {
if (this.match_type == MatchTypeEnum.MATCH_IMAGE_NAME_EQUALS) {
return '(Images.name="' + this.match_from + '")';
} else if (this.match_type == MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL) {
return '(LOWER(Images.name) REGEXP LOWER(".*'
+ escape_regex(this.match_from)
+ '.*"))';
} else if (this.match_type == MatchTypeEnum.MATCH_ALBUM_EQUALS) {
return '(Albums.relativePath = "' + this.match_from + '")';
} else if (this.match_type == MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD) {
return '(Albums.relativePath REGEXP "'
+ escape_regex(this.match_from)
+ '(\/[^\/]+)*")';
} else if (this.match_type == MatchTypeEnum.MATCH_ALBUM_NATURAL) {
throw new Error("Natural matching on album names is not yet supported.");
} else if (this.match_type == MatchTypeEnum.MATCH_ALBUM_NAME_EQUALS) {
return '(Albums.relativePath REGEXP "\/(.*\/)*'
+ escape_regex(this.match_from)
+ '(\/[^\/]+)*")';
} else if (this.match_type == MatchTypeEnum.MATCH_TAG_EQUALS) {
return '(Tags.name="' + this.match_from + '")';
}
console.log(this);
throw new Error("Unsupported matching filter for SQL generation.");
}
simplify() { return this; }
}
export const LogicalOperatorEnum = {
AND: 1,
OR: 2,
}
export class LogicalOperatorFilter extends ResultFilter {
constructor(type, a, b, op) { super(type); this.sub_filter_a = a; this.sub_filter_b = b; this.operator = op; }
sub_filter_a = false;
sub_filter_b = false;
operator = LogicalOperatorEnum.AND;
to_sql_where() {
var where1 = this.sub_filter_a.to_sql_where();
var where2 = this.sub_filter_b.to_sql_where();
var operator_str = "";
if (this.operator === LogicalOperatorEnum.AND) {
operator_str = " AND ";
} else if (this.operator === LogicalOperatorEnum.OR) {
operator_str = " OR ";
} else {
throw new Error('Unsupported logical operator: ' + this.operator);
}
return "(" + where1 + operator_str + where2 + ")";
}
simplify() {
var a = this.sub_filter_a.simplify();
var b = this.sub_filter_b.simplify();
if (this.operator === LogicalOperatorEnum.OR) {
if (a.is_true() || b.is_true()) {
return new ConstFilter(this.return_type, true);
}
if (a.is_false()) { return b; }
if (b.is_false()) { return a; }
}
if (this.operator === LogicalOperatorEnum.AND) {
if (a.is_false() || b.is_false()) {
return new ConstFilter(this.return_type, false);
}
if (a.is_true()) { return b; }
if (b.is_true()) { return a; }
}
return this;
}
}
export class UserQuery {
image_filter = new ConstFilter(ResultTypeEnum.IMAGE, true);
album_filter = new ConstFilter(ResultTypeEnum.ALBUM, true);
tag_filter = new ConstFilter(ResultTypeEnum.TAG, true);
}
// This query will return database entries with the fields "id", "uniqueHash", "relativePath" (of the album) and "name" for each matching image.
export function image_query_with_where(maybe_where) {
return "SELECT Images.id, Images.name, Images.uniqueHash, Albums.relativePath FROM Images INNER JOIN Albums ON Images.album=Albums.id LEFT JOIN ImageTags ON Images.id=ImageTags.imageid LEFT JOIN Tags ON ImageTags.tagid=Tags.id " + (maybe_where ? maybe_where : "" + ";");
// TODO: GROUP BY is not working for some reason.
}
// This query will return database entries with the fields "id" and "relativePath" for each matching album.
export function album_query_with_where(maybe_where) {
return "SELECT Albums.id, Albums.relativePath FROM Albums " + (maybe_where ? maybe_where : "") + ";";
}
// This query will return database entries with the fields "id" and "name" for each matching tag.
export function tag_query_with_where(maybe_where) {
return "SELECT Tags.id, Tags.name FROM Tags " + (maybe_where ? maybe_where : "") + ";";
}
export function maybe_image_query(user_query) {
var where = false;
if (user_query.image_filter) {
where = "WHERE " + user_query.image_filter.to_sql_where();
}
return image_query_with_where(where);
}
export function maybe_album_query(user_query) {
var where = false;
if (user_query.album_filter) {
where = "WHERE " + user_query.album_filter.to_sql_where();
}
return album_query_with_where(where);
}
export function maybe_tag_query(user_query) {
var where = false;
if (user_query.tag_filter) {
where = "WHERE (" + user_query.tag_filter.to_sql_where() + " AND Tags.pid = 0 AND NOT Tags.name=\"_Digikam_Internal_Tags_\")"; // TODO this way of doing the pid is hacky
}
return tag_query_with_where(where);
}
export function filter_is_const_false(filter) {
if (filter instanceof ConstFilter && filter.constval === false) {
return true;
}
// TODO resolve recursively
return false;
}
function filter_from_text_segment(result_type, segment) {
var filter = false;
var name_filter;
if (result_type === ResultTypeEnum.IMAGE) {
// Option 1: match on image name
name_filter = new MatchingFilter(
result_type,
segment['text'],
MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL
);
// Option 2: match on album path (TODO: need natural matching)
var album_filter = new MatchingFilter(
result_type,
segment['text'],
MatchTypeEnum.MATCH_ALBUM_NAME_EQUALS
);
filter = new LogicalOperatorFilter(result_type, name_filter, album_filter, LogicalOperatorEnum.OR);
if (segment['negated']) {
filter = new NegationFilter(result_type, filter);
}
} else if (result_type === ResultTypeEnum.ALBUM) {
// TODO: We need a natural matcher for album names
name_filter = new MatchingFilter(
result_type,
segment['text'],
MatchTypeEnum.MATCH_ALBUM_NAME_EQUALS
);
filter = name_filter;
if (segment['negated']) {
filter = new NegationFilter(result_type, filter);
}
} else if (result_type === ResultTypeEnum.TAG) {
// Match against the tag name.
name_filter = new MatchingFilter(
result_type,
segment['text'],
MatchTypeEnum.MATCH_TAG_EQUALS
);
filter = name_filter;
if (segment['negated']) {
filter = new NegationFilter(result_type, filter);
}
}
return filter.simplify();
}
export function user_query_from_search_string(search_string) {
const parser = require('search-string');
var parsed = parser.parse(search_string);
//var conditions = parsed.getParsedQuery();
var texts = parsed.getTextSegments();
var r = new UserQuery();
texts.forEach(text => {
r.image_filter = new LogicalOperatorFilter(ResultTypeEnum.IMAGE, r.image_filter,
filter_from_text_segment(ResultTypeEnum.IMAGE, text), LogicalOperatorEnum.AND).simplify();
r.album_filter = new LogicalOperatorFilter(ResultTypeEnum.ALBUM, r.album_filter,
filter_from_text_segment(ResultTypeEnum.ALBUM, text), LogicalOperatorEnum.AND).simplify();
r.tag_filter = new LogicalOperatorFilter(ResultTypeEnum.TAG, r.tag_filter,
filter_from_text_segment(ResultTypeEnum.TAG, text), LogicalOperatorEnum.AND).simplify();
});
return r;
}
export function user_query_from_browsed_album(album_path) {
var r = new UserQuery();
r.image_filter = new MatchingFilter(
ResultTypeEnum.IMAGE,
album_path,
MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD,
false).simplify();
r.album_filter = new ConstFilter(ResultTypeEnum.ALBUM, false).simplify();
r.tag_filter = new ConstFilter(ResultTypeEnum.TAG, false).simplify();
return r;
}
export function user_query_from_browsed_tag(name) {
var r = new UserQuery();
r.image_filter = new MatchingFilter(
ResultTypeEnum.IMAGE,
name,
MatchTypeEnum.MATCH_TAG_EQUALS,
false).simplify();
r.album_filter = new ConstFilter(ResultTypeEnum.ALBUM, false).simplify();
r.tag_filter = new ConstFilter(ResultTypeEnum.TAG, false).simplify();
return r;
}