🏡 index : quine-global/quine-anaconda-webui.git

/*
 * Copyright (C) 2022 Red Hat, Inc.
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */
import cockpit from "cockpit";

import {
    getDevicesAction,
    getDiskSelectionAction,
    getPartitioningDataAction,
    setAppliedPartitioningAction,
    setStorageScenarioAction,
} from "../actions/storage-actions.js";

import { debug, error } from "../helpers/log.js";
import { _callClient, _getProperty } from "./helpers.js";

const INTERFACE_NAME = "org.fedoraproject.Anaconda.Modules.Storage";
const OBJECT_PATH = "/org/fedoraproject/Anaconda/Modules/Storage";

const callClient = (...args) => {
    return _callClient(StorageClient, OBJECT_PATH, INTERFACE_NAME, ...args);
};
const getProperty = (...args) => {
    return _getProperty(StorageClient, OBJECT_PATH, INTERFACE_NAME, ...args);
};

export class StorageClient {
    constructor (address, dispatch) {
        if (StorageClient.instance && (!address || StorageClient.instance.address === address)) {
            return StorageClient.instance;
        }

        StorageClient.instance?.client.close();

        StorageClient.instance = this;

        this.client = cockpit.dbus(
            INTERFACE_NAME,
            { address, bus: "none", superuser: "try" }
        );
        this.address = address;
        this.dispatch = dispatch;
    }

    async init (args = {}) { // eslint-disable-line no-unused-vars -- optional bootstrap args from Application
        this.client.addEventListener("close", () => error("Storage client closed"));

        this.startEventMonitor();

        await this.initData();
    }

    stopEventMonitor () {
        this._subscription?.remove();
    }

    startEventMonitor () {
        this._subscription = this.client.subscribe(
            { },
            (path, iface, signal, args) => {
                switch (signal) {
                case "PropertiesChanged":
                    if (args[0] === "org.fedoraproject.Anaconda.Modules.Storage.DiskSelection") {
                        this.dispatch(getDiskSelectionAction());
                    } else if (args[0] === "org.fedoraproject.Anaconda.Modules.Storage.Partitioning.Manual" && Object.hasOwn(args[1], "Requests")) {
                        this.dispatch(getPartitioningDataAction({ partitioning: path, requests: args[1].Requests.v }));
                    } else if (args[0] === "org.fedoraproject.Anaconda.Modules.Storage.Partitioning.Automatic" && Object.hasOwn(args[1], "Request")) {
                        this.dispatch(getPartitioningDataAction({ partitioning: path, requests: [args[1].Request.v] }));
                    } else if (args[0] === INTERFACE_NAME && Object.hasOwn(args[1], "CreatedPartitioning")) {
                        const last = args[1].CreatedPartitioning.v.length - 1;
                        this.dispatch(getPartitioningDataAction({ partitioning: args[1].CreatedPartitioning.v[last] }));
                    } else if (args[0] === INTERFACE_NAME && Object.hasOwn(args[1], "AppliedPartitioning")) {
                        // First set the partitioning data and then get the device information
                        // as we store this in the store according the the deviceTree
                        this.dispatch(setAppliedPartitioningAction({ appliedPartitioning: args[1].AppliedPartitioning.v }));
                        this.dispatch(getDevicesAction());
                    } else {
                        debug(`Unhandled signal on ${path}: ${iface}.${signal} ${JSON.stringify(args)}`);
                    }
                    break;
                default:
                    debug(`Unhandled signal on ${path}: ${iface}.${signal} ${JSON.stringify(args)}`);
                }
            });
    }

    async initData () {
        const partitioning = await getProperty("CreatedPartitioning");

        // When there is just one partitioning created when the module is initialized we can assume
        // that it is the one specified in kickstart
        if (partitioning.length === 1) {
            this.dispatch(setStorageScenarioAction("use-configured-storage-kickstart"));
        }

        if (partitioning.length !== 0) {
            const lastPartitioning = partitioning[partitioning.length - 1];
            await this.dispatch(getPartitioningDataAction({ partitioning: lastPartitioning }));
        }

        const appliedPartitioning = await getProperty("AppliedPartitioning");
        this.dispatch(setAppliedPartitioningAction({ appliedPartitioning }));

        await this.dispatch(getDevicesAction());
        await this.dispatch(getDiskSelectionAction());
    }
}

/**
 * @param {string} task         DBus path to a task
 * @param {string} onSuccess    Callback to run after Succeeded signal is received
 * @param {string} onFail       Callback to run as an error handler
 *
 * @returns {Promise}           Resolves a DBus path to a task
 */
export const runStorageTask = ({ onFail, onSuccess, task }) => {
    // FIXME: This is a workaround for 'Succeeded' signal being emited twice
    let succeededEmitted = false;
    const taskProxy = new StorageClient().client.proxy(
        "org.fedoraproject.Anaconda.Task",
        task
    );
    const addEventListeners = () => {
        taskProxy.addEventListener("Stopped", () => taskProxy.Finish().catch(onFail));
        taskProxy.addEventListener("Succeeded", () => {
            if (succeededEmitted) {
                return;
            }
            succeededEmitted = true;
            onSuccess();
        });
    };
    taskProxy.wait(() => {
        addEventListeners();
        taskProxy.Start().catch(onFail);
    });
};

export const runStorageTaskAsync = ({ onSuccess, task }) => {
    return new Promise((resolve, reject) => {
        runStorageTask({
            onFail: reject,
            onSuccess: () => {
                Promise.resolve()
                        .then(() => (onSuccess !== undefined ? onSuccess() : undefined))
                        .then(resolve)
                        .catch(reject);
            },
            task,
        });
    });
};

/**
 * @returns {Promise}           Resolves a DBus path to a task
 */
export const scanDevicesWithTask = () => {
    return callClient("ScanDevicesWithTask", []);
};