"use strict";
/*
 * Copyright 2023 balena.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This file handles the writer process.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanup = cleanup;
exports.write = write;
const multi_write_1 = require("etcher-sdk/build/multi-write");
const os_1 = require("os");
const tmp_1 = require("etcher-sdk/build/tmp");
const source_destination_1 = require("etcher-sdk/build/source-destination");
const utils_1 = require("../shared/utils");
const errors_1 = require("../shared/errors");
const axios_1 = __importDefault(require("axios"));
const lodash_1 = require("lodash");
const api_1 = require("./api");
async function write(options) {
    /**
     * @summary Failure handler (non-fatal errors)
     * @param {SourceDestination} destination - destination
     * @param {Error} error - error
     */
    const onFail = (destination, error) => {
        (0, api_1.emitFail)({
            // TODO: device should be destination
            // @ts-ignore (destination.drive is private)
            device: destination.drive,
            error: (0, errors_1.toJSON)(error),
        });
    };
    /**
     * @summary Progress handler
     * @param {Object} state - progress state
     * @example
     * writer.on('progress', onProgress)
     */
    const onProgress = (state) => {
        (0, api_1.emitState)(state);
    };
    // Write the image to the destinations
    const destinations = options.destinations.map((d) => d.device);
    const imagePath = options.image.path;
    (0, api_1.emitLog)(`Image: ${imagePath}`);
    (0, api_1.emitLog)(`Devices: ${destinations.join(', ')}`);
    (0, api_1.emitLog)(`Auto blockmapping: ${options.autoBlockmapping}`);
    (0, api_1.emitLog)(`Decompress first: ${options.decompressFirst}`);
    const dests = options.destinations.map((destination) => {
        return new source_destination_1.BlockDevice({
            drive: destination,
            unmountOnSuccess: true,
            write: true,
            direct: true,
        });
    });
    const { SourceType } = options;
    try {
        let source;
        if (options.image.drive) {
            source = new source_destination_1.BlockDevice({
                drive: options.image.drive,
                direct: !options.autoBlockmapping,
            });
        }
        else {
            if (SourceType === source_destination_1.File.name) {
                source = new source_destination_1.File({
                    path: imagePath,
                });
            }
            else {
                const decodedImagePath = decodeURIComponent(imagePath);
                if ((0, utils_1.isJson)(decodedImagePath)) {
                    const imagePathObject = JSON.parse(decodedImagePath);
                    source = new source_destination_1.Http({
                        url: imagePathObject.url,
                        avoidRandomAccess: true,
                        axiosInstance: axios_1.default.create((0, lodash_1.omit)(imagePathObject, ['url'])),
                        auth: options.image.auth,
                    });
                }
                else {
                    source = new source_destination_1.Http({
                        url: imagePath,
                        avoidRandomAccess: true,
                        auth: options.image.auth,
                    });
                }
            }
        }
        const results = await writeAndValidate({
            source,
            destinations: dests,
            verify: true,
            autoBlockmapping: options.autoBlockmapping,
            decompressFirst: options.decompressFirst,
            onProgress,
            onFail,
        });
        return results;
    }
    catch (error) {
        return { errors: [error] };
    }
}
/** @summary clean up tmp files */
async function cleanup(until) {
    await (0, tmp_1.cleanupTmpFiles)(until, multi_write_1.DECOMPRESSED_IMAGE_PREFIX);
}
/**
 * @summary writes the source to the destinations and validates the writes
 * @param {SourceDestination} source - source
 * @param {SourceDestination[]} destinations - destinations
 * @param {Boolean} verify - whether to validate the writes or not
 * @param {Boolean} autoBlockmapping - whether to trim ext partitions before writing
 * @param {Function} onProgress - function to call on progress
 * @param {Function} onFail - function to call on fail
 * @returns {Promise<{ bytesWritten, devices, errors }>}
 */
async function writeAndValidate({ source, destinations, verify, autoBlockmapping, decompressFirst, onProgress, onFail, }) {
    const { sourceMetadata, failures, bytesWritten } = await (0, multi_write_1.decompressThenFlash)({
        source,
        destinations,
        onFail,
        onProgress,
        verify,
        trim: autoBlockmapping,
        numBuffers: Math.min(2 + (destinations.length - 1) * 32, 256, Math.floor((0, os_1.totalmem)() / 1024 ** 2 / 8)),
        decompressFirst,
    });
    const result = {
        bytesWritten,
        devices: {
            failed: failures.size,
            successful: destinations.length - failures.size,
        },
        errors: [],
        sourceMetadata,
    };
    for (const [destination, error] of failures) {
        const err = error;
        const drive = destination;
        err.device = drive.device;
        err.description = drive.description;
        result.errors.push(err);
    }
    return result;
}
