-
Notifications
You must be signed in to change notification settings - Fork 0
/
recordings.service.ts
180 lines (151 loc) · 6.07 KB
/
recordings.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import { Injectable } from "@angular/core";
import { Observable, of, throwError } from "rxjs";
import { map } from 'rxjs/operators';
import { isEmpty, orderBy } from "lodash-es";
import { Recording } from "../shared/recording.model";
import { environment } from "src/environments/environment.base";
import { SerialisePipe } from "../shared/serialise.pipe";
import { IoService } from "../core/io.service";
import { FuzzyService } from "../core/fuzzy.interface";
const DATA_FOLDER = environment.dataFolder;
const DEFAULT_SORT = environment.defaultSort;
@Injectable()
export class RecordingsService {
// This actually represents the server-side collection of recordings. Hence its being private.
private collection: Recording[] | null;
constructor(
private ioService: IoService,
private fuzzyService: FuzzyService,
private serialise: SerialisePipe
) {
this.collection = null;
}
/**
* Fetches and converts the server-side file holding all the recordings into individual recordings.
* @param filename - Name of server-side file under the default data folder.
* @param sortOptions - By default, it sorts the recordings alphabetically.
* @returns Observable with the new collection of recordings.
*/
load (filename: string, sortOptions: any = DEFAULT_SORT): Observable<Recording[]> {
return this.ioService.fetch(DATA_FOLDER + filename).pipe(
map((response: string) => {
const parsed = this.ioService.parse(response);
if (isEmpty(sortOptions)) {
return parsed;
}
// With a remote API, the sorting here would likely have been done on the server-side.
return this.collection = orderBy(parsed, sortOptions);
})
);
}
/**
* Checks if a given recording's ISRC is already in use by other recordings in the collection.
* Effectively, it tests the ideal uniquess of the recording provided.
* @param recording - Recording to look for.
* @returns Observable with true if a recording with the same ISRC is found. False if not there,
* was not provided in the first place or has no ISRC.
*/
hasRecording(recording: Recording | undefined): Observable<boolean> {
try {
if (!this.collection || !recording || isEmpty(recording) || !recording.isrc) {
return of(false);
}
return of(this.collection.some(item => item.isrc === recording.isrc));
} catch(error) {
return throwError(error);
}
}
/**
* Performs a fuzzy search on a set of recordings either with a serialised string version of a given
* recording or a plain string. After that, the recordings' order is re-balanced in terms of their
* durations. This is mainly due to the numeric nature of the duration, which does not lend itself
* to distance-based fuzzy algorithms.
* @param query - String or recording used as fuzzy query.
* @returns Observable with collection of recordings.
*/
fuzzySearch(query: Recording | string): Observable<Recording[]> {
const searchKeys = this.fuzzyService.searchKeys;
let recordings;
try {
if (!this.collection) {
return of([]);
}
if (typeof query === 'string') {
recordings = this.fuzzyService.search(this.collection, query);
// Recording passed in => serialises only values for keys in fuzzy options and re-balances by duration
} else {
recordings = this.fuzzyService.search(this.collection, this.serialise.transform(query, searchKeys));
if (searchKeys.indexOf('duration') === -1) {
recordings = this.fuzzyService.sortDurationDiff(recordings, query.duration);
}
}
// If the query was a recording, the score should be removed.
return of(recordings.map(result => result.item ? result.item : result));
} catch(error) {
return throwError(error);
}
}
/**
* Adds a new recording to an existing collection without modifying it.
* @param recording - New item to add.
* @returns Observable with new collection of recordings. The original collection is returned back
* if an empty or no recording is passed in.
*/
add(recording: Recording | undefined): Observable<Recording[]> {
try {
if (!this.collection) {
throw new Error('Can\'t add to a non-existent collection');
}
if (!recording || isEmpty(recording)) {
return of(this.collection);
}
// Again, mimicking the server's behaviour regarding sorting.
this.collection = [...this.collection, recording];
return of(orderBy(this.collection, DEFAULT_SORT));
} catch(error) {
return throwError(error);
}
}
/**
* Removes all recordings that have the same serialised data as a given one.
* @param recording - Object representative of the recording to be removed.
* @returns Observable with new collection of recordings. The original collection is returned back
* if an empty or no recording is passed in.
*/
remove(recording: Recording | undefined): Observable<Recording[]> {
try {
if (!this.collection) {
throw new Error('Can\'t remove from a non-existent collection');
}
if (!recording || isEmpty(recording)) {
return of(this.collection);
}
this.collection = this.collection.filter(item => {
return this.serialise.transform(item) !== this.serialise.transform(recording);
});
return of(this.collection);
} catch(error) {
return throwError(error);
}
}
/**
* Sorts all recordings by field(s) and a specific order.
* @param sortBy - List of recording field names whose values will determine the sorting.
* @param order - Ascending or descending direction.
* @returns Observable with new collection of recordings.
*/
sort(
sortBy: string[] = DEFAULT_SORT,
order: ('asc' | 'desc')[] = []
): Observable<Recording[]> {
try {
if (!this.collection) {
throw new Error('Can\'t sort a non-existent collection');
}
this.collection = orderBy(this.collection, sortBy, order);
return of(this.collection);
} catch(error) {
return throwError(error);
}
}
}