//The MIT License // //Copyright (c) 2009 nodchip // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in //all copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. package tv.dyndns.kishibe.qmaclone.server; import java.util.Map.Entry; import java.util.NavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.inject.Inject; import tv.dyndns.kishibe.qmaclone.client.constant.Constant; import tv.dyndns.kishibe.qmaclone.client.packet.PacketChatMessage; import tv.dyndns.kishibe.qmaclone.client.packet.PacketChatMessages; import tv.dyndns.kishibe.qmaclone.client.packet.RestrictionType; import tv.dyndns.kishibe.qmaclone.server.database.Database; import tv.dyndns.kishibe.qmaclone.server.database.DatabaseException; import tv.dyndns.kishibe.qmaclone.server.websocket.MessageSender; public class ChatManager { private static final Logger logger = Logger.getLogger(ChatManager.class.getName()); private static final Object STATIC_KEY = new Object(); private final LoadingCache<Object, NavigableMap<Integer, PacketChatMessage>> data = CacheBuilder .newBuilder().concurrencyLevel(1) .build(new CacheLoader<Object, NavigableMap<Integer, PacketChatMessage>>() { @Override public NavigableMap<Integer, PacketChatMessage> load(Object arg0) throws Exception { return loadData(); } }); private final Object writeLock = new Object(); private final MessageSender<PacketChatMessages> chatMessagesMessageSender; private final Database database; private final ThreadPool threadPool; private final RestrictedUserUtils restrictedUserUtils; private final ChatPostCounter chatPostCounter; @Inject public ChatManager(Database database, ThreadPool threadPool, RestrictedUserUtils restrictedUserUtils, ChatPostCounter chatPostCounter, ThreadPool threadPool2, MessageSender<PacketChatMessages> chatMessagesMessageSender) { this.database = Preconditions.checkNotNull(database); this.threadPool = Preconditions.checkNotNull(threadPool); this.restrictedUserUtils = Preconditions.checkNotNull(restrictedUserUtils); this.chatMessagesMessageSender = Preconditions.checkNotNull(chatMessagesMessageSender); this.chatPostCounter = Preconditions.checkNotNull(chatPostCounter); threadPool.addMinuteTasks(chatPostCounter); } private NavigableMap<Integer, PacketChatMessage> loadData() { NavigableMap<Integer, PacketChatMessage> data = new ConcurrentSkipListMap<Integer, PacketChatMessage>(); try { data.putAll(database.getLatestChatData()); } catch (DatabaseException e) { logger.log(Level.WARNING, "チャットデータの読み込みに失敗しました", e); } return data; } private NavigableMap<Integer, PacketChatMessage> getData() { try { return data.get(STATIC_KEY); } catch (ExecutionException e) { logger.log(Level.WARNING, "チャットデータの取得に失敗しました", e); return null; } } /** * 発言を追加する * * @param message * 発言 * @param remoteAddress * クライアント側IPアドレス */ public void write(final PacketChatMessage message, String remoteAddress) { chatPostCounter.add(message.userCode, remoteAddress); if (!chatPostCounter.isAbleToPost(message.userCode, remoteAddress)) { try { database.addRestrictedUserCode(message.userCode, RestrictionType.CHAT); if (!remoteAddress.equals("127.0.0.1")) { database.addRestrictedRemoteAddress(remoteAddress, RestrictionType.CHAT); } } catch (DatabaseException e) { logger.log(Level.WARNING, "制限ユーザーの追加に失敗しました。処理を続行します。", e); } } NavigableMap<Integer, PacketChatMessage> data = getData(); message.remoteAddress = remoteAddress; message.date = System.currentTimeMillis(); synchronized (writeLock) { message.resId = data.isEmpty() ? 1 : (data.lastEntry().getValue().resId + 1); data.put(message.resId, message); chatMessagesMessageSender.send(PacketChatMessages.fromMessage(message)); } data.remove(message.resId - Constant.CHAT_MAX_RESPONSES - 1); threadPool.execute(new Runnable() { public void run() { try { database.addChatLog(message); } catch (DatabaseException e) { logger.log(Level.WARNING, "チャットデータの保存に失敗しました", e); } } }); } /** * 発言を読み込む * * @param nextResponseId * 次に読み込むレスポンス番号 * @return 発言リスト */ public PacketChatMessages read(int nextResponseId) { NavigableMap<Integer, PacketChatMessage> data = getData(); for (Entry<Integer, PacketChatMessage> entry : data.entrySet()) { PacketChatMessage chatData = entry.getValue(); int userCode = chatData.userCode; String remoteAddress = chatData.remoteAddress; try { chatData.restricted = restrictedUserUtils.checkAndUpdateRestrictedUser(userCode, remoteAddress, RestrictionType.CHAT); } catch (DatabaseException e) { logger.log(Level.WARNING, "制限ユーザーの取得に失敗しました。処理を続行します。", entry); } } return PacketChatMessages.fromMessages(data.tailMap(nextResponseId).values()); } /** * チャットメッセージ送信インスタンスを返す * * @return チャットメッセージ送信インスタンス */ public MessageSender<PacketChatMessages> getChatMessagesMessageSender() { return chatMessagesMessageSender; } }