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 { 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 { return new Promise(resolve => { setTimeout(() => resolve(this.getTime()), 2000); }); } getSize(): Promise { 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 { return new Promise(resolve => { setTimeout(() => resolve(this.getSize()), 2000); }); } getCountSong(type: string): Promise { return this.http .get(this.elsUrl + type + ElsService.ACTION_COUNT) .toPromise() .then(res => res.json().count as number) .catch(this.handleError); } getCountNeverListenSong(): Promise { 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 { return new Promise(resolve => { setTimeout(() => resolve(this.getCountSong(type)), 2000); }); } getMostPlayedTrack(): Observable { // 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 { 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 { 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 { 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 { 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(res, albumName)); } getArtist(artistName: string): Observable { 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(res, artistName)); } getGenres(ordering: string = 'desc'): Observable { 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) => this.hitsToBuckets(hits)); } getGenreCount(): Observable { 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 { 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) => this.hitsToBuckets(hits)); } getArtistFromAlbumName(albumname: string): Observable { 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) => { // TODO Use a method (duplicated code ?) const result: Array = []; 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(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 = []; res.json().hits.hits.forEach((hit) => { result.push(hit._source); }); return result; } private hitsToBuckets(hits: Array): Bucket[] { const result: Array = []; hits.forEach((bucket) => { result.push(bucket); }); return result; } private handleError(error: any): Promise { console.error('An error occurred', error); // for demo purposes only return Promise.reject(error.message || error); } }