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:
parent
3ad2f6612f
commit
d993a3275b
@ -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>{
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
],
|
||||
|
@ -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) {
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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);
|
||||
|
@ -1,8 +1,8 @@
|
||||
export enum SearchTypes {
|
||||
image,
|
||||
directory,
|
||||
keyword,
|
||||
position
|
||||
directory = 1,
|
||||
keyword = 2,
|
||||
position = 3,
|
||||
image = 4
|
||||
}
|
||||
|
||||
export class AutoCompleteItem {
|
||||
|
@ -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>;
|
||||
|
@ -13,7 +13,7 @@ export interface PhotoMetadata {
|
||||
cameraData: CameraMetadata;
|
||||
positionData: PositionMetaData;
|
||||
size: ImageSize;
|
||||
creationDate: Date;
|
||||
creationDate: number;
|
||||
}
|
||||
|
||||
export interface ImageSize {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user