museria 0.2.49 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +10 -2
- package/.github/workflows/build.yml +3 -3
- package/.github/workflows/publish.yml +3 -3
- package/README.md +55 -59
- package/bin/actions.js +28 -28
- package/bin/index.js +4 -4
- package/bin/runner.js +1 -1
- package/bin/utils.js +6 -2
- package/dist/client/museria.client.js +7 -7
- package/dist/face/45a265d0f07b31cde85f.ttf +0 -0
- package/dist/face/6205fd00fb1b573e9f0f.ttf +0 -0
- package/dist/face/8d3cabfc66809162fb4d.woff2 +0 -0
- package/dist/face/fb8184add5a3101ad0a3.woff2 +0 -0
- package/dist/face/museria.face.js +33 -13
- package/dist/face/style.css +13 -11
- package/package.json +41 -40
- package/src/browser/client/index.js +2 -1
- package/src/browser/face/client.js +2 -1
- package/src/browser/face/controllers/app/app.html +77 -69
- package/src/browser/face/controllers/app/app.js +14 -7
- package/src/browser/face/controllers/app/app.scss +2 -22
- package/src/browser/face/index.js +3 -3
- package/src/browser/face/styles/main.scss +91 -11
- package/src/browser/face/styles/vars.scss +0 -1
- package/src/client.js +73 -74
- package/src/collection/transports/music/index.js +20 -18
- package/src/db/transports/database/index.js +7 -5
- package/src/db/transports/loki/index.js +30 -25
- package/src/errors.js +2 -1
- package/src/index.js +8 -6
- package/src/node.js +312 -323
- package/src/schema.js +27 -29
- package/src/server/transports/express/api/butler/controllers.js +7 -10
- package/src/server/transports/express/api/butler/routes.js +5 -5
- package/src/server/transports/express/api/master/controllers.js +7 -10
- package/src/server/transports/express/api/master/routes.js +5 -5
- package/src/server/transports/express/api/node/controllers.js +52 -61
- package/src/server/transports/express/api/node/routes.js +10 -10
- package/src/server/transports/express/api/routes.js +1 -1
- package/src/server/transports/express/api/slave/controllers.js +7 -10
- package/src/server/transports/express/api/slave/routes.js +6 -6
- package/src/server/transports/express/client/controllers.js +40 -61
- package/src/server/transports/express/client/routes.js +33 -39
- package/src/server/transports/express/controllers.js +10 -21
- package/src/server/transports/express/index.js +23 -20
- package/src/server/transports/express/midds.js +67 -67
- package/src/server/transports/express/routes.js +12 -12
- package/src/utils.js +175 -184
- package/test/client.js +311 -305
- package/test/db/database.js +32 -28
- package/test/db/loki.js +78 -74
- package/test/group.js +161 -156
- package/test/index.js +20 -10
- package/test/node.js +461 -460
- package/test/routes.js +404 -399
- package/test/server/express.js +35 -31
- package/test/services.js +25 -18
- package/test/tools.js +8 -6
- package/test/utils.js +236 -234
- package/webpack.client.js +9 -7
- package/webpack.face.js +8 -6
- package/dist/face/fa-brands-400.eot +0 -0
- package/dist/face/fa-brands-400.svg +0 -3717
- package/dist/face/fa-brands-400.ttf +0 -0
- package/dist/face/fa-brands-400.woff +0 -0
- package/dist/face/fa-brands-400.woff2 +0 -0
- package/dist/face/fa-solid-900.eot +0 -0
- package/dist/face/fa-solid-900.svg +0 -5034
- package/dist/face/fa-solid-900.ttf +0 -0
- package/dist/face/fa-solid-900.woff +0 -0
- package/dist/face/fa-solid-900.woff2 +0 -0
- /package/dist/face/{open-sans.ttf → 17e98b9e5586529b13cc.ttf} +0 -0
- /package/dist/face/{proxima-nova.ttf → 326601dfabd91e3f016c.ttf} +0 -0
- /package/dist/face/{logo.svg → ee9c6af64aa224827cec.svg} +0 -0
package/src/node.js
CHANGED
@@ -1,36 +1,42 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
import { merge, omit, random, assign } from "lodash-es";
|
2
|
+
import path from "path";
|
3
|
+
import sharp from "sharp";
|
4
|
+
import fse from "fs-extra";
|
5
|
+
import qs from "querystring";
|
6
|
+
import SplayTree from "splaytree";
|
7
|
+
import databaseLokiMuseria from "./db/transports/loki/index.js";
|
8
|
+
import serverExpressMuseria from "./server/transports/express/index.js";
|
9
|
+
import musicCollection from "./collection/transports/music/index.js";
|
10
|
+
import approvalCaptcha from "spreadable/src/approval/transports/captcha/index.js";
|
11
|
+
import nodeMetastocle from "metastocle/src/node.js";
|
12
|
+
import nodeStoracle from "storacle/src/node.js";
|
13
|
+
import schema from "./schema.js";
|
14
|
+
import utils from "./utils.js";
|
15
|
+
import errors from "./errors.js";
|
16
|
+
import pack from "../package.json" with { type: "json" }
|
17
|
+
|
18
|
+
const DatabaseLokiMuseria = databaseLokiMuseria();
|
19
|
+
const ServerExpressMuseria = serverExpressMuseria();
|
20
|
+
const MusicCollection = musicCollection();
|
21
|
+
const ApprovalCaptcha = approvalCaptcha();
|
22
|
+
const NodeMetastocle = nodeMetastocle();
|
23
|
+
const NodeStoracle = nodeStoracle(NodeMetastocle);
|
24
|
+
|
25
|
+
export default (Parent) => {
|
20
26
|
/**
|
21
27
|
* Class to manage the node
|
22
28
|
*/
|
23
29
|
return class NodeMuseria extends (Parent || NodeStoracle) {
|
24
|
-
static get version
|
25
|
-
static get codename
|
26
|
-
static get DatabaseTransport
|
27
|
-
static get ServerTransport
|
30
|
+
static get version() { return pack.version; }
|
31
|
+
static get codename() { return pack.name; }
|
32
|
+
static get DatabaseTransport() { return DatabaseLokiMuseria; }
|
33
|
+
static get ServerTransport() { return ServerExpressMuseria; }
|
28
34
|
|
29
35
|
/**
|
30
36
|
* @see NodeStoracle
|
31
37
|
*/
|
32
38
|
constructor(options = {}) {
|
33
|
-
options =
|
39
|
+
options = merge({
|
34
40
|
request: {
|
35
41
|
fileStoringNodeTimeout: '10m'
|
36
42
|
},
|
@@ -42,7 +48,7 @@ module.exports = (Parent) => {
|
|
42
48
|
queue: true,
|
43
49
|
loki: {
|
44
50
|
unique: ['title', 'fileHash'],
|
45
|
-
},
|
51
|
+
},
|
46
52
|
limitationOrder: ['priority', '$accessedAt'],
|
47
53
|
duplicationKey: 'fileHash',
|
48
54
|
schema: schema.getMusicCollection()
|
@@ -74,14 +80,13 @@ module.exports = (Parent) => {
|
|
74
80
|
mimeWhitelist: [
|
75
81
|
'audio/mp3',
|
76
82
|
'audio/mpeg',
|
77
|
-
'audio/mpeg3'
|
83
|
+
'audio/mpeg3'
|
78
84
|
]
|
79
85
|
},
|
80
86
|
task: {
|
81
87
|
cleanUpMusicInterval: '1m'
|
82
88
|
}
|
83
89
|
}, options);
|
84
|
-
|
85
90
|
super(options);
|
86
91
|
this.__addingFiles = {};
|
87
92
|
}
|
@@ -90,51 +95,51 @@ module.exports = (Parent) => {
|
|
90
95
|
* @see NodeStoracle.prototype.initBeforeSync
|
91
96
|
*/
|
92
97
|
async initBeforeSync() {
|
93
|
-
await super.initBeforeSync.apply(this, arguments);
|
98
|
+
await super.initBeforeSync.apply(this, arguments);
|
94
99
|
await this.normalizeSongTitles();
|
95
100
|
await this.cleanUpMusic();
|
96
|
-
}
|
101
|
+
}
|
97
102
|
|
98
103
|
/**
|
99
104
|
* @see NodeStoracle.prototype.prepareServices
|
100
105
|
*/
|
101
106
|
async prepareServices() {
|
102
107
|
await super.prepareServices.apply(this, arguments);
|
103
|
-
await this.addApproval('addSong', new ApprovalCaptcha({ period: this.options.request.fileStoringNodeTimeout }));
|
108
|
+
await this.addApproval('addSong', new ApprovalCaptcha({ period: this.options.request.fileStoringNodeTimeout }));
|
104
109
|
await this.addCollection('music', new MusicCollection(this.options.collections.music));
|
105
110
|
}
|
106
111
|
|
107
112
|
/**
|
108
113
|
* Prepare the task service
|
109
|
-
*
|
114
|
+
*
|
110
115
|
* @async
|
111
116
|
*/
|
112
|
-
async prepareTask() {
|
117
|
+
async prepareTask() {
|
113
118
|
await super.prepareTask.apply(this, arguments);
|
114
|
-
|
115
|
-
if(!this.task) {
|
119
|
+
|
120
|
+
if (!this.task) {
|
116
121
|
return;
|
117
122
|
}
|
118
123
|
|
119
|
-
if(this.options.task.cleanUpMusicInterval) {
|
124
|
+
if (this.options.task.cleanUpMusicInterval) {
|
120
125
|
await this.task.add('cleanUpMusic', this.options.task.cleanUpMusicInterval, () => this.cleanUpMusic());
|
121
126
|
}
|
122
127
|
}
|
123
128
|
|
124
|
-
/**
|
129
|
+
/**
|
125
130
|
* Normalize the song titles
|
126
|
-
*
|
131
|
+
*
|
127
132
|
* @async
|
128
133
|
*/
|
129
134
|
async normalizeSongTitles() {
|
130
135
|
const docs = await this.db.getDocuments('music');
|
131
136
|
const titles = {};
|
132
|
-
|
133
|
-
for(let i = 0; i < docs.length; i++) {
|
137
|
+
|
138
|
+
for (let i = 0; i < docs.length; i++) {
|
134
139
|
const doc = docs[i];
|
135
140
|
const title = utils.beautifySongTitle(doc.title);
|
136
|
-
|
137
|
-
if(titles[title] && titles[title] != doc.$loki) {
|
141
|
+
|
142
|
+
if (titles[title] && titles[title] != doc.$loki) {
|
138
143
|
await this.db.deleteDocument(doc);
|
139
144
|
continue;
|
140
145
|
}
|
@@ -145,43 +150,38 @@ module.exports = (Parent) => {
|
|
145
150
|
}
|
146
151
|
}
|
147
152
|
|
148
|
-
/**
|
153
|
+
/**
|
149
154
|
* Clean up the music
|
150
|
-
*
|
155
|
+
*
|
151
156
|
* @async
|
152
157
|
*/
|
153
158
|
async cleanUpMusic() {
|
154
159
|
const docs = await this.db.getDocuments('music');
|
155
160
|
const hashes = {};
|
156
161
|
|
157
|
-
for(let i = 0; i < docs.length; i++) {
|
162
|
+
for (let i = 0; i < docs.length; i++) {
|
158
163
|
const doc = docs[i];
|
159
164
|
|
160
|
-
if(
|
161
|
-
!doc.fileHash ||
|
165
|
+
if (!doc.fileHash ||
|
162
166
|
typeof doc.fileHash != 'string' ||
|
163
|
-
(
|
164
|
-
!this.isFileAdding(doc.fileHash) &&
|
167
|
+
(!this.isFileAdding(doc.fileHash) &&
|
165
168
|
!await this.hasFile(doc.fileHash) &&
|
166
|
-
await this.db.getMusicByFileHash(doc.fileHash)
|
167
|
-
)
|
168
|
-
) {
|
169
|
+
await this.db.getMusicByFileHash(doc.fileHash))) {
|
169
170
|
await this.db.deleteDocument(doc);
|
170
171
|
continue;
|
171
172
|
}
|
172
173
|
|
173
174
|
hashes[doc.fileHash] = true;
|
174
175
|
}
|
175
|
-
|
176
|
-
await this.iterateFiles(async filePath => {
|
176
|
+
await this.iterateFiles(async (filePath) => {
|
177
177
|
try {
|
178
178
|
const hash = path.basename(filePath);
|
179
|
-
|
180
|
-
if(!hashes[hash] && !this.isFileAdding(hash) && !await this.db.getMusicByFileHash(hash)) {
|
179
|
+
|
180
|
+
if (!hashes[hash] && !this.isFileAdding(hash) && !await this.db.getMusicByFileHash(hash)) {
|
181
181
|
await this.removeFileFromStorage(hash);
|
182
182
|
}
|
183
183
|
}
|
184
|
-
catch(err) {
|
184
|
+
catch (err) {
|
185
185
|
this.logger.warn(err.stack);
|
186
186
|
}
|
187
187
|
});
|
@@ -190,17 +190,17 @@ module.exports = (Parent) => {
|
|
190
190
|
/**
|
191
191
|
* @see NodeStoracle.prototype.calculateStorageInfo
|
192
192
|
*/
|
193
|
-
async calculateStorageInfo() {
|
194
|
-
let limit = this.options.collections.music.limit;
|
195
|
-
await super.calculateStorageInfo.apply(this, arguments);
|
196
|
-
|
197
|
-
if(limit != 'auto') {
|
193
|
+
async calculateStorageInfo() {
|
194
|
+
let limit = this.options.collections.music.limit;
|
195
|
+
await super.calculateStorageInfo.apply(this, arguments);
|
196
|
+
|
197
|
+
if (limit != 'auto') {
|
198
198
|
return;
|
199
199
|
}
|
200
|
-
|
200
|
+
|
201
201
|
const filesTotalSize = await this.db.getData('filesTotalSize');
|
202
202
|
const filesCount = await this.db.getData('filesCount');
|
203
|
-
const avgSize = filesTotalSize && filesCount? filesTotalSize / filesCount: this.fileMaxSize;
|
203
|
+
const avgSize = filesTotalSize && filesCount ? filesTotalSize / filesCount : this.fileMaxSize;
|
204
204
|
limit = Math.floor(this.storageDataSize / avgSize) - 1;
|
205
205
|
limit < 1 && (limit = 1);
|
206
206
|
const collection = await this.getCollection('music');
|
@@ -210,59 +210,59 @@ module.exports = (Parent) => {
|
|
210
210
|
/**
|
211
211
|
* @see NodeStoracle.prototype.getStatusInfo
|
212
212
|
*/
|
213
|
-
async getStatusInfo(pretty = false) {
|
213
|
+
async getStatusInfo(pretty = false) {
|
214
214
|
const collection = await this.getCollection('music');
|
215
|
-
return
|
215
|
+
return merge(await super.getStatusInfo(pretty), { collectionLimit: collection.limit });
|
216
216
|
}
|
217
217
|
|
218
218
|
/**
|
219
219
|
* @see NodeStoracle.prototype.getStorageCleaningUpTree
|
220
220
|
*/
|
221
|
-
async getStorageCleaningUpTree() {
|
221
|
+
async getStorageCleaningUpTree() {
|
222
222
|
const docs = await this.db.getDocuments('music');
|
223
223
|
const hashes = {};
|
224
|
-
|
225
|
-
for(let i = 0; i < docs.length; i++) {
|
224
|
+
|
225
|
+
for (let i = 0; i < docs.length; i++) {
|
226
226
|
const doc = docs[i];
|
227
|
-
|
228
|
-
if(!doc.fileHash || typeof doc.fileHash != 'string') {
|
227
|
+
|
228
|
+
if (!doc.fileHash || typeof doc.fileHash != 'string') {
|
229
229
|
continue;
|
230
230
|
}
|
231
231
|
|
232
232
|
hashes[doc.fileHash] = doc;
|
233
233
|
}
|
234
|
-
|
234
|
+
|
235
235
|
const tree = new SplayTree((a, b) => {
|
236
|
-
if(a.priority == b.priority) {
|
236
|
+
if (a.priority == b.priority) {
|
237
237
|
return a.accessedAt - b.accessedAt;
|
238
238
|
}
|
239
|
-
|
239
|
+
|
240
240
|
return a.priority - b.priority;
|
241
241
|
});
|
242
242
|
await this.iterateFiles(async (filePath, stat) => {
|
243
243
|
const hash = path.basename(filePath);
|
244
244
|
const doc = hashes[hash] || await this.db.getMusicByFileHash(hash);
|
245
245
|
|
246
|
-
if(!this.isFileAdding(hash)) {
|
247
|
-
const accessedAt = doc? doc.$accessedAt: 0;
|
248
|
-
const priority = doc? doc.priority: -1;
|
246
|
+
if (!this.isFileAdding(hash)) {
|
247
|
+
const accessedAt = doc ? doc.$accessedAt : 0;
|
248
|
+
const priority = doc ? doc.priority : -1;
|
249
249
|
tree.insert({ accessedAt, priority }, { size: stat.size, path: filePath });
|
250
|
-
}
|
250
|
+
}
|
251
251
|
});
|
252
252
|
return tree;
|
253
253
|
}
|
254
|
-
|
254
|
+
|
255
255
|
/**
|
256
256
|
* Add the song
|
257
|
-
*
|
257
|
+
*
|
258
258
|
* @async
|
259
|
-
* @param {string|Buffer|
|
259
|
+
* @param {string|Buffer|fse.ReadStream} file
|
260
260
|
* @param {object} [options]
|
261
261
|
* @returns {string}
|
262
262
|
*/
|
263
263
|
async addSong(file, options = {}) {
|
264
264
|
const destroyFileStream = () => utils.isFileReadStream(file) && file.destroy();
|
265
|
-
|
265
|
+
|
266
266
|
try {
|
267
267
|
options = Object.assign({
|
268
268
|
priority: 0,
|
@@ -271,52 +271,48 @@ module.exports = (Parent) => {
|
|
271
271
|
this.songPriorityTest(options);
|
272
272
|
file = await this.prepareSongFileBeforeAddition(file);
|
273
273
|
const timer = this.createRequestTimer(options.timeout);
|
274
|
-
const collection = await this.getCollection('music');
|
274
|
+
const collection = await this.getCollection('music');
|
275
275
|
const tags = await utils.getSongTags(file);
|
276
|
-
this.songTitleTest(tags.fullTitle);
|
276
|
+
this.songTitleTest(tags.fullTitle);
|
277
277
|
const fileInfo = await utils.getFileInfo(file);
|
278
278
|
const info = { collection: 'music', pkValue: tags.fullTitle, fileInfo };
|
279
279
|
const masterRequestTimeout = await this.getRequestMasterTimeout();
|
280
|
-
|
281
|
-
if(typeof file == 'string') {
|
282
|
-
file =
|
280
|
+
|
281
|
+
if (typeof file == 'string') {
|
282
|
+
file = fse.createReadStream(file);
|
283
283
|
}
|
284
284
|
|
285
285
|
const results = await this.requestNetwork('get-document-addition-info', {
|
286
286
|
body: { info },
|
287
|
-
timeout: timer(
|
288
|
-
|
289
|
-
{ min: masterRequestTimeout, grabFree: true }
|
290
|
-
),
|
291
|
-
responseSchema: schema.getDocumentAdditionInfoMasterResponse({
|
287
|
+
timeout: timer([masterRequestTimeout, this.options.request.fileStoringNodeTimeout], { min: masterRequestTimeout, grabFree: true }),
|
288
|
+
responseSchema: schema.getDocumentAdditionInfoMasterResponse({
|
292
289
|
networkOptimum: await this.getNetworkOptimum(),
|
293
290
|
schema: collection.schema
|
294
291
|
})
|
295
292
|
});
|
296
|
-
|
297
293
|
const limit = await this.getDocumentDuplicatesCount(info);
|
298
294
|
const filterOptions = Object.assign(await this.getDocumentAdditionInfoFilterOptions(info), { limit });
|
299
295
|
const candidates = await this.filterCandidatesMatrix(results.map(r => r.candidates), filterOptions);
|
300
|
-
|
301
|
-
if(!candidates.length) {
|
296
|
+
|
297
|
+
if (!candidates.length) {
|
302
298
|
throw new errors.WorkError('Not found a suitable server to store the song', 'ERR_MUSERIA_NOT_FOUND_STORAGE');
|
303
299
|
}
|
304
300
|
|
305
301
|
const suspicious = candidates.filter(c => !c.existenceInfo)[0];
|
306
302
|
suspicious && await this.db.addBehaviorCandidate('addSong', suspicious.address);
|
307
|
-
const servers = candidates.map(c => c.address);
|
303
|
+
const servers = candidates.map(c => c.address);
|
308
304
|
const dupOptions = Object.assign({}, options, { timeout: timer() });
|
309
|
-
const dupInfo = Object.assign({ title: tags.fullTitle }, fileInfo);
|
305
|
+
const dupInfo = Object.assign({ title: tags.fullTitle }, fileInfo);
|
310
306
|
const result = await this.duplicateSong(servers, file, dupInfo, dupOptions);
|
311
307
|
|
312
|
-
if(!result) {
|
308
|
+
if (!result) {
|
313
309
|
throw new errors.WorkError('Not found an available server to store the file', 'ERR_MUSERIA_NOT_FOUND_STORAGE');
|
314
310
|
}
|
315
311
|
|
316
312
|
destroyFileStream();
|
317
|
-
return
|
313
|
+
return omit(result, ['address']);
|
318
314
|
}
|
319
|
-
catch(err) {
|
315
|
+
catch (err) {
|
320
316
|
destroyFileStream();
|
321
317
|
throw err;
|
322
318
|
}
|
@@ -324,36 +320,36 @@ module.exports = (Parent) => {
|
|
324
320
|
|
325
321
|
/**
|
326
322
|
* Prepare the song file before the addition
|
327
|
-
*
|
323
|
+
*
|
328
324
|
* @async
|
329
|
-
* @param {string|Buffer|
|
330
|
-
* @returns {string|Buffer|
|
325
|
+
* @param {string|Buffer|fse.ReadStream} file
|
326
|
+
* @returns {string|Buffer|fse.ReadStream}
|
331
327
|
*/
|
332
328
|
async prepareSongFileBeforeAddition(file) {
|
333
329
|
const tags = await utils.getSongTags(file);
|
334
330
|
let changed = false;
|
335
|
-
|
336
|
-
if(this.options.music.prepareTitle) {
|
331
|
+
|
332
|
+
if (this.options.music.prepareTitle) {
|
337
333
|
this.songTitleTest(tags.fullTitle);
|
338
|
-
tags.fullTitle = await this.prepareSongTitle(tags.fullTitle);
|
334
|
+
tags.fullTitle = await this.prepareSongTitle(tags.fullTitle);
|
339
335
|
changed = true;
|
340
336
|
}
|
341
337
|
|
342
|
-
if(tags.APIC && this.options.music.prepareCover) {
|
338
|
+
if (tags.APIC && this.options.music.prepareCover) {
|
343
339
|
tags.APIC = await this.prepareSongCover(tags.APIC);
|
344
340
|
changed = true;
|
345
341
|
}
|
346
342
|
|
347
|
-
if(!changed) {
|
343
|
+
if (!changed) {
|
348
344
|
return file;
|
349
345
|
}
|
350
|
-
|
346
|
+
|
351
347
|
return await utils.setSongTags(file, tags);
|
352
348
|
}
|
353
349
|
|
354
350
|
/**
|
355
351
|
* Prepare the song cover
|
356
|
-
*
|
352
|
+
*
|
357
353
|
* @async
|
358
354
|
* @param {Buffer} buffer
|
359
355
|
* @returns {Buffer}
|
@@ -367,51 +363,51 @@ module.exports = (Parent) => {
|
|
367
363
|
let width = metadata.width;
|
368
364
|
let height = metadata.height;
|
369
365
|
|
370
|
-
if(minSize && (width < minSize || height < minSize
|
366
|
+
if (minSize && (width < minSize || height < minSize)) {
|
371
367
|
throw new errors.WorkError(`Minimum size of a cover width or height is ${minSize}px`, 'ERR_MUSERIA_COVER_MIN_SIZE');
|
372
|
-
}
|
368
|
+
}
|
373
369
|
|
374
|
-
let dev;
|
370
|
+
let dev;
|
375
371
|
let maxDev;
|
376
|
-
|
377
|
-
if(width > maxSize) {
|
372
|
+
|
373
|
+
if (width > maxSize) {
|
378
374
|
maxDev = height / maxSize;
|
379
375
|
dev = width / maxSize;
|
380
376
|
}
|
381
377
|
else {
|
382
378
|
maxDev = width / maxSize;
|
383
|
-
dev = height / maxSize;
|
379
|
+
dev = height / maxSize;
|
384
380
|
}
|
385
381
|
|
386
382
|
dev > maxDev && (dev = maxDev);
|
387
383
|
width = Math.floor(width / dev);
|
388
|
-
height =
|
389
|
-
const size = width > height? height: width;
|
384
|
+
height = Math.floor(height / dev);
|
385
|
+
const size = width > height ? height : width;
|
390
386
|
let buff = await image
|
391
387
|
.jpeg({ quality: this.options.music.coverQuality })
|
392
388
|
.resize(width, height)
|
393
|
-
.extract({
|
389
|
+
.extract({
|
394
390
|
left: Math.floor((width - size) / 2),
|
395
391
|
top: Math.floor((height - size) / 2),
|
396
392
|
width: size,
|
397
393
|
height: size
|
398
394
|
})
|
399
395
|
.toBuffer();
|
400
|
-
|
401
|
-
if(buff.byteLength > metadata.size) {
|
396
|
+
|
397
|
+
if (buff.byteLength > metadata.size) {
|
402
398
|
buff = buffer;
|
403
|
-
}
|
404
|
-
|
405
|
-
if(buff.byteLength > maxFileSize) {
|
399
|
+
}
|
400
|
+
|
401
|
+
if (buff.byteLength > maxFileSize) {
|
406
402
|
throw new errors.WorkError(`Maximum size of a cover file is ${maxFileSize} byte(s)`, 'ERR_MUSERIA_COVER_MAX_FILE_SIZE');
|
407
|
-
}
|
403
|
+
}
|
408
404
|
|
409
405
|
return buff;
|
410
406
|
}
|
411
407
|
|
412
408
|
/**
|
413
409
|
* Prepare the song title
|
414
|
-
*
|
410
|
+
*
|
415
411
|
* @async
|
416
412
|
* @param {string} title
|
417
413
|
* @returns {string}
|
@@ -419,23 +415,23 @@ module.exports = (Parent) => {
|
|
419
415
|
async prepareSongTitle(title) {
|
420
416
|
return utils.beautifySongTitle(title);
|
421
417
|
}
|
422
|
-
|
418
|
+
|
423
419
|
/**
|
424
|
-
* Get the song info
|
425
|
-
*
|
420
|
+
* Get the song info
|
421
|
+
*
|
426
422
|
* @async
|
427
423
|
* @param {string} title
|
428
424
|
* @param {object} [options]
|
429
425
|
* @returns {object[]}
|
430
426
|
*/
|
431
|
-
|
427
|
+
async getSongInfo(title, options = {}) {
|
432
428
|
title = utils.prepareComparisonSongTitle(title);
|
433
429
|
const collection = await this.getCollection('music');
|
434
|
-
const actions = utils.prepareDocumentGettingActions({
|
430
|
+
const actions = utils.prepareDocumentGettingActions({
|
435
431
|
offset: 0,
|
436
432
|
limit: 0,
|
437
433
|
removeDuplicates: false,
|
438
|
-
filter: {
|
434
|
+
filter: {
|
439
435
|
compTitle: {
|
440
436
|
$mus: {
|
441
437
|
value: title,
|
@@ -445,13 +441,13 @@ module.exports = (Parent) => {
|
|
445
441
|
}
|
446
442
|
},
|
447
443
|
sort: this.getFindingSongsSort()
|
448
|
-
});
|
444
|
+
});
|
449
445
|
await collection.actionsGettingTest(actions);
|
450
446
|
const results = await this.requestNetwork('get-documents', {
|
451
447
|
body: { actions, collection: 'music' },
|
452
448
|
timeout: options.timeout,
|
453
449
|
responseSchema: schema.getDocumentsMasterResponse({ schema: collection.schema })
|
454
|
-
});
|
450
|
+
});
|
455
451
|
results.forEach((result) => {
|
456
452
|
result.documents.forEach(doc => {
|
457
453
|
doc.main = 1;
|
@@ -459,51 +455,51 @@ module.exports = (Parent) => {
|
|
459
455
|
doc.intScore = parseInt(doc.score * 100);
|
460
456
|
doc.random = Math.random();
|
461
457
|
});
|
462
|
-
})
|
458
|
+
});
|
463
459
|
const result = await this.handleDocumentsGettingForClient(collection, results, actions);
|
464
|
-
const documents = result.documents.map(doc =>
|
460
|
+
const documents = result.documents.map(doc => omit(doc, ['main', 'address', 'random', 'intScore', 'compTitle']));
|
465
461
|
return documents;
|
466
462
|
}
|
467
463
|
|
468
464
|
/**
|
469
465
|
* Find songs
|
470
|
-
*
|
466
|
+
*
|
471
467
|
* @async
|
472
468
|
* @param {string} str
|
473
469
|
* @param {object} [options]
|
474
470
|
* @returns {object[]}
|
475
471
|
*/
|
476
|
-
|
472
|
+
async findSongs(str, options = {}) {
|
477
473
|
const title = utils.prepareComparisonSongTitle(str);
|
478
474
|
str = utils.prepareSongFindingString(str);
|
479
|
-
|
480
|
-
if(str.length < this.options.music.findingStringMinLength) {
|
481
|
-
const msg = `You have to pass at least "${
|
475
|
+
|
476
|
+
if (str.length < this.options.music.findingStringMinLength) {
|
477
|
+
const msg = `You have to pass at least "${this.options.music.findingStringMinLength}" symbol(s)`;
|
482
478
|
throw new errors.WorkError(msg, 'ERR_MUSERIA_FINDING_SONGS_STRING_LENGTH');
|
483
479
|
}
|
484
|
-
|
485
|
-
if(!str) {
|
480
|
+
|
481
|
+
if (!str) {
|
486
482
|
return [];
|
487
483
|
}
|
488
|
-
|
484
|
+
|
489
485
|
const collection = await this.getCollection('music');
|
490
|
-
let limit = options.limit === undefined? this.options.music.findingLimit: options.limit;
|
486
|
+
let limit = options.limit === undefined ? this.options.music.findingLimit : options.limit;
|
491
487
|
limit > this.options.music.findingLimit && (limit = this.options.music.findingLimit);
|
492
|
-
limit < 0 && (limit = 0);
|
493
|
-
const actions = utils.prepareDocumentGettingActions({
|
488
|
+
limit < 0 && (limit = 0);
|
489
|
+
const actions = utils.prepareDocumentGettingActions({
|
494
490
|
offset: 0,
|
495
491
|
limit,
|
496
492
|
removeDuplicates: true,
|
497
|
-
filter: {
|
493
|
+
filter: {
|
498
494
|
compTitle: {
|
499
495
|
$or: [
|
500
496
|
{ $milk: str },
|
501
|
-
{
|
497
|
+
{
|
502
498
|
$mus: {
|
503
499
|
value: title,
|
504
500
|
similarity: this.options.music.similarity,
|
505
501
|
beautify: false
|
506
|
-
}
|
502
|
+
}
|
507
503
|
}
|
508
504
|
]
|
509
505
|
}
|
@@ -514,83 +510,82 @@ module.exports = (Parent) => {
|
|
514
510
|
body: { actions, collection: 'music' },
|
515
511
|
timeout: options.timeout,
|
516
512
|
responseSchema: schema.getDocumentsMasterResponse({ schema: collection.schema })
|
517
|
-
});
|
518
|
-
|
513
|
+
});
|
519
514
|
const titles = {};
|
520
|
-
let documents = results.reduce((p, c) => p.concat(c.documents), []);
|
515
|
+
let documents = results.reduce((p, c) => p.concat(c.documents), []);
|
521
516
|
documents = this.uniqDocuments(collection, documents);
|
522
517
|
documents.forEach((doc) => {
|
523
518
|
doc.main = 0;
|
524
519
|
doc.score = utils.getStringSimilarity(str, doc.compTitle, { ignoreOrder: true });
|
525
520
|
doc.intScore = parseInt(doc.score * 100);
|
526
521
|
doc.random = Math.random();
|
527
|
-
titles[doc.title]? titles[doc.title].push(doc): titles[doc.title] = [doc];
|
522
|
+
titles[doc.title] ? titles[doc.title].push(doc) : titles[doc.title] = [doc];
|
528
523
|
});
|
529
|
-
|
530
|
-
for(let key in titles) {
|
524
|
+
|
525
|
+
for (let key in titles) {
|
531
526
|
const docs = titles[key];
|
532
|
-
docs[
|
527
|
+
docs[random(0, docs.length - 1)].main = 1;
|
533
528
|
}
|
534
529
|
|
535
530
|
actions.removeDuplicates = false;
|
536
531
|
const result = await this.handleDocumentsGettingForClient(collection, [{ documents }], actions);
|
537
|
-
documents = result.documents.map(doc =>
|
532
|
+
documents = result.documents.map(doc => omit(doc, ['main', 'address', 'random', 'intScore', 'compTitle']));
|
538
533
|
return documents;
|
539
534
|
}
|
540
535
|
|
541
536
|
/**
|
542
537
|
* Find the artist songs
|
543
|
-
*
|
538
|
+
*
|
544
539
|
* @async
|
545
540
|
* @param {string} artist
|
546
541
|
* @param {object} [options]
|
547
542
|
* @returns {object[]}
|
548
543
|
*/
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
for(let key in titles) {
|
581
|
-
const docs = titles[key];
|
582
|
-
docs[_.random(0, docs.length - 1)].main = 1;
|
583
|
-
}
|
544
|
+
async findArtistSongs(artist, options = {}) {
|
545
|
+
if (!artist || typeof artist != 'string') {
|
546
|
+
return [];
|
547
|
+
}
|
548
|
+
|
549
|
+
artist = utils.prepareSongFindingString(artist);
|
550
|
+
const collection = await this.getCollection('music');
|
551
|
+
const actions = utils.prepareDocumentGettingActions({
|
552
|
+
offset: 0,
|
553
|
+
limit: 0,
|
554
|
+
removeDuplicates: true,
|
555
|
+
filter: {
|
556
|
+
compTitle: {
|
557
|
+
$art: artist
|
558
|
+
}
|
559
|
+
},
|
560
|
+
sort: this.getFindingSongsSort()
|
561
|
+
});
|
562
|
+
const results = await this.requestNetwork('get-documents', {
|
563
|
+
body: { actions, collection: 'music' },
|
564
|
+
timeout: options.timeout,
|
565
|
+
responseSchema: schema.getDocumentsMasterResponse({ schema: collection.schema })
|
566
|
+
});
|
567
|
+
const titles = {};
|
568
|
+
let documents = results.reduce((p, c) => p.concat(c.documents), []);
|
569
|
+
documents = this.uniqDocuments(collection, documents);
|
570
|
+
documents.forEach((doc) => {
|
571
|
+
doc.main = 0;
|
572
|
+
titles[doc.title] ? titles[doc.title].push(doc) : titles[doc.title] = [doc];
|
573
|
+
});
|
584
574
|
|
585
|
-
|
586
|
-
const
|
587
|
-
|
588
|
-
return documents;
|
575
|
+
for (let key in titles) {
|
576
|
+
const docs = titles[key];
|
577
|
+
docs[random(0, docs.length - 1)].main = 1;
|
589
578
|
}
|
590
579
|
|
580
|
+
actions.removeDuplicates = false;
|
581
|
+
const result = await this.handleDocumentsGettingForClient(collection, [{ documents }], actions);
|
582
|
+
documents = result.documents.map(doc => omit(doc, ['main', 'address']));
|
583
|
+
return documents;
|
584
|
+
}
|
585
|
+
|
591
586
|
/**
|
592
587
|
* Get the song link
|
593
|
-
*
|
588
|
+
*
|
594
589
|
* @async
|
595
590
|
* @param {string} title
|
596
591
|
* @param {string} type
|
@@ -598,53 +593,52 @@ module.exports = (Parent) => {
|
|
598
593
|
* @returns {string}
|
599
594
|
*/
|
600
595
|
async getSongLink(title, type, options = {}) {
|
601
|
-
if(type != 'audio' && type != 'cover') {
|
596
|
+
if (type != 'audio' && type != 'cover') {
|
602
597
|
throw new errors.WorkError(`Link type must be "audio" or "cover", not "${type}"`, 'ERR_MUSERIA_SONG_LINK_TYPE');
|
603
598
|
}
|
604
599
|
|
605
600
|
this.songTitleTest(title);
|
606
|
-
options =
|
601
|
+
options = merge({
|
607
602
|
cache: true
|
608
603
|
}, options);
|
609
|
-
title = utils.beautifySongTitle(title);
|
610
|
-
|
611
|
-
LOOKING_FOR_CACHE: if(this.cacheFile && options.cache) {
|
604
|
+
title = utils.beautifySongTitle(title);
|
605
|
+
|
606
|
+
LOOKING_FOR_CACHE: if (this.cacheFile && options.cache) {
|
612
607
|
const cache = await this.cacheFile.get(title);
|
613
|
-
|
614
|
-
if(!cache) {
|
608
|
+
if (!cache) {
|
615
609
|
break LOOKING_FOR_CACHE;
|
616
610
|
}
|
617
611
|
|
618
612
|
const link = cache.value[`${type}Link`];
|
619
|
-
|
620
|
-
if(await this.checkCacheLink(link)) {
|
613
|
+
|
614
|
+
if (await this.checkCacheLink(link)) {
|
621
615
|
return link;
|
622
616
|
}
|
623
617
|
|
624
|
-
const obj =
|
618
|
+
const obj = merge({}, cache.value, { [`${type}Link`]: '' });
|
625
619
|
|
626
|
-
if(!obj.audioLink && !obj.coverLink) {
|
620
|
+
if (!obj.audioLink && !obj.coverLink) {
|
627
621
|
await this.cacheFile.remove(title);
|
628
622
|
break LOOKING_FOR_CACHE;
|
629
623
|
}
|
630
624
|
|
631
625
|
await this.cacheFile.set(title, obj);
|
632
|
-
}
|
626
|
+
}
|
633
627
|
|
634
|
-
const info = (await this.getSongInfo(title, options)).filter(c => c[`${type}Link`]);
|
628
|
+
const info = (await this.getSongInfo(title, options)).filter(c => c[`${type}Link`]);
|
635
629
|
const selected = info[0];
|
636
|
-
|
637
|
-
if(options.cache && selected) {
|
630
|
+
|
631
|
+
if (options.cache && selected) {
|
638
632
|
await this.updateSongCache(title, selected);
|
639
633
|
selected.title != title && await this.updateSongCache(selected.title, selected);
|
640
634
|
}
|
641
|
-
|
642
|
-
return selected? selected[`${type}Link`]: '';
|
635
|
+
|
636
|
+
return selected ? selected[`${type}Link`] : '';
|
643
637
|
}
|
644
638
|
|
645
639
|
/**
|
646
640
|
* Get the song audio link
|
647
|
-
*
|
641
|
+
*
|
648
642
|
* @see NodeMuseria.prototype.getSongAudioLink
|
649
643
|
*/
|
650
644
|
async getSongAudioLink(title, options = {}) {
|
@@ -653,7 +647,7 @@ module.exports = (Parent) => {
|
|
653
647
|
|
654
648
|
/**
|
655
649
|
* Get the song cover link
|
656
|
-
*
|
650
|
+
*
|
657
651
|
* @see NodeMuseria.prototype.getSongCoverLink
|
658
652
|
*/
|
659
653
|
async getSongCoverLink(title, options = {}) {
|
@@ -662,22 +656,22 @@ module.exports = (Parent) => {
|
|
662
656
|
|
663
657
|
/**
|
664
658
|
* Get the song info filter options
|
665
|
-
*
|
659
|
+
*
|
666
660
|
* @async
|
667
661
|
* @returns {object}
|
668
662
|
*/
|
669
663
|
async getSongInfoFilterOptions() {
|
670
664
|
return {
|
671
665
|
fnFilter: c => (
|
672
|
-
utils.isValidSongAudioLink(c.audioLink) &&
|
666
|
+
utils.isValidSongAudioLink(c.audioLink) &&
|
673
667
|
(!c.coverLink || utils.isValidSongCoverLink(c.coverLink))
|
674
668
|
)
|
675
|
-
}
|
669
|
+
};
|
676
670
|
}
|
677
671
|
|
678
672
|
/**
|
679
673
|
* Remove the song
|
680
|
-
*
|
674
|
+
*
|
681
675
|
* @async
|
682
676
|
* @param {string} title
|
683
677
|
* @param {object} [options]
|
@@ -695,28 +689,28 @@ module.exports = (Parent) => {
|
|
695
689
|
|
696
690
|
/**
|
697
691
|
* Update the song cache
|
698
|
-
*
|
692
|
+
*
|
699
693
|
* @async
|
700
694
|
* @param {string} title
|
701
695
|
* @param {object} value
|
702
696
|
* @param {string} value.audioLink
|
703
697
|
* @param {string} [value.coverLink]
|
704
698
|
*/
|
705
|
-
async updateSongCache(title, value) {
|
706
|
-
if(!this.cacheFile) {
|
699
|
+
async updateSongCache(title, value) {
|
700
|
+
if (!this.cacheFile) {
|
707
701
|
return;
|
708
702
|
}
|
709
|
-
|
710
|
-
const cache = await this.cacheFile.get(title);
|
711
|
-
let obj = { audioLink: value.audioLink, coverLink: value.coverLink };
|
703
|
+
|
704
|
+
const cache = await this.cacheFile.get(title);
|
705
|
+
let obj = { audioLink: value.audioLink, coverLink: value.coverLink };
|
712
706
|
!utils.isValidSongAudioLink(obj.audioLink) && delete obj.audioLink;
|
713
707
|
!utils.isValidSongCoverLink(obj.coverLink) && delete obj.coverLink;
|
714
|
-
obj =
|
708
|
+
obj = merge(cache ? cache.value : {}, obj);
|
715
709
|
|
716
|
-
if(!Object.keys(obj).length) {
|
710
|
+
if (!Object.keys(obj).length) {
|
717
711
|
return;
|
718
712
|
}
|
719
|
-
|
713
|
+
|
720
714
|
await this.cacheFile.set(title, obj);
|
721
715
|
}
|
722
716
|
|
@@ -725,30 +719,29 @@ module.exports = (Parent) => {
|
|
725
719
|
*/
|
726
720
|
async duplicateSong(servers, file, info, options = {}) {
|
727
721
|
const query = qs.stringify({ title: info.title });
|
728
|
-
options =
|
722
|
+
options = assign({}, {
|
729
723
|
cache: true,
|
730
|
-
action: `add-song?${
|
731
|
-
formData: {
|
732
|
-
exported: options.exported? '1': '',
|
733
|
-
controlled: options.controlled? '1': '',
|
734
|
-
priority: String(options.priority || 0),
|
735
|
-
approvalInfo: options.approvalInfo? JSON.stringify(options.approvalInfo): ''
|
724
|
+
action: `add-song?${query}`,
|
725
|
+
formData: {
|
726
|
+
exported: options.exported ? '1' : '',
|
727
|
+
controlled: options.controlled ? '1' : '',
|
728
|
+
priority: String(options.priority || 0),
|
729
|
+
approvalInfo: options.approvalInfo ? JSON.stringify(options.approvalInfo) : ''
|
736
730
|
},
|
737
731
|
responseSchema: schema.getSongAdditionResponse()
|
738
732
|
}, options);
|
739
|
-
|
740
|
-
const result = await super.duplicateFile(servers, file, info, _.omit(options, ['priority']));
|
733
|
+
const result = await super.duplicateFile(servers, file, info, omit(options, ['priority']));
|
741
734
|
result && options.cache && await this.updateSongCache(result.title, result);
|
742
735
|
return result;
|
743
736
|
}
|
744
737
|
|
745
738
|
/**
|
746
739
|
* Export all songs to another server
|
747
|
-
*
|
740
|
+
*
|
748
741
|
* @see NodeStoracle.prototype.exportFiles
|
749
742
|
*/
|
750
|
-
async exportSongs(address, options = {}) {
|
751
|
-
options =
|
743
|
+
async exportSongs(address, options = {}) {
|
744
|
+
options = merge({
|
752
745
|
strict: false
|
753
746
|
}, options);
|
754
747
|
let success = 0;
|
@@ -760,56 +753,55 @@ module.exports = (Parent) => {
|
|
760
753
|
});
|
761
754
|
const docs = await this.db.getDocuments('music');
|
762
755
|
const hashes = {};
|
763
|
-
|
764
|
-
for(let i = 0; i < docs.length; i++) {
|
756
|
+
|
757
|
+
for (let i = 0; i < docs.length; i++) {
|
765
758
|
const doc = docs[i];
|
766
|
-
|
767
|
-
if(!doc.fileHash || typeof doc.fileHash != 'string') {
|
759
|
+
|
760
|
+
if (!doc.fileHash || typeof doc.fileHash != 'string') {
|
768
761
|
continue;
|
769
762
|
}
|
770
|
-
|
771
763
|
hashes[doc.fileHash] = doc;
|
772
764
|
}
|
773
|
-
|
765
|
+
|
774
766
|
await this.iterateFiles(async (filePath) => {
|
775
767
|
const fileInfo = await utils.getFileInfo(filePath);
|
776
768
|
const doc = hashes[fileInfo.hash];
|
777
769
|
|
778
|
-
if(!doc) {
|
770
|
+
if (!doc) {
|
779
771
|
return;
|
780
772
|
}
|
781
773
|
|
782
774
|
const info = Object.assign({ title: doc.title }, fileInfo);
|
783
775
|
let file;
|
784
|
-
|
776
|
+
|
785
777
|
try {
|
786
|
-
file =
|
787
|
-
await this.duplicateSong([address], file, info, {
|
778
|
+
file = fse.createReadStream(filePath);
|
779
|
+
await this.duplicateSong([address], file, info, {
|
788
780
|
exported: true,
|
789
781
|
priority: doc.priority,
|
790
|
-
timeout: timer()
|
782
|
+
timeout: timer()
|
791
783
|
});
|
792
784
|
success++;
|
793
785
|
file.destroy();
|
794
786
|
this.logger.info(`Song "${doc.title}" has been exported`);
|
795
787
|
}
|
796
|
-
catch(err) {
|
797
|
-
file.destroy();
|
798
|
-
|
799
|
-
if(options.strict) {
|
788
|
+
catch (err) {
|
789
|
+
file.destroy();
|
790
|
+
|
791
|
+
if (options.strict) {
|
800
792
|
throw err;
|
801
793
|
}
|
802
|
-
|
794
|
+
|
803
795
|
fail++;
|
804
796
|
this.logger.warn(err.stack);
|
805
797
|
this.logger.info(`Song "${doc.title}" has been failed`);
|
806
798
|
}
|
807
799
|
});
|
808
800
|
|
809
|
-
if(!success && !fail) {
|
801
|
+
if (!success && !fail) {
|
810
802
|
this.logger.info(`There haven't been songs to export`);
|
811
803
|
}
|
812
|
-
else if(!fail) {
|
804
|
+
else if (!fail) {
|
813
805
|
this.logger.info(`${success} song(s) have been exported`);
|
814
806
|
}
|
815
807
|
else {
|
@@ -821,7 +813,7 @@ module.exports = (Parent) => {
|
|
821
813
|
* @see NodeMetastocle.prototype.getDocumentAdditionInfoFilterOptions
|
822
814
|
*/
|
823
815
|
async getDocumentAdditionInfoFilterOptions() {
|
824
|
-
return
|
816
|
+
return merge(await super.getDocumentAdditionInfoFilterOptions.apply(this, arguments), {
|
825
817
|
uniq: 'address',
|
826
818
|
fnCompare: await this.createSongAdditionComparisonFunction(),
|
827
819
|
fnFilter: c => c.isAvailable
|
@@ -832,10 +824,9 @@ module.exports = (Parent) => {
|
|
832
824
|
* @see NodeMetastocle.prototype.getDocumentExistenceInfo
|
833
825
|
*/
|
834
826
|
async getDocumentExistenceInfo(info) {
|
835
|
-
if(info.collection == 'music') {
|
827
|
+
if (info.collection == 'music') {
|
836
828
|
return await this.db.getMusicByPk(info.pkValue);
|
837
|
-
}
|
838
|
-
|
829
|
+
}
|
839
830
|
return await super.getDocumentExistenceInfo.apply(this, arguments);
|
840
831
|
}
|
841
832
|
|
@@ -843,51 +834,50 @@ module.exports = (Parent) => {
|
|
843
834
|
* @see NodeMetastocle.prototype.documentAvailabilityTest
|
844
835
|
*/
|
845
836
|
async documentAvailabilityTest(info = {}) {
|
846
|
-
if(info.collection == 'music') {
|
837
|
+
if (info.collection == 'music') {
|
847
838
|
await this.fileAvailabilityTest(info.fileInfo);
|
848
839
|
const existent = await this.db.getMusicByPk(info.pkValue);
|
849
|
-
|
850
|
-
if(existent) {
|
840
|
+
|
841
|
+
if (existent) {
|
851
842
|
return;
|
852
843
|
}
|
853
844
|
}
|
854
|
-
|
845
|
+
|
855
846
|
return await super.documentAvailabilityTest.apply(this, arguments);
|
856
847
|
}
|
857
848
|
|
858
849
|
/**
|
859
850
|
* Create a document addition comparison function
|
860
|
-
*
|
851
|
+
*
|
861
852
|
* @async
|
862
853
|
* @returns {function}
|
863
854
|
*/
|
864
855
|
async createSongAdditionComparisonFunction() {
|
865
856
|
const obj = await this.prepareCandidateSuscpicionInfo('addSong');
|
866
857
|
const fn = await this.createDocumentAdditionComparisonFunction();
|
867
|
-
|
868
858
|
return (a, b) => {
|
869
|
-
if(a.existenceInfo && !b.existenceInfo) {
|
859
|
+
if (a.existenceInfo && !b.existenceInfo) {
|
870
860
|
return -1;
|
871
861
|
}
|
872
862
|
|
873
|
-
if(!a.existenceInfo && b.existenceInfo) {
|
863
|
+
if (!a.existenceInfo && b.existenceInfo) {
|
874
864
|
return 1;
|
875
865
|
}
|
876
866
|
|
877
867
|
const suspicionLevelA = obj[a.address] || 0;
|
878
868
|
const suspicionLevelB = obj[b.address] || 0;
|
879
869
|
|
880
|
-
if(suspicionLevelA != suspicionLevelB) {
|
870
|
+
if (suspicionLevelA != suspicionLevelB) {
|
881
871
|
return suspicionLevelA - suspicionLevelB;
|
882
872
|
}
|
883
873
|
|
884
874
|
return fn(a, b);
|
885
|
-
}
|
875
|
+
};
|
886
876
|
}
|
887
877
|
|
888
878
|
/**
|
889
879
|
* Create the song audio link
|
890
|
-
*
|
880
|
+
*
|
891
881
|
* @async
|
892
882
|
* @param {object} document
|
893
883
|
* @param {object} document.fileHash
|
@@ -901,7 +891,7 @@ module.exports = (Parent) => {
|
|
901
891
|
|
902
892
|
/**
|
903
893
|
* Create the song cover link
|
904
|
-
*
|
894
|
+
*
|
905
895
|
* @async
|
906
896
|
* @param {object} document
|
907
897
|
* @param {object} document.fileHash
|
@@ -913,27 +903,27 @@ module.exports = (Parent) => {
|
|
913
903
|
const hash = document.fileHash;
|
914
904
|
const filePath = this.getFilePath(hash);
|
915
905
|
tags = tags || await utils.getSongTags(filePath);
|
916
|
-
|
917
|
-
if(!tags.APIC) {
|
906
|
+
|
907
|
+
if (!tags.APIC) {
|
918
908
|
return '';
|
919
909
|
}
|
920
910
|
|
921
911
|
const buff = await this.getSongCoverHeadersBuffer(tags.APIC);
|
922
912
|
const info = await utils.getFileInfo(buff, { hash: false });
|
923
913
|
const code = utils.encodeSongTitle(document.title);
|
924
|
-
return `${this.getRequestProtocol()}://${this.address}/cover/${code}${info.ext? '.' + info.ext: ''}?f=${hash}`;
|
914
|
+
return `${this.getRequestProtocol()}://${this.address}/cover/${code}${info.ext ? '.' + info.ext : ''}?f=${hash}`;
|
925
915
|
}
|
926
916
|
|
927
917
|
/**
|
928
918
|
* @see NodeStoracle.prototype.removeFileFromStorage
|
929
|
-
*
|
919
|
+
*
|
930
920
|
* @param [options]
|
931
921
|
*/
|
932
922
|
async removeFileFromStorage(hash, options = {}) {
|
933
923
|
await super.removeFileFromStorage.apply(this, arguments);
|
934
924
|
!options.ignoreDocument && await this.db.removeMusicByFileHash(hash);
|
935
|
-
}
|
936
|
-
|
925
|
+
}
|
926
|
+
|
937
927
|
/**
|
938
928
|
* @see NodeStoracle.prototype.emptyStorage
|
939
929
|
*/
|
@@ -944,7 +934,7 @@ module.exports = (Parent) => {
|
|
944
934
|
|
945
935
|
/**
|
946
936
|
* Check the song relevance
|
947
|
-
*
|
937
|
+
*
|
948
938
|
* @async
|
949
939
|
* @param {string} filePathSource
|
950
940
|
* @param {string} filePathTarget
|
@@ -953,32 +943,32 @@ module.exports = (Parent) => {
|
|
953
943
|
async checkSongRelevance(filePathSource, filePathTarget) {
|
954
944
|
const hashSource = path.basename(filePathSource);
|
955
945
|
const hashTarget = path.basename(filePathTarget);
|
956
|
-
|
957
|
-
if(!this.hasFile(hashSource)) {
|
946
|
+
|
947
|
+
if (!this.hasFile(hashSource)) {
|
958
948
|
return false;
|
959
949
|
}
|
960
950
|
|
961
|
-
if(!await fse.pathExists(filePathTarget)) {
|
951
|
+
if (!await fse.pathExists(filePathTarget)) {
|
962
952
|
return true;
|
963
953
|
}
|
964
954
|
|
965
|
-
if(hashSource == hashTarget) {
|
955
|
+
if (hashSource == hashTarget) {
|
966
956
|
return true;
|
967
957
|
}
|
968
958
|
|
969
959
|
let time = this.options.music.relevanceTime;
|
970
|
-
|
971
|
-
if(time <= 0) {
|
960
|
+
|
961
|
+
if (time <= 0) {
|
972
962
|
return false;
|
973
963
|
}
|
974
964
|
|
975
965
|
let mdSource;
|
976
966
|
let mdTarget;
|
977
|
-
|
967
|
+
|
978
968
|
try {
|
979
969
|
mdSource = await utils.getSongMetadata(filePathSource);
|
980
970
|
}
|
981
|
-
catch(err) {
|
971
|
+
catch (err) {
|
982
972
|
this.logger.warn(err.stack);
|
983
973
|
return false;
|
984
974
|
}
|
@@ -986,13 +976,13 @@ module.exports = (Parent) => {
|
|
986
976
|
try {
|
987
977
|
mdTarget = await utils.getSongMetadata(filePathTarget);
|
988
978
|
}
|
989
|
-
catch(err) {
|
979
|
+
catch (err) {
|
990
980
|
this.logger.warn(err.stack);
|
991
981
|
return true;
|
992
982
|
}
|
993
|
-
|
983
|
+
|
994
984
|
const criterias = 2;
|
995
|
-
const step = Math.round(time / criterias);
|
985
|
+
const step = Math.round(time / criterias);
|
996
986
|
mdTarget.duration > mdSource.duration && (time -= step);
|
997
987
|
mdTarget.sampleRate > mdSource.sampleRate && (time -= step / 2);
|
998
988
|
mdTarget.bitrate > mdSource.bitrate && (time -= step / 2);
|
@@ -1011,17 +1001,17 @@ module.exports = (Parent) => {
|
|
1011
1001
|
let res;
|
1012
1002
|
let isError = false;
|
1013
1003
|
this.__addingFiles[hash] = true;
|
1014
|
-
|
1004
|
+
|
1015
1005
|
try {
|
1016
1006
|
res = await fn();
|
1017
1007
|
}
|
1018
|
-
catch(err) {
|
1008
|
+
catch (err) {
|
1019
1009
|
isError = true;
|
1020
1010
|
}
|
1021
1011
|
|
1022
1012
|
delete this.__addingFiles[hash];
|
1023
|
-
|
1024
|
-
if(isError) {
|
1013
|
+
|
1014
|
+
if (isError) {
|
1025
1015
|
throw res;
|
1026
1016
|
}
|
1027
1017
|
|
@@ -1030,16 +1020,16 @@ module.exports = (Parent) => {
|
|
1030
1020
|
|
1031
1021
|
/**
|
1032
1022
|
* Get the song audio file headers buffer
|
1033
|
-
*
|
1023
|
+
*
|
1034
1024
|
* @see NodeMuseria.prototype.getSongHeadersBuffer
|
1035
1025
|
*/
|
1036
1026
|
async getSongAudioHeadersBuffer(content) {
|
1037
1027
|
return this.getSongHeadersBuffer(content, this.options.music.audioHeadersMaxSize);
|
1038
1028
|
}
|
1039
|
-
|
1029
|
+
|
1040
1030
|
/**
|
1041
1031
|
* Get the song cover file headers buffer
|
1042
|
-
*
|
1032
|
+
*
|
1043
1033
|
* @see NodeMuseria.prototype.getSongHeadersBuffer
|
1044
1034
|
*/
|
1045
1035
|
async getSongCoverHeadersBuffer(content) {
|
@@ -1048,17 +1038,17 @@ module.exports = (Parent) => {
|
|
1048
1038
|
|
1049
1039
|
/**
|
1050
1040
|
* Get the song file headers buffer
|
1051
|
-
*
|
1041
|
+
*
|
1052
1042
|
* @async
|
1053
|
-
* @param {string|Buffer} content
|
1054
|
-
* @param {number} limit
|
1043
|
+
* @param {string|Buffer} content
|
1044
|
+
* @param {number} limit
|
1055
1045
|
* @returns {Buffer}
|
1056
1046
|
*/
|
1057
1047
|
async getSongHeadersBuffer(content, limit) {
|
1058
|
-
if(typeof content == 'string') {
|
1048
|
+
if (typeof content == 'string') {
|
1059
1049
|
return new Promise((resolve, reject) => {
|
1060
1050
|
const chunks = [];
|
1061
|
-
|
1051
|
+
fse.createReadStream(content, { start: 0, end: limit })
|
1062
1052
|
.on('error', reject)
|
1063
1053
|
.on('data', data => chunks.push(data))
|
1064
1054
|
.on('end', () => resolve(Buffer.concat(chunks)));
|
@@ -1067,20 +1057,19 @@ module.exports = (Parent) => {
|
|
1067
1057
|
|
1068
1058
|
return content.slice(0, limit);
|
1069
1059
|
}
|
1070
|
-
|
1071
1060
|
/**
|
1072
1061
|
* Get finding songs sort
|
1073
|
-
*
|
1062
|
+
*
|
1074
1063
|
* @returns {array}
|
1075
1064
|
*/
|
1076
1065
|
getFindingSongsSort() {
|
1077
|
-
return [['main', 'desc'],['intScore', 'desc'], ['priority', 'desc'], ['random', 'asc']];
|
1066
|
+
return [['main', 'desc'], ['intScore', 'desc'], ['priority', 'desc'], ['random', 'asc']];
|
1078
1067
|
}
|
1079
1068
|
|
1080
1069
|
/**
|
1081
1070
|
* Check the file is adding
|
1082
|
-
*
|
1083
|
-
* @param {string} hash
|
1071
|
+
*
|
1072
|
+
* @param {string} hash
|
1084
1073
|
* @returns {boolean}
|
1085
1074
|
*/
|
1086
1075
|
isFileAdding(hash) {
|
@@ -1089,42 +1078,42 @@ module.exports = (Parent) => {
|
|
1089
1078
|
|
1090
1079
|
/**
|
1091
1080
|
* Test the song title
|
1092
|
-
*
|
1093
|
-
* @param {string} title
|
1081
|
+
*
|
1082
|
+
* @param {string} title
|
1094
1083
|
*/
|
1095
1084
|
songTitleTest(title) {
|
1096
|
-
if(!utils.isSongTitle(title)) {
|
1085
|
+
if (!utils.isSongTitle(title)) {
|
1097
1086
|
throw new errors.WorkError(`Wrong song title "${title}"`, 'ERR_MUSERIA_SONG_WRONG_TITLE');
|
1098
1087
|
}
|
1099
1088
|
}
|
1100
1089
|
|
1101
1090
|
/**
|
1102
1091
|
* Test the song title
|
1103
|
-
*
|
1104
|
-
* @param {object} info
|
1092
|
+
*
|
1093
|
+
* @param {object} info
|
1105
1094
|
* @param {number} info.priority
|
1106
1095
|
* @param {boolean} info.controlled
|
1107
1096
|
* @param {boolean} [info.exported]
|
1108
1097
|
*/
|
1109
1098
|
songPriorityTest({ priority, controlled, exported }) {
|
1110
|
-
if(!utils.isValidSongPriority(priority)) {
|
1099
|
+
if (!utils.isValidSongPriority(priority)) {
|
1111
1100
|
const msg = 'Song priority must be an integer from -1 to 1';
|
1112
1101
|
throw new errors.WorkError(msg, 'ERR_MUSERIA_SONG_WRONG_PRIORITY');
|
1113
1102
|
}
|
1114
1103
|
|
1115
|
-
if(priority > 0 && !controlled && !exported) {
|
1104
|
+
if (priority > 0 && !controlled && !exported) {
|
1116
1105
|
const msg = 'Priority 1 is possible only if "controlled" is true';
|
1117
1106
|
throw new errors.WorkError(msg, 'ERR_MUSERIA_SONG_WRONG_PRIORITY_CONTROLLED');
|
1118
1107
|
}
|
1119
1108
|
}
|
1120
1109
|
|
1121
|
-
/**
|
1110
|
+
/**
|
1122
1111
|
* @see NodeStoracle.prototype.calculateTempFileMinSize
|
1123
1112
|
*/
|
1124
1113
|
calculateTempFileMinSize(size) {
|
1125
1114
|
return size * 2 + this.fileMaxSize;
|
1126
1115
|
}
|
1127
|
-
|
1116
|
+
|
1128
1117
|
/**
|
1129
1118
|
* Prepare the options
|
1130
1119
|
*/
|
@@ -1133,7 +1122,7 @@ module.exports = (Parent) => {
|
|
1133
1122
|
this.options.music.relevanceTime = utils.getMs(this.options.music.relevanceTime);
|
1134
1123
|
this.options.music.audioHeadersMaxSize = utils.getBytes(this.options.music.audioHeadersMaxSize);
|
1135
1124
|
this.options.music.coverHeadersMaxSize = utils.getBytes(this.options.music.coverHeadersMaxSize);
|
1136
|
-
this.options.music.coverMaxFileSize = utils.getBytes(this.options.music.coverMaxFileSize);
|
1125
|
+
this.options.music.coverMaxFileSize = utils.getBytes(this.options.music.coverMaxFileSize);
|
1137
1126
|
}
|
1138
|
-
}
|
1139
|
-
};
|
1127
|
+
};
|
1128
|
+
};
|