diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 70167f0..ecc6ff1 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -10,6 +10,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; public class ActionHandler { @@ -581,8 +585,17 @@ public class ActionHandler { } - public void userMenu(UUID internal_uuid) { + public void userMenu(UUID internal_uuid) throws IOException { while (true) { + + if (Session.forceRefreshChatList) { + System.out.println("🔁 Refresh triggered by real-time event."); + requestChatList(); // با sendWithResponse جواب می‌گیری + Session.forceRefreshChatList = false; + } + + + System.out.println("\nUser Menu:"); System.out.println("1. Show chat list"); System.out.println("2. Search"); @@ -607,35 +620,29 @@ public class ActionHandler { } public void showChatListAndSelect() { - if (Session.chatList == null || Session.chatList.isEmpty()) { - System.out.println("No chats available."); + + + List chatList = Session.getChatList(); + if (chatList.isEmpty()) { + System.out.println("📭 You have no chats."); return; } System.out.println("\nYour Chats:"); - for (int i = 0; i < Session.chatList.size(); i++) { - ChatEntry entry = Session.chatList.get(i); - String time = (entry.getLastMessageTime() == null) - ? "No messages yet" - : entry.getLastMessageTime().toString(); - System.out.println((i + 1) + ". [" + entry.getType() + "] " + - entry.getName() + " - Last: " + time); + for (int i = 0; i < chatList.size(); i++) { + ChatEntry entry = chatList.get(i); + String last = entry.getLastMessageTime() == null ? "No messages yet" : entry.getLastMessageTime().toString(); + System.out.printf("%d. [%s] %s - Last: %s\n", i + 1, entry.getType(), entry.getName(), last); } System.out.print("Select a chat by number: "); - int choice = Integer.parseInt(scanner.nextLine()) - 1; - - if(choice == -1){ - System.out.println("Exit..."); - return; - } - if (choice < -1 || choice >= Session.chatList.size()) { - System.out.println("Invalid selection."); + int choice = Integer.parseInt(scanner.nextLine()); + if (choice < 1 || choice > chatList.size()) { + System.out.println("❌ Invalid choice."); return; } - ChatEntry selected = Session.chatList.get(choice); - openChat(selected); + openChat(chatList.get(choice - 1)); } @@ -692,6 +699,12 @@ public class ActionHandler { private boolean showPrivateChatMenu(ChatEntry chat) { + + if (forceExitChat) { + forceExitChat = false; + System.out.println("🚪 Exiting chat due to real-time update."); + return false; + } System.out.println("1. Send message"); System.out.println("2. Block/Unblock"); System.out.println("3. Delete chat (one-sided)"); @@ -734,6 +747,12 @@ public class ActionHandler { private boolean showGroupChatMenu(ChatEntry chat) { + if (forceExitChat) { + forceExitChat = false; + System.out.println("🚪 Exiting chat due to real-time update."); + return false; + } + boolean isAdmin = chat.isAdmin(); boolean isOwner = chat.isOwner(); JSONObject perms = getGroupPermissions(chat.getId()); @@ -818,6 +837,12 @@ public class ActionHandler { private boolean showChannelChatMenu(ChatEntry chat) { + if (forceExitChat) { + forceExitChat = false; + System.out.println("🚪 Exiting chat due to real-time update."); + return false; + } + chat = fetchChatInfo(chat.getId().toString(), chat.getType()); boolean isAdmin = chat.isAdmin(); boolean isOwner = chat.isOwner(); @@ -997,7 +1022,6 @@ public class ActionHandler { leaveChat(groupId, "group"); } - private void removeMemberFromGroup(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_members"); @@ -1319,7 +1343,7 @@ public class ActionHandler { } JSONObject selected = admins.getJSONObject(choice); - String newOwnerId = selected.getString("user_id"); + String newOwnerId = selected.getString("internal_id"); // ✅ اصلاح شده JSONObject promoteReq = new JSONObject(); promoteReq.put("action", "transfer_channel_ownership"); @@ -2002,21 +2026,25 @@ public class ActionHandler { } - - - - - private JSONObject sendWithResponse(JSONObject request) { + private static JSONObject sendWithResponse(JSONObject request) { try { if (!request.has("action") || request.isNull("action")) { System.err.println("❌ Invalid request: missing action."); return null; } - String action = request.getString("action"); - this.out.println(request.toString()); + String requestId = UUID.randomUUID().toString(); + request.put("request_id", requestId); - JSONObject response = TelegramClient.responseQueue.take(); + BlockingQueue queue = new LinkedBlockingQueue<>(); + TelegramClient.pendingResponses.put(requestId, queue); + + TelegramClient.getInstance().getOut().println(request.toString()); + + // Wait for response + JSONObject response = queue.take(); + + TelegramClient.pendingResponses.remove(requestId); if (response == null) { System.out.println("⚠️ No response received."); @@ -2033,18 +2061,23 @@ public class ActionHandler { } } - public static void requestChatList() throws IOException { - System.out.println("🟢 [requestChatList] Sending chat list request..."); + public void requestChatList() { JSONObject req = new JSONObject(); req.put("action", "get_chat_list"); - req.put("user_id", TelegramClient.loggedInUserId.toString()); + req.put("user_id", Session.getUserUUID()); - System.out.println("📤 [SEND] " + req.toString(2)); - - TelegramClient.send(req); + JSONObject res = sendWithResponse(req); + if (res.getString("status").equals("success")) { + JSONArray chats = res.getJSONObject("data").getJSONArray("chat_list"); + Session.updateChatList(chats); + System.out.println("✅ Chat list updated."); + } else { + System.out.println("❌ Failed to update chat list."); + } } + public static void requestChatInfo(String chatId, String chatType) throws IOException { JSONObject req = new JSONObject(); req.put("action", "get_chat_info"); @@ -2054,32 +2087,32 @@ public class ActionHandler { } - public static void handleChatListResponse(JSONObject response) { - if (response.getString("status").equals("success")) { - JSONArray chats = response.getJSONArray("data"); - - Session.chatList.clear(); - - for (int i = 0; i < chats.length(); i++) { - JSONObject chatJson = chats.getJSONObject(i); - - UUID internalId = UUID.fromString(chatJson.getString("internal_id")); - String displayId = chatJson.getString("id"); - String name = chatJson.getString("name"); - String imageUrl = chatJson.optString("image_url", ""); - String type = chatJson.getString("type"); - LocalDateTime lastMessageTime = LocalDateTime.parse(chatJson.getString("last_message_time")); - - ChatEntry chat = new ChatEntry(internalId, displayId, name, imageUrl, type, lastMessageTime); - Session.chatList.add(chat); - } - - System.out.println("\n✅ Updated Chat List:"); - displayChatList(); - } else { - System.out.println("⚠️ Failed to fetch chat list: " + response.getString("message")); - } - } +// public static void handleChatListResponse(JSONObject response) { +// if (response.getString("status").equals("success")) { +// JSONArray chats = response.getJSONArray("data"); +// +// Session.chatList.clear(); +// +// for (int i = 0; i < chats.length(); i++) { +// JSONObject chatJson = chats.getJSONObject(i); +// +// UUID internalId = UUID.fromString(chatJson.getString("internal_id")); +// String displayId = chatJson.getString("id"); +// String name = chatJson.getString("name"); +// String imageUrl = chatJson.optString("image_url", ""); +// String type = chatJson.getString("type"); +// LocalDateTime lastMessageTime = LocalDateTime.parse(chatJson.getString("last_message_time")); +// +// ChatEntry chat = new ChatEntry(internalId, displayId, name, imageUrl, type, lastMessageTime); +// Session.chatList.add(chat); +// } +// +// System.out.println("\n✅ Updated Chat List:"); +// displayChatList(); +// } else { +// System.out.println("⚠️ Failed to fetch chat list: " + response.getString("message")); +// } +// } public static void displayChatList() { if (Session.chatList == null || Session.chatList.isEmpty()) { diff --git a/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java b/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java index 46c1137..19e24bb 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java +++ b/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java @@ -1,20 +1,38 @@ package org.to.telegramfinalproject.Client; -public class EventProcessorThread extends Thread { - private final ActionHandler handler; +import org.json.JSONObject; - public EventProcessorThread(ActionHandler handler) { - this.handler = handler; - setDaemon(true); - } +import java.io.BufferedReader; +//public class EventProcessorThread extends Thread { +// private final ActionHandler handler; +// private final BufferedReader in; +// +// public EventProcessorThread(ActionHandler handler, BufferedReader in) { +// this.handler = handler; +// this.in = in; +// setDaemon(true); +// } +// // @Override // public void run() { -// while (true) { -// try { -// Thread.sleep(2000); -// handler.processIncomingEvents(); -// } catch (InterruptedException ignored) {} +// try { +// System.out.println("👂 Real-Time Listener started."); +// String line; +// while ((line = in.readLine()) != null) { +// JSONObject json = new JSONObject(line); +// System.out.println("📥 Received raw line: " + line); +// +// if (json.has("action")) { +// // پیام real-time +// handler.processIncomingEvent(json); +// } else { +// // پیام پاسخ معمولی +// TelegramClient.responseQueue.put(json); +// } +// } +// } catch (Exception e) { +// System.err.println("❌ Error in EventProcessorThread: " + e.getMessage()); // } // } -} +//} diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 9637231..af5413f 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -4,6 +4,7 @@ import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; +import java.util.concurrent.BlockingQueue; public class IncomingMessageListener implements Runnable { private final BufferedReader in; @@ -22,10 +23,20 @@ public class IncomingMessageListener implements Runnable { JSONObject response = new JSONObject(line); System.out.println("📥 Received raw line: " + line); - if (response.has("action")) { - System.out.println("🎯 [Listener] Action received: " + response.toString(2)); + //if it has reqID answer + if (response.has("request_id")) { + String requestId = response.getString("request_id"); + BlockingQueue queue = TelegramClient.pendingResponses.get(requestId); + if (queue != null) { + queue.put(response); // send it to the correct line + continue; + } + } + //if it has action check it + if (response.has("action")) { String action = response.getString("action"); + System.out.println("🎯 [Listener] Action received: " + response.toString(2)); System.out.println("🎯 Received action: " + action); if (isRealTimeEvent(action)) { @@ -33,10 +44,11 @@ public class IncomingMessageListener implements Runnable { } else { TelegramClient.responseQueue.put(response); } + } else if (response.has("status") && response.has("message")) { - TelegramClient.responseQueue.put(response); + TelegramClient.responseQueue.put(response); // general answer } else { - TelegramClient.responseQueue.put(response); + TelegramClient.responseQueue.put(response); // fallback } } @@ -65,8 +77,13 @@ public class IncomingMessageListener implements Runnable { switch (action) { case "added_to_group", "added_to_channel", "removed_from_group", "removed_from_channel", "chat_deleted" -> { - System.out.println("\n🔄 Chat list changed. Updating..."); - ActionHandler.requestChatList(); + System.out.println("🔄 Chat list changed. Updating..."); + Session.forceRefreshChatList = true; + System.out.println("🧪 Calling requestChatList() after being added"); + + String chatId = msg.getString("chat_id"); + String chatType = msg.getString("chat_type"); + ActionHandler.requestChatInfo(chatId, chatType); if (action.equals("removed_from_group") || action.equals("removed_from_channel") || action.equals("chat_deleted")) { System.out.println("🚫 You were removed from the chat or chat was deleted. Exiting..."); @@ -94,7 +111,6 @@ public class IncomingMessageListener implements Runnable { System.out.print(">> "); } - private void displayRealTimeMessage(String action, JSONObject msg) { switch (action) { case "new_message" -> { @@ -135,4 +151,4 @@ public class IncomingMessageListener implements Runnable { } } } -} \ No newline at end of file +} diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index 64270a1..bcd741c 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -1,15 +1,20 @@ package org.to.telegramfinalproject.Client; +import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.ChatEntry; +import java.util.ArrayList; import java.util.List; +import java.util.UUID; // method for save data from server response public class Session { public static JSONObject currentUser; - public static List chatList; + public static List chatList = new ArrayList<>(); + public static volatile boolean forceRefreshChatList = false; + public static String getUserUUID() { if (currentUser.has("uuid")) return currentUser.getString("uuid"); @@ -18,4 +23,26 @@ public class Session { throw new RuntimeException("❌ No UUID found in currentUser!"); } + public static void updateChatList(JSONArray chatArray) { + chatList.clear(); + for (int i = 0; i < chatArray.length(); i++) { + JSONObject obj = chatArray.getJSONObject(i); + ChatEntry entry = new ChatEntry( + UUID.fromString(obj.getString("internal_id")), + obj.optString("id", ""), // displayId + obj.optString("name", ""), // name + obj.optString("image_url", ""), + obj.getString("type"), + null, // last message time (if needed, parse it) + obj.optBoolean("is_owner", false), + obj.optBoolean("is_admin", false) + ); + entry.setPermissions(obj.optJSONObject("permissions")); // اگر permissions وجود داره + chatList.add(entry); + } + } + public static List getChatList() { + return chatList; + } + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index c8cc081..c294739 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -7,9 +7,11 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; +import java.util.Map; import java.util.Scanner; import java.util.UUID; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; public class TelegramClient { @@ -22,6 +24,8 @@ public class TelegramClient { private ActionHandler handler; public static BlockingQueue responseQueue = new LinkedBlockingQueue<>(); public static UUID loggedInUserId = null; + public static final Map> pendingResponses = new ConcurrentHashMap<>(); + private static TelegramClient instance; @@ -53,7 +57,7 @@ public class TelegramClient { } } - private void showMainMenu() { + private void showMainMenu() throws IOException { while (true) { System.out.println("Main Menu:"); System.out.println("1. Register"); @@ -104,4 +108,9 @@ public class TelegramClient { public static void main(String[] args) { new TelegramClient().start(); } + + public PrintWriter getOut() { + return out; + } + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 4375894..a1f5b03 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -406,12 +406,10 @@ public class GroupDatabase { public static List getGroupAdminsAndOwner(UUID groupId) { List admins = new ArrayList<>(); - String sql = """ - SELECT u.internal_uuid, u.user_id, gm.role, gm.permissions, u.profile_name - FROM group_members gm - JOIN users u ON gm.user_id = u.internal_uuid - WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin') - """; + String sql = "SELECT gm.user_id, gm.role, gm.permissions, u.profile_name " + + "FROM group_members gm " + + "JOIN users u ON gm.user_id = u.internal_uuid " + + "WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin')"; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { @@ -421,8 +419,7 @@ public class GroupDatabase { while (rs.next()) { JSONObject obj = new JSONObject(); - obj.put("internal_uuid", rs.getObject("internal_uuid").toString()); // این قسمت اضافه شد - obj.put("user_id", rs.getString("user_id")); + obj.put("user_id", rs.getObject("user_id").toString()); obj.put("role", rs.getString("role")); obj.put("permissions", new JSONObject(rs.getString("permissions"))); obj.put("profile_name", rs.getString("profile_name")); @@ -447,7 +444,7 @@ public class GroupDatabase { stmt.setObject(2, userId); ResultSet rs = stmt.executeQuery(); - return rs.next(); // اگر رکوردی پیدا شد یعنی owner است + return rs.next(); } catch (SQLException e) { e.printStackTrace(); } @@ -463,7 +460,7 @@ public class GroupDatabase { ResultSet rs = stmt.executeQuery(); if (rs.next()) { String role = rs.getString("role"); - return "admin".equals(role) || "owner".equals(role); // owner هم admin هست + return "admin".equals(role) || "owner".equals(role); } } catch (SQLException e) { e.printStackTrace(); @@ -511,7 +508,7 @@ public class GroupDatabase { while (rs.next()) { JSONObject member = new JSONObject(); member.put("profile_name", rs.getString("profile_name")); - member.put("user_id", rs.getString("user_id")); // آیدی قابل نمایش + member.put("user_id", rs.getString("user_id")); member.put("internal_uuid", rs.getObject("internal_uuid").toString()); member.put("role", rs.getString("role")); diff --git a/src/main/java/org/to/telegramfinalproject/Models/ResponseModel.java b/src/main/java/org/to/telegramfinalproject/Models/ResponseModel.java index 7434561..f127dab 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/ResponseModel.java +++ b/src/main/java/org/to/telegramfinalproject/Models/ResponseModel.java @@ -6,6 +6,8 @@ public class ResponseModel { private String status; private String message; private JSONObject data; + private String requestId; + public ResponseModel(String status, String message) { @@ -29,4 +31,20 @@ public class ResponseModel { } public JSONObject getData() {return this.data;} + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + + public JSONObject toJson() { + JSONObject json = new JSONObject(); + json.put("status", this.status); + json.put("message", this.message); + json.put("data", this.data != null ? this.data : JSONObject.NULL); + if (this.requestId != null) { + json.put("request_id", this.requestId); + } + return json; + } } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index f6d1986..cdc1cfd 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -189,6 +189,10 @@ public class ClientHandler implements Runnable { List results = new ArrayList<>(); String user_Id = requestJson.getString("user_id"); User currentUser = new userDatabase().findByUserId(user_Id); + if (currentUser == null) { + response = new ResponseModel("error", "User not found or not logged in."); + break; + } UUID currentUserUUID = currentUser.getInternal_uuid(); for (User u : new userDatabase().searchUsers(keyword, currentUserUUID)) { @@ -241,10 +245,12 @@ public class ClientHandler implements Runnable { } case "search": { + String keyword = requestJson.optString("keyword"); List results = new ArrayList<>(); String user_Id = requestJson.getString("user_id"); User currentUser = new userDatabase().findByUserId(user_Id); + UUID currentUserUUID = currentUser.getInternal_uuid(); for (User u : new userDatabase().searchUsers(keyword, currentUserUUID)) { @@ -410,6 +416,8 @@ public class ClientHandler implements Runnable { case "get_chat_info": { + + try { String id = requestJson.getString("receiver_id"); String type = requestJson.getString("receiver_type"); @@ -1114,7 +1122,6 @@ public class ClientHandler implements Runnable { } UUID groupId = UUID.fromString(requestJson.getString("group_id")); UUID newOwnerUserId = UUID.fromString(requestJson.getString("new_owner_user_id")); - User newOwner = userDatabase.findByInternalUUID(newOwnerUserId); if (newOwner == null) { response = new ResponseModel("error", "New owner not found."); @@ -1570,6 +1577,15 @@ public class ClientHandler implements Runnable { responseJson.put("status", response.getStatus()); responseJson.put("message", response.getMessage()); responseJson.put("data", response.getData() != null ? response.getData() : JSONObject.NULL); + if (requestJson.has("request_id")) { + response.setRequestId(requestJson.getString("request_id")); + } + + if (requestJson.has("request_id")) { + String requestId = requestJson.getString("request_id"); + response.setRequestId(requestId); + responseJson.put("request_id", requestId); + } out.println(responseJson.toString());