/* * 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", []); };