Commit 0ffda196 authored by Laura Heimann's avatar Laura Heimann

added library database

parent 5f79e795
{
"name": "spinshare-client",
"version": "2.5.0",
"version": "2.5.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -2468,34 +2468,11 @@
"dev": true
},
"axios": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "1.5.10"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
"follow-redirects": "^1.10.0"
}
},
"babel-loader": {
......@@ -3491,6 +3468,11 @@
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
"dev": true
},
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
},
"check-types": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
......@@ -4314,6 +4296,11 @@
"which": "^1.2.9"
}
},
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
},
"crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
......@@ -6129,7 +6116,6 @@
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
"integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
"dev": true,
"requires": {
"debug": "^3.0.0"
},
......@@ -6138,7 +6124,6 @@
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
......@@ -7759,8 +7744,7 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-callable": {
"version": "1.1.5",
......@@ -8589,6 +8573,16 @@
"object-visit": "^1.0.0"
}
},
"md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"requires": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"md5-file": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz",
......@@ -8919,8 +8913,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"multicast-dns": {
"version": "6.2.3",
......
......@@ -21,10 +21,11 @@
"dependencies": {
"@mdi/font": "^5.8.55",
"adm-zip": "^0.4.16",
"axios": "^0.19.2",
"axios": "^0.21.1",
"core-js": "^3.6.4",
"get-folder-size": "^2.0.1",
"glob": "^7.1.6",
"md5": "^2.3.0",
"md5-file": "^5.0.0",
"moment": "^2.27.0",
"ncp": "^2.0.0",
......
<template>
<div :class="'song-item-local ' + (isSpinShare ? '' : 'song-item-onlylocal')" v-on:contextmenu="showContextMenu($event)" v-on:click="openInClient()">
<div class="song-cover" v-bind:style="'background-image: '+ backgroundImage +' , url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'" v-observe-visibility="visibilityChanged">
<div class="song-cover" v-bind:style="'background-image: ' + backgroundImage + ' , url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'" v-observe-visibility="visibilityChanged">
<div class="song-charter-info">
<div class="song-charter"><i class="mdi mdi-account-circle"></i><span>{{ detail.charter ? detail.charter : "Unknown" }}</span></div>
<div class="song-charter"><i class="mdi mdi-account-circle"></i><span>{{ charter ? charter : "Unknown" }}</span></div>
</div>
</div>
<div class="song-metadata">
<div class="song-title">{{ detail.title ? detail.title : "Untitled" }}</div>
<div class="song-artist">{{ detail.artist ? detail.artist : "Unknown" }}</div>
<div class="song-title">{{ title ? title : "Untitled" }}</div>
<div class="song-artist">{{ artist ? artist : "Unknown" }}</div>
<div class="song-difficulties">
<div v-if="hasEasyDifficulty" class="difficulty"><span>E</span> {{ easyDifficulty ? easyDifficulty : 0 }}</div>
<div v-if="hasNormalDifficulty" class="difficulty"><span>N</span> {{ normalDifficulty ? normalDifficulty : 0 }}</div>
<div v-if="hasHardDifficulty" class="difficulty"><span>H</span> {{ hardDifficulty ? hardDifficulty : 0 }}</div>
<div v-if="hasExpertDifficulty" class="difficulty"><span>EX</span> {{ expertDifficulty ? expertDifficulty : 0 }}</div>
<div v-if="hasXDDifficulty" class="difficulty"><span>XD</span> {{ XDDifficulty ? XDDifficulty : 0 }}</div>
</div>
</div>
</div>
</template>
<script>
import { remote } from 'electron';
import fs from 'fs';
import path from 'path';
const { shell } = remote;
import SSAPI from '@/modules/module.api.js';
export default {
name: 'SongLocalItem',
props: [
'id',
'file',
'detail',
'cover',
'isSpinShare'
'title',
'subtitle',
'artist',
'charter',
'hasEasyDifficulty',
'hasNormalDifficulty',
'hasHardDifficulty',
'hasExpertDifficulty',
'hasXDDifficulty',
'easyDifficulty',
'normalDifficulty',
'hardDifficulty',
'expertDifficulty',
'XDDifficulty',
'paths'
],
data: function() {
return {
isContextMenuActive: false,
backgroundImage: "none;"
backgroundImage: "none;",
baseFileName: "",
isSpinShare: false,
spinShareID: null,
}
},
computed: {
imageUrl() {
return this.$data.backgroundImage
return this.$data.backgroundImage;
}
},
mounted: function() {
let ssapi = new SSAPI();
this.$data.baseFileName = path.basename(this.$props.paths.srtb).replace(".srtb", "");
ssapi.getSongDetail(this.$data.baseFileName).then(data => {
if(data.status == 200) {
this.$data.isSpinShare = true;
this.$data.spinShareID = data.data.id;
}
});
},
methods: {
showContextMenu: function(e) {
......@@ -49,7 +80,7 @@
}
items.push({ icon: "folder-outline", title: this.$t('contextmenu.openInExplorer'), method: () => { this.openInExplorer(); } });
items.push({ icon: "delete", title: this.$t('contextmenu.delete'), method: () => { this.$parent.$parent.$emit('delete', this.$props.file); } });
items.push({ icon: "delete", title: this.$t('contextmenu.delete'), method: () => { this.$parent.$parent.$emit('delete', this.$props.paths.srtb); } });
this.$root.$emit('showContextMenu', {
x: e.pageX,
......@@ -59,15 +90,15 @@
},
openInClient: function() {
if(this.isSpinShare) {
if(this.isSpinShare.includes("spinshare_")) {
this.$router.push({ name: 'SongDetailReviews', params: { id: this.isSpinShare } });
if(this.isSpinShare) {
this.$router.push({ name: 'SongDetailReviews', params: { id: this.spinShareID } });
}
}
},
openOnSpinShare: function() {
if(this.isSpinShare) {
if(this.isSpinShare.includes("spinshare_")) {
shell.openExternal("https://spinsha.re/song/" + this.isSpinShare);
if(this.isSpinShare) {
shell.openExternal("https://spinsha.re/song/" + this.spinShareID);
}
}
},
......@@ -76,10 +107,8 @@
},
visibilityChanged (isVisible, entry) {
if (isVisible) {
this.$data.backgroundImage = "url(" + this.$props.cover + ")";
}
//url(" + require('@/assets/img/defaultAlbumArt.jpg') + ");
else {
this.$data.backgroundImage = "url(" + this.$props.paths.coverBase64 + ")";
} else {
this.$data.backgroundImage = "none;";
}
}
......@@ -138,6 +167,8 @@
& .song-metadata {
padding: 15px;
color: #fff;
text-decoration: none;
& .song-title {
font-weight: bold;
......@@ -150,6 +181,25 @@
overflow: hidden;
white-space: nowrap;
}
& .song-difficulties {
margin-top: 10px;
height: 20px;
display: flex;
& .difficulty {
background: #fff;
color: #000;
border-radius: 4px;
padding: 3px 5px;
margin-right: 4px;
font-size: 10px;
& span {
// padding-right: 3px;
font-weight: bold;
}
}
}
}
&:not(.song-item-onlylocal):hover {
......
......@@ -8,6 +8,10 @@ import VueTilt from 'vue-tilt.js'
import { ipcRenderer } from 'electron';
import VueObserveVisibility from 'vue-observe-visibility'
import { VTooltip, VPopover, VClosePopover } from 'v-tooltip'
import ChartLibrary from '@/modules/module.library.js';
let chartLibrary = new ChartLibrary();
chartLibrary.updateLibrary();
Vue.use(VueTilt);
Vue.use(VueObserveVisibility);
......
const { electron, remote } = require('electron');
const app = remote.app;
const md5 = require('md5');
const fs = require('fs');
const { glob } = require('glob');
const path = require('path');
const UserSettings = require('@/modules/module.usersettings.js');
const { createHmac } = require('crypto');
class ChartLibrary {
constructor() {
const userDataPath = app.getPath('userData');
this.path = path.join(userDataPath, 'ChartLibrary.json');
}
async getLibrary() {
let userSettings = new UserSettings();
let data = this.parseDataFile(this.path, false);
console.log("[ChartLibrary] Get Library");
if(data == false) {
return this.updateLibrary();
} else {
if(data.folderMD5 != md5(userSettings.get('gameDirectory'))) {
return this.updateLibrary();
} else {
return data;
}
}
}
async updateLibrary() {
let userSettings = new UserSettings();
let data = {
folderMD5: "",
charts: []
};
console.log("[ChartLibrary] Library Refresh");
data.folderMD5 = md5(userSettings.get('gameDirectory'));
// Find all SRTB files
let srtbFiles = glob.sync(path.join(userSettings.get('gameDirectory'), "*.srtb"));
srtbFiles.forEach((file) => {
// Parse SRTB file
try {
data.charts.push(this.parseSRTBFile(file));
} catch(error) {
console.error(error);
}
});
console.log("[ChartLibrary] Library Refresh Done.");
// Save changes
fs.writeFileSync(this.path, JSON.stringify(data));
console.log("[ChartLibrary] Wrote Library to: " + this.path);
return data;
}
parseSRTBFile(srtbFilePath) {
let userSettings = new UserSettings();
let data = {
title: "",
subtitle: "",
artist: "",
charter: "",
hasEasyDifficulty: false,
hasNormalDifficulty: false,
hasHardDifficulty: false,
hasExpertDifficulty: false,
hasXDDifficulty: false,
easyDifficulty: 0,
normalDifficulty: 0,
hardDifficulty: 0,
expertDifficulty: 0,
XDDifficulty: 0,
srtbHash: "",
paths: {
srtb: "",
ogg: "",
cover: "",
coverBase64: "",
}
}
let srtbRawData = fs.readFileSync(srtbFilePath, "utf-8");
let srtbData = JSON.parse( srtbRawData );
data.paths.srtb = srtbFilePath;
data.srtbHash = md5( srtbData );
srtbData.largeStringValuesContainer.values.forEach(lSV => {
let lSVData = JSON.parse( lSV.val );
// Parse TrackInfo
if(lSV.key.includes("TrackInfo")) {
// Extract values
data.title = lSVData.title;
data.subtitle = lSVData.subtitle;
data.artist = lSVData.artistName;
data.charter = lSVData.charter;
data.paths.cover = glob.sync(path.join(userSettings.get('gameDirectory'), "AlbumArt", lSVData.albumArtReference.assetName + '.*'))[0];
if(data.paths.cover != undefined) {
try {
data.paths.coverBase64 = "data:image/jpg;base64," + fs.readFileSync(data.paths.cover, { encoding: 'base64' })
} catch(error) {
console.error(error);
}
}
lSVData.difficulties.forEach(difficulty => {
if(difficulty._active) {
switch(difficulty._difficulty) {
case 2:
// Easy
data.hasEasyDifficulty = true;
break;
case 3:
// Normal
data.hasNormalDifficulty = true;
break;
case 4:
// Hard
data.hasHardDifficulty = true;
break;
case 5:
// Expert
data.hasExpertDifficulty = true;
break;
case 6:
// XD
data.hasXDDifficulty = true;
break;
}
}
});
}
// Parse TrackData
if(lSV.key.includes("TrackData")) {
if(lSVData != null) {
switch(lSVData.difficultyType) {
case 2:
// Easy
data.easyDifficulty = lSVData.difficultyRating;
break;
case 3:
// Normal
data.normalDifficulty = lSVData.difficultyRating;
break;
case 4:
// Hard
data.hardDifficulty = lSVData.difficultyRating;
break;
case 5:
// Expert
data.expertDifficulty = lSVData.difficultyRating;
break;
case 6:
// XD
data.XDDifficulty = lSVData.difficultyRating;
break;
}
}
}
// Parse ClipInfo
if(lSV.key.includes("ClipInfo")) {
data.paths.ogg = glob.sync(path.join(userSettings.get('gameDirectory'), "AudioClips", lSVData.clipAssetReference.assetName + '.*'))[0];
}
});
// console.log("[ChartLibrary] Parsed SRTB: " + data.paths.srtb);
return data;
}
parseDataFile(filePath, defaults) {
try {
return JSON.parse(fs.readFileSync(filePath));
} catch(error) {
return defaults;
}
}
}
module.exports = ChartLibrary;
\ No newline at end of file
......@@ -4,7 +4,7 @@
<div class="title">{{ $t('library.header') }}</div>
<div class="actions">
<div class="button" v-on:click="install()">{{ $t('library.actions.install') }}</div>
<div class="button" v-on:click="refreshLibrary()">{{ $t('library.actions.refresh') }}</div>
<div class="button" v-on:click="refreshLibrary(true)">{{ $t('library.actions.refresh') }}</div>
<div class="button" v-on:click="openLibrary()">{{ $t('library.actions.open') }}</div>
<span></span>
</div>
......@@ -25,12 +25,12 @@
<template v-slot:song-list>
<SongLocalItem
v-for="song in librarySongs"
v-bind:key="song.detail.id"
v-bind:key="song.paths.srtb"
v-bind="song" />
</template>
</SongRow>
<div class="loading" v-if="!apiFinished">
<div class="loading" v-if="isLoading">
<Loading />
</div>
......@@ -47,6 +47,7 @@
import path from 'path';
import UserSettings from '@/modules/module.usersettings.js';
import ChartLibrary from '@/modules/module.library.js';
import SSAPI from '@/modules/module.api.js';
import SRXD from '@/modules/module.srxd.js';
......@@ -63,8 +64,7 @@
showDeleteOverlay: false,
deleteFiles: [],
hasUnusedFiles: false,
apiFinished: false,
useAPIForLibrary: false
isLoading: false
}
},
components: {
......@@ -74,11 +74,8 @@
DeleteOverlay
},
mounted: function() {
// Put initial refresh on a timeout so the loading animation is rendered first
setTimeout(() => {
this.refreshLibrary();
}, 50);
this.refreshLibrary(false);
this.$on('delete', (file) => {
this.$data.showDeleteOverlay = true;
this.$data.deleteFiles = this.getConnectedFiles(file);
......@@ -91,7 +88,7 @@
this.$data.deleteFiles.forEach((file) => {
fs.unlinkSync(file);
});
this.refreshLibrary();
this.refreshLibrary(false);
this.$data.showDeleteOverlay = false;
this.$data.deleteFiles = "";
});
......@@ -101,62 +98,30 @@
// Refresh if the Download Queue finished an item
ipcRenderer.on("download-complete", (event, downloadItem) => {
this.refreshLibrary();
this.refreshLibrary(false);
});
},
methods: {
refreshLibrary: async function() {
refreshLibrary: async function(hardRefresh = false) {
let ssapi = new SSAPI();
let userSettings = new UserSettings();
let chartLibrary = new ChartLibrary();
this.$data.hasUnusedFiles = false;
this.$data.librarySongs = [];
this.$data.apiFinished = false;
await ssapi.ping().then((data) => {
this.$data.useAPIForLibrary = true;
}).catch((error) => {
console.log(error);
this.$data.useAPIForLibrary = false;
console.log("ERROR WHILE PINGING API");
});
// Load local .srtb
glob(path.join(userSettings.get('gameDirectory'), "*.srtb"), (error, files) => {
files.forEach((file) => {
// Get Detail Data
this.getSongDetail(file).then((songDetail) => {
let librarySong = {};
let fileReference = false;
if(path.basename(file).includes("spinshare_")) {
fileReference = path.basename(file).replace(".srtb", "");
}
let songCover = glob.sync(path.join(userSettings.get('gameDirectory'), "AlbumArt", songDetail.coverReference + ".*"))[0];
if(songCover) {
songCover = "data:image/jpg;base64," + fs.readFileSync(songCover, { encoding: 'base64' });
}
librarySong = {
file: file,
detail: songDetail,
cover: songCover,
modifiedDate: fs.statSync(file).mtime,
isSpinShare: fileReference
};
this.$data.librarySongs.push(librarySong);
this.$data.apiFinished = true;
});
});
this.$data.isLoading = true;
// Order library by modifiedDate
this.$data.librarySongs.sort(function(a, b) {
return new Date(b.modifiedDate) - new Date(a.modifiedDate);
if(hardRefresh) {
chartLibrary.updateLibrary().then(data => {
this.$data.isLoading = false;
this.$data.librarySongs = data.charts;
});
});
} else {
chartLibrary.getLibrary().then(data => {
this.$data.isLoading = false;
this.$data.librarySongs = data.charts;
});
}
this.getUnusedFiles().then((data) => {
if(data.differingAssets.length > 0) {
......@@ -295,7 +260,7 @@
srxdControl.installBackup(extractResult, userSettings.get('gameDirectory')).then((result) => {
console.log("[COPY] Backup installed!");
setTimeout(() => {
this.refreshLibrary();
this.refreshLibrary(false);
}, 250);
}).catch(error => {
console.error(error);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment