Skip to content

Commit

Permalink
Cobbled-together quick-install feature!
Browse files Browse the repository at this point in the history
  • Loading branch information
fasterthanlime committed Oct 13, 2016
1 parent 03422b0 commit 2170d3d
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 43 deletions.
2 changes: 1 addition & 1 deletion appsrc/components/download-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class DownloadRow extends Component {
<linearGradient id='downloadGradient' x1='0' y1='0' x2='0' y2='1'>
<stop offset='0%' stopColor={gradientColor} stopOpacity={0.2}/>
<stop offset='50%' stopColor={gradientColor} stopOpacity={0.2}/>
<stop offset='100%' stopColor={gradientColor} stopOpacity={0.0}/>
<stop offset='100%' stopColor={gradientColor} stopOpacity={0.1}/>
</linearGradient>
</defs>
<Area type='monotone' curve={false} dot={false} isAnimationActive={false} dataKey='bps' fill='url(#downloadGradient)' fillOpacity={1.0}/>
Expand Down
6 changes: 4 additions & 2 deletions appsrc/tasks/download/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import invariant from 'invariant'
import butler from '../../util/butler'

export default async function apply (opts) {
const {buildId, buildUserVersion, globalMarket, cave, gameId, patchPath, signaturePath, outPath} = opts
const {buildId, buildUserVersion, installedArchiveMtime, globalMarket, cave,
gameId, patchPath, signaturePath, outPath} = opts
invariant(typeof globalMarket === 'object', 'apply must have globalMarket')
invariant(typeof cave === 'object', 'apply must have cave')
invariant(gameId, 'apply must have gameId')
Expand All @@ -15,7 +16,8 @@ export default async function apply (opts) {

const caveUpdate = {
buildId,
buildUserVersion
buildUserVersion,
installedArchiveMtime
}
await globalMarket.saveEntity('caves', cave.id, caveUpdate, {wait: true})

Expand Down
1 change: 1 addition & 0 deletions appsrc/tasks/download/download-patches.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ async function doApply (opts, patch) {
gameId,
buildId: patch.entry.id,
buildUserVersion: patch.entry.userVersion,
installedArchiveMtime: Date.parse(patch.entry.updatedAt),
patchPath: patch.patchPath,
signaturePath: patch.signaturePath,
outPath: cavePath,
Expand Down
59 changes: 46 additions & 13 deletions appsrc/tasks/download/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import invariant from 'invariant'
import mklog from '../../util/log'
const log = mklog('download')

import pathmaker from '../../util/pathmaker'
import client from '../../util/api'
import butler from '../../util/butler'

Expand All @@ -16,32 +17,64 @@ export default async function start (out, opts) {
}

const {upload, gameId, destPath, downloadKey, credentials} = opts
invariant(gameId, 'startDownload opts must be linked to gameId')
invariant(gameId, 'startDownload opts must have gameId')
invariant(typeof upload === 'object', 'startDownload opts must have upload object')
invariant(typeof destPath === 'string', 'startDownload opts must have a dest path')
invariant(credentials && credentials.key, 'startDownload must have valid API credentials')

// Get download URL
const api = client.withKey(credentials.key)

const onProgress = (e) => out.emit('progress', e)
const uploadURL = api.downloadUploadURL(downloadKey, upload.id)

try {
await butler.cp({
if (upload.buildId) {
log(opts, `Downloading wharf-enabled upload, build #${upload.buildId}`)

const {game} = opts
invariant(game, 'startDownload opts must have game')

// TODO: use manifest URL if available
const archiveURL = api.downloadBuildURL(downloadKey, upload.id, upload.buildId, 'archive')
const signatureURL = api.downloadBuildURL(downloadKey, upload.id, upload.buildId, 'signature')

const store = require('../../store').default
const {defaultInstallLocation} = store.getState().preferences
const installLocation = defaultInstallLocation
const installFolder = pathmaker.sanitize(game.title)

const cave = {
installLocation,
installFolder,
patchScheme: 2 // see pathmaker
}

const fullInstallFolder = pathmaker.appPath(cave)
log(opts, `Doing decompressing download to ${fullInstallFolder}`)

await butler.verify(signatureURL, fullInstallFolder, {
...opts,
src: uploadURL,
dest: destPath,
resume: true,
heal: `archive,${archiveURL}`,
emitter: out,
onProgress
})
} catch (e) {
if (e.errors && e.errors[0] === 'invalid upload') {
const e = new Error('invalid upload')
e.itchReason = 'upload-gone'
} else {
const uploadURL = api.downloadUploadURL(downloadKey, upload.id)

try {
await butler.cp({
...opts,
src: uploadURL,
dest: destPath,
resume: true,
emitter: out,
onProgress
})
} catch (e) {
if (e.errors && e.errors[0] === 'invalid upload') {
const e = new Error('invalid upload')
e.itchReason = 'upload-gone'
throw e
}
throw e
}
throw e
}
}
54 changes: 31 additions & 23 deletions appsrc/tasks/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export default async function start (out, opts) {
invariant(opts.upload, 'install must have an upload')
const {market, credentials, globalMarket, archivePath, downloadKey, game, upload, installLocation = defaultInstallLocation(), handPicked} = opts

const experimentalZeroExtract = !!opts.upload.buildId

let checkTimestamps = true

const grabCave = () => globalMarket.getEntities('caves')::findWhere({gameId: game.id})
Expand Down Expand Up @@ -55,7 +57,7 @@ export default async function start (out, opts) {
downloadKey
}

if (!opts.reinstall) {
if (!opts.reinstall && !experimentalZeroExtract) {
const installFolderExists = async function () {
const fullPath = pathmaker.appPath(cave)
return await sf.exists(fullPath)
Expand All @@ -79,34 +81,40 @@ export default async function start (out, opts) {

market.saveEntity('games', game.id, game)

let destPath = pathmaker.appPath(cave)
let amtime
if (experimentalZeroExtract) {
amtime = Date.parse(upload.build.updatedAt)
} else {
let destPath = pathmaker.appPath(cave)

let archiveStat
try {
archiveStat = await sf.lstat(archivePath)
} catch (e) {
log(opts, 'where did our archive go? re-downloading...')
throw new Transition({to: 'download', reason: 'missing-download'})
}

let archiveStat
try {
archiveStat = await sf.lstat(archivePath)
} catch (e) {
log(opts, 'where did our archive go? re-downloading...')
throw new Transition({to: 'download', reason: 'missing-download'})
}
let imtime = Date.parse(cave.installedArchiveMtime)
amtime = archiveStat.mtime
log(opts, `comparing mtimes, installed = ${imtime}, archive = ${amtime}`)

let imtime = Date.parse(cave.installedArchiveMtime)
let amtime = archiveStat.mtime
log(opts, `comparing mtimes, installed = ${imtime}, archive = ${amtime}`)
if (checkTimestamps && imtime && !(amtime > imtime)) {
log(opts, 'archive isn\'t more recent, nothing to install')
return {caveId: cave.id}
}

if (checkTimestamps && imtime && !(amtime > imtime)) {
log(opts, 'archive isn\'t more recent, nothing to install')
return {caveId: cave.id}
}
let coreOpts = {
...opts,
cave,
destPath,
onProgress: (e) => out.emit('progress', e)
}

let coreOpts = {
...opts,
cave,
destPath,
onProgress: (e) => out.emit('progress', e)
globalMarket.saveEntity('caves', cave.id, {launchable: false})
await core.install(out, coreOpts)
}

globalMarket.saveEntity('caves', cave.id, {launchable: false})
await core.install(out, coreOpts)
globalMarket.saveEntity('caves', cave.id, {
game,
installedBy: {
Expand Down
14 changes: 11 additions & 3 deletions appsrc/util/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,12 +249,20 @@ export class AuthenticatedClient {
}
}

downloadBuildURL (downloadKey, uploadId, buildId) {
downloadBuildURL (downloadKey, uploadId, buildId, extra) {
let path = ''

if (downloadKey) {
return this.itchfsURL(`/download-key/${downloadKey.id}/download/${uploadId}/builds/${buildId}`)
path = `/download-key/${downloadKey.id}/download/${uploadId}/builds/${buildId}`
} else {
return this.itchfsURL(`/upload/${uploadId}/download/builds/${buildId}`)
path = `/upload/${uploadId}/download/builds/${buildId}`
}

if (extra) {
path = `${path}/${extra}`
}

return this.itchfsURL(path)
}

async subkey (gameId, scope) {
Expand Down
16 changes: 15 additions & 1 deletion appsrc/util/butler.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,20 @@ async function ditto (src, dst, opts = {}) {
return await butler(opts, 'ditto', args)
}

/* Verifies ${dir} against ${signature}, heals against opts.heal if given */
async function verify (signature, dir, opts = {}) {
invariant(typeof signature === 'string', 'verify has string signature')
invariant(typeof dir === 'string', 'verify has string dir')

const args = [signature, dir]
const {heal} = opts
if (heal) {
args.push('--heal')
args.push(heal)
}
return await butler(opts, 'verify', args)
}

async function sanityCheck () {
try {
const res = await spawn({
Expand All @@ -184,5 +198,5 @@ async function sanityCheck () {
}

export default {
cp, dl, apply, untar, unzip, wipe, mkdir, ditto, sanityCheck
cp, dl, apply, untar, unzip, wipe, mkdir, ditto, verify, sanityCheck
}

0 comments on commit 2170d3d

Please sign in to comment.