packages/bemuse-tools/src/indexer.js
import { basename, dirname, join } from 'path'
import _ from 'lodash'
import chalk from 'chalk'
import fs from 'fs'
import { getSongInfo } from 'bemuse-indexer'
import glob from 'glob-promise'
import json from 'format-json'
import yaml from 'js-yaml'
const { readFile, writeFile, stat: fileStat } = fs.promises
function Cache(path) {
const data = load()
const stream = fs.createWriteStream(path, { encoding: 'utf-8', flags: 'a' })
function load() {
const out = {}
let text
try {
text = fs.readFileSync(path, 'utf-8')
} catch (e) {
return out
}
text.split(/\n/).forEach(function (line) {
if (line.length < 34) return
const md5 = line.substr(0, 32)
const payload = JSON.parse(line.substr(33))
out[md5] = payload
})
return out
}
return {
get: function (key) {
return data[key]
},
put: function (key, value) {
if (key.length !== 32) throw new Error('Keys should be 32 chars only')
data[key] = value
stream.write(key + ' ' + JSON.stringify(value) + '\n')
return value
},
}
}
export async function index(path, { recursive }) {
const stat = await fileStat(path)
if (!stat.isDirectory()) throw new Error('Not a directory: ' + path)
const cache = new Cache(join(path, 'index.cache'))
console.log('-> Scanning files...')
const dirs = new Map()
const pattern = (recursive ? '**/' : '') + '*/*.{bms,bme,bml,bmson}'
for (const name of await glob(pattern, { cwd: path })) {
const bmsPath = join(path, name)
put(dirs, dirname(bmsPath), () => []).push(basename(bmsPath))
}
const songs = []
const maxDirLength = _(Array.from(dirs.keys())).map('length').max()
for (const [dir, files] of dirs) {
const filesToParse = []
for (const file of files) {
const buf = await readFile(join(dir, file))
if (buf.length > 1048576) {
console.error(chalk.red('BMS file is too long:'), join(dir, file))
continue
}
filesToParse.push({ name: file, data: buf })
}
const extra = await getExtra(dir)
const song = await getSongInfo(filesToParse, { cache, extra })
song.id = dir
song.path = dir
const levels = _(song.charts)
.sortBy((chart) => chart.info.level)
.map((chart) => {
const ch =
chart.keys === '5K'
? chalk.gray
: chart.keys === '7K'
? chalk.green
: chart.keys === '10K'
? chalk.magenta
: chart.keys === '14K'
? chalk.red
: chalk.inverse
return ch(chart.info.level)
})
console.log(
chalk.dim(_.padEnd(dir, maxDirLength)),
chalk.yellow(_.padStart(Math.round(song.bpm) + 'bpm', 7)),
chalk.cyan('[' + song.genre + ']'),
song.artist + '-' + song.title,
levels.join(' '),
song.readme ? '' : chalk.red('[no-meta]')
)
songs.push(song)
}
const collection = {
songs: songs,
}
await writeFile(join(path, 'index.json'), json.diffy(collection))
}
async function getExtra(dir) {
let readme
let extra = {}
try {
readme = await readFile(join(dir, 'README.md'), 'utf-8')
extra.readme = 'README.md'
} catch (e) {
readme = null
}
if (readme !== null) {
try {
const meta = yaml.safeLoad(readme.substr(0, readme.indexOf('---', 3)))
extra = Object.assign({}, meta, extra)
} catch (e) {
console.error(chalk.red('Unable to read metadata:'), '' + e)
}
}
return extra
}
function put(map, key, f) {
if (map.has(key)) {
return map.get(key)
} else {
const object = f(key)
map.set(key, object)
return object
}
}