timelimit-android/contrib/update-device-db.mjs
2022-05-09 02:00:00 +02:00

139 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { deepStrictEqual } from 'assert'
import { get } from 'https'
import { parse } from 'csv-parse/sync'
import { deflateSync } from 'zlib'
import { writeFileSync } from 'fs'
import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
function pullString(url) {
return new Promise((resolve, reject) => {
get(url, (res) => {
if (res.statusCode !== 200) {
reject(new Error('unexpected status code'))
res.resume()
return
}
const buffers = []
res
.on('data', buffers.push.bind(buffers))
.on('end', () => {
resolve(Buffer.concat(buffers))
})
.on('error', reject)
}).on('error', reject)
})
}
async function main() {
const data =
(await pullString('https://storage.googleapis.com/play_public/supported_devices.csv'))
.toString('utf-16le')
const parsedData = parse(data, {})
deepStrictEqual(parsedData[0], ['Retail Branding', 'Marketing Name', 'Device', 'Model'])
const rows = parsedData.slice(1).map((row) => {
if (row.length !== 4) throw new Error(`got ${row.length} columns: ${row}`)
const [brand, marketingName, device, model] = row
return { brand, marketingName, device, model }
})
const relevantRows = rows.filter((row) => row.marketingName !== row.model)
relevantRows.sort((a, b) => {
if (a.device < b.device) return -1
else if (a.device > b.device) return 1
else if (a.model < b.model) return -1
else if (a.model > b.model) return 1
else return 0
})
const relevantStringsSet = new Set()
relevantRows.forEach((row) => {
relevantStringsSet.add(row.device)
relevantStringsSet.add(row.model)
relevantStringsSet.add(row.marketingName)
})
const relevantStrings = Array.from(relevantStringsSet)
relevantStrings.sort(); relevantStrings.reverse()
const stringToIndex = new Map()
let relevantStringPool = Buffer.alloc(0)
let relevantStringPoolAddCounter = 0
for (const str of relevantStrings) {
if (stringToIndex.has(str)) continue
const oldIndex = relevantStringPool.indexOf(str)
if (oldIndex === -1) {
stringToIndex.set(str, relevantStringPool.length)
relevantStringPool = Buffer.concat([relevantStringPool, Buffer.from(str)])
relevantStringPoolAddCounter++
} else {
stringToIndex.set(str, oldIndex)
}
}
// 4 bytes per string: start index (3 byte) + length (1 byte)
// 3 strings (device, model, marketingName)
const deviceInfoBuffer = Buffer.alloc(relevantRows.length * (3 * 4))
const writeString = (outputIndex, str) => {
const startIndex = stringToIndex.get(str)
if (startIndex === undefined) throw new Error()
if (startIndex > 0xffffff) throw new Error()
deviceInfoBuffer.writeUInt8((startIndex >> 16) & 0xff, outputIndex * 4 + 0)
deviceInfoBuffer.writeUInt8((startIndex >> 8) & 0xff, outputIndex * 4 + 1)
deviceInfoBuffer.writeUInt8(startIndex & 0xff, outputIndex * 4 + 2)
deviceInfoBuffer.writeUInt8(str.length, outputIndex * 4 + 3)
}
relevantRows.forEach((row, index) => {
const outputIndex = index * 3
writeString(outputIndex + 0, row.device)
writeString(outputIndex + 1, row.model)
writeString(outputIndex + 2, row.marketingName)
})
const relevantStringBuffer = Buffer.from(relevantStringPool, 'utf8')
const lengthDataBuffer = Buffer.alloc(8)
lengthDataBuffer.writeUInt32BE(relevantStringBuffer.length, 0)
lengthDataBuffer.writeUInt32BE(relevantRows.length, 4)
const resultData = Buffer.concat([lengthDataBuffer, relevantStringBuffer, deviceInfoBuffer])
const resultDataCompressed = deflateSync(resultData, {})
writeFileSync(resolve(__dirname, '../app/src/main/assets/device-names.bin'), resultDataCompressed)
console.log({
rows: rows.length,
relevantRows: relevantRows.length,
relevantStringCount: relevantStrings.length,
relevantStringPoolAddCounter,
relevantStringPoolLength: relevantStringPool.length,
resultDataLength: resultData.length,
resultDataCompressedLength: resultDataCompressed.length,
compressedBytesPerDevices: resultDataCompressed.length / relevantRows.length
})
}
main().catch((ex) => {
console.warn(ex)
process.exit(1)
})