initial import
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package io.dietz.ed.companion;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.dietz.ed.companion.component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.GeometryFactory;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
|
||||
import io.dietz.ed.companion.models.eddb.System;
|
||||
|
||||
public class SystemDeserializer extends JsonDeserializer<System> {
|
||||
|
||||
@Override
|
||||
public System deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
SystemJson jsonSystem = p.readValueAs(SystemJson.class);
|
||||
|
||||
GeometryFactory gf = new GeometryFactory();
|
||||
Point test = gf.createPoint(new Coordinate(jsonSystem.x, jsonSystem.y, jsonSystem.z));
|
||||
|
||||
return new System(jsonSystem.id, jsonSystem.name, test);
|
||||
}
|
||||
|
||||
private static class SystemJson {
|
||||
|
||||
public long id;
|
||||
|
||||
public String name;
|
||||
|
||||
public double x;
|
||||
public double y;
|
||||
public double z;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.dietz.ed.companion.component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.zeromq.SocketType;
|
||||
import org.zeromq.ZContext;
|
||||
import org.zeromq.ZMQ;
|
||||
|
||||
import io.dietz.ed.companion.domain.MarketWithRoot;
|
||||
|
||||
public class ZeroMqHandler implements Runnable {
|
||||
|
||||
private ZContext context;
|
||||
|
||||
@Value("${eddn.filter:https://eddn.edcd.io/schemas/commodity/3}")
|
||||
private String filterSchema;
|
||||
@Value("${eddn.host:eddn.edcd.io}")
|
||||
private String host;
|
||||
@Value("${eddn.port:9500}")
|
||||
private String port;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public void msg(String msg) {
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
pump();
|
||||
context.close();
|
||||
}
|
||||
|
||||
public synchronized void pump() {
|
||||
Inflater inflater = new Inflater();
|
||||
context = new ZContext();
|
||||
ZMQ.Socket socket = context.createSocket(SocketType.SUB);
|
||||
socket.subscribe("");
|
||||
socket.setReceiveTimeOut(30000);
|
||||
socket.connect("tcp://" + host + ":" + port);
|
||||
|
||||
msg("EDDN Relay connected");
|
||||
ZMQ.Poller poller = context.createPoller(2);
|
||||
poller.register(socket, ZMQ.Poller.POLLIN);
|
||||
byte[] output = new byte[256 * 1024];
|
||||
while (true) {
|
||||
int poll = poller.poll(-1);
|
||||
if (poll == ZMQ.Poller.POLLIN) {
|
||||
|
||||
if (poller.pollin(0)) {
|
||||
byte[] recv = socket.recv(ZMQ.NOBLOCK);
|
||||
if (recv.length > 0) {
|
||||
// decompress
|
||||
inflater.reset();
|
||||
inflater.setInput(recv);
|
||||
try {
|
||||
int outlen = inflater.inflate(output);
|
||||
String outputString = new String(output, 0, outlen, "UTF-8");
|
||||
|
||||
// outputString contains a json message
|
||||
if (outputString.contains(filterSchema)) {
|
||||
msg(outputString);
|
||||
var test = objectMapper.readValue(outputString, MarketWithRoot.class);
|
||||
msg(test.getMarket().toString());
|
||||
}
|
||||
|
||||
} catch (DataFormatException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.dietz.ed.companion.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
import io.dietz.ed.companion.models.eddb.System;
|
||||
import io.dietz.ed.companion.repositories.SystemRepository;
|
||||
|
||||
@Configuration
|
||||
public class DatabaseConfiguration {
|
||||
|
||||
@Autowired
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
SystemRepository systemRepository;
|
||||
|
||||
@EventListener({ ContextRefreshedEvent.class })
|
||||
public void onContextRefreshed() {
|
||||
if (systemRepository.count() == 0) {
|
||||
try (Stream<String> stream = Files.lines(Paths.get("/home/vscode/systems_populated.jsonl"))) {
|
||||
stream.forEach(systemString -> {
|
||||
try {
|
||||
System system = objectMapper.readValue(systemString, System.class);
|
||||
systemRepository.save(system);
|
||||
java.lang.System.out.println(system);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
systemRepository.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.dietz.ed.companion.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class JsonConfiguration {
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
return new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.dietz.ed.companion.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
|
||||
import io.dietz.ed.companion.component.ZeroMqHandler;
|
||||
|
||||
@Configuration
|
||||
public class ZeroMqConfiguration {
|
||||
|
||||
@Autowired
|
||||
protected ZeroMqHandler zeroMqHandler;
|
||||
|
||||
@Autowired
|
||||
protected TaskExecutor taskExecutor;
|
||||
|
||||
@EventListener({ContextRefreshedEvent.class})
|
||||
public void onContextRefreshed() {
|
||||
taskExecutor.execute(zeroMqHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZeroMqHandler zeroMqHandler() {
|
||||
return new ZeroMqHandler();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.dietz.ed.companion.controllers;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.dietz.ed.companion.models.UpdateFeedItem;
|
||||
import io.dietz.ed.companion.service.UpdateFeedsService;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/feed")
|
||||
public class FeedController {
|
||||
|
||||
@Autowired
|
||||
private UpdateFeedsService updateChannelsService;
|
||||
|
||||
@GetMapping(path = "/updates/{channel}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<ServerSentEvent<UpdateFeedItem>> updates(@PathVariable String channel) {
|
||||
return updateChannelsService.connectToFeed(channel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.dietz.ed.companion.controllers;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class HomeController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(final Model model) {
|
||||
return "index";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.dietz.ed.companion.controllers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.dietz.ed.companion.domain.State;
|
||||
import io.dietz.ed.companion.models.UpdateFeedItem;
|
||||
import io.dietz.ed.companion.models.UpdateStatusResponse;
|
||||
import io.dietz.ed.companion.service.UpdateFeedsService;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/update")
|
||||
public class StatusController {
|
||||
|
||||
@Autowired
|
||||
private UpdateFeedsService updateChannelsService;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@PostMapping("/status/{feedId}")
|
||||
private Mono<UpdateStatusResponse> updateStatus(@PathVariable String feedId, @RequestBody String rawPayload) {
|
||||
System.out.println(rawPayload);
|
||||
return Mono.create(sink -> {
|
||||
if (updateChannelsService.hasActiveFeed(feedId)) {
|
||||
UpdateFeedItem updateFeedItem = new UpdateFeedItem(feedId, "credits");
|
||||
State payload;
|
||||
try {
|
||||
payload = objectMapper.readValue(rawPayload, State.class);
|
||||
updateFeedItem.setContent(Long.valueOf(payload.getCredits()));
|
||||
updateChannelsService.sendFeedUpdate(updateFeedItem);
|
||||
} catch (JsonProcessingException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
sink.success(new UpdateStatusResponse());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.dietz.ed.companion.domain;
|
||||
|
||||
public class Commodity {
|
||||
|
||||
public String name;
|
||||
public long buyPrice;
|
||||
public long sellPrice;
|
||||
public long meanPrice;
|
||||
public int demand;
|
||||
public int stock;
|
||||
|
||||
public int demandBracket;
|
||||
public int stockBracket;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.dietz.ed.companion.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Market {
|
||||
|
||||
public long marketId;
|
||||
public String systemName;
|
||||
public String stationName;
|
||||
public List<Commodity> commodities;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return systemName + " - " + stationName + " (" + marketId + ")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.dietz.ed.companion.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class MarketWithRoot {
|
||||
|
||||
@JsonProperty("message")
|
||||
protected Market market;
|
||||
|
||||
public Market getMarket() {
|
||||
return market;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.dietz.ed.companion.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class State {
|
||||
|
||||
@JsonProperty("Credits")
|
||||
private long credits;
|
||||
|
||||
public long getCredits() {
|
||||
return credits;
|
||||
}
|
||||
|
||||
public void setCredits(long credits) {
|
||||
this.credits = credits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "State [credits=" + credits + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.dietz.ed.companion.models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class UpdateFeedItem {
|
||||
|
||||
private final String feedId;
|
||||
private final String type;
|
||||
private final LocalDateTime createDate;
|
||||
private Object content;
|
||||
|
||||
public UpdateFeedItem(String feedId, String type) {
|
||||
this.feedId = feedId;
|
||||
this.type = type;
|
||||
this.createDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public String getFeedId() {
|
||||
return feedId;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateDate() {
|
||||
return createDate;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Object getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(Object content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.dietz.ed.companion.models;
|
||||
|
||||
public class UpdateStatusResponse {
|
||||
|
||||
public String message = "ok";
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.dietz.ed.companion.models.eddb;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
public class Station {
|
||||
@Id
|
||||
protected long id;
|
||||
|
||||
protected String name;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "system_id")
|
||||
protected System system;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.dietz.ed.companion.models.eddb;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
import org.locationtech.jts.geom.Point;
|
||||
|
||||
import io.dietz.ed.companion.component.SystemDeserializer;
|
||||
|
||||
@Entity
|
||||
@JsonDeserialize(using = SystemDeserializer.class)
|
||||
public class System {
|
||||
@Id
|
||||
protected long id;
|
||||
|
||||
protected String name;
|
||||
|
||||
protected Point location;
|
||||
|
||||
public System() {
|
||||
}
|
||||
|
||||
public System(long id, String name, Point location) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.dietz.ed.companion.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import io.dietz.ed.companion.models.eddb.System;
|
||||
|
||||
public interface SystemRepository extends JpaRepository<System, Long> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package io.dietz.ed.companion.service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.integration.dsl.MessageChannels;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import io.dietz.ed.companion.models.UpdateFeedItem;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.FluxSink;
|
||||
import reactor.core.publisher.FluxSink.OverflowStrategy;
|
||||
|
||||
@Service
|
||||
public class UpdateFeedsService {
|
||||
|
||||
private Map<String, Set<MessageHandler>> activeFeeds = new HashMap<>();
|
||||
|
||||
private SubscribableChannel channel = MessageChannels.publishSubscribe().get();
|
||||
|
||||
public Flux<ServerSentEvent<UpdateFeedItem>> connectToFeed(String feedId) {
|
||||
System.out.println("creating flux for '" + feedId + "'");
|
||||
Flux<UpdateFeedItem> flux = Flux
|
||||
.create((FluxSink<UpdateFeedItem> sink) -> {
|
||||
MessageHandler handler = message -> sink.next(UpdateFeedItem.class.cast(message.getPayload()));
|
||||
sink.onCancel(() -> {
|
||||
channel.unsubscribe(handler);
|
||||
removeActiveFeedHandler(feedId, handler);
|
||||
|
||||
System.out.println("connection '" + feedId + "' closed");
|
||||
});
|
||||
channel.subscribe(handler);
|
||||
storeActiveFeedHandler(feedId, handler);
|
||||
|
||||
System.out.println("connection '" + feedId + "' opened");
|
||||
}, OverflowStrategy.LATEST)
|
||||
.filter(updateFeedItem -> updateFeedItem.getFeedId().equals(feedId));
|
||||
|
||||
return wrap(flux);
|
||||
}
|
||||
|
||||
private <T> Flux<ServerSentEvent<T>> wrap(Flux<T> fluxToWrap) {
|
||||
return Flux.merge(fluxToWrap.map(t -> ServerSentEvent.builder(t).build()), Flux.interval(Duration.ofSeconds(15)).map(aLong -> ServerSentEvent.<T>builder().comment("keep alive").build()));
|
||||
}
|
||||
|
||||
private void storeActiveFeedHandler(String feedId, MessageHandler handler) {
|
||||
if(!activeFeeds.containsKey(feedId)) {
|
||||
activeFeeds.put(feedId, new HashSet<>());
|
||||
}
|
||||
activeFeeds.get(feedId).add(handler);
|
||||
}
|
||||
|
||||
private void removeActiveFeedHandler(String feedId, MessageHandler handler) {
|
||||
activeFeeds.get(feedId).remove(handler);
|
||||
if(activeFeeds.get(feedId).isEmpty()) {
|
||||
activeFeeds.remove(feedId);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasActiveFeed(String feedId) {
|
||||
return activeFeeds.containsKey(feedId);
|
||||
}
|
||||
|
||||
public void sendFeedUpdate(UpdateFeedItem updateFeedItem) {
|
||||
Message<UpdateFeedItem> message = new GenericMessage<>(updateFeedItem);
|
||||
channel.send(message);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user