YesPlayMusic/src/main/cache.ts

327 lines
8.7 KiB
TypeScript
Raw Normal View History

2022-03-30 00:53:05 +08:00
import { db, Tables } from './db'
import type { FetchTracksResponse } from '../renderer/api/track'
2022-04-09 00:28:37 +08:00
import { app } from 'electron'
2022-03-19 17:03:29 +08:00
import { Request, Response } from 'express'
2022-04-16 13:30:25 +08:00
import log from './log'
2022-03-19 17:03:29 +08:00
import fs from 'fs'
import * as musicMetadata from 'music-metadata'
2022-04-12 01:48:14 +08:00
import { APIs } from './CacheAPIsName'
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
class Cache {
constructor() {
//
}
set(api: string, data: any, query: any = {}) {
switch (api) {
case APIs.UserPlaylist:
case APIs.UserAccount:
case APIs.Personalized:
case APIs.RecommendResource:
case APIs.Likelist: {
if (!data) return
db.upsert(Tables.AccountData, {
id: api,
json: JSON.stringify(data),
updateAt: Date.now(),
})
break
}
case APIs.SongDetail: {
if (!data.songs) return
const tracks = (data as FetchTracksResponse).songs.map(t => ({
id: t.id,
json: JSON.stringify(t),
updatedAt: Date.now(),
}))
2022-04-12 01:48:14 +08:00
db.upsertMany(Tables.Track, tracks)
break
}
case APIs.Album: {
if (!data.album) return
data.album.songs = data.songs
db.upsert(Tables.Album, {
id: data.album.id,
json: JSON.stringify(data.album),
updatedAt: Date.now(),
})
break
}
case APIs.PlaylistDetail: {
if (!data.playlist) return
db.upsert(Tables.Playlist, {
id: data.playlist.id,
json: JSON.stringify(data),
updatedAt: Date.now(),
})
break
}
case APIs.Artists: {
if (!data.artist) return
db.upsert(Tables.Artist, {
id: data.artist.id,
json: JSON.stringify(data),
updatedAt: Date.now(),
})
break
}
case APIs.ArtistAlbum: {
if (!data.hotAlbums) return
db.createMany(
Tables.Album,
data.hotAlbums.map((a: Album) => ({
id: a.id,
json: JSON.stringify(a),
updatedAt: Date.now(),
}))
)
const modifiedData = {
...data,
hotAlbums: data.hotAlbums.map((a: Album) => a.id),
}
db.upsert(Tables.ArtistAlbum, {
id: data.artist.id,
json: JSON.stringify(modifiedData),
updatedAt: Date.now(),
})
break
}
case APIs.Lyric: {
if (!data.lrc) return
db.upsert(Tables.Lyric, {
id: query.id,
json: JSON.stringify(data),
updatedAt: Date.now(),
})
break
}
case APIs.CoverColor: {
if (!data.id || !data.color) return
if (/^#([a-fA-F0-9]){3}$|[a-fA-F0-9]{6}$/.test(data.color) === false) {
return
}
db.upsert(Tables.CoverColor, {
id: data.id,
color: data.color,
})
}
}
2022-03-19 17:03:29 +08:00
}
2022-04-12 01:48:14 +08:00
get(api: string, query: any): any {
switch (api) {
case APIs.UserPlaylist:
case APIs.UserAccount:
case APIs.Personalized:
case APIs.RecommendResource:
case APIs.Likelist: {
const data = db.find(Tables.AccountData, api)
if (data?.json) return JSON.parse(data.json)
break
}
case APIs.SongDetail: {
const ids: string[] = query?.ids.split(',')
if (ids.length === 0) return
2022-03-24 14:23:04 +08:00
2022-04-12 01:48:14 +08:00
let isIDsValid = true
ids.forEach(id => {
if (id === '' || isNaN(Number(id))) isIDsValid = false
})
if (!isIDsValid) return
2022-03-24 14:23:04 +08:00
2022-04-12 01:48:14 +08:00
const tracksRaw = db.findMany(Tables.Track, ids)
2022-03-30 00:53:05 +08:00
2022-04-12 01:48:14 +08:00
if (tracksRaw.length !== ids.length) {
return
}
const tracks = ids.map(id => {
const track = tracksRaw.find(t => t.id === Number(id)) as any
return JSON.parse(track.json)
})
2022-03-19 17:03:29 +08:00
return {
code: 200,
2022-04-12 01:48:14 +08:00
songs: tracks,
privileges: {},
}
2022-04-12 01:48:14 +08:00
}
case APIs.Album: {
if (isNaN(Number(query?.id))) return
const data = db.find(Tables.Album, query.id)
if (data?.json)
return {
resourceState: true,
songs: [],
code: 200,
album: JSON.parse(data.json),
}
break
}
case APIs.PlaylistDetail: {
if (isNaN(Number(query?.id))) return
const data = db.find(Tables.Playlist, query.id)
if (data?.json) return JSON.parse(data.json)
break
}
case APIs.Artists: {
if (isNaN(Number(query?.id))) return
const data = db.find(Tables.Artist, query.id)
if (data?.json) return JSON.parse(data.json)
break
}
case APIs.ArtistAlbum: {
if (isNaN(Number(query?.id))) return
2022-04-12 01:48:14 +08:00
const artistAlbumsRaw = db.find(Tables.ArtistAlbum, query.id)
if (!artistAlbumsRaw?.json) return
const artistAlbums = JSON.parse(artistAlbumsRaw.json)
2022-04-12 01:48:14 +08:00
const albumsRaw = db.findMany(Tables.Album, artistAlbums.hotAlbums)
if (albumsRaw.length !== artistAlbums.hotAlbums.length) return
const albums = albumsRaw.map(a => JSON.parse(a.json))
2022-04-12 01:48:14 +08:00
artistAlbums.hotAlbums = artistAlbums.hotAlbums.map((id: number) =>
albums.find(a => a.id === id)
)
return artistAlbums
}
case APIs.Lyric: {
if (isNaN(Number(query?.id))) return
const data = db.find(Tables.Lyric, query.id)
if (data?.json) return JSON.parse(data.json)
break
}
case APIs.CoverColor: {
if (isNaN(Number(query?.id))) return
return db.find(Tables.CoverColor, query.id)?.color
}
}
2022-03-19 17:03:29 +08:00
}
2022-04-12 01:48:14 +08:00
getForExpress(api: string, req: Request) {
// Get track detail cache
if (api === APIs.SongDetail) {
const cache = this.get(api, req.query)
if (cache) {
2022-04-16 13:30:25 +08:00
log.debug(`[cache] Cache hit for ${req.path}`)
2022-04-12 01:48:14 +08:00
return cache
}
2022-03-19 17:03:29 +08:00
}
2022-04-12 01:48:14 +08:00
// Get audio cache if API is song/detail
if (api === APIs.SongUrl) {
const cache = db.find(Tables.Audio, Number(req.query.id))
if (!cache) return
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
const audioFileName = `${cache.id}-${cache.br}.${cache.type}`
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
const isAudioFileExists = fs.existsSync(
`${app.getPath('userData')}/audio_cache/${audioFileName}`
)
if (!isAudioFileExists) return
2022-03-19 17:03:29 +08:00
2022-04-16 13:30:25 +08:00
log.debug(`[cache] Audio cache hit for ${req.path}`)
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
return {
data: [
{
source: cache.source,
id: cache.id,
url: `http://127.0.0.1:42710/yesplaymusic/audio/${audioFileName}`,
br: cache.br,
size: 0,
md5: '',
code: 200,
expi: 0,
type: cache.type,
gain: 0,
fee: 8,
uf: null,
payed: 0,
flag: 4,
canExtend: false,
freeTrialInfo: null,
level: 'standard',
encodeType: cache.type,
freeTrialPrivilege: {
resConsumable: false,
userConsumable: false,
listenType: null,
},
freeTimeTrialPrivilege: {
resConsumable: false,
userConsumable: false,
type: 0,
remainTime: 0,
},
urlSource: 0,
2022-03-19 17:03:29 +08:00
},
2022-04-12 01:48:14 +08:00
],
code: 200,
}
2022-03-19 17:03:29 +08:00
}
}
2022-04-12 01:48:14 +08:00
getAudio(fileName: string, res: Response) {
if (!fileName) {
return res.status(400).send({ error: 'No filename provided' })
}
const id = Number(fileName.split('-')[0])
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
try {
const path = `${app.getPath('userData')}/audio_cache/${fileName}`
const audio = fs.readFileSync(path)
if (audio.byteLength === 0) {
db.delete(Tables.Audio, id)
fs.unlinkSync(path)
return res.status(404).send({ error: 'Audio not found' })
}
res.send(audio)
} catch (error) {
res.status(500).send({ error })
2022-03-19 17:03:29 +08:00
}
}
2022-04-12 01:48:14 +08:00
async setAudio(
buffer: Buffer,
{ id, source }: { id: number; source: string }
) {
const path = `${app.getPath('userData')}/audio_cache`
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
try {
fs.statSync(path)
} catch (e) {
fs.mkdirSync(path)
}
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
const meta = await musicMetadata.parseBuffer(buffer)
const br = meta.format.bitrate
const type = {
'MPEG 1 Layer 3': 'mp3',
'Ogg Vorbis': 'ogg',
AAC: 'm4a',
FLAC: 'flac',
unknown: 'unknown',
}[meta.format.codec ?? 'unknown']
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
await fs.writeFile(`${path}/${id}-${br}.${type}`, buffer, error => {
if (error) {
2022-04-16 13:30:25 +08:00
return log.error(`[cache] cacheAudio failed: ${error}`)
2022-04-12 01:48:14 +08:00
}
2022-04-16 13:30:25 +08:00
log.info(`Audio file ${id}-${br}.${type} cached!`)
2022-03-19 17:03:29 +08:00
2022-04-12 01:48:14 +08:00
db.upsert(Tables.Audio, {
id,
br,
type,
source,
updateAt: Date.now(),
})
2022-03-19 17:03:29 +08:00
2022-04-16 13:30:25 +08:00
log.info(`[cache] cacheAudio ${id}-${br}.${type}`)
2022-04-12 01:48:14 +08:00
})
}
2022-03-19 17:03:29 +08:00
}
2022-04-12 01:48:14 +08:00
export default new Cache()