import * as BluebirdPromise from 'bluebird' import { move, unlink } from 'fs-extra'; import { lt as lessThan } from 'semver' import { CoError } from '../../../modules/co-error' import PresentationSettings from '../../../modules/presentation-settings' import PresentationVersion from '../../../modules/presentation-version' import packPresentation, { getPresentationPackagePath, isPresentationPackageExist, packPresentationWithDiff } from '../../../modules/pack-presentation' import { PresentationClient, PresentationDetail, Status } from '../../../services/presentation' import PresentationChecksums from '../src/PresentationChecksums' import * as validator from '@cobalt-engine/co-validator' import { tmpdir } from 'os'; import { join } from 'path'; const glob = require('globby'); export function push(presentationChecksums: PresentationChecksums, presentationClient: PresentationClient, presentationSettings: PresentationSettings, presentationVersionHandler: PresentationVersion, presentationDir: string, force: boolean = false): BluebirdPromise { return BluebirdPromise.resolve(validator.validate(presentationDir)) .then(() => presentationSettings.get('id')) .then((id: string) => { return doesPresentationExist(presentationClient)(id) .then((exists) => { if (exists) { return updateRemotePresentation( presentationChecksums, presentationClient, presentationSettings, presentationVersionHandler, presentationDir, force)(id); } else { return createPresentation(presentationClient, presentationDir, presentationSettings, presentationVersionHandler) .then(() => presentationChecksums.createChecksums(presentationDir)) } }) }) } function doesPresentationExist(presentationClient: PresentationClient): (id: string) => BluebirdPromise { return function (id) { return presentationClient.doesPresentationExist(id); } } function getRemotePresentationId(presentationClient: PresentationClient): (id: string) => BluebirdPromise { return function (id) { return presentationClient.getPresentationById(id) .get('id') } } function updateRemotePresentation(presentationChecksums: PresentationChecksums, presentationClient: PresentationClient, presentationSettings: PresentationSettings, presentationVersionHandler: PresentationVersion, presentationDir: string, force: boolean): (id: string) => BluebirdPromise { return function (id) { return updatePresentation(presentationChecksums, presentationClient, presentationSettings, presentationVersionHandler, presentationDir, id, force) } } export function createRemotePresentation(presentationClient: PresentationClient, presentationDir: string, presentationSettings: PresentationSettings, presentationVersionHandler: PresentationVersion): (err: CoError) => BluebirdPromise { return function (err) { if (err.code === 'PRESENTATION_NOT_EXIST') { return createPresentation(presentationClient, presentationDir, presentationSettings, presentationVersionHandler) } throw err } } function updatePresentation(presentationChecksums: PresentationChecksums, presentationClient: PresentationClient, presentationSettings: PresentationSettings, presentationVersionHandler: PresentationVersion, presentationDir: string, id: string, force: boolean): BluebirdPromise { return checkPresentationStatus(presentationClient, id) .then(() => checkPresentationVersion(presentationClient, presentationVersionHandler, presentationDir, id, force)) .then(() => uploadFullPresentation( presentationChecksums, presentationClient, presentationDir, presentationSettings, presentationVersionHandler, id )) .then(() => presentationChecksums.createChecksums(presentationDir)) } function checkPresentationStatus(presentationClient: PresentationClient, id: string): BluebirdPromise { return presentationClient.getPresentationStatus(id) .then((status: Status) => { if (status === Status.InProgress) { throw new CoError('PUSHED_PRESENTATION_IN_PROGRESS') } else if (status === Status.Failed) { throw new CoError('PUSHED_PRESENTATION_IN_FAILED') } }) } function checkPresentationVersion(presentationClient: PresentationClient, presentationVersionHandler: PresentationVersion, presentationDir: string, id: string, force: boolean): BluebirdPromise { if (force) { return BluebirdPromise.resolve(); } return BluebirdPromise.all([ presentationVersionHandler.get(), presentationClient.getPresentationLatestVersion(id) ]) .spread((localVersion: string, remoteVersion: string) => { if (lessThan(localVersion, remoteVersion)) { throw new CoError('OUTDATED_LOCAL_VERSION', localVersion, remoteVersion) } }) } async function uploadFullPresentation(presentationChecksums: PresentationChecksums, presentationClient: PresentationClient, presentationDir: string, presentationSettings: PresentationSettings, presentationVersionHandler: PresentationVersion, id: string): BluebirdPromise { const packagePath = getPresentationPackagePath(id); const packageExists = await isPresentationPackageExist(packagePath); if (packageExists) { await packPresentationWithDiff(presentationChecksums, presentationDir, packagePath); } else { await packPresentation(presentationDir, packagePath); } const [presentationData] = await BluebirdPromise.all([ presentationClient.updatePresentation(packagePath, id), cleanUpPreviousPackages(), ]); return updateLocalPresentationData(presentationDir, presentationSettings, presentationVersionHandler)(presentationData); } async function cleanUpPreviousPackages() { const tmp = tmpdir(); const now = Date.now(); const ttl = 7 * 24 * 60 * 60 * 1000; // 7 days const result = await glob('package*.zip', { cwd: tmp, stats: true, onlyFiles: true, }); await BluebirdPromise.all(result.map((file: any) => { const zipPath = join(tmp, file.name); if (now - file.stats.mtimeMs > ttl) { return unlink(zipPath); } return BluebirdPromise.resolve(); })); } function createPresentation(presentationClient: PresentationClient, presentationDir: string, presentationSettings: PresentationSettings, presentationVersionHandler: PresentationVersion): BluebirdPromise { return packPresentation(presentationDir) .then((packagePath: string) => { return presentationClient.createPresentation(packagePath) .then(updateLocalPresentationData(presentationDir, presentationSettings, presentationVersionHandler)) .then((id) => { const newPackagePath = getPresentationPackagePath(id); return move(packagePath, newPackagePath); }); }) } function updateLocalPresentationData(presentationDir: string, presentationSettings: PresentationSettings, presentationVersionHandler: PresentationVersion): (data: PresentationDetail) => BluebirdPromise { return function (data) { const { id, tag, version } = data return presentationVersionHandler.set(version) .then(() => presentationSettings.set('tag', tag)) .then(() => presentationSettings.set('id', id)) .then(() => id) } }