From b95d2f39532512ab88ddd028ca39e7728ce281fa Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Wed, 28 Feb 2024 09:48:43 +0700 Subject: [PATCH] Implemented saving to disk storage --- .../dragonestia/picker/config/TestConfig.java | 10 +- .../picker/controller/NodeController.java | 6 +- .../picker/controller/RoomController.java | 6 +- .../ru/dragonestia/picker/model/Node.java | 2 +- .../ru/dragonestia/picker/model/Room.java | 5 +- .../picker/model/type/SlotLimit.java | 4 +- .../picker/repository/NodeRepository.java | 3 +- .../picker/repository/RoomRepository.java | 2 +- .../repository/impl/NodeRepositoryImpl.java | 5 +- .../repository/impl/RoomRepositoryImpl.java | 5 +- .../picker/service/RoomService.java | 8 +- .../picker/service/impl/NodeServiceImpl.java | 8 +- .../picker/service/impl/RoomServiceImpl.java | 14 ++- .../picker/storage/NodeAndRoomStorage.java | 17 +++ .../picker/storage/impl/FileStorageImpl.java | 108 ++++++++++++++++++ .../picker/storage/impl/NullStorageImpl.java | 27 +++++ .../picker/cp/config/ServerConfig.java | 2 +- 17 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/ru/dragonestia/picker/storage/NodeAndRoomStorage.java create mode 100644 app/src/main/java/ru/dragonestia/picker/storage/impl/FileStorageImpl.java create mode 100644 app/src/main/java/ru/dragonestia/picker/storage/impl/NullStorageImpl.java diff --git a/app/src/main/java/ru/dragonestia/picker/config/TestConfig.java b/app/src/main/java/ru/dragonestia/picker/config/TestConfig.java index 9284107..2204764 100644 --- a/app/src/main/java/ru/dragonestia/picker/config/TestConfig.java +++ b/app/src/main/java/ru/dragonestia/picker/config/TestConfig.java @@ -42,9 +42,9 @@ public class TestConfig implements WebMvcConfigurer { @Bean void createNodes() { - createNodeWithContent(new Node("game-servers", PickingMode.ROUND_ROBIN)); - createNodeWithContent(new Node("game-lobbies", PickingMode.LEAST_PICKED)); - createNodeWithContent(new Node("hub", PickingMode.SEQUENTIAL_FILLING)); + createNodeWithContent(new Node("game-servers", PickingMode.ROUND_ROBIN, false)); + createNodeWithContent(new Node("game-lobbies", PickingMode.LEAST_PICKED, false)); + createNodeWithContent(new Node("hub", PickingMode.SEQUENTIAL_FILLING, false)); } @SneakyThrows @@ -54,7 +54,7 @@ public class TestConfig implements WebMvcConfigurer { for (int i = 1; i <= 5; i++) { var slots = 5 * i; - var room = Room.create("test-" + i, node, SlotLimit.of(slots), json.writeValueAsString(generatePayload())); + var room = Room.create("test-" + i, node, SlotLimit.of(slots), json.writeValueAsString(generatePayload()), false); roomRepository.create(room); for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) { @@ -64,7 +64,7 @@ public class TestConfig implements WebMvcConfigurer { } for (int i = 0; i < 5; i++) { - var room = Room.create(randomUUID().toString(), node, SlotLimit.unlimited(), json.writeValueAsString(generatePayload())); + var room = Room.create(randomUUID().toString(), node, SlotLimit.unlimited(), json.writeValueAsString(generatePayload()), false); room.setLocked((i & 1) == 0); roomRepository.create(room); } diff --git a/app/src/main/java/ru/dragonestia/picker/controller/NodeController.java b/app/src/main/java/ru/dragonestia/picker/controller/NodeController.java index a8e2932..6f352c4 100644 --- a/app/src/main/java/ru/dragonestia/picker/controller/NodeController.java +++ b/app/src/main/java/ru/dragonestia/picker/controller/NodeController.java @@ -41,10 +41,10 @@ public class NodeController { @PostMapping ResponseEntity registerNode( @Parameter(description = "Node identifier") @RequestParam(name = "nodeId") String nodeId, - @Parameter(description = "Picking mode method") @RequestParam(name = "method") PickingMode method + @Parameter(description = "Picking mode method") @RequestParam(name = "method") PickingMode method, + @Parameter(description = "Save node") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist ) { - - nodeService.create(new Node(nodeId, method)); + nodeService.create(new Node(nodeId, method, persist)); return ResponseEntity.ok().build(); } diff --git a/app/src/main/java/ru/dragonestia/picker/controller/RoomController.java b/app/src/main/java/ru/dragonestia/picker/controller/RoomController.java index fe4095c..d76aac2 100644 --- a/app/src/main/java/ru/dragonestia/picker/controller/RoomController.java +++ b/app/src/main/java/ru/dragonestia/picker/controller/RoomController.java @@ -50,10 +50,11 @@ public class RoomController { @Parameter(description = "Room identifier") @RequestParam(name = "roomId") String roomId, @Parameter(description = "Maximum users count in room") @RequestParam(name = "slots") int slots, @Parameter(description = "Payload. Some data") @RequestParam(name = "payload") String payload, - @Parameter(description = "Lock for picking") @RequestParam(name = "locked", defaultValue = "false") boolean locked + @Parameter(description = "Lock for picking") @RequestParam(name = "locked", required = false, defaultValue = "false") boolean locked, + @Parameter(description = "Save room") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist ) { var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); - var room = Room.create(roomId, node, SlotLimit.of(slots), payload); + var room = Room.create(roomId, node, SlotLimit.of(slots), payload, persist); room.setLocked(locked); roomService.create(room); @@ -105,6 +106,7 @@ public class RoomController { var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); var room = roomService.find(node, roomId).orElseThrow(() -> new RoomNotFoundException(nodeId, roomId)); room.setLocked(value); + roomService.updateState(room); return ResponseEntity.ok(true); } } diff --git a/app/src/main/java/ru/dragonestia/picker/model/Node.java b/app/src/main/java/ru/dragonestia/picker/model/Node.java index d60b16a..7c04fd6 100644 --- a/app/src/main/java/ru/dragonestia/picker/model/Node.java +++ b/app/src/main/java/ru/dragonestia/picker/model/Node.java @@ -4,7 +4,7 @@ import lombok.NonNull; import ru.dragonestia.picker.api.repository.response.type.RNode; import ru.dragonestia.picker.api.repository.response.type.type.PickingMode; -public record Node(@NonNull String id, @NonNull PickingMode mode) { +public record Node(@NonNull String id, @NonNull PickingMode mode, boolean persist) { @Override public int hashCode() { diff --git a/app/src/main/java/ru/dragonestia/picker/model/Room.java b/app/src/main/java/ru/dragonestia/picker/model/Room.java index 5f6da44..85b7e18 100644 --- a/app/src/main/java/ru/dragonestia/picker/model/Room.java +++ b/app/src/main/java/ru/dragonestia/picker/model/Room.java @@ -16,10 +16,11 @@ public class Room { private final String nodeId; private final SlotLimit slots; private final String payload; + private final boolean persist; private boolean locked = false; - public static Room create(String roomId, Node node, SlotLimit limit, String payload) { - return new Room(roomId, node.id(), limit, payload); + public static Room create(String roomId, Node node, SlotLimit limit, String payload, boolean persist) { + return new Room(roomId, node.id(), limit, payload, persist); } public void setLocked(boolean value) { diff --git a/app/src/main/java/ru/dragonestia/picker/model/type/SlotLimit.java b/app/src/main/java/ru/dragonestia/picker/model/type/SlotLimit.java index d5a11ba..45269d0 100644 --- a/app/src/main/java/ru/dragonestia/picker/model/type/SlotLimit.java +++ b/app/src/main/java/ru/dragonestia/picker/model/type/SlotLimit.java @@ -9,7 +9,9 @@ public class SlotLimit { public final static int UNLIMITED_VALUE = -1; - private final int slots; + private int slots; + + private SlotLimit() {} private SlotLimit(int slots) { this.slots = slots; diff --git a/app/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java b/app/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java index 61d0367..7aa60da 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java @@ -2,6 +2,7 @@ package ru.dragonestia.picker.repository; import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; import java.util.List; import java.util.Optional; @@ -10,7 +11,7 @@ public interface NodeRepository { void create(Node node) throws NodeAlreadyExistException; - void delete(Node node); + List delete(Node node); Optional find(String nodeId); diff --git a/app/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java b/app/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java index 32de79b..83d1179 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java @@ -29,5 +29,5 @@ public interface RoomRepository { void onCreateNode(Node node); - void onRemoveNode(Node node); + List onRemoveNode(Node node); } diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java index e7b5f45..f1bbf98 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.repository.NodeRepository; import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache; @@ -38,14 +39,14 @@ public class NodeRepositoryImpl implements NodeRepository { } @Override - public void delete(Node node) { + public List delete(Node node) { synchronized (nodeMap) { nodeMap.remove(node.id()); pickerRepository.remove(node.id()); nodeId2PickerModeCache.remove(node.id()); } - roomRepository.onRemoveNode(node); + return roomRepository.onRemoveNode(node); } @Override diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java index 0267f7d..c033da9 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java @@ -122,10 +122,13 @@ public class RoomRepositoryImpl implements RoomRepository { } @Override - public void onRemoveNode(Node node) { + public List onRemoveNode(Node node) { + List deleted; synchronized (node2roomsMap) { + deleted = node2roomsMap.get(node).values().stream().map(container -> container.room).toList(); node2roomsMap.remove(node); } + return deleted; } private record RoomContainer(Room room, AtomicInteger used) { diff --git a/app/src/main/java/ru/dragonestia/picker/service/RoomService.java b/app/src/main/java/ru/dragonestia/picker/service/RoomService.java index 6bc35d1..848d357 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/RoomService.java +++ b/app/src/main/java/ru/dragonestia/picker/service/RoomService.java @@ -24,11 +24,7 @@ public interface RoomService { List getAllRoomsWithDetailsResponse(Node node, Set details); - default int countAvailable(Node node) { - return countAvailable(node, 1); - } - - int countAvailable(Node node, int requiredSlots); - Room pickAvailable(Node node, List users); + + void updateState(Room room); } diff --git a/app/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java b/app/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java index 3d4c9ac..c37b81c 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java @@ -9,6 +9,7 @@ import ru.dragonestia.picker.api.repository.response.type.RNode; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.repository.NodeRepository; import ru.dragonestia.picker.service.NodeService; +import ru.dragonestia.picker.storage.NodeAndRoomStorage; import ru.dragonestia.picker.util.DetailsExtractor; import ru.dragonestia.picker.util.NamingValidator; @@ -24,16 +25,21 @@ public class NodeServiceImpl implements NodeService { private final NodeRepository nodeRepository; private final DetailsExtractor detailsExtractor; private final NamingValidator namingValidator; + private final NodeAndRoomStorage storage; @Override public void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException { namingValidator.validateNodeId(node.id()); nodeRepository.create(node); + storage.saveNode(node); } @Override public void remove(Node node) { - nodeRepository.delete(node); + for (var room: nodeRepository.delete(node)) { + storage.removeRoom(room); + } + storage.removeNode(node); } @Override diff --git a/app/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java b/app/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java index 05a071b..07dc55a 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java @@ -12,6 +12,7 @@ import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.service.RoomService; +import ru.dragonestia.picker.storage.NodeAndRoomStorage; import ru.dragonestia.picker.util.DetailsExtractor; import ru.dragonestia.picker.util.NamingValidator; @@ -28,16 +29,19 @@ public class RoomServiceImpl implements RoomService { private final RoomRepository roomRepository; private final DetailsExtractor detailsExtractor; private final NamingValidator namingValidator; + private final NodeAndRoomStorage storage; @Override public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException { namingValidator.validateRoomId(room.getNodeId(), room.getId()); roomRepository.create(room); + storage.saveRoom(room); } @Override public void remove(Room room) { roomRepository.remove(room); + storage.removeRoom(room); } @Override @@ -59,14 +63,14 @@ public class RoomServiceImpl implements RoomService { return response; } - @Override - public int countAvailable(Node node, int requiredSlots) { - throw new RuntimeException("Not implemented"); - } - @Override public Room pickAvailable(Node node, List users) { return roomRepository.pickFree(node, users) .orElseThrow(() -> new RuntimeException("There are no rooms available")); } + + @Override + public void updateState(Room room) { + storage.saveRoom(room); + } } diff --git a/app/src/main/java/ru/dragonestia/picker/storage/NodeAndRoomStorage.java b/app/src/main/java/ru/dragonestia/picker/storage/NodeAndRoomStorage.java new file mode 100644 index 0000000..c16b9f0 --- /dev/null +++ b/app/src/main/java/ru/dragonestia/picker/storage/NodeAndRoomStorage.java @@ -0,0 +1,17 @@ +package ru.dragonestia.picker.storage; + +import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; + +public interface NodeAndRoomStorage { + + void loadAll(); + + void saveNode(Node node); + + void removeNode(Node node); + + void saveRoom(Room room); + + void removeRoom(Room room); +} diff --git a/app/src/main/java/ru/dragonestia/picker/storage/impl/FileStorageImpl.java b/app/src/main/java/ru/dragonestia/picker/storage/impl/FileStorageImpl.java new file mode 100644 index 0000000..529708c --- /dev/null +++ b/app/src/main/java/ru/dragonestia/picker/storage/impl/FileStorageImpl.java @@ -0,0 +1,108 @@ +package ru.dragonestia.picker.storage.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; +import ru.dragonestia.picker.repository.NodeRepository; +import ru.dragonestia.picker.repository.RoomRepository; +import ru.dragonestia.picker.storage.NodeAndRoomStorage; + +import java.io.File; +import java.util.Objects; + +@Log4j2 +@Profile("!test") +@Component +@RequiredArgsConstructor +public class FileStorageImpl implements NodeAndRoomStorage { + + @Value("${ROOMPICKER_DATA_PATH:./appdata}") + private String path; + + private final NodeRepository nodeRepository; + private final RoomRepository roomRepository; + private final ObjectMapper objectMapper; + + @PostConstruct + @Override + public void loadAll() { + var dir = new File(path); + var reader = objectMapper.reader(); + + if (!dir.exists()) dir.mkdirs(); + + var nodeDir = new File(path + "/nodes"); + if (!nodeDir.exists()) nodeDir.mkdir(); + for (var file: Objects.requireNonNull(nodeDir.listFiles(File::isFile))) { + try { + var node = reader.readValue(file, Node.class); + nodeRepository.create(node); + } catch (Exception ex) { + log.error("Cannot read node file '%s'".formatted(file.getName())); + log.throwing(ex); + } + } + + var roomDir = new File(path + "/rooms"); + if (!roomDir.exists()) roomDir.mkdir(); + for (var file: Objects.requireNonNull(roomDir.listFiles(File::isFile))) { + try { + var room = reader.readValue(file, Room.class); + roomRepository.create(room); + } catch (Exception ex) { + log.error("Cannot read room file '%s'".formatted(file.getName())); + log.throwing(ex); + } + } + } + + @Override + public void saveNode(Node node) { + if (!node.persist()) return; + var nodeFile = new File(path + "/nodes/" + node.id() + ".json"); + var writer = objectMapper.writer(); + + try { + writer.writeValue(nodeFile, node); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public void removeNode(Node node) { + if (!node.persist()) return; + new File(path + "/nodes/" + node.id() + ".json").delete(); + + log.info("Removed node '%s' from disk storage".formatted(node.id())); + } + + @SneakyThrows + @Override + public void saveRoom(Room room) { + if (!room.isPersist()) return; + var roomFile = new File(path + "/rooms/" + room.getNodeId() + "." + room.getId() + ".json"); + var writer = objectMapper.writer(); + + try { + writer.writeValue(roomFile, room); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public void removeRoom(Room room) { + if (!room.isPersist()) return; + new File(path + "/rooms/" + room.getNodeId() + "." + room.getId() + ".json").delete(); + + log.info("Removed room '%s/%s' from disk storage".formatted(room.getNodeId(), room.getId())); + } +} diff --git a/app/src/main/java/ru/dragonestia/picker/storage/impl/NullStorageImpl.java b/app/src/main/java/ru/dragonestia/picker/storage/impl/NullStorageImpl.java new file mode 100644 index 0000000..b51c9b2 --- /dev/null +++ b/app/src/main/java/ru/dragonestia/picker/storage/impl/NullStorageImpl.java @@ -0,0 +1,27 @@ +package ru.dragonestia.picker.storage.impl; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; +import ru.dragonestia.picker.storage.NodeAndRoomStorage; + +@Profile("test") +@Component +public class NullStorageImpl implements NodeAndRoomStorage { + + @Override + public void loadAll() {} + + @Override + public void saveNode(Node node) {} + + @Override + public void removeNode(Node node) {} + + @Override + public void saveRoom(Room room) {} + + @Override + public void removeRoom(Room room) {} +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/config/ServerConfig.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/config/ServerConfig.java index 8800fd9..5e33e37 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/config/ServerConfig.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/config/ServerConfig.java @@ -9,7 +9,7 @@ import java.net.URI; @Configuration public class ServerConfig { - @Value("${DLB_HOST_URL:http://localhost:8080/}") + @Value("${ROOMPICKER_HOST_URL:http://localhost:8080/}") private String serverUrl; @Bean