001/*
002 * VM-Operator
003 * Copyright (C) 2023 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.vmoperator.runner.qemu;
020
021import java.util.Map;
022import java.util.Objects;
023import java.util.Optional;
024import java.util.concurrent.ConcurrentHashMap;
025import org.jdrupes.vmoperator.runner.qemu.commands.QmpChangeMedium;
026import org.jdrupes.vmoperator.runner.qemu.commands.QmpOpenTray;
027import org.jdrupes.vmoperator.runner.qemu.commands.QmpRemoveMedium;
028import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
029import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
030import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
031import org.jdrupes.vmoperator.runner.qemu.events.TrayMovedEvent;
032import org.jgrapes.core.Channel;
033import org.jgrapes.core.Component;
034import org.jgrapes.core.annotation.Handler;
035
036/**
037 * The Class CdMediaController.
038 */
039public class CdMediaController extends Component {
040
041    /**
042     * The Enum TrayState.
043     */
044    public enum TrayState {
045        OPEN, CLOSED
046    }
047
048    private final Map<String, TrayState> trayState = new ConcurrentHashMap<>();
049    private final Map<String, String> current = new ConcurrentHashMap<>();
050    private final Map<String, String> pending = new ConcurrentHashMap<>();
051
052    /**
053     * Instantiates a new cdrom controller.
054     *
055     * @param componentChannel the component channel
056     */
057    public CdMediaController(Channel componentChannel) {
058        super(componentChannel);
059    }
060
061    /**
062     * On configure qemu.
063     *
064     * @param event the event
065     */
066    @Handler
067    @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops",
068        "PMD.ForLoopCanBeForeach" })
069    public void onConfigureQemu(ConfigureQemu event) {
070        if (event.runState() == RunState.TERMINATING) {
071            return;
072        }
073
074        // Compare known and desired images.
075        int cdCounter = 0;
076        var drives = event.configuration().vm.drives;
077        for (int i = 0; i < drives.length; i++) {
078            if (!"ide-cd".equals(drives[i].type)) {
079                continue;
080            }
081            @SuppressWarnings("PMD.AssignmentInOperand")
082            var driveId = "cd" + cdCounter++;
083            var newFile = Optional.ofNullable(drives[i].file).orElse("");
084            if (event.runState() == RunState.STARTING) {
085                current.put(driveId, newFile);
086                continue;
087            }
088            if (!Objects.equals(current.get(driveId), newFile)) {
089                pending.put(driveId, newFile);
090                if (trayState.computeIfAbsent(driveId,
091                    k -> TrayState.CLOSED) == TrayState.CLOSED) {
092                    fire(new MonitorCommand(new QmpOpenTray(driveId)));
093                    continue;
094                }
095                changeMedium(driveId);
096            }
097        }
098
099    }
100
101    private void changeMedium(String driveId) {
102        current.put(driveId, pending.get(driveId));
103        if (pending.get(driveId).isEmpty()) {
104            fire(new MonitorCommand(new QmpRemoveMedium(driveId)));
105        } else {
106            fire(new MonitorCommand(
107                new QmpChangeMedium(driveId, pending.get(driveId))));
108        }
109    }
110
111    /**
112     * On monitor event.
113     *
114     * @param event the event
115     */
116    @Handler
117    public void onTrayMovedEvent(TrayMovedEvent event) {
118        trayState.put(event.driveId(), event.trayState());
119        if (event.trayState() == TrayState.OPEN
120            && pending.containsKey(event.driveId())) {
121            changeMedium(event.driveId());
122        }
123    }
124}