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 */
039@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
040public class CdMediaController extends Component {
041
042    /**
043     * The Enum TrayState.
044     */
045    public enum TrayState {
046        OPEN, CLOSED
047    }
048
049    private final Map<String, TrayState> trayState = new ConcurrentHashMap<>();
050    private final Map<String, String> current = new ConcurrentHashMap<>();
051    private final Map<String, String> pending = new ConcurrentHashMap<>();
052
053    /**
054     * Instantiates a new cdrom controller.
055     *
056     * @param componentChannel the component channel
057     */
058    @SuppressWarnings("PMD.AssignmentToNonFinalStatic")
059    public CdMediaController(Channel componentChannel) {
060        super(componentChannel);
061    }
062
063    /**
064     * On configure qemu.
065     *
066     * @param event the event
067     */
068    @Handler
069    @SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
070        "PMD.AvoidInstantiatingObjectsInLoops" })
071    public void onConfigureQemu(ConfigureQemu event) {
072        if (event.runState() == RunState.TERMINATING) {
073            return;
074        }
075
076        // Compare known and desired images.
077        int cdCounter = 0;
078        var drives = event.configuration().vm.drives;
079        for (int i = 0; i < drives.length; i++) {
080            if (!"ide-cd".equals(drives[i].type)) {
081                continue;
082            }
083            var driveId = "cd" + cdCounter++;
084            var newFile = Optional.ofNullable(drives[i].file).orElse("");
085            if (event.runState() == RunState.STARTING) {
086                current.put(driveId, newFile);
087                continue;
088            }
089            if (!Objects.equals(current.get(driveId), newFile)) {
090                pending.put(driveId, newFile);
091                if (trayState.computeIfAbsent(driveId,
092                    k -> TrayState.CLOSED) == TrayState.CLOSED) {
093                    fire(new MonitorCommand(new QmpOpenTray(driveId)));
094                    continue;
095                }
096                changeMedium(driveId);
097            }
098        }
099
100    }
101
102    private void changeMedium(String driveId) {
103        current.put(driveId, pending.get(driveId));
104        if (pending.get(driveId).isEmpty()) {
105            fire(new MonitorCommand(new QmpRemoveMedium(driveId)));
106        } else {
107            fire(new MonitorCommand(
108                new QmpChangeMedium(driveId, pending.get(driveId))));
109        }
110    }
111
112    /**
113     * On monitor event.
114     *
115     * @param event the event
116     */
117    @Handler
118    public void onTrayMovedEvent(TrayMovedEvent event) {
119        trayState.put(event.driveId(), event.trayState());
120        if (event.trayState() == TrayState.OPEN
121            && pending.containsKey(event.driveId())) {
122            changeMedium(event.driveId());
123        }
124    }
125}