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    public void onConfigureQemu(ConfigureQemu event) {
069        if (event.runState() == RunState.TERMINATING) {
070            return;
071        }
072
073        // Compare known and desired images.
074        int cdCounter = 0;
075        var drives = event.configuration().vm.drives;
076        for (int i = 0; i < drives.length; i++) {
077            if (!"ide-cd".equals(drives[i].type)) {
078                continue;
079            }
080            var driveId = "cd" + cdCounter++;
081            var newFile = Optional.ofNullable(drives[i].file).orElse("");
082            if (event.runState() == RunState.STARTING) {
083                current.put(driveId, newFile);
084                continue;
085            }
086            if (!Objects.equals(current.get(driveId), newFile)) {
087                pending.put(driveId, newFile);
088                if (trayState.computeIfAbsent(driveId,
089                    k -> TrayState.CLOSED) == TrayState.CLOSED) {
090                    fire(new MonitorCommand(new QmpOpenTray(driveId)));
091                    continue;
092                }
093                changeMedium(driveId);
094            }
095        }
096
097    }
098
099    private void changeMedium(String driveId) {
100        current.put(driveId, pending.get(driveId));
101        if (pending.get(driveId).isEmpty()) {
102            fire(new MonitorCommand(new QmpRemoveMedium(driveId)));
103        } else {
104            fire(new MonitorCommand(
105                new QmpChangeMedium(driveId, pending.get(driveId))));
106        }
107    }
108
109    /**
110     * On monitor event.
111     *
112     * @param event the event
113     */
114    @Handler
115    public void onTrayMovedEvent(TrayMovedEvent event) {
116        trayState.put(event.driveId(), event.trayState());
117        if (event.trayState() == TrayState.OPEN
118            && pending.containsKey(event.driveId())) {
119            changeMedium(event.driveId());
120        }
121    }
122}