|
|
|
@ -89,16 +89,15 @@ export const MatchTypeEnum = { |
|
|
|
|
MATCH_TAG_NAME_EQUALS: 10, // Match on the local tag name (excluding parents)
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
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; } |
|
|
|
@ -108,7 +107,7 @@ export class ResultFilter { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class ConstFilter extends ResultFilter { |
|
|
|
|
constructor(type, val) { super(type); this.constval = val; } |
|
|
|
|
constructor(val) { super(); this.constval = val; } |
|
|
|
|
constval = true; // True lets everything through, false rejects everything
|
|
|
|
|
|
|
|
|
|
to_sql_where() { |
|
|
|
@ -121,12 +120,12 @@ export class ConstFilter extends ResultFilter { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class NegationFilter extends ResultFilter { |
|
|
|
|
constructor(rtype, body) { |
|
|
|
|
super(rtype); |
|
|
|
|
constructor(body) { |
|
|
|
|
super(); |
|
|
|
|
this.body = body; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
body = new ConstFilter(this.return_type, false); |
|
|
|
|
body = new ConstFilter(false); |
|
|
|
|
|
|
|
|
|
to_sql_where() { |
|
|
|
|
return "NOT (" + this.body.to_sql_where() + ")"; |
|
|
|
@ -137,10 +136,10 @@ export class NegationFilter extends ResultFilter { |
|
|
|
|
simplify() { |
|
|
|
|
var f = this.body.simplify(); |
|
|
|
|
if (f.is_true()) { |
|
|
|
|
return new ConstFilter(this.result_type, false); |
|
|
|
|
return new ConstFilter(false); |
|
|
|
|
} |
|
|
|
|
if (f.is_false()) { |
|
|
|
|
return new ConstFilter(this.result_type, true); |
|
|
|
|
return new ConstFilter(true); |
|
|
|
|
} |
|
|
|
|
if (f.is_negation()) { |
|
|
|
|
return f.body; |
|
|
|
@ -155,8 +154,8 @@ export const TimeFilterTypeEnum = { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class TimeFilter extends ResultFilter { |
|
|
|
|
constructor(rtype, time, type) { |
|
|
|
|
super(rtype); |
|
|
|
|
constructor(time, type) { |
|
|
|
|
super(); |
|
|
|
|
this.time = time; |
|
|
|
|
this.type = type; |
|
|
|
|
} |
|
|
|
@ -186,8 +185,8 @@ export const ImageTypeEnum = { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class ImageTypeFilter extends ResultFilter { |
|
|
|
|
constructor(rtype, type) { |
|
|
|
|
super(rtype); |
|
|
|
|
constructor(type) { |
|
|
|
|
super(); |
|
|
|
|
this.type = type; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -202,8 +201,8 @@ export class ImageTypeFilter extends ResultFilter { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class MatchingFilter extends ResultFilter { |
|
|
|
|
constructor(rtype, from, mtype) { |
|
|
|
|
super(rtype); |
|
|
|
|
constructor(from, mtype) { |
|
|
|
|
super(); |
|
|
|
|
this.match_from = from; |
|
|
|
|
this.match_type = mtype; |
|
|
|
|
} |
|
|
|
@ -258,14 +257,16 @@ export const LogicalOperatorEnum = { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
constructor(operands, op) { super(); this.operands = operands; this.operator = op; } |
|
|
|
|
operands = 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 operand_wheres = []; |
|
|
|
|
for (let i = 0; i < this.operands.length; i++) { |
|
|
|
|
operand_wheres.push(this.operands[i].to_sql_where()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var operator_str = ""; |
|
|
|
|
if (this.operator === LogicalOperatorEnum.AND) { |
|
|
|
|
operator_str = " AND "; |
|
|
|
@ -274,30 +275,51 @@ export class LogicalOperatorFilter extends ResultFilter { |
|
|
|
|
} else { |
|
|
|
|
throw new Error('Unsupported logical operator: ' + this.operator); |
|
|
|
|
} |
|
|
|
|
return "(" + where1 + operator_str + where2 + ")"; |
|
|
|
|
var retstr = "(" + operand_wheres[0]; |
|
|
|
|
for (let i = 1; i < this.operands.length; i++) { |
|
|
|
|
retstr += operator_str + operand_wheres[i]; |
|
|
|
|
} |
|
|
|
|
retstr += ")"; |
|
|
|
|
return retstr; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
simplify() { |
|
|
|
|
var a = this.sub_filter_a.simplify(); |
|
|
|
|
var b = this.sub_filter_b.simplify(); |
|
|
|
|
var simple = []; |
|
|
|
|
for (let i = 0; i < this.operands.length; i++) { |
|
|
|
|
simple.push(this.operands[i].simplify()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (this.operator === LogicalOperatorEnum.OR) { |
|
|
|
|
if (a.is_true() || b.is_true()) { |
|
|
|
|
return new ConstFilter(this.return_type, true); |
|
|
|
|
for (let i = simple.length - 1; i >= 0; i--) { |
|
|
|
|
if (simple[i].is_true()) { |
|
|
|
|
return new ConstFilter(true); |
|
|
|
|
} |
|
|
|
|
if (simple[i].is_false()) { |
|
|
|
|
// Remove this element, because OR FALSE means nothing
|
|
|
|
|
simple.splice(i, 1); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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); |
|
|
|
|
for (let i = simple.length - 1; i >= 0; i--) { |
|
|
|
|
if (simple[i].is_false()) { |
|
|
|
|
return new ConstFilter(false); |
|
|
|
|
} |
|
|
|
|
if (simple[i].is_true()) { |
|
|
|
|
// Remove this element, because AND TRUE means nothing
|
|
|
|
|
simple.splice(i, 1); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (a.is_true()) { return b; } |
|
|
|
|
if (b.is_true()) { return a; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return this; |
|
|
|
|
if(simple.length == 0) { |
|
|
|
|
return null; |
|
|
|
|
} else if(simple.length == 1) { |
|
|
|
|
return simple[0]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return new LogicalOperatorFilter(simple, this.operator); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -306,8 +328,8 @@ export class LocationFilter extends ResultFilter { |
|
|
|
|
// to a location and filters objects outside said location.
|
|
|
|
|
// Locations are directly stored from Nominatim API responses,
|
|
|
|
|
// which have GeoJSON and other metadata.
|
|
|
|
|
constructor(rtype, geo_area) { |
|
|
|
|
super(rtype); |
|
|
|
|
constructor(geo_area) { |
|
|
|
|
super(); |
|
|
|
|
this.geo_area = geo_area; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -327,9 +349,7 @@ export class LocationFilter extends ResultFilter { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class UserQuery { |
|
|
|
|
image_filter = new ConstFilter(ResultTypeEnum.IMAGE, false); |
|
|
|
|
album_filter = new ConstFilter(ResultTypeEnum.ALBUM, false); |
|
|
|
|
tag_filter = new ConstFilter(ResultTypeEnum.TAG, false); |
|
|
|
|
filter = new ConstFilter(false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// This query will return database entries with the fields "id", "uniqueHash", "relativePath" (of the album) and "name" for each matching image.
|
|
|
|
@ -378,24 +398,24 @@ export function tag_query_with_where(maybe_where) { |
|
|
|
|
|
|
|
|
|
export function maybe_image_query(user_query, database) { |
|
|
|
|
var where = false; |
|
|
|
|
if (user_query.image_filter) { |
|
|
|
|
where = "WHERE " + user_query.image_filter.to_sql_where(); |
|
|
|
|
if (user_query.filter) { |
|
|
|
|
where = "WHERE " + user_query.filter.to_sql_where(); |
|
|
|
|
} |
|
|
|
|
return image_query_with_where(where, database); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function maybe_album_query(user_query, database) { |
|
|
|
|
var where = false; |
|
|
|
|
if (user_query.album_filter) { |
|
|
|
|
where = "WHERE " + user_query.album_filter.to_sql_where(); |
|
|
|
|
if (user_query.filter) { |
|
|
|
|
where = "WHERE " + user_query.filter.to_sql_where(); |
|
|
|
|
} |
|
|
|
|
return album_query_with_where(where, database); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function maybe_tag_query(user_query) { |
|
|
|
|
var where = false; |
|
|
|
|
if (user_query.tag_filter) { |
|
|
|
|
where = "WHERE " + user_query.tag_filter.to_sql_where(); |
|
|
|
|
if (user_query.filter) { |
|
|
|
|
where = "WHERE " + user_query.filter.to_sql_where(); |
|
|
|
|
} |
|
|
|
|
return tag_query_with_where(where); |
|
|
|
|
} |
|
|
|
@ -408,51 +428,31 @@ export function filter_is_const_false(filter) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function filter_from_text_segment(result_type, segment) { |
|
|
|
|
function filter_from_text_segment(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_OR_CHILD |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
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_OR_CHILD |
|
|
|
|
); |
|
|
|
|
filter = name_filter; |
|
|
|
|
if (segment['negated']) { |
|
|
|
|
filter = new NegationFilter(result_type, filter); |
|
|
|
|
} |
|
|
|
|
} else if (result_type === ResultTypeEnum.TAG) { |
|
|
|
|
// TODO: We need a natural matcher for tag names
|
|
|
|
|
name_filter = new MatchingFilter( |
|
|
|
|
result_type, |
|
|
|
|
segment['text'], |
|
|
|
|
MatchTypeEnum.MATCH_TAG_NAME_EQUALS_OR_CHILD |
|
|
|
|
); |
|
|
|
|
filter = name_filter; |
|
|
|
|
if (segment['negated']) { |
|
|
|
|
filter = new NegationFilter(result_type, filter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Option 1: match on image name
|
|
|
|
|
var image_filter = new MatchingFilter( |
|
|
|
|
segment['text'], |
|
|
|
|
MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Option 2: match on album path (TODO: need natural matching)
|
|
|
|
|
var album_filter = new MatchingFilter( |
|
|
|
|
segment['text'], |
|
|
|
|
MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Option 3: match on tag name
|
|
|
|
|
// TODO: We need a natural matcher for tag names
|
|
|
|
|
var tag_filter = new MatchingFilter( |
|
|
|
|
segment['text'], |
|
|
|
|
MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
filter = new LogicalOperatorFilter([image_filter, album_filter, tag_filter], LogicalOperatorEnum.OR); |
|
|
|
|
if (segment['negated']) { |
|
|
|
|
filter = new NegationFilter(filter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return filter.simplify(); |
|
|
|
@ -468,12 +468,8 @@ export function user_query_from_search_string(search_string) { |
|
|
|
|
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(); |
|
|
|
|
r.filter = new LogicalOperatorFilter([r.filter, filter_from_text_segment(text)], |
|
|
|
|
LogicalOperatorEnum.OR).simplify(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
return r; |
|
|
|
@ -481,21 +477,8 @@ export function user_query_from_search_string(search_string) { |
|
|
|
|
|
|
|
|
|
export function user_query_from_browsed_album(album_path) { |
|
|
|
|
var r = new UserQuery(); |
|
|
|
|
r.image_filter = |
|
|
|
|
r.filter = |
|
|
|
|
new MatchingFilter( |
|
|
|
|
ResultTypeEnum.IMAGE, |
|
|
|
|
album_path, |
|
|
|
|
MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD, |
|
|
|
|
false).simplify(); |
|
|
|
|
r.album_filter = |
|
|
|
|
new MatchingFilter( |
|
|
|
|
ResultTypeEnum.ALBUM, |
|
|
|
|
album_path, |
|
|
|
|
MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD, |
|
|
|
|
false).simplify(); |
|
|
|
|
r.tag_filter = |
|
|
|
|
new MatchingFilter( |
|
|
|
|
ResultTypeEnum.TAG, |
|
|
|
|
album_path, |
|
|
|
|
MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD, |
|
|
|
|
false).simplify(); |
|
|
|
@ -504,24 +487,10 @@ export function user_query_from_browsed_album(album_path) { |
|
|
|
|
|
|
|
|
|
export function user_query_from_browsed_tag(tag_path) { |
|
|
|
|
var r = new UserQuery(); |
|
|
|
|
r.image_filter = |
|
|
|
|
new MatchingFilter( |
|
|
|
|
ResultTypeEnum.IMAGE, |
|
|
|
|
tag_path, |
|
|
|
|
MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD, |
|
|
|
|
false).simplify(); |
|
|
|
|
r.album_filter = |
|
|
|
|
r.filter = |
|
|
|
|
new MatchingFilter( |
|
|
|
|
ResultTypeEnum.ALBUM, |
|
|
|
|
tag_path, |
|
|
|
|
MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD, |
|
|
|
|
false).simplify(); |
|
|
|
|
r.tag_filter = |
|
|
|
|
new MatchingFilter( |
|
|
|
|
ResultTypeEnum.TAG, |
|
|
|
|
tag_path, |
|
|
|
|
MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD, |
|
|
|
|
false).simplify(); |
|
|
|
|
|
|
|
|
|
return r; |
|
|
|
|
} |