320 lines
10 KiB
TypeScript
320 lines
10 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { Headers, Http, Response } from '@angular/http';
|
|
|
|
import { Observable } from 'rxjs/Observable';
|
|
import 'rxjs/add/operator/toPromise';
|
|
import 'rxjs/add/operator/map';
|
|
|
|
import { Song } from './object/song';
|
|
import { Album } from './object/album';
|
|
import { Artist } from './object/artist';
|
|
import { Bucket } from './object/bucket';
|
|
|
|
@Injectable()
|
|
export class ElsService {
|
|
public static readonly DEFAULT_SIZE: number = 50;
|
|
private static readonly INDEX_NAME = 'itunessongs';
|
|
|
|
private static readonly ACTION_SEARCH = '/_search';
|
|
private static readonly ACTION_COUNT = '/_count';
|
|
|
|
private elsUrl = 'http://localhost:9200/' + ElsService.INDEX_NAME + '/';
|
|
private headers = new Headers({'Content-Type': 'application/json'});
|
|
|
|
constructor(private http: Http) { }
|
|
|
|
getTime(): Promise<number> {
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
aggs: {
|
|
sum_time: {
|
|
sum: { field: 'Total Time'}
|
|
}
|
|
},
|
|
'size': 0
|
|
}), {headers: this.headers})
|
|
.toPromise()
|
|
.then(res => res.json().aggregations.sum_time.value as number)
|
|
.catch(this.handleError);
|
|
}
|
|
|
|
getTimeSlowly(): Promise<number> {
|
|
return new Promise(resolve => {
|
|
setTimeout(() => resolve(this.getTime()), 2000);
|
|
});
|
|
}
|
|
|
|
getSize(): Promise<number> {
|
|
return this.http
|
|
.post(this.elsUrl + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
aggs: {
|
|
sum_time: {
|
|
sum: { field: 'Size' }
|
|
}
|
|
},
|
|
'size': 0
|
|
}), {headers: this.headers})
|
|
.toPromise()
|
|
.then(res => res.json().aggregations.sum_time.value as number)
|
|
.catch(this.handleError);
|
|
}
|
|
|
|
getSizeSlowly(): Promise<number> {
|
|
return new Promise(resolve => {
|
|
setTimeout(() => resolve(this.getSize()), 2000);
|
|
});
|
|
}
|
|
|
|
getCountSong(type: string): Promise<number> {
|
|
return this.http
|
|
.get(this.elsUrl + type + ElsService.ACTION_COUNT)
|
|
.toPromise()
|
|
.then(res => res.json().count as number)
|
|
.catch(this.handleError);
|
|
}
|
|
|
|
getCountNeverListenSong(): Promise<number> {
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_COUNT,
|
|
JSON.stringify({
|
|
'query': {
|
|
'bool': {
|
|
'must_not': {
|
|
'exists': { 'field': 'Play Count'}
|
|
}
|
|
}
|
|
}
|
|
}), {headers: this.headers})
|
|
.toPromise()
|
|
.then(res => res.json().count as number)
|
|
.catch(this.handleError);
|
|
}
|
|
|
|
getTrackCountSlowly(type: string): Promise<number> {
|
|
return new Promise(resolve => {
|
|
setTimeout(() => resolve(this.getCountSong(type)), 2000);
|
|
});
|
|
}
|
|
|
|
getMostPlayedTrack(): Observable<Song[]> {
|
|
// Thank to http://chariotsolutions.com/blog/post/angular2-observables-http-separating-services-components/
|
|
// for the map part
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'sort': [ {
|
|
'Play Count': {
|
|
'order': 'desc'
|
|
}
|
|
} ],
|
|
'size': 5
|
|
}), {headers: this.headers})
|
|
.map(res => this.responseToSongs(res));
|
|
}
|
|
|
|
getAlbumSongs(albumName: string, from: number = 0): Observable<Song[]> {
|
|
console.info('getAlbumSongs- Album name: ' + albumName + ' - from: ' + from);
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'query': {
|
|
'match_phrase': { 'Album': albumName }
|
|
},
|
|
'size': ElsService.DEFAULT_SIZE,
|
|
'from': from
|
|
}), {headers: this.headers})
|
|
.map(res => this.responseToSongs(res));
|
|
}
|
|
|
|
getGenreSongs(genreName: string, from: number = 0): Observable<Song[]> {
|
|
console.info('getGenreSongs- Genre name: ' + genreName + ' - from: ' + from);
|
|
// TODO Code repetition => to refactor
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'query': {
|
|
'match_phrase': { 'Genre': genreName }
|
|
},
|
|
'size': ElsService.DEFAULT_SIZE,
|
|
'from': from
|
|
}), {headers: this.headers})
|
|
.map(res => this.responseToSongs(res));
|
|
}
|
|
|
|
getArtistSongs(artistName: string, from: number = 0): Observable<Song[]> {
|
|
console.info('getArtistSongs- Artist name: ' + artistName + ' - from: ' + from);
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'query': {
|
|
'bool': {
|
|
'should': [
|
|
{'match_phrase' : { 'Album Artist' : artistName }},
|
|
{'match_phrase' : { 'Artist' : artistName }}
|
|
]
|
|
}
|
|
},
|
|
'size': ElsService.DEFAULT_SIZE,
|
|
'from': from
|
|
}), {headers: this.headers})
|
|
.map(res => this.responseToSongs(res));
|
|
}
|
|
|
|
getAlbum(albumName: string): Observable<Album> {
|
|
return this.http
|
|
.post(this.elsUrl + 'album' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'query': {
|
|
'match_phrase': { 'Album': albumName }
|
|
},
|
|
'size': ElsService.DEFAULT_SIZE
|
|
}), {headers: this.headers})
|
|
.map(res => this.responseToOneTypedResult<Album>(res, albumName));
|
|
}
|
|
|
|
getArtist(artistName: string): Observable<Artist> {
|
|
return this.http
|
|
.post(this.elsUrl + 'artist' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'query': {
|
|
'match_phrase': { 'Artist': artistName }
|
|
},
|
|
'size': ElsService.DEFAULT_SIZE
|
|
}), {headers: this.headers})
|
|
.map(res => this.responseToOneTypedResult<Artist>(res, artistName));
|
|
}
|
|
|
|
getGenres(ordering: string = 'desc'): Observable<Bucket[]> {
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'aggs' : {
|
|
'genres' : {
|
|
'terms' : {
|
|
'field' : 'Genre.original',
|
|
'size' : 10,
|
|
'missing': 'N/A',
|
|
'order': { '_count' : ordering }
|
|
}
|
|
}
|
|
},
|
|
'size': 0
|
|
}), {headers: this.headers})
|
|
.map(res => res.json().aggregations.genres.buckets)
|
|
.map((hits: Array<any>) => this.hitsToBuckets(hits));
|
|
}
|
|
|
|
getGenreCount(): Observable<number> {
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'aggs' : {
|
|
'genres' : {
|
|
'cardinality' : {
|
|
'field' : 'Genre.original',
|
|
'missing': 'N/A',
|
|
}
|
|
}
|
|
},
|
|
'size': 0
|
|
}), {headers: this.headers})
|
|
.map(res => res.json().aggregations.genres.value);
|
|
}
|
|
|
|
getLastAddedAlbums(month: number): Observable<Bucket[]> {
|
|
return this.http
|
|
.post(this.elsUrl + 'song' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'query': {
|
|
'range' : {
|
|
'Date Added' : {
|
|
'gte' : 'now-' + month + 'M/d',
|
|
'lte' : 'now/d'
|
|
}
|
|
}
|
|
},
|
|
'aggs' : {
|
|
'album' : {
|
|
'terms' : {
|
|
'field' : 'Album.original',
|
|
'size': 150
|
|
}
|
|
}
|
|
},
|
|
'size': 0
|
|
}), {headers: this.headers})
|
|
.map(res => res.json().aggregations.album.buckets)
|
|
// TODO Take in consideration "sum_other_doc_count"
|
|
.map((hits: Array<any>) => this.hitsToBuckets(hits));
|
|
}
|
|
|
|
getArtistFromAlbumName(albumname: string): Observable<Album[]> {
|
|
return this.http
|
|
.post(this.elsUrl + 'album' + ElsService.ACTION_SEARCH,
|
|
JSON.stringify({
|
|
'query': {
|
|
'match_phrase' : {
|
|
'Album' : albumname
|
|
}
|
|
}
|
|
}), {headers: this.headers})
|
|
.map(res => res.json().hits.hits)
|
|
.map((hits: Array<any>) => {
|
|
// TODO Use a method (duplicated code ?)
|
|
const result: Array<Album> = [];
|
|
hits.forEach((hit) => {
|
|
result.push(hit._source);
|
|
});
|
|
return result;
|
|
});
|
|
}
|
|
|
|
/** Process a result to return just one result.
|
|
* Used to get an album or an artist.
|
|
* Take a name to put in console output if no result or more than one result.
|
|
*
|
|
* @param res Response to process
|
|
* @param name The searched name - for console output
|
|
*/
|
|
private responseToOneTypedResult<T>(res: Response, name: string): T {
|
|
const hits = res.json().hits.hits;
|
|
|
|
if (hits.length < 1) {
|
|
console.info('No result found for name: "' + name);
|
|
return undefined;
|
|
}
|
|
if (hits.length > 1) {
|
|
// TODO Cumul results (for album)
|
|
console.error('More than one result for name: "' + name + '". Found (' + hits.length + '), return the first.');
|
|
}
|
|
return hits[0]._source;
|
|
}
|
|
|
|
/** Process a response to a array of songs.
|
|
*
|
|
* @param res Response to process
|
|
*/
|
|
private responseToSongs(res: Response): Song[] {
|
|
const result: Array<Song> = [];
|
|
res.json().hits.hits.forEach((hit) => {
|
|
result.push(hit._source);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
private hitsToBuckets(hits: Array<any>): Bucket[] {
|
|
const result: Array<Bucket> = [];
|
|
hits.forEach((bucket) => {
|
|
result.push(bucket);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
private handleError(error: any): Promise<any> {
|
|
console.error('An error occurred', error); // for demo purposes only
|
|
return Promise.reject(error.message || error);
|
|
}
|
|
}
|