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}