museria 0.2.38 → 0.2.42

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * museria face
3
- * @version 0.2.38
3
+ * @version 0.2.42
4
4
  * {@link https://github.com/ortexx/museria}
5
5
  */
6
6
  /*!
@@ -17,4 +17,4 @@
17
17
  */@font-face{font-display:block;font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(fa-solid-900.eot);src:url(fa-solid-900.eot) format("embedded-opentype"),url(fa-solid-900.woff2) format("woff2"),url(fa-solid-900.woff) format("woff"),url(fa-solid-900.ttf) format("truetype"),url(fa-solid-900.svg) format("svg")}.fa,.fas{font-family:Font Awesome\ 5 Free;font-weight:900}/*!
18
18
  * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
19
19
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
20
- */@font-face{font-display:block;font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(fa-brands-400.eot);src:url(fa-brands-400.eot) format("embedded-opentype"),url(fa-brands-400.woff2) format("woff2"),url(fa-brands-400.woff) format("woff"),url(fa-brands-400.ttf) format("truetype"),url(fa-brands-400.svg) format("svg")}.fab{font-family:Font Awesome\ 5 Brands;font-weight:400}@font-face{font-family:ProximaNova;src:url(proxima-nova.ttf)}@font-face{font-family:OpenSans;src:url(open-sans.ttf)}*{margin:0;padding:0}body{background:#353535;color:#ccc;font-family:OpenSans;height:100%;overflow-y:scroll}.custom-file input{cursor:pointer;text-indent:-1000px}.custom-control-label{cursor:pointer;opacity:.8}.custom-control-label:hover{opacity:1}.btn{font-weight:700;padding:0;text-transform:uppercase}.btn:active,.btn:focus{box-shadow:none!important;outline:none!important}.btn[disabled]{cursor:not-allowed}a{color:#ccc;text-decoration:underline}a:hover{color:#ccc;text-decoration:none}.input-group-lg input[type=text]{height:auto;padding:.42rem 1rem .45rem}.alert-success{background-color:#25301f;border-color:#28a745;color:#aed3b7}.alert-danger{background-color:#301f1f;border-color:#c00;color:#d3aeae}.alert-info{background-color:#1f2730;border-color:#0af;color:#aec7d3}.invalid-feedback{color:#f39595}.close-fa{color:#666;cursor:pointer;opacity:.7;position:absolute;right:.9em;top:50%;transform:translateY(-50%);z-index:2}.close-fa:hover{opacity:1}.app{display:flex;flex-direction:column;height:100vh}.app .wmodal-overlay{align-items:center;background:rgba(34,34,34,.8);cursor:pointer;display:flex;height:100vh;justify-content:center;left:0;position:fixed;top:0;width:100%;z-index:11000}.app .wmodal-close{color:#666;cursor:pointer;font-size:1.8em;opacity:.4;position:absolute;right:.55em;top:.5em}.app .wmodal-close:hover{opacity:1}.app .wmodal-body{background:#000;box-shadow:0 0 4px 2px hsla(0,0%,40%,.6);box-sizing:border-box;cursor:default;padding:3.5em 2.5em;position:relative}.app .upload-events .wmodal-close{font-size:2em;right:1em;top:50%;transform:translateY(-50%)}.app .upload-events .wmodal-body{padding:2em 5em 2em 2em;word-break:break-all}.app .upload-events .wmodal-body.danger{box-shadow:0 0 4px 2px rgba(204,0,0,.6)}.app .upload-events .wmodal-body.success{box-shadow:0 0 4px 2px rgba(40,167,69,.6)}.app .finding-list>*{display:block}.app .finding-list>:not(:first-child){margin-top:.5em}.app .captcha input{background:#151515;border-color:#292929;width:240px}.app .captcha input:focus{background:#151515;border-color:#666}.app .captcha-preloader{color:#666;font-size:1.7em;text-align:center}.app .header{background:#333;background:radial-gradient(#666,#464646,#333);padding:8.2em 0 6.6em;text-shadow:0 0 .8em #333}.app .header .logo{display:flex;font-family:ProximaNova;font-weight:700;text-decoration:none;text-transform:uppercase}.app .header .logo-img{display:inline-block;vertical-align:middle;width:3em}.app .header .logo-title{color:#fff;display:inline-block;font-size:3em;letter-spacing:.05em;margin-left:.1em;vertical-align:middle}.app .header .logo-description-start{color:#d7cf9b;font-size:2em;letter-spacing:.07em;margin-left:.1em;margin-top:-.3em}.app .header .logo-description-end{color:#d7cf9b;font-size:7.2em;letter-spacing:.03em;margin-left:.2em;margin-top:-.21em}.app .header .logo-description-end-lg{color:#d7cf9b;font-size:3.6em;letter-spacing:.06em;margin-top:-.2em}.app .content{flex:1}.app .footer{background:#333;background:radial-gradient(#555,#424242,#333);color:#ccc;padding:1.5em 0 3em}.app .footer h5{color:#d7cf9b;font-weight:700;margin-bottom:.6em;opacity:.7;text-shadow:0 0 1em #000;text-transform:uppercase}.app .footer a{color:#ccc;text-decoration:none}.app .footer a:hover{text-decoration:underline}.app .footer ul.nav>li{font-size:.9em;line-height:1.8em;word-break:break-all}.app .song-search .alert.alert-success a{color:#aed3b7;text-decoration:none}.app .song-search .alert.alert-success a:hover{color:#28a745}.app .song-search-input{position:relative}.app .song-search-input>i{color:#888;left:.7em;position:absolute;top:50%;transform:translateY(-50%);z-index:1}.app .song-search-input>i.song-search-loading{margin-top:-.37em}.app .song-search-input>i.song-search-close{cursor:pointer;left:unset;opacity:.6;right:.7em}.app .song-search-input>i.song-search-close:hover{opacity:1}.app .song-search-input>input{padding-left:2.5em}.app .song-search-input>input:focus{z-index:0}.app .cover-img{height:2.22em;left:2px;object-fit:cover;pointer-events:none;position:absolute;top:1px;width:2.22em;z-index:2}.app .cover-remove{right:4.5em}.app .cover-label.with-img{padding-left:3em}.app .upload-preloader-btn i{color:#ccc;font-size:1.4em;margin-left:.3em;margin-top:-.1em;vertical-align:middle}.app .song-priority{font-size:.9em}.app .image-link{align-items:center;color:#fff;display:inline-flex;flex-direction:row;opacity:.7;text-decoration:none}.app .image-link:hover{opacity:1}.app .image-link-pic{font-size:3em}.app .image-link-title{font-size:3em;margin-left:.5em;text-transform:uppercase}.app #audio-file{left:-10000px}.app .questions{display:inline-block;list-style:none;margin:0;text-align:left}.app .questions>li{opacity:.9}.app .questions>li:not(:last-child){margin-bottom:2em}.app .questions>li>:first-child{color:#fff;font-size:1.5em;font-weight:700;text-transform:uppercase}.app .questions>li>:last-child{color:#ccc;font-size:1.4em}.app .questions>li>:last-child:before{color:#86e29b;content:"↵";display:inline-block;font-weight:700;margin:0 .4em 0 0;transform:scaleX(-1)}
20
+ */@font-face{font-display:block;font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(fa-brands-400.eot);src:url(fa-brands-400.eot) format("embedded-opentype"),url(fa-brands-400.woff2) format("woff2"),url(fa-brands-400.woff) format("woff"),url(fa-brands-400.ttf) format("truetype"),url(fa-brands-400.svg) format("svg")}.fab{font-family:Font Awesome\ 5 Brands;font-weight:400}@font-face{font-family:ProximaNova;src:url(proxima-nova.ttf)}@font-face{font-family:OpenSans;src:url(open-sans.ttf)}*{margin:0;padding:0}body{background:#353535;color:#ccc;font-family:OpenSans;height:100%;overflow-y:scroll}.custom-file input{cursor:pointer;text-indent:-1000px}.custom-control-label{cursor:pointer;opacity:.8}.custom-control-label:hover{opacity:1}.btn{font-weight:700;padding:0;text-transform:uppercase}.btn:active,.btn:focus{box-shadow:none!important;outline:none!important}.btn[disabled]{cursor:not-allowed}a{color:#ccc;text-decoration:underline}a:hover{color:#ccc;text-decoration:none}.input-group-lg input[type=text]{height:auto;padding:.42rem 1rem .45rem}.alert-success{background-color:#25301f;border-color:#28a745;color:#aed3b7}.alert-danger{background-color:#301f1f;border-color:#c00;color:#d3aeae}.alert-info{background-color:#1f2730;border-color:#0af;color:#aec7d3}.invalid-feedback{color:#f39595}.close-fa{color:#666;cursor:pointer;opacity:.7;position:absolute;right:.9em;top:50%;transform:translateY(-50%);z-index:2}.close-fa:hover{opacity:1}.app{display:flex;flex-direction:column;height:100vh}.app .wmodal-overlay{align-items:center;background:rgba(34,34,34,.8);cursor:pointer;display:flex;height:100vh;justify-content:center;left:0;position:fixed;top:0;width:100%;z-index:11000}.app .wmodal-close{color:#666;cursor:pointer;font-size:1.8em;opacity:.4;position:absolute;right:.55em;top:.5em}.app .wmodal-close:hover{opacity:1}.app .wmodal-body{background:#000;box-shadow:0 0 4px 2px hsla(0,0%,40%,.6);box-sizing:border-box;cursor:default;padding:3.5em 2.5em;position:relative}.app .upload-events .wmodal-close{font-size:2em;right:1em;top:50%;transform:translateY(-50%)}.app .upload-events .wmodal-body{padding:2em 5em 2em 2em;word-break:break-all}.app .upload-events .wmodal-body.danger{box-shadow:0 0 4px 2px rgba(204,0,0,.6)}.app .upload-events .wmodal-body.success{box-shadow:0 0 4px 2px rgba(40,167,69,.6)}.app .finding-list>*{display:block}.app .finding-list>:not(:first-child){margin-top:.5em}.app .captcha input{background:#151515;border-color:#292929;width:240px}.app .captcha input:focus{background:#151515;border-color:#666}.app .captcha-preloader{color:#666;font-size:1.7em;text-align:center}.app .header{background:#333;background:radial-gradient(#666,#464646,#333);padding:8.2em 0 6.6em;position:relative;text-shadow:0 0 .8em #333}.app .header .songs-counter-text{bottom:1.5em;color:#666;font-weight:700;left:50%;position:absolute;text-align:center;text-transform:uppercase;transform:translateX(-50%)}.app .header .songs-counter-text b{color:#888}.app .header .logo{display:flex;font-family:ProximaNova;font-weight:700;text-decoration:none;text-transform:uppercase}.app .header .logo-img{display:inline-block;vertical-align:middle;width:3em}.app .header .logo-title{color:#fff;display:inline-block;font-size:3em;letter-spacing:.05em;margin-left:.1em;vertical-align:middle}.app .header .logo-description-start{color:#d7cf9b;font-size:2em;letter-spacing:.07em;margin-left:.1em;margin-top:-.3em}.app .header .logo-description-end{color:#d7cf9b;font-size:7.2em;letter-spacing:.03em;margin-left:.2em;margin-top:-.21em}.app .header .logo-description-end-lg{color:#d7cf9b;font-size:3.6em;letter-spacing:.06em;margin-top:-.2em}.app .content{flex:1}.app .footer{background:#333;background:radial-gradient(#555,#424242,#333);color:#ccc;padding:1.5em 0 3em}.app .footer h5{color:#d7cf9b;font-weight:700;margin-bottom:.6em;opacity:.7;text-shadow:0 0 1em #000;text-transform:uppercase}.app .footer a{color:#ccc;text-decoration:none}.app .footer a:hover{text-decoration:underline}.app .footer ul.nav>li{font-size:.9em;line-height:1.8em;word-break:break-all}.app .song-search .alert.alert-success a{color:#aed3b7;text-decoration:none}.app .song-search .alert.alert-success a:hover{color:#28a745}.app .song-search-input{position:relative}.app .song-search-input>i{color:#888;left:.7em;position:absolute;top:50%;transform:translateY(-50%);z-index:1}.app .song-search-input>i.song-search-loading{margin-top:-.37em}.app .song-search-input>i.song-search-close{cursor:pointer;left:unset;opacity:.6;right:.7em}.app .song-search-input>i.song-search-close:hover{opacity:1}.app .song-search-input>input{padding-left:2.5em;padding-right:2em}.app .song-search-input>input:focus{z-index:0}.app .cover-img{height:2.22em;left:2px;object-fit:cover;pointer-events:none;position:absolute;top:1px;width:2.22em;z-index:2}.app .cover-remove{right:4.5em}.app .cover-label.with-img{padding-left:3em}.app .upload-preloader-btn i{color:#ccc;font-size:1.4em;margin-left:.3em;margin-top:-.1em;vertical-align:middle}.app .song-priority{font-size:.9em}.app .image-link{align-items:center;color:#fff;display:inline-flex;flex-direction:row;opacity:.7;text-decoration:none}.app .image-link:hover{opacity:1}.app .image-link-pic{font-size:3em}.app .image-link-title{font-size:3em;margin-left:.5em;text-transform:uppercase}.app #audio-file{left:-10000px}.app .questions{display:inline-block;list-style:none;margin:0;text-align:left}.app .questions>li{opacity:.9}.app .questions>li:not(:last-child){margin-bottom:2em}.app .questions>li>:first-child{color:#fff;font-size:1.5em;font-weight:700;text-transform:uppercase}.app .questions>li>:last-child{color:#ccc;font-size:1.4em}.app .questions>li>:last-child:before{color:#86e29b;content:"↵";display:inline-block;font-weight:700;margin:0 .4em 0 0;transform:scaleX(-1)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "museria",
3
- "version": "0.2.38",
3
+ "version": "0.2.42",
4
4
  "description": "Decentralized music storage",
5
5
  "main": "./src/index.js",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "homepage": "https://github.com/ortexx/museria",
14
14
  "scripts": {
15
15
  "eslint": "eslint src bin test",
16
- "test": "mocha ./test/index.js --timeout=20000",
16
+ "test": "mocha ./test/index.js --timeout=30000",
17
17
  "build-client": "webpack --config=webpack.client.js",
18
18
  "build-client-prod": "cross-env NODE_ENV=production webpack --config=webpack.client.js",
19
19
  "build-face": "webpack --config=webpack.face.js",
@@ -69,8 +69,7 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "@fortawesome/fontawesome-free": "^5.15.0",
72
- "akili": "^1.2.17",
73
- "array-chunk-reader": "^0.1.15",
72
+ "akili": "^1.2.19",
74
73
  "base64url": "^3.0.1",
75
74
  "bootstrap": "^4.5.3",
76
75
  "chalk": "^4.1.2",
@@ -78,7 +77,7 @@
78
77
  "express": "^4.17.1",
79
78
  "fs-extra": "^9.0.1",
80
79
  "lodash": "^4.17.20",
81
- "metastocle": "^0.2.24",
80
+ "metastocle": "^0.2.26",
82
81
  "music-metadata": "^6.4.0",
83
82
  "node-fetch": "^2.6.1",
84
83
  "node-id3": "^0.2.2",
@@ -86,8 +85,8 @@
86
85
  "serve-favicon": "^2.5.0",
87
86
  "sharp": "^0.25.2",
88
87
  "splaytree": "^3.1.0",
89
- "spreadable": "^0.2.20",
90
- "storacle": "^0.2.20",
88
+ "spreadable": "^0.2.22",
89
+ "storacle": "^0.2.22",
91
90
  "transliteration": "^2.2.0"
92
91
  },
93
92
  "repository": {
@@ -1,4 +1,4 @@
1
- <div class="app container-fluid">
1
+ <div class="app container-fluid">
2
2
  <if recreate is="${ this.showCaptcha }" class="captcha wmodal">
3
3
  <div
4
4
  class="wmodal-overlay"
@@ -48,6 +48,9 @@
48
48
  </div>
49
49
  </if>
50
50
  <header class="header row">
51
+ <div class="songs-counter-text">
52
+ There ${this.songsCount == 1? 'is': 'are'} <b>${ Number(this.songsCount).toLocaleString() }</b> song${this.songsCount == 1? '': 's'} on the network
53
+ </div>
51
54
  <a state="app" class="logo mx-auto px-4">
52
55
  <div>
53
56
  <img src="./img/logo.svg" class="logo-img">
@@ -55,12 +58,12 @@
55
58
  <div class="logo-description-start">decentralized</div>
56
59
  <div class="logo-description-end-lg d-lg-none">storage</div>
57
60
  </div>
58
- <div class="logo-description-end d-none d-lg-block">storage</div>
61
+ <div class="logo-description-end d-none d-lg-block">storage</div>
59
62
  </a>
60
63
  </header>
61
- <div class="content">
64
+ <div class="content">
62
65
  <div class="row pt-5 pb-3">
63
- <div class="song-search col-lg-6 col-12 pl-5 pr-5 pr-lg-4 pt-4">
66
+ <div class="song-search col-lg-6 col-12 pl-5 pr-5 pr-lg-4 pt-4">
64
67
  <div class="song-search-input input-group input-group-lg">
65
68
  <i hidden="${ this.isFinding }" class="fas fa-search fa-lg"></i>
66
69
  <i hidden="${ !this.isFinding }" class="fas fa-circle-notch fa-spin song-search-loading fa-lg"></i>
@@ -240,10 +243,9 @@
240
243
  </div>
241
244
  </li>
242
245
  <li>
243
- <div>How many songs are in this node?</div>
246
+ <div>Where can I get the node status?</div>
244
247
  <div>
245
- There are <b class="text-warning">${ this.songsCount }</b> song(s) at the moment. You also can find more info about the node status
246
- <a href="/status?pretty" target="_blank">here</a>.
248
+ There is a special route <a href="/status?pretty" target="_blank">here</a>.
247
249
  </div>
248
250
  </li>
249
251
  <li>
@@ -1,7 +1,6 @@
1
1
  import './app.scss';
2
2
  import Akili from 'akili';
3
3
  import router from 'akili/src/services/router';
4
- import request from 'akili/src/services/request';
5
4
  import client from '../../client';
6
5
 
7
6
  export default class App extends Akili.Component {
@@ -19,7 +18,7 @@ export default class App extends Akili.Component {
19
18
  created() {
20
19
  this.captchaWidth = 240;
21
20
  this.findingSongsLimit = 10;
22
- this.songsCountInterval = 10 * 1000;
21
+ this.songsCountInterval = 15 * 1000;
23
22
  this.songsCountIntervalObj = null;
24
23
  this.scope.songsCount = 0;
25
24
  this.scope.searchInputValue = this.transition.query.f;
@@ -83,8 +82,7 @@ export default class App extends Akili.Component {
83
82
  }
84
83
 
85
84
  async setSongsCount() {
86
- const data = (await request.get('/status', { json: true })).data;
87
- this.scope.songsCount = data.filesCount;
85
+ this.scope.songsCount = await client.getNetworkFilesCount();
88
86
  }
89
87
 
90
88
  setFindingValue(val) {
@@ -3,7 +3,7 @@
3
3
  .app {
4
4
  display: flex;
5
5
  height: 100vh;
6
- flex-direction: column;
6
+ flex-direction: column;
7
7
 
8
8
  .wmodal {
9
9
  &-overlay {
@@ -96,11 +96,27 @@
96
96
  }
97
97
 
98
98
  & .header {
99
+ position: relative;
99
100
  background: #333;
100
101
  background: radial-gradient(#666,#464646, #333);
101
102
  padding: 8.2em 0 6.6em 0;
102
103
  text-shadow: 0px 0px 0.8em #333;
103
104
 
105
+ & .songs-counter-text {
106
+ position: absolute;
107
+ left: 50%;
108
+ transform: translateX(-50%);
109
+ bottom: 1.5em;
110
+ color: $gray;
111
+ text-transform: uppercase;
112
+ font-weight: bold;
113
+ text-align: center;
114
+
115
+ & b {
116
+ color: $light;
117
+ }
118
+ }
119
+
104
120
  & .logo {
105
121
  display: flex;
106
122
  font-family: ProximaNova;
@@ -197,7 +213,7 @@
197
213
  }
198
214
 
199
215
  &-input {
200
- position: relative;
216
+ position: relative;
201
217
 
202
218
  & > i {
203
219
  position: absolute;
@@ -224,8 +240,9 @@
224
240
  }
225
241
 
226
242
  & > input {
227
- padding-left: 2.5em;
228
-
243
+ padding-left: 2.5em;
244
+ padding-right: 2em;
245
+
229
246
  &:focus {
230
247
  z-index: 0;
231
248
  }
package/src/client.js CHANGED
@@ -24,7 +24,7 @@ module.exports = (Parent) => {
24
24
  }
25
25
  }, options);
26
26
  super(options);
27
- }
27
+ }
28
28
 
29
29
  /**
30
30
  * Get the song complete info
@@ -2,7 +2,6 @@ const DatabaseMuseria = require('../database')();
2
2
  const DatabaseLoki = require('spreadable/src/db/transports/loki')(DatabaseMuseria);
3
3
  const DatabaseLokiMetastocle = require('metastocle/src/db/transports/loki')(DatabaseLoki);
4
4
  const DatabaseLokiStoracle = require('storacle/src/db/transports/loki')(DatabaseLokiMetastocle);
5
- const ArrayChunkReader = require('array-chunk-reader');
6
5
  const utils = require('../../../utils');
7
6
 
8
7
  module.exports = (Parent) => {
@@ -20,10 +19,10 @@ module.exports = (Parent) => {
20
19
  }, options);
21
20
  const fullName = this.createCollectionName('music');
22
21
  const documents = this.col[fullName].find();
23
- const reader = new ArrayChunkReader(documents, { limit: 1000, log: false });
24
22
  let max = null;
25
23
 
26
- await reader.start((doc) => {
24
+ for(let i = 0; i < documents.length; i++) {
25
+ const doc = documents[i];
27
26
  let score = doc.compTitle === title? 1: 0;
28
27
 
29
28
  if(!score) {
@@ -35,18 +34,18 @@ module.exports = (Parent) => {
35
34
 
36
35
  if(score === 1) {
37
36
  max = { score, doc };
38
- return reader.stop();
37
+ break;
39
38
  }
40
39
 
41
40
  if(!max || score > max.score) {
42
41
  max = { score, doc };
43
- return;
42
+ continue;
44
43
  }
45
44
 
46
45
  if(score == max.score && Math.random() > 0.5) {
47
46
  max = { score, doc };
48
47
  }
49
- });
48
+ }
50
49
 
51
50
  if(max && max.score >= options.similarity) {
52
51
  return this.prepareDocumentToGet(max.doc);
package/src/node.js CHANGED
@@ -5,7 +5,6 @@ const sharp = require('sharp');
5
5
  const fse = require('fs-extra');
6
6
  const qs = require('querystring');
7
7
  const SplayTree = require('splaytree');
8
- const ArrayChunkReader = require('array-chunk-reader');
9
8
  const DatabaseLokiMuseria = require('./db/transports/loki')();
10
9
  const ServerExpressMuseria = require('./server/transports/express')();
11
10
  const MusicCollection = require('./collection/transports/music')();
@@ -79,8 +78,7 @@ module.exports = (Parent) => {
79
78
  ]
80
79
  },
81
80
  task: {
82
- cleanUpMusicInterval: '1m',
83
- normalizeSongTitlesInterval: '10m',
81
+ cleanUpMusicInterval: '1m'
84
82
  }
85
83
  }, options);
86
84
 
@@ -121,10 +119,6 @@ module.exports = (Parent) => {
121
119
  if(this.options.task.cleanUpMusicInterval) {
122
120
  await this.task.add('cleanUpMusic', this.options.task.cleanUpMusicInterval, () => this.cleanUpMusic());
123
121
  }
124
-
125
- if(this.options.task.normalizeSongTitlesInterval) {
126
- await this.task.add('normalizeSongTitles', this.options.task.normalizeSongTitlesInterval, () => this.normalizeSongTitles());
127
- }
128
122
  }
129
123
 
130
124
  /**
@@ -134,11 +128,21 @@ module.exports = (Parent) => {
134
128
  */
135
129
  async normalizeSongTitles() {
136
130
  const docs = await this.db.getDocuments('music');
137
- const reader = new ArrayChunkReader(docs, { size: 1000, log: false });
138
- await reader.start(async (doc) => {
139
- doc.title = utils.beautifySongTitle(doc.title);
131
+ const titles = {};
132
+
133
+ for(let i = 0; i < docs.length; i++) {
134
+ const doc = docs[i];
135
+ const title = utils.beautifySongTitle(doc.title);
136
+
137
+ if(titles[title] && titles[title] != doc.$loki) {
138
+ await this.db.deleteDocument(doc);
139
+ continue;
140
+ }
141
+
142
+ doc.title = title;
143
+ titles[title] = doc.$loki;
140
144
  await this.db.updateMusicDocument(doc, { beautify: false });
141
- });
145
+ }
142
146
  }
143
147
 
144
148
  /**
@@ -422,8 +426,7 @@ module.exports = (Parent) => {
422
426
  * @returns {object[]}
423
427
  */
424
428
  async getSongInfo(title, options = {}) {
425
- title = utils.beautifySongTitle(title);
426
- this.songTitleTest(title);
429
+ title = utils.prepareComparisonSongTitle(title);
427
430
  const collection = await this.getCollection('music');
428
431
  const actions = utils.prepareDocumentGettingActions({
429
432
  offset: 0,
@@ -468,7 +471,7 @@ module.exports = (Parent) => {
468
471
  * @returns {object[]}
469
472
  */
470
473
  async findSongs(str, options = {}) {
471
- const title = utils.beautifySongTitle(str);
474
+ const title = utils.prepareComparisonSongTitle(str);
472
475
  str = utils.prepareSongFindingString(str);
473
476
 
474
477
  if(str.length < this.options.music.findingStringMinLength) {
@@ -8,7 +8,7 @@ module.exports.requestSong = node => {
8
8
  return async (req, res, next) => {
9
9
  try {
10
10
  const title = req.query.title;
11
- node.songTitleTest(title);
11
+ node.songTitleTest(title);
12
12
  const link = await node.getSongLink(title, req.query.type);
13
13
 
14
14
  if(!link) {
@@ -131,4 +131,4 @@ module.exports.removeSong = node => {
131
131
  next(err);
132
132
  }
133
133
  }
134
- };
134
+ };
@@ -121,5 +121,5 @@ module.exports = [
121
121
  midds.requestQueueClient,
122
122
  controllers.removeSong
123
123
  ]
124
- }
124
+ }
125
125
  ];
@@ -1,4 +1,5 @@
1
- const ServerExpressMetastocle = require('metastocle/src/server/transports/express')();
1
+ const ServerExpressStoracle = require('storacle/src/server/transports/express')();
2
+ const ServerExpressMetastocle = require('metastocle/src/server/transports/express')(ServerExpressStoracle);
2
3
  const routes = require('./routes');
3
4
  const routesClient = require('./client/routes');
4
5
  const routesApi = require('./api/routes');
@@ -29,7 +30,7 @@ module.exports = (Parent) => {
29
30
  const remove = [
30
31
  'addDocument', 'updateDocuments',
31
32
  'deleteDocuments', 'getDocumentsCount',
32
- 'getDocumentByPk', 'getDocuments'
33
+ 'getDocumentByPk', 'getDocuments', 'removeFile'
33
34
  ];
34
35
  return super.getClientRoutes().filter(r => !remove.includes(r.name)).concat(routesClient);
35
36
  }
@@ -45,7 +46,7 @@ module.exports = (Parent) => {
45
46
  * @see ServerExpressMetastocle.prototype.getApiMasterRoutes
46
47
  */
47
48
  getApiMasterRoutes() {
48
- const remove = ['updateDocuments', 'deleteDocuments'];
49
+ const remove = ['updateDocuments', 'deleteDocuments', 'removeFile'];
49
50
  return super.getApiMasterRoutes().filter(r => !remove.includes(r.name)).concat(routesApiMaster);
50
51
  }
51
52
 
@@ -53,7 +54,7 @@ module.exports = (Parent) => {
53
54
  * @see ServerExpressMetastocle.prototype.getApiButlerRoutes
54
55
  */
55
56
  getApiButlerRoutes() {
56
- const remove = ['updateDocuments', 'deleteDocuments'];
57
+ const remove = ['updateDocuments', 'deleteDocuments', 'removeFile'];
57
58
  return super.getApiButlerRoutes().filter(r => !remove.includes(r.name)).concat(routesApiButler);
58
59
  }
59
60
 
@@ -61,7 +62,7 @@ module.exports = (Parent) => {
61
62
  * @see ServerExpressMetastocle.prototype.getApiSlaveRoutes
62
63
  */
63
64
  getApiSlaveRoutes() {
64
- const remove = ['updateDocuments', 'deleteDocuments'];
65
+ const remove = ['updateDocuments', 'deleteDocuments', 'removeFile'];
65
66
  return super.getApiSlaveRoutes().filter(r => !remove.includes(r.name)).concat(routesApiSlave);
66
67
  }
67
68
 
@@ -69,7 +70,7 @@ module.exports = (Parent) => {
69
70
  * @see ServerExpressMetastocle.prototype.getApiNodeRoutes
70
71
  */
71
72
  getApiNodeRoutes() {
72
- const remove = ['addDocument'];
73
+ const remove = ['addDocument', 'storFile'];
73
74
  return super.getApiNodeRoutes().filter(r => !remove.includes(r.name)).concat(routesApiNode);
74
75
  }
75
76
  }
@@ -66,7 +66,7 @@ midds.fileAccess = node => {
66
66
 
67
67
  if(!doc) {
68
68
  const titleHash = String(req.params.hash).split('.')[0];
69
- const title = utils.decodeSongTitle(titleHash);
69
+ const title = utils.decodeSongTitle(titleHash);
70
70
  doc = await node.db.getMusicByPk(title);
71
71
  }
72
72
 
package/src/utils.js CHANGED
@@ -9,7 +9,7 @@ const mm = require('music-metadata');
9
9
  const base64url = require('base64url');
10
10
 
11
11
  utils.regexSongLinks = /(([a-z]+:\/\/)?[-\p{L}\p{N}]+\.[\p{L}]{2,}|[a-z]+:\/\/(\[:*[\w\d]+:[\w\d:]+\]|\d+\.[\d.]+))\S*/igu;
12
- utils.regexSongFeats = /[([\s]+((ft\.?|feat\.?|featuring)[\s]+((?!(\s+[-([)\]]+))[^)\]])+)\s*[)\]]*([\s]+[-([]+|$)/i;
12
+ utils.regexSongFeats = /[([\s]+((ft\.|feat\.)[\s]+((?!(\s+[-([)\]]+))[^)\]])+)\s*[)\]]*([\s]+[-([]+|$)/iu;
13
13
 
14
14
  utils.heritableSongTags = [
15
15
  'TALB', 'TCOM', 'TCON', 'TCOP', 'TDAT', 'TEXT', 'TIT1', 'TIT3', 'TLAN',
@@ -125,11 +125,20 @@ utils.beautifySongTitle = function (title) {
125
125
  .replace(/[\sᅠ]+/g, ' ')
126
126
  .replace(/([([])\s+/g, '$1')
127
127
  .replace(/\s+([)\]])/g, '$1')
128
+ .replace(/([([]+)(featuring|feat|ft)(\s)/ig, '$1feat.$3')
129
+ .replace(/([([\s]+)(feat\.|ft\.)(\s)/ig, '$1feat.$3')
128
130
  .toLowerCase();
129
131
 
130
132
  if(!/[^\s]+ - [^\s]+/.test(title)) {
131
133
  return '';
132
134
  }
135
+
136
+ const arr = title.split(/\(?feat\./i);
137
+
138
+ if(arr.length > 2) {
139
+ arr.splice(2, arr.length - 2);
140
+ title = arr.join('(feat.').trim();
141
+ }
133
142
 
134
143
  const sides = this.splitSongTitle(title);
135
144
  let artists = sides[0].split(/,[\s]*/);
@@ -139,26 +148,26 @@ utils.beautifySongTitle = function (title) {
139
148
  if(!mainArtist) {
140
149
  return '';
141
150
  }
142
-
143
- const match = title.match(this.regexSongFeats);
151
+
152
+ const match = sides[1].match(this.regexSongFeats);
144
153
  let feats = (match? match[1]: '').replace(/,([^\s])/, ', $1').trim();
145
154
  title = `${mainArtist} - ${sides[1]}`;
146
- title = title.replace(this.regexSongFeats, '$5');
147
- artists = artists.map(a => a.trim());
148
-
155
+ title = title.replace(this.regexSongFeats, '$5');
156
+ feats && (artists = artists.concat(feats.replace(/feat\./i, '').split(',')));
157
+ artists = [...new Set(artists.map(a => a.trim()).filter(v => v))];
158
+
149
159
  if(artists.length) {
150
- feats = feats? [feats].concat(artists).join(', '): `ft. ${ artists.join(', ') }`;
160
+ feats = `feat. ${ artists.join(', ') }`;
151
161
  }
152
162
 
153
163
  feats && (title += ` (${feats})`);
154
- title = title
155
- .replace(/([([\s]+)(feat\.?|ft\.?|featuring)(\s)/i, '$1feat.$3')
164
+ title = title
156
165
  .replace(/\[\]|\(\)/g, '')
157
166
  .replace(/\s+/g, ' ')
158
167
  .split(' ')
159
168
  .map(p => p? (p[0].toUpperCase() + p.slice(1)): p)
160
169
  .join(' ')
161
- .trim()
170
+ .trim();
162
171
  return title;
163
172
  };
164
173
 
@@ -234,8 +243,8 @@ utils.getSongArtists = function (title, options = {}) {
234
243
  const sides = this.splitSongTitle(title);
235
244
  let artists = sides[0].split(/,/);
236
245
  const match = title.match(this.regexSongFeats);
237
- let feats = (match? match[1]: '').replace(/(feat|ft|featuring)\.?/i, '');
238
- return artists.concat(feats.split(',')).map(v => v.trim()).filter(v => v);
246
+ let feats = (match? match[1]: '').replace(/^feat\./i, '');
247
+ return [...new Set(artists.concat(feats.split(',')).map(v => v.trim()).filter(v => v))];
239
248
  };
240
249
 
241
250
  /**
package/test/client.js CHANGED
@@ -336,7 +336,7 @@ describe('Client', () => {
336
336
  assert.isNull(await node.db.getMusicByPk(title), 'check the database');
337
337
  assert.isFalse(await node.hasFile(doc.fileHash), 'check the file');
338
338
  });
339
- });
339
+ });
340
340
 
341
341
  describe('.createRequestedSongAudioLink()', () => {
342
342
  it('should return the right link', async () => {
package/test/group.js CHANGED
@@ -164,5 +164,5 @@ describe('group communication', () => {
164
164
 
165
165
  assert.isOk(fCount >= length * duplicates, 'check the files');
166
166
  assert.isOk(dCount >= length * duplicates, 'check the documents');
167
- });
167
+ });
168
168
  });
package/test/node.js CHANGED
@@ -108,17 +108,13 @@ describe('Node', () => {
108
108
  });
109
109
 
110
110
  describe('.getSongInfo()', () => {
111
- it('should throw an error', async () => {
112
- try {
113
- await node.getSongInfo('unexistent song');
114
- throw new Error('Fail');
115
- }
116
- catch (err) {
117
- assert.isOk(err.message.match('Wrong song title'));
118
- }
111
+ it('should return an empty array with a wrong title', async () => {
112
+ const title = 'unexistent';
113
+ const result = await node.getSongInfo(title);
114
+ assert.lengthOf(result, 0);
119
115
  });
120
116
 
121
- it('should return an empty array', async () => {
117
+ it('should return an empty array with a right title', async () => {
122
118
  const title = 'unexistent - unexistent';
123
119
  const result = await node.getSongInfo(title);
124
120
  assert.lengthOf(result, 0);
package/test/routes.js CHANGED
@@ -361,7 +361,7 @@ describe('routes', () => {
361
361
  assert.isNull(await node.db.getMusicByPk(title), 'check the database');
362
362
  assert.isFalse(await node.hasFile(doc.fileHash), 'check the file');
363
363
  });
364
- });
364
+ });
365
365
 
366
366
  describe('/api/master/remove-song/', function () {
367
367
  it('should return an auth error', async function () {
package/test/utils.js CHANGED
@@ -67,14 +67,13 @@ describe('utils', () => {
67
67
 
68
68
  it('should place feats together', () => {
69
69
  const res = utils.beautifySongTitle('artist1, artist2,artist3 - title (feat. artist4, artist5,artist6)');
70
- assert.equal(res, 'Artist1 - Title (feat. Artist4, Artist5, Artist6, Artist2, Artist3)');
70
+ assert.equal(res, 'Artist1 - Title (feat. Artist2, Artist3, Artist4, Artist5, Artist6)');
71
71
  });
72
72
 
73
73
  it('should bring all feats to a single form', () => {
74
74
  assert.equal(utils.beautifySongTitle('artist - title (ft. Artist)'), 'Artist - Title (feat. Artist)', 'check "ft"');
75
75
  assert.equal(utils.beautifySongTitle('artist - title (feat. Artist)'), 'Artist - Title (feat. Artist)', 'check "feat"');
76
- assert.equal(utils.beautifySongTitle('artist - title (featuring Artist)'), 'Artist - Title (feat. Artist)', 'check "featuring"');
77
- assert.equal(utils.beautifySongTitle('artist - title ft Artist'), 'Artist - Title (feat. Artist)', 'check without brackets');
76
+ assert.equal(utils.beautifySongTitle('artist - title ft. Artist'), 'Artist - Title (feat. Artist)', 'check without brackets');
78
77
  });
79
78
 
80
79
  it('should change the dash type', () => {
@@ -133,8 +132,8 @@ describe('utils', () => {
133
132
  });
134
133
 
135
134
  it('should return the right array', () => {
136
- const artists = utils.getSongArtists('artist,artist2 - title ft. artist3, artist4', { beautify: false });
137
- assert.equal(artists.join(','), 'artist,artist2,artist3,artist4');
135
+ const artists = utils.getSongArtists('artist,artist2 - title ft. artist3, artist4');
136
+ assert.equal(artists.join(','), 'Artist,Artist2,Artist3,Artist4');
138
137
  });
139
138
  });
140
139