1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08: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>{ let directory = <DirectoryDTO>{
name: directoryName, name: directoryName,
path: directoryParent, path: directoryParent,
lastUpdate: new Date(), lastUpdate: Date.now(),
directories: [], directories: [],
photos: [] photos: []
}; };
@ -46,7 +46,7 @@ export class DiskManager {
directory.directories.push(<DirectoryDTO>{ directory.directories.push(<DirectoryDTO>{
name: file, name: file,
path: relativeDirectoryName, path: relativeDirectoryName,
lastUpdate: new Date(), lastUpdate: Date.now(),
directories: [], directories: [],
photos: [] photos: []
}); });
@ -85,11 +85,9 @@ export class DiskManager {
let extension = mime.lookup(fullPath); let extension = mime.lookup(fullPath);
if (imageMimeTypes.indexOf(extension) !== -1) { return imageMimeTypes.indexOf(extension) !== -1;
return true;
}
return false;
} }
/* /*
@ -168,7 +166,7 @@ export class DiskManager {
}; };
let keywords: [string] = iptcData.keywords.map((s: string) => decode(s)); 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>{ let metadata: PhotoMetadata = <PhotoMetadata>{

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import {ISearchManager} from "../interfaces/ISearchManager";
import {SearchResultDTO} from "../../../common/entities/SearchResult"; import {SearchResultDTO} from "../../../common/entities/SearchResult";
import {MySQLConnection} from "./MySQLConnection"; import {MySQLConnection} from "./MySQLConnection";
import {PhotoEntity} from "./enitites/PhotoEntity"; import {PhotoEntity} from "./enitites/PhotoEntity";
import {DirectoryEnitity} from "./enitites/DirectoryEntity"; import {DirectoryEntity} from "./enitites/DirectoryEntity";
import {PositionMetaData} from "../../../common/entities/PhotoDTO"; import {PositionMetaData} from "../../../common/entities/PhotoDTO";
export class SearchManager implements ISearchManager { export class SearchManager implements ISearchManager {
@ -15,7 +15,7 @@ export class SearchManager implements ISearchManager {
try { try {
let result: Array<AutoCompleteItem> = []; let result: Array<AutoCompleteItem> = [];
let photoRepository = connection.getRepository(PhotoEntity); let photoRepository = connection.getRepository(PhotoEntity);
let directoryRepository = connection.getRepository(DirectoryEnitity); let directoryRepository = connection.getRepository(DirectoryEntity);
(await photoRepository (await photoRepository
@ -71,11 +71,101 @@ export class SearchManager implements ISearchManager {
} }
search(text: string, searchType: SearchTypes, cb: (error: any, result: SearchResultDTO) => void) { 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) { 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) { private encapsulateAutoComplete(values: Array<string>, type: SearchTypes) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,9 +17,6 @@ export class GalleryCacheService {
let directory: DirectoryDTO = JSON.parse(value); 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) => { directory.photos.forEach((photo: PhotoDTO) => {
photo.directory = directory; 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) => { message.result.directory.photos.forEach((photo: PhotoDTO) => {
photo.directory = message.result.directory; photo.directory = message.result.directory;
}); });

View File

@ -88,7 +88,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
private sortPhotos() { private sortPhotos() {
//sort pohots by date //sort pohots by date
this.photos.sort((a: PhotoDTO, b: PhotoDTO) => { 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"> <div class="photo-position" *ngIf="gridPhoto.photo.metadata.positionData">
<span class="glyphicon glyphicon-map-marker"></span> <span class="glyphicon glyphicon-map-marker"></span>
<template [ngIf]="getPositionText()"> <template [ngIf]="getPositionText()">
<a [routerLink]="['Search',{searchText: getPositionText(), type:SearchTypes[SearchTypes.position]}]" <a [routerLink]="['/search', getPositionText(), {type:SearchTypes[SearchTypes.position]}]"
*ngIf="searchEnabled"> *ngIf="searchEnabled">
{{getPositionText()}} {{getPositionText()}}
</a> </a>
@ -23,7 +23,7 @@
<div class="photo-keywords"> <div class="photo-keywords">
<template ngFor let-keyword [ngForOf]="gridPhoto.photo.metadata.keywords" let-last="last"> <template ngFor let-keyword [ngForOf]="gridPhoto.photo.metadata.keywords" let-last="last">
<a *ngIf="searchEnabled" <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> <span *ngIf="!searchEnabled">#{{keyword}}</span>
<template [ngIf]="!last">,</template> <template [ngIf]="!last">,</template>
</template> </template>

View File

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

View File

@ -16,6 +16,10 @@ export class GallerySearchComponent {
autoCompleteItems: Array<AutoCompleteRenderItem> = []; autoCompleteItems: Array<AutoCompleteRenderItem> = [];
private searchText: string = ""; private searchText: string = "";
private cache = {
lastAutocomplete: "",
lastInstantSearch: ""
};
SearchTypes: any = []; SearchTypes: any = [];
@ -37,13 +41,15 @@ export class GallerySearchComponent {
onSearchChange(event: KeyboardEvent) { 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); 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); this._galleryService.instantSearch(searchText);
} }
} }

View File

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