OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / android / app / update-pick.js

52 lines · javascript ref:
// Which update to offer, from the forge's release index (DESIGN-android.md §6).
// Pure, no React Native: tested under Bare (test/update-pick.test.js).

const TAG = /^android-v(\d+\.\d+\.\d+)$/
const SEMVER = /^(\d+)\.(\d+)\.(\d+)$/
const APK = 'dist/cthuloid-lights.apk'
const SHA = APK + '.sha256'

export function compareSemver (a, b) {
  const x = SEMVER.exec(a)
  const y = SEMVER.exec(b)
  for (let i = 1; i <= 3; i++) {
    const d = Number(x[i]) - Number(y[i])
    if (d !== 0) return d
  }
  return 0
}

// files: release-index rows { repo, version, path }; path is
// '<repo>/versions/<version>/<file>', served at <base>/releases/<repo>/<version>/<file>.
// Returns { version, apkUrl, shaUrl } for the newest COMPLETE release newer than
// `installed`, else null. Complete = both the APK and its .sha256 are listed: the
// forge backends replicate file by file, and a half-copied release must not be offered.
export function pickUpdate (files, installed, base) {
  if (typeof installed !== 'string' || !SEMVER.test(installed)) return null
  const seen = new Map()   // semver -> Set of files
  for (const f of files) {
    if (!f || f.repo !== 'cthuloid') continue
    const m = TAG.exec(f.version)
    if (!m) continue
    const prefix = 'cthuloid/versions/' + f.version + '/'
    if (typeof f.path !== 'string' || !f.path.startsWith(prefix)) continue
    if (!seen.has(m[1])) seen.set(m[1], new Set())
    seen.get(m[1]).add(f.path.slice(prefix.length))
  }
  let best = null
  for (const [v, have] of seen) {
    if (!have.has(APK) || !have.has(SHA)) continue
    if (compareSemver(v, installed) <= 0) continue
    if (best === null || compareSemver(v, best) > 0) best = v
  }
  if (best === null) return null
  const dir = base + '/releases/cthuloid/android-v' + best + '/'
  return { version: best, apkUrl: dir + APK, shaUrl: dir + SHA }
}

// The .sha256 file is `sha256sum` output: '<hex>  <name>\n'.
export function parseSha256 (text) {
  const m = /^([0-9a-fA-F]{64})(\s|$)/.exec(String(text || '').trim())
  return m ? m[1].toLowerCase() : null
}