Add NoteService to handle note operations

NoteService should be the only class with access to NoteDao;
nowhere else should NoteDao be accessed directly.

Channel dependencies are static in PacketProcessor, for now.
Ideally they would be injected in the constructor,
but since the constructor is private and I don't want to open
up that can of worms, I'll leave it like this.
At the very least, now we have a way of injecting services into
the handlers. This will make further restructuring way easier.
This commit is contained in:
P0nk
2022-12-27 10:34:55 +01:00
parent 5f1f5b7dcd
commit 389b3ad2a4
15 changed files with 199 additions and 110 deletions

View File

@@ -41,9 +41,6 @@ import constants.id.MapId;
import constants.id.MobId;
import constants.inventory.ItemConstants;
import constants.skills.*;
import database.DaoException;
import database.NoteDao;
import model.Note;
import net.packet.Packet;
import net.server.PlayerBuffValueHolder;
import net.server.PlayerCoolDownValueHolder;
@@ -9627,18 +9624,6 @@ public class Character extends AbstractCharacterObject {
client.announceHint(msg, length);
}
public void showNote() {
final List<Note> notes;
try {
notes = NoteDao.findAllByTo(name);
} catch (DaoException e) {
log.error("Failed to find notes for chr name {}", name, e);
return;
}
sendPacket(PacketCreator.showNotes(notes));
}
public void silentGiveBuffs(List<Pair<Long, PlayerBuffValueHolder>> buffs) {
for (Pair<Long, PlayerBuffValueHolder> mbsv : buffs) {
PlayerBuffValueHolder mbsvh = mbsv.getRight();

View File

@@ -31,7 +31,7 @@ import client.inventory.Item;
import client.inventory.ItemFactory;
import client.inventory.manipulator.InventoryManipulator;
import database.DaoException;
import database.NoteDao;
import database.note.NoteDao;
import model.Note;
import net.server.Server;
import net.server.world.World;

View File

@@ -1,5 +1,6 @@
package database;
package database.note;
import database.DaoException;
import model.Note;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.JdbiException;
@@ -27,7 +28,7 @@ public class NoteDao {
}
}
public static List<Note> findAllByTo(String to) {
public List<Note> findAllByTo(String to) {
try (Handle handle = DatabaseConnection.getHandle()) {
return handle.createQuery("""
SELECT *
@@ -38,11 +39,11 @@ public class NoteDao {
.mapTo(Note.class)
.list();
} catch (JdbiException e) {
throw new DaoException("Failed to find notes with \"to\": %s".formatted(to), e);
throw new DaoException("Failed to find notes sent to: %s".formatted(to), e);
}
}
public static Optional<Note> delete(int id) {
public Optional<Note> delete(int id) {
try (Handle handle = DatabaseConnection.getHandle()) {
Optional<Note> note = findById(handle, id);
if (note.isEmpty()) {

View File

@@ -1,4 +1,4 @@
package database;
package database.note;
import model.Note;
import org.jdbi.v3.core.mapper.RowMapper;

View File

@@ -0,0 +1,12 @@
package net;
import service.NoteService;
import java.util.Objects;
public record ChannelDependencies(NoteService noteService) {
public ChannelDependencies {
Objects.requireNonNull(noteService);
}
}

View File

@@ -37,6 +37,9 @@ import java.util.Map;
public final class PacketProcessor {
private static final Logger log = LoggerFactory.getLogger(PacketProcessor.class);
private static final Map<String, PacketProcessor> instances = new LinkedHashMap<>();
private static ChannelDependencies channelDependencies;
private PacketHandler[] handlers;
private PacketProcessor() {
@@ -49,11 +52,19 @@ public final class PacketProcessor {
handlers = new PacketHandler[maxRecvOp + 1];
}
public static void registerGameHandlerDependencies(ChannelDependencies channelDependencies) {
PacketProcessor.channelDependencies = channelDependencies;
}
public static PacketProcessor getLoginServerProcessor() {
return getProcessor(LoginServer.WORLD_ID, LoginServer.CHANNEL_ID);
}
public static PacketProcessor getChannelServerProcessor(int world, int channel) {
if (channelDependencies == null) {
throw new IllegalStateException("Unable to get channel server processor - dependencies are not registered");
}
return getProcessor(world, channel);
}
@@ -141,7 +152,7 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.ITEM_SORT, new InventoryMergeHandler());
registerHandler(RecvOpcode.ITEM_MOVE, new ItemMoveHandler());
registerHandler(RecvOpcode.MESO_DROP, new MesoDropHandler());
registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler());
registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler(channelDependencies.noteService()));
registerHandler(RecvOpcode.CHANGE_MAP, new ChangeMapHandler());
registerHandler(RecvOpcode.MOVE_LIFE, new MoveLifeHandler());
registerHandler(RecvOpcode.CLOSE_RANGE_ATTACK, new CloseRangeDamageHandler());
@@ -149,7 +160,7 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.MAGIC_ATTACK, new MagicDamageHandler());
registerHandler(RecvOpcode.TAKE_DAMAGE, new TakeDamageHandler());
registerHandler(RecvOpcode.MOVE_PLAYER, new MovePlayerHandler());
registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler());
registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler(channelDependencies.noteService()));
registerHandler(RecvOpcode.USE_ITEM, new UseItemHandler());
registerHandler(RecvOpcode.USE_RETURN_SCROLL, new UseItemHandler());
registerHandler(RecvOpcode.USE_UPGRADE_SCROLL, new ScrollHandler());
@@ -191,7 +202,7 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.MESSENGER, new MessengerHandler());
registerHandler(RecvOpcode.NPC_ACTION, new NPCAnimationHandler());
registerHandler(RecvOpcode.CHECK_CASH, new TouchingCashShopHandler());
registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler());
registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler(channelDependencies.noteService()));
registerHandler(RecvOpcode.COUPON_CODE, new CouponCodeHandler());
registerHandler(RecvOpcode.SPAWN_PET, new SpawnPetHandler());
registerHandler(RecvOpcode.MOVE_PET, new MovePetHandler());
@@ -204,11 +215,11 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.CANCEL_DEBUFF, new CancelDebuffHandler());
registerHandler(RecvOpcode.USE_SKILL_BOOK, new SkillBookHandler());
registerHandler(RecvOpcode.SKILL_MACRO, new SkillMacroHandler());
registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler());
registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler(channelDependencies.noteService()));
registerHandler(RecvOpcode.CLOSE_CHALKBOARD, new CloseChalkboardHandler());
registerHandler(RecvOpcode.USE_MOUNT_FOOD, new UseMountFoodHandler());
registerHandler(RecvOpcode.MTS_OPERATION, new MTSHandler());
registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler());
registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler(channelDependencies.noteService()));
registerHandler(RecvOpcode.SPOUSE_CHAT, new SpouseChatHandler());
registerHandler(RecvOpcode.PET_AUTO_POT, new PetAutoPotHandler());
registerHandler(RecvOpcode.PET_EXCLUDE_ITEMS, new PetExcludeItemsHandler());

View File

@@ -35,6 +35,9 @@ import constants.game.GameConstants;
import constants.inventory.ItemConstants;
import constants.net.OpcodeConstants;
import constants.net.ServerConstants;
import database.note.NoteDao;
import net.ChannelDependencies;
import net.PacketProcessor;
import net.netty.LoginServer;
import net.packet.Packet;
import net.server.channel.Channel;
@@ -55,6 +58,7 @@ import server.TimerManager;
import server.expeditions.ExpeditionBossLog;
import server.life.PlayerNPCFactory;
import server.quest.Quest;
import service.NoteService;
import tools.DatabaseConnection;
import tools.Pair;
@@ -838,6 +842,8 @@ public class Server {
throw new IllegalStateException("Failed to initiate a connection to the database");
}
registerChannelDependencies();
final ExecutorService initExecutor = Executors.newFixedThreadPool(10);
// Run slow operations asynchronously to make startup faster
final List<Future<?>> futures = new ArrayList<>();
@@ -914,6 +920,16 @@ public class Server {
}
}
private void registerChannelDependencies() {
NoteDao noteDao = new NoteDao();
ChannelDependencies channelDependencies = new ChannelDependencies(
new NoteService(noteDao)
);
PacketProcessor.registerGameHandlerDependencies(channelDependencies);
}
private LoginServer initLoginServer(int port) {
LoginServer loginServer = new LoginServer(port);
loginServer.start();

View File

@@ -32,9 +32,6 @@ import client.inventory.manipulator.InventoryManipulator;
import config.YamlConfig;
import constants.id.ItemId;
import constants.inventory.ItemConstants;
import database.DaoException;
import database.NoteDao;
import model.Note;
import net.AbstractPacketHandler;
import net.packet.InPacket;
import net.server.Server;
@@ -44,10 +41,10 @@ import server.CashShop;
import server.CashShop.CashItem;
import server.CashShop.CashItemFactory;
import server.ItemInformationProvider;
import service.NoteService;
import tools.PacketCreator;
import tools.Pair;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
@@ -57,6 +54,12 @@ import static java.util.concurrent.TimeUnit.DAYS;
public final class CashOperationHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(CashOperationHandler.class);
private final NoteService noteService;
public CashOperationHandler(NoteService noteService) {
this.noteService = noteService;
}
@Override
public void handlePacket(InPacket p, Client c) {
Character chr = c.getPlayer();
@@ -132,11 +135,12 @@ public final class CashOperationHandler extends AbstractPacketHandler {
c.sendPacket(PacketCreator.showGiftSucceed(recipient.get("name"), cItem));
c.sendPacket(PacketCreator.showCash(chr));
sendGiftNotificationNote(chr.getName(), recipient.get("name"));
String noteMessage = chr.getName() + " has sent you a gift! Go check out the Cash Shop.";
noteService.sendNormal(noteMessage, chr.getName(), recipient.get("name"));
Character receiver = c.getChannelServer().getPlayerStorage().getCharacterByName(recipient.get("name"));
if (receiver != null) {
receiver.showNote();
noteService.show(receiver);
}
} else if (action == 0x05) { // Modify wish list
cs.clearWishList();
@@ -331,8 +335,8 @@ public final class CashOperationHandler extends AbstractPacketHandler {
cs.gainCash(toCharge, itemRing, chr.getWorld());
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight());
chr.addCrushRing(Ring.loadFromDb(rings.getLeft()));
sendGiftNote(text, chr.getName(), partner.getName());
partner.showNote();
noteService.sendWithFame(text, chr.getName(), partner.getName());
noteService.show(partner);
}
}
} else {
@@ -390,8 +394,8 @@ public final class CashOperationHandler extends AbstractPacketHandler {
cs.gainCash(payment, -itemRing.getPrice());
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight());
chr.addFriendshipRing(Ring.loadFromDb(rings.getLeft()));
sendGiftNote(text, chr.getName(), partner.getName());
partner.showNote();
noteService.sendWithFame(text, chr.getName(), partner.getName());
noteService.show(partner);
}
}
} else {
@@ -487,23 +491,4 @@ public final class CashOperationHandler extends AbstractPacketHandler {
return false;
}
}
private void sendGiftNote(String message, String from, String to) {
Note giftNote = Note.createGift(message, from, to, Server.getInstance().getCurrentTime());
sendGiftNote(giftNote);
}
private void sendGiftNotificationNote(String from, String to) {
String message = from + " has sent you a gift! Go check out the Cash Shop.";
Note giftNotificationNote = Note.createNormal(message, from, to, Server.getInstance().getCurrentTime());
sendGiftNote(giftNotificationNote);
}
private void sendGiftNote(Note giftNote) {
try {
NoteDao.save(giftNote);
} catch (DaoException e) {
log.error("Failed to send gift note {}", giftNote, e);
}
}
}

View File

@@ -22,22 +22,25 @@
package net.server.channel.handlers;
import client.Client;
import database.DaoException;
import database.NoteDao;
import model.Note;
import net.AbstractPacketHandler;
import net.packet.InPacket;
import net.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import service.NoteService;
import tools.PacketCreator;
import java.sql.SQLException;
import java.util.Optional;
public final class NoteActionHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(NoteActionHandler.class);
private final NoteService noteService;
public NoteActionHandler(NoteService noteService) {
this.noteService = noteService;
}
@Override
public void handlePacket(InPacket p, Client c) {
int action = p.readByte();
@@ -48,7 +51,7 @@ public final class NoteActionHandler extends AbstractPacketHandler {
c.sendPacket(PacketCreator.showCashInventory(c));
}
boolean sendNoteSuccess = sendGiftReplyNote(message, c.getPlayer().getName(), charname);
boolean sendNoteSuccess = noteService.sendWithFame(message, c.getPlayer().getName(), charname);
if (sendNoteSuccess) {
c.getPlayer().getCashShop().decreaseNotes();
}
@@ -61,36 +64,17 @@ public final class NoteActionHandler extends AbstractPacketHandler {
int id = p.readInt();
p.readByte(); //Fame, but we read it from the database :)
final Optional<Note> note;
try {
note = NoteDao.delete(id);
} catch (DaoException e) {
log.error("Failed to delete note {}", id, e);
Optional<Note> discardedNote = noteService.discard(id);
if (discardedNote.isEmpty()) {
log.warn("Note with id {} not able to be discarded. Already discarded?", id);
continue;
}
if (note.isEmpty()) {
log.warn("Note with id {} not able to be deleted. Already deleted?", id);
continue;
}
fame += note.get().fame();
fame += discardedNote.get().fame();
}
if (fame > 0) {
c.getPlayer().gainFame(fame);
}
}
}
private boolean sendGiftReplyNote(String message, String from, String to) {
Note giftReplyNote = Note.createGift(message, from, to, Server.getInstance().getCurrentTime());
try {
NoteDao.save(giftReplyNote);
} catch (DaoException e) {
log.error("Failed to send gift reply note {}", giftReplyNote, e);
return false;
}
return true;
}
}

View File

@@ -46,6 +46,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scripting.event.EventInstanceManager;
import server.life.MobSkill;
import service.NoteService;
import tools.DatabaseConnection;
import tools.PacketCreator;
import tools.Pair;
@@ -63,6 +64,12 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(PlayerLoggedinHandler.class);
private static final Set<Integer> attemptingLoginAccounts = new HashSet<>();
private final NoteService noteService;
public PlayerLoggedinHandler(NoteService noteService) {
this.noteService = noteService;
}
private boolean tryAcquireAccount(int accId) {
synchronized (attemptingLoginAccounts) {
if (attemptingLoginAccounts.contains(accId)) {
@@ -302,7 +309,8 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
}
}
player.showNote();
noteService.show(player);
if (player.getParty() != null) {
PartyCharacter pchar = player.getMPC();

View File

@@ -30,18 +30,15 @@ import client.inventory.Item;
import client.inventory.manipulator.InventoryManipulator;
import client.processor.npc.DueyProcessor;
import constants.id.ItemId;
import database.DaoException;
import database.NoteDao;
import model.Note;
import net.AbstractPacketHandler;
import net.packet.InPacket;
import net.server.Server;
import net.server.channel.Channel;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scripting.event.EventInstanceManager;
import server.ItemInformationProvider;
import service.NoteService;
import tools.DatabaseConnection;
import tools.PacketCreator;
import tools.Pair;
@@ -60,6 +57,12 @@ import java.sql.SQLException;
public final class RingActionHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(RingActionHandler.class);
private final NoteService noteService;
public RingActionHandler(NoteService noteService) {
this.noteService = noteService;
}
private static int getEngagementBoxId(int useItemId) {
return switch (useItemId) {
case ItemId.ENGAGEMENT_BOX_MOONSTONE -> ItemId.EMPTY_ENGAGEMENT_BOX_MOONSTONE;
@@ -431,7 +434,7 @@ public final class RingActionHandler extends AbstractPacketHandler {
if (guestChr != null && guestChr.isLoggedinWorld()) {
guestChr.dropMessage(6, "[Wedding] %s".formatted(dueyMessage));
} else {
sendWeddingInvitationNote(dueyMessage, groom, name);
noteService.sendNormal(dueyMessage, groom, name);
}
Item weddingTicket = new Item(newItemId, (short) 0, (short) 1);
@@ -525,13 +528,4 @@ public final class RingActionHandler extends AbstractPacketHandler {
c.sendPacket(PacketCreator.enableActions());
}
private void sendWeddingInvitationNote(String message, String from, String to) {
Note invitationNote = Note.createNormal(message, from, to, Server.getInstance().getCurrentTime());
try {
NoteDao.save(invitationNote);
} catch (DaoException e) {
log.error("Failed to save wedding invitation note: {}", invitationNote, e);
}
}
}

View File

@@ -36,9 +36,6 @@ import constants.game.GameConstants;
import constants.id.ItemId;
import constants.id.MapId;
import constants.inventory.ItemConstants;
import database.DaoException;
import database.NoteDao;
import model.Note;
import net.AbstractPacketHandler;
import net.packet.InPacket;
import net.server.Server;
@@ -49,10 +46,10 @@ import server.Shop;
import server.ShopFactory;
import server.TimerManager;
import server.maps.*;
import service.NoteService;
import tools.PacketCreator;
import tools.Pair;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -63,6 +60,12 @@ import static java.util.concurrent.TimeUnit.SECONDS;
public final class UseCashItemHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(UseCashItemHandler.class);
private final NoteService noteService;
public UseCashItemHandler(NoteService noteService) {
this.noteService = noteService;
}
@Override
public void handlePacket(InPacket p, Client c) {
final Character player = c.getPlayer();
@@ -361,13 +364,10 @@ public final class UseCashItemHandler extends AbstractPacketHandler {
} else if (itemType == 509) {
String sendTo = p.readString();
String msg = p.readString();
Note note = Note.createNormal(msg, player.getName(), sendTo, Server.getInstance().getCurrentTime());
try {
NoteDao.save(note);
} catch (DaoException e) {
log.error("Failed to save note {}", note, e);
boolean sendSuccess = noteService.sendNormal(msg, player.getName(), sendTo);
if (sendSuccess) {
remove(c, position, itemId);
}
remove(c, position, itemId);
} else if (itemType == 510) {
player.getMap().broadcastMessage(PacketCreator.musicChange("Jukebox/Congratulation"));
remove(c, position, itemId);

View File

@@ -25,7 +25,7 @@ import client.Character;
import client.Client;
import config.YamlConfig;
import database.DaoException;
import database.NoteDao;
import database.note.NoteDao;
import model.Note;
import net.packet.Packet;
import net.server.PlayerStorage;

View File

@@ -0,0 +1,93 @@
package service;
import client.Character;
import database.DaoException;
import database.note.NoteDao;
import model.Note;
import net.packet.Packet;
import net.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.PacketCreator;
import java.util.List;
import java.util.Optional;
public class NoteService {
private static final Logger log = LoggerFactory.getLogger(NoteService.class);
private final NoteDao noteDao;
public NoteService(NoteDao noteDao) {
this.noteDao = noteDao;
}
/**
* Send normal note from one character to another
*
* @return Send success
*/
public boolean sendNormal(String message, String senderName, String receiverName) {
Note normalNote = Note.createNormal(message, senderName, receiverName, Server.getInstance().getCurrentTime());
return send(normalNote);
}
/**
* Send note which will increase the receiver's fame by one.
*
* @return Send success
*/
public boolean sendWithFame(String message, String senderName, String receiverName) {
Note noteWithFame = Note.createGift(message, senderName, receiverName, Server.getInstance().getCurrentTime());
return send(noteWithFame);
}
private boolean send(Note note) {
try {
NoteDao.save(note);
return true;
} catch (DaoException e) {
log.error("Failed to send note {}", note, e);
return false;
}
}
/**
* Show unread notes
*
* @param chr Note recipient
*/
public void show(Character chr) {
if (chr == null) {
throw new IllegalArgumentException("Unable to show notes - chr is null");
}
final List<Note> notes;
try {
notes = noteDao.findAllByTo(chr.getName());
} catch (DaoException e) {
log.error("Failed to find notes sent to chr name {}", chr.getName(), e);
return;
}
Packet showNotesPacket = PacketCreator.showNotes(notes);
chr.sendPacket(showNotesPacket);
}
/**
* Discard a read note
*
* @param id Id of note to discard
* @return Discarded note. Empty optional if failed to discard.
*/
public Optional<Note> discard(int id) {
try {
return noteDao.delete(id);
} catch (DaoException e) {
log.error("Failed to discard note with id {}", id, e);
return Optional.empty();
}
}
}

View File

@@ -3,7 +3,7 @@ package tools;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import config.YamlConfig;
import database.NoteRowMapper;
import database.note.NoteRowMapper;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.Jdbi;
import org.slf4j.Logger;