initial import

This commit is contained in:
2021-05-05 19:17:01 +00:00
commit 09e1d643b6
54 changed files with 14073 additions and 0 deletions
@@ -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);
}
}