001/*
002 * VM-Operator
003 * Copyright (C) 2025 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.io.IOException;
022import java.util.Deque;
023import java.util.concurrent.ConcurrentLinkedDeque;
024import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentConnected;
025import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogIn;
026import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLogOut;
027import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedIn;
028import org.jdrupes.vmoperator.runner.qemu.events.VmopAgentLoggedOut;
029import org.jgrapes.core.Channel;
030import org.jgrapes.core.Event;
031import org.jgrapes.core.annotation.Handler;
032
033/**
034 * A component that handles the communication over the vmop agent
035 * socket.
036 * 
037 * If the log level for this class is set to fine, the messages 
038 * exchanged on the socket are logged.
039 */
040public class VmopAgentClient extends AgentConnector {
041
042    private final Deque<Event<?>> executing = new ConcurrentLinkedDeque<>();
043
044    /**
045     * Instantiates a new VM operator agent client.
046     *
047     * @param componentChannel the component channel
048     * @throws IOException Signals that an I/O exception has occurred.
049     */
050    public VmopAgentClient(Channel componentChannel) throws IOException {
051        super(componentChannel);
052    }
053
054    /**
055     * On vmop agent login.
056     *
057     * @param event the event
058     * @throws IOException Signals that an I/O exception has occurred.
059     */
060    @Handler
061    public void onVmopAgentLogIn(VmopAgentLogIn event) throws IOException {
062        logger.fine(() -> "vmop agent(out): login " + event.user());
063        if (writer().isPresent()) {
064            executing.add(event);
065            sendCommand("login " + event.user());
066        }
067    }
068
069    /**
070     * On vmop agent logout.
071     *
072     * @param event the event
073     * @throws IOException Signals that an I/O exception has occurred.
074     */
075    @Handler
076    public void onVmopAgentLogout(VmopAgentLogOut event) throws IOException {
077        logger.fine(() -> "vmop agent(out): logout");
078        if (writer().isPresent()) {
079            executing.add(event);
080            sendCommand("logout");
081        }
082    }
083
084    @Override
085    @SuppressWarnings({ "PMD.UnnecessaryReturn",
086        "PMD.AvoidLiteralsInIfCondition" })
087    protected void processInput(String line) throws IOException {
088        logger.fine(() -> "vmop agent(in): " + line);
089
090        // Check validity
091        if (line.isEmpty() || !Character.isDigit(line.charAt(0))) {
092            logger.warning(() -> "Illegal response: " + line);
093            return;
094        }
095
096        // Check positive responses
097        if (line.startsWith("220 ")) {
098            rep().fire(new VmopAgentConnected());
099            return;
100        }
101        if (line.startsWith("201 ")) {
102            Event<?> cmd = executing.pop();
103            if (cmd instanceof VmopAgentLogIn login) {
104                rep().fire(new VmopAgentLoggedIn(login));
105            } else {
106                logger.severe(() -> "Response " + line
107                    + " does not match executing command " + cmd);
108            }
109            return;
110        }
111        if (line.startsWith("202 ")) {
112            Event<?> cmd = executing.pop();
113            if (cmd instanceof VmopAgentLogOut logout) {
114                rep().fire(new VmopAgentLoggedOut(logout));
115            } else {
116                logger.severe(() -> "Response " + line
117                    + "does not match executing command " + cmd);
118            }
119            return;
120        }
121
122        // Ignore unhandled continuations
123        if (line.charAt(0) == '1') {
124            return;
125        }
126
127        // Error
128        logger.warning(() -> "Error response: " + line);
129        executing.pop();
130    }
131
132}