001package jmri.server.json.audioicon;
002
003import com.fasterxml.jackson.databind.JsonNode;
004import com.fasterxml.jackson.databind.node.ObjectNode;
005
006import java.beans.PropertyChangeEvent;
007import java.beans.PropertyChangeListener;
008import java.io.IOException;
009import java.util.HashMap;
010import java.util.concurrent.ThreadLocalRandom;
011
012import javax.servlet.http.HttpServletResponse;
013
014import jmri.Audio;
015import jmri.JmriException;
016import jmri.jmrit.audio.AudioSource;
017import jmri.jmrit.display.AudioIcon;
018import jmri.server.json.JSON;
019import jmri.server.json.JsonConnection;
020import jmri.server.json.JsonException;
021import jmri.server.json.JsonRequest;
022import jmri.server.json.JsonSocketService;
023import static jmri.server.json.audioicon.JsonAudioIconServiceFactory.AUDIO_ICON;
024
025/**
026 * JSON socket service provider for managing {@link jmri.jmrit.display.AudioIcon}s.
027 *
028 * @author Randall Wood
029 * @author Daniel Bergqvist (C) 2023
030 */
031public class JsonAudioIconSocketService extends JsonSocketService<JsonAudioIconHttpService> {
032
033    private final HashMap<AudioIcon, AudioIconListener> beanListeners = new HashMap<>();
034
035    public JsonAudioIconSocketService(JsonConnection connection) {
036        super(connection, new JsonAudioIconHttpService(connection.getObjectMapper()));
037    }
038
039    @Override
040    public void onList(String type, JsonNode data, JsonRequest request) throws IOException, JmriException, JsonException {
041        throw new JsonException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, Bundle.getMessage(request.locale, "GetListNotAllowed", type), request.id);
042    }
043
044    @Override
045    public void onMessage(String type, JsonNode data, JsonRequest request) throws IOException, JmriException, JsonException {
046        if (request.method.equals(JSON.GET)) {
047            connection.sendMessage(service.doGet(type, "audioicon", data, request), request.id);
048        }
049        AudioIcon audioIcon = AudioIcon.IDENTITY_MANAGER.getAudioIcon(data.get("identity").asInt());
050        if (!beanListeners.containsKey(audioIcon)) {
051            addListenerToBean(audioIcon);
052        }
053    }
054
055    @Override
056    public void onClose() {
057        beanListeners.values().stream().forEach(listener -> listener._audioIcon.removePropertyChangeListener(listener));
058        beanListeners.clear();
059    }
060
061    protected void addListenerToBean(AudioIcon audioIcon) {
062        if (audioIcon != null) {
063            AudioIconListener listener = new AudioIconListener(audioIcon);
064            audioIcon.addPropertyChangeListener(listener);
065            this.beanListeners.put(audioIcon, listener);
066        }
067    }
068
069    private class AudioIconListener implements PropertyChangeListener {
070
071        public final AudioIcon _audioIcon;
072
073        public AudioIconListener(AudioIcon audioIcon) {
074            this._audioIcon = audioIcon;
075        }
076
077        @Override
078        public void propertyChange(PropertyChangeEvent evt) {
079            // Do nothing if unknown property
080            if (!evt.getPropertyName().equals(AudioIcon.PROPERTY_COMMAND)) return;
081
082            try {
083                String command;
084                int numLoops = 0;
085                switch (evt.getNewValue().toString()) {
086                    case AudioIcon.PROPERTY_COMMAND_PLAY:
087                        command = JSON.AUDIO_COMMAND_PLAY;
088                        Audio audio = _audioIcon.getAudio();
089                        if (audio instanceof AudioSource) {
090                            AudioSource source = (AudioSource)audio;
091                            if (source.isLooped()) {
092                                if (source.getMinLoops() != source.getMaxLoops()) {
093                                    numLoops = source.getMinLoops()
094                                            + ThreadLocalRandom.current().nextInt(
095                                                    source.getMaxLoops() - source.getMinLoops());
096                                } else {
097                                    numLoops = source.getMinLoops();
098                                }
099                            } else {
100                                numLoops = 1;
101                            }
102                        }
103                        break;
104                    case AudioIcon.PROPERTY_COMMAND_STOP:
105                        command = JSON.AUDIO_COMMAND_STOP;
106                        break;
107                    default:
108                        // Do nothing if unknown property command
109                        log.debug("Unknown command: {}", evt.getNewValue());
110                        return;
111                }
112
113                JsonRequest request = new JsonRequest(getLocale(), getVersion(), JSON.GET, 0);
114
115                ObjectNode root = connection.getObjectMapper().createObjectNode();
116                root.put(JSON.TYPE, AUDIO_ICON);
117                root.put(JSON.METHOD, JSON.GET);
118                root.put(JSON.ID, request.id);
119
120                ObjectNode data = root.with(JSON.DATA);
121                data.put(JSON.AUDIO_ICON_IDENTITY, _audioIcon.getIdentity());
122                data.put(JSON.AUDIO_COMMAND, command);
123                data.put(JSON.AUDIO_COMMAND_PLAY_NUM_LOOPS, numLoops);
124                connection.sendMessage(root, 0);
125            } catch (IOException ex) {
126                // if we get an error, unregister as listener
127                this._audioIcon.removePropertyChangeListener(this);
128                beanListeners.remove(this._audioIcon);
129            }
130        }
131    }
132
133    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JsonAudioIconSocketService.class);
134}