1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2024-11-28 08:58:49 +02:00

implementing mysql search and instant search

This commit is contained in:
Braun Patrik 2016-12-28 12:30:26 +01:00
parent 3ad2f6612f
commit d993a3275b
17 changed files with 139 additions and 52 deletions

View File

@ -26,7 +26,7 @@ export class DiskManager {
let directory = <DirectoryDTO>{
name: directoryName,
path: directoryParent,
lastUpdate: new Date(),
lastUpdate: Date.now(),
directories: [],
photos: []
};
@ -46,7 +46,7 @@ export class DiskManager {
directory.directories.push(<DirectoryDTO>{
name: file,
path: relativeDirectoryName,
lastUpdate: new Date(),
lastUpdate: Date.now(),
directories: [],
photos: []
});
@ -85,11 +85,9 @@ export class DiskManager {
let extension = mime.lookup(fullPath);
if (imageMimeTypes.indexOf(extension) !== -1) {
return true;
}
return imageMimeTypes.indexOf(extension) !== -1;
return false;
}
/*
@ -168,7 +166,7 @@ export class DiskManager {
};
let keywords: [string] = iptcData.keywords.map((s: string) => decode(s));
let creationDate: Date = iptcData.date_time;
let creationDate: number = iptcData.date_time.getTime();
let metadata: PhotoMetadata = <PhotoMetadata>{

View File

@ -1,7 +1,7 @@
import {IGalleryManager} from "../interfaces/IGalleryManager";
import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import * as path from "path";
import {DirectoryEnitity} from "./enitites/DirectoryEntity";
import {DirectoryEntity} from "./enitites/DirectoryEntity";
import {MySQLConnection} from "./MySQLConnection";
import {DiskManager} from "../DiskManger";
import {PhotoEntity} from "./enitites/PhotoEntity";
@ -18,7 +18,7 @@ export class GalleryManager implements IGalleryManager {
MySQLConnection.getConnection().then(async connection => {
let dir = await connection
.getRepository(DirectoryEnitity)
.getRepository(DirectoryEntity)
.createQueryBuilder("directory_entity")
.where("directory_entity.name = :name AND directory_entity.path = :path", {
name: directoryName,
@ -54,7 +54,7 @@ export class GalleryManager implements IGalleryManager {
DiskManager.scanDirectory(relativeDirectoryName, (err, scannedDirectory) => {
MySQLConnection.getConnection().then(async connection => {
let directoryRepository = connection.getRepository(DirectoryEnitity);
let directoryRepository = connection.getRepository(DirectoryEntity);
let photosRepository = connection.getRepository(PhotoEntity);
let parentDir = await directoryRepository.persist(scannedDirectory);

View File

@ -4,7 +4,7 @@ import {Config} from "../../config/Config";
import {UserEntity} from "./enitites/UserEntity";
import {UserRoles} from "../../../common/entities/UserDTO";
import {PhotoEntity, PhotoMetadataEntity} from "./enitites/PhotoEntity";
import {DirectoryEnitity} from "./enitites/DirectoryEntity";
import {DirectoryEntity} from "./enitites/DirectoryEntity";
export class MySQLConnection {
@ -34,7 +34,7 @@ export class MySQLConnection {
},
entities: [
UserEntity,
DirectoryEnitity,
DirectoryEntity,
PhotoMetadataEntity,
PhotoEntity
],

View File

@ -3,7 +3,7 @@ import {ISearchManager} from "../interfaces/ISearchManager";
import {SearchResultDTO} from "../../../common/entities/SearchResult";
import {MySQLConnection} from "./MySQLConnection";
import {PhotoEntity} from "./enitites/PhotoEntity";
import {DirectoryEnitity} from "./enitites/DirectoryEntity";
import {DirectoryEntity} from "./enitites/DirectoryEntity";
import {PositionMetaData} from "../../../common/entities/PhotoDTO";
export class SearchManager implements ISearchManager {
@ -15,7 +15,7 @@ export class SearchManager implements ISearchManager {
try {
let result: Array<AutoCompleteItem> = [];
let photoRepository = connection.getRepository(PhotoEntity);
let directoryRepository = connection.getRepository(DirectoryEnitity);
let directoryRepository = connection.getRepository(DirectoryEntity);
(await photoRepository
@ -71,11 +71,101 @@ export class SearchManager implements ISearchManager {
}
search(text: string, searchType: SearchTypes, cb: (error: any, result: SearchResultDTO) => void) {
throw new Error("not implemented");
MySQLConnection.getConnection().then(async connection => {
let result: SearchResultDTO = <SearchResultDTO>{
searchText: text,
searchType: searchType,
directories: [],
photos: []
};
let query = connection
.getRepository(PhotoEntity)
.createQueryBuilder("photo");
if (!searchType || searchType === SearchTypes.image) {
query.orWhere('photo.name LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"});
}
if (!searchType || searchType === SearchTypes.position) {
query.orWhere('photo.metadata.positionData LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"});
}
if (!searchType || searchType === SearchTypes.keyword) {
query.orWhere('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"});
}
let photos = await query
.innerJoinAndSelect("photo.directory", "directory")
.getMany();
if (photos) {
for (let i = 0; i < photos.length; i++) {
photos[i].metadata.keywords = <any>JSON.parse(<any>photos[i].metadata.keywords);
photos[i].metadata.cameraData = <any>JSON.parse(<any>photos[i].metadata.cameraData);
photos[i].metadata.positionData = <any>JSON.parse(<any>photos[i].metadata.positionData);
photos[i].metadata.size = <any>JSON.parse(<any>photos[i].metadata.size);
}
result.photos = photos;
}
result.directories = await connection
.getRepository(DirectoryEntity)
.createQueryBuilder("dir")
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"})
.getMany();
return cb(null, result);
}).catch((error) => {
return cb(error, null);
});
}
instantSearch(text: string, cb: (error: any, result: SearchResultDTO) => void) {
throw new Error("not implemented");
MySQLConnection.getConnection().then(async connection => {
let result: SearchResultDTO = <SearchResultDTO>{
searchText: text,
directories: [],
photos: []
};
let photos = await connection
.getRepository(PhotoEntity)
.createQueryBuilder("photo")
.where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"})
.orWhere('photo.metadata.positionData LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"})
.orWhere('photo.name LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"})
.innerJoinAndSelect("photo.directory", "directory")
.setLimit(10)
.getMany();
if (photos) {
for (let i = 0; i < photos.length; i++) {
photos[i].metadata.keywords = <any>JSON.parse(<any>photos[i].metadata.keywords);
photos[i].metadata.cameraData = <any>JSON.parse(<any>photos[i].metadata.cameraData);
photos[i].metadata.positionData = <any>JSON.parse(<any>photos[i].metadata.positionData);
photos[i].metadata.size = <any>JSON.parse(<any>photos[i].metadata.size);
}
result.photos = photos;
}
let directories = await connection
.getRepository(DirectoryEntity)
.createQueryBuilder("dir")
.where('dir.name LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"})
.setLimit(10)
.getMany();
result.directories = directories;
return cb(null, result);
}).catch((error) => {
return cb(error, null);
});
}
private encapsulateAutoComplete(values: Array<string>, type: SearchTypes) {

View File

@ -3,7 +3,7 @@ import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
import {PhotoEntity} from "./PhotoEntity";
@Table()
export class DirectoryEnitity implements DirectoryDTO {
export class DirectoryEntity implements DirectoryDTO {
@PrimaryGeneratedColumn()
id: number;
@ -19,14 +19,14 @@ export class DirectoryEnitity implements DirectoryDTO {
path: string;
@Column('datetime')
public lastUpdate: Date;
@Column('number')
public lastUpdate: number;
@ManyToOne(type => DirectoryEnitity, directory => directory.directories)
public parent: DirectoryEnitity;
@ManyToOne(type => DirectoryEntity, directory => directory.directories)
public parent: DirectoryEntity;
@OneToMany(type => DirectoryEnitity, dir => dir.parent)
public directories: Array<DirectoryEnitity>;
@OneToMany(type => DirectoryEntity, dir => dir.parent)
public directories: Array<DirectoryEntity>;
@OneToMany(type => PhotoEntity, photo => photo.directory)
public photos: Array<PhotoEntity>;

View File

@ -7,7 +7,7 @@ import {
ImageSize,
PositionMetaData
} from "../../../../common/entities/PhotoDTO";
import {DirectoryEnitity} from "./DirectoryEntity";
import {DirectoryEntity} from "./DirectoryEntity";
@Table()
export class PhotoEntity implements PhotoDTO {
@ -18,7 +18,7 @@ export class PhotoEntity implements PhotoDTO {
@Column("string")
name: string;
@ManyToOne(type => DirectoryEnitity, directory => directory.photos)
@ManyToOne(type => DirectoryEntity, directory => directory.photos)
directory: DirectoryDTO;
@Embedded(type => PhotoMetadataEntity)
@ -44,8 +44,8 @@ export class PhotoMetadataEntity implements PhotoMetadata {
@Column("string")
size: ImageSize;
@Column("datetime")
creationDate: Date;
@Column("number")
creationDate: number;
}
/*

View File

@ -94,7 +94,7 @@ export class Server {
throw error;
}
var bind = typeof Config.Server.port === 'string'
const bind = typeof Config.Server.port === 'string'
? 'Pipe ' + Config.Server.port
: 'Port ' + Config.Server.port;
@ -118,8 +118,8 @@ export class Server {
* Event listener for HTTP server "listening" event.
*/
private onListening = () => {
var addr = this.server.address();
var bind = typeof addr === 'string'
let addr = this.server.address();
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
this.debug('Listening on ' + bind);

View File

@ -1,8 +1,8 @@
export enum SearchTypes {
image,
directory,
keyword,
position
directory = 1,
keyword = 2,
position = 3,
image = 4
}
export class AutoCompleteItem {

View File

@ -4,7 +4,7 @@ export interface DirectoryDTO {
id: number;
name: string;
path: string;
lastUpdate: Date;
lastUpdate: number;
parent: DirectoryDTO;
directories: Array<DirectoryDTO>;
photos: Array<PhotoDTO>;

View File

@ -13,7 +13,7 @@ export interface PhotoMetadata {
cameraData: CameraMetadata;
positionData: PositionMetaData;
size: ImageSize;
creationDate: Date;
creationDate: number;
}
export interface ImageSize {

View File

@ -17,9 +17,6 @@ export class GalleryCacheService {
let directory: DirectoryDTO = JSON.parse(value);
directory.photos.forEach((photo: PhotoDTO) => {
photo.metadata.creationDate = new Date(<any>photo.metadata.creationDate);
});
directory.photos.forEach((photo: PhotoDTO) => {
photo.directory = directory;

View File

@ -38,10 +38,6 @@ export class GalleryService {
}
message.result.directory.photos.forEach((photo: PhotoDTO) => {
photo.metadata.creationDate = new Date(<any>photo.metadata.creationDate);
});
message.result.directory.photos.forEach((photo: PhotoDTO) => {
photo.directory = message.result.directory;
});

View File

@ -88,7 +88,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
private sortPhotos() {
//sort pohots by date
this.photos.sort((a: PhotoDTO, b: PhotoDTO) => {
return a.metadata.creationDate.getTime() - b.metadata.creationDate.getTime();
return a.metadata.creationDate - b.metadata.creationDate;
});
}

View File

@ -12,7 +12,7 @@
<div class="photo-position" *ngIf="gridPhoto.photo.metadata.positionData">
<span class="glyphicon glyphicon-map-marker"></span>
<template [ngIf]="getPositionText()">
<a [routerLink]="['Search',{searchText: getPositionText(), type:SearchTypes[SearchTypes.position]}]"
<a [routerLink]="['/search', getPositionText(), {type:SearchTypes[SearchTypes.position]}]"
*ngIf="searchEnabled">
{{getPositionText()}}
</a>
@ -23,7 +23,7 @@
<div class="photo-keywords">
<template ngFor let-keyword [ngForOf]="gridPhoto.photo.metadata.keywords" let-last="last">
<a *ngIf="searchEnabled"
[routerLink]="['Search',{searchText: keyword, type: SearchTypes[SearchTypes.keyword]}]">#{{keyword}}</a>
[routerLink]="['/search', keyword, {type: SearchTypes[SearchTypes.keyword]}]">#{{keyword}}</a>
<span *ngIf="!searchEnabled">#{{keyword}}</span>
<template [ngIf]="!last">,</template>
</template>

View File

@ -9,14 +9,14 @@
<div class="autocomplete-list" *ngIf="autoCompleteItems.length > 0"
(mouseover)="setMouseOverAutoComplete(true)" (mouseout)="setMouseOverAutoComplete(false)">
<div class="autocomplete-item" *ngFor="let item of autoCompleteItems">
<a [routerLink]="['Search',{searchText: item.text, type: SearchTypes[item.type]}]">
<a [routerLink]="['/search', item.text, {type: SearchTypes[item.type]}]">
<span [ngSwitch]="item.type">
<span *ngSwitchCase="0" class="glyphicon glyphicon-picture"></span>
<span *ngSwitchCase="1" class="glyphicon glyphicon-folder-open"></span>
<span *ngSwitchCase="2" class="glyphicon glyphicon-tag"></span>
<span *ngSwitchCase="3" class="glyphicon glyphicon-map-marker"></span>
</span>
{{item.preText}}<strong>{{item.highLightText}}</strong>{{item.postText}}
{{item.preText}}<strong>{{item.highLightText}}</strong>{{item.postText}}
</a>
</div>
</div>

View File

@ -16,6 +16,10 @@ export class GallerySearchComponent {
autoCompleteItems: Array<AutoCompleteRenderItem> = [];
private searchText: string = "";
private cache = {
lastAutocomplete: "",
lastInstantSearch: ""
};
SearchTypes: any = [];
@ -37,13 +41,15 @@ export class GallerySearchComponent {
onSearchChange(event: KeyboardEvent) {
let searchText = (<HTMLInputElement>event.target).value;
let searchText = (<HTMLInputElement>event.target).value.trim();
if (Config.Client.Search.autocompleteEnabled) {
if (Config.Client.Search.autocompleteEnabled && this.cache.lastAutocomplete != searchText) {
this.cache.lastAutocomplete = searchText;
this.autocomplete(searchText);
}
if (Config.Client.Search.instantSearchEnabled) {
if (Config.Client.Search.instantSearchEnabled && this.cache.lastInstantSearch != searchText) {
this.cache.lastInstantSearch = searchText;
this._galleryService.instantSearch(searchText);
}
}

View File

@ -19,4 +19,4 @@
"exclude": [
"node_modules"
]
}
}