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

/*
 * Copyright (C) 2023 Red Hat, Inc.
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

import cockpit from "cockpit";

import { installationState } from "../apis/installation_state.js";
import {
    getActions,
    getAncestors,
    getDeviceData,
    getDiskFreeSpace,
    getDiskTotalSpace,
    getExistingSystems,
    getFormatData,
    getMountPoints,
} from "../apis/storage_devicetree.js";
import {
    getAllDiskSelection,
    getUsableDisks,
} from "../apis/storage_disks_selection.js";
import {
    gatherRequests,
    getAutomaticPartitioningRequest,
    getDeviceTree,
    getPartitioningMethod,
} from "../apis/storage_partitioning.js";

export const getDevicesAction = () => {
    return async (dispatch) => {
        if (installationState.active) {
            return;
        }

        dispatch({
            payload: { isFetching: true },
            type: "SET_IS_FETCHING",
        });

        const actions = await getActions();
        const mountPoints = await getMountPoints();
        const existingSystems = await getExistingSystems();

        // Start from stable physical disks (GetUsableDisks) rather than GetDevices
        // which returns all devices including transient LUKS containers created during
        // encryption setup. Walking the tree top-down via children.v avoids the race
        // where GetDevices snapshots an ID that vanishes before GetDeviceData is called.
        const rootDiskIds = await getUsableDisks();
        const ancestorIds = await getAncestors({ diskIds: rootDiskIds });
        const deviceData = {};
        const toVisit = [...new Set([...rootDiskIds, ...ancestorIds])];
        const visited = new Set();

        while (toVisit.length > 0) {
            // Installation may have started mid-walk and torn down a device already queued here.
            if (installationState.active) {
                dispatch({
                    payload: { isFetching: false },
                    type: "SET_IS_FETCHING",
                });
                return;
            }

            const device = toVisit.pop();
            if (visited.has(device)) continue;
            visited.add(device);

            try {
                const devData = await getDeviceData({ disk: device });

                // Empty DeviceData (device-id="") means the device vanished between
                // enumeration and fetch — skip it and do not recurse into its children.
                if (!devData["device-id"]?.v) continue;

                if (devData["is-disk"].v) {
                    const free = await getDiskFreeSpace({ diskNames: [device] });
                    // extend it with variants to keep the format consistent
                    devData.free = cockpit.variant(String, free);

                    const total = await getDiskTotalSpace({ diskNames: [device] });
                    devData.total = cockpit.variant(String, total);
                } else {
                    devData.free = cockpit.variant(String, 0);
                    devData.total = cockpit.variant(String, devData.size.v);
                }

                const formatData = await getFormatData({ diskName: device });
                devData.formatData = formatData;

                deviceData[device] = devData;

                for (const childId of (devData.children?.v ?? [])) {
                    if (!visited.has(childId)) toVisit.push(childId);
                }
            } catch (error) {
                if (error.name === "org.fedoraproject.Anaconda.Modules.Storage.UnknownDeviceError") {
                    continue;
                } else {
                    throw error;
                }
            }
        }

        dispatch({
            payload: {
                actions,
                devices: deviceData,
                existingSystems,
                mountPoints,
            },
            type: "GET_DEVICES_DATA"
        });

        dispatch({
            payload: { isFetching: false },
            type: "SET_IS_FETCHING",
        });
    };
};

export const getDiskSelectionAction = () => {
    return async (dispatch) => {
        const usableDisks = await getUsableDisks();
        const diskSelection = await getAllDiskSelection();

        return dispatch({
            payload: {
                diskSelection: {
                    ignoredDisks: diskSelection[0].IgnoredDisks.v,
                    selectedDisks: diskSelection[0].SelectedDisks.v,
                    usableDisks,
                }
            },
            type: "GET_DISK_SELECTION",
        });
    };
};

export const getPartitioningDataAction = ({ partitioning, requests }) => {
    return async (dispatch) => {
        const props = { path: partitioning };
        const convertRequests = reqs => reqs.map(request => Object.entries(request).reduce((acc, [key, value]) => ({ ...acc, [key]: value.v }), {}));
        const deviceTreePath = await getDeviceTree({ partitioning });

        if (!requests) {
            props.method = await getPartitioningMethod({ partitioning });
            if (props.method === "MANUAL") {
                const reqs = await gatherRequests({ partitioning });

                props.requests = convertRequests(reqs);
            } else if (props.method === "AUTOMATIC") {
                const reqs = await getAutomaticPartitioningRequest({ partitioning });

                props.requests = convertRequests([reqs]);
            } else {
                props.requests = [];
            }
        } else {
            props.requests = convertRequests(requests);
        }

        return dispatch({
            payload: { deviceTree: { path: deviceTreePath }, partitioningData: props, path: partitioning },
            type: "GET_PARTITIONING_DATA"
        });
    };
};

export const setAppliedPartitioningAction = ({ appliedPartitioning }) => {
    return {
        payload: { appliedPartitioning },
        type: "SET_APPLIED_PARTITIONING",
    };
};

export const setStorageScenarioAction = (scenario) => {
    window.sessionStorage.setItem("storage-scenario-id", scenario);

    return {
        payload: { scenario },
        type: "SET_STORAGE_SCENARIO",
    };
};

export const setLuksEncryptionDataAction = (payload) => {
    return {
        payload,
        type: "SET_LUKS_ENCRYPTION_DATA",
    };
};