Profile logic for UI
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Database.PrivateChatDatabase;
|
||||
@@ -9,10 +10,12 @@ import org.to.telegramfinalproject.Models.ContactEntry;
|
||||
import org.to.telegramfinalproject.Models.SearchRequestModel;
|
||||
import org.to.telegramfinalproject.Models.SearchResultModel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
@@ -4227,4 +4230,401 @@ public class ActionHandler {
|
||||
}
|
||||
|
||||
|
||||
public void sendMessageInteractive(UUID receiverId, String receiverType) {
|
||||
Scanner sc = new Scanner(System.in);
|
||||
|
||||
System.out.print("Type (TEXT / IMAGE / AUDIO): ");
|
||||
String type = sc.nextLine().trim().toUpperCase();
|
||||
while (!Set.of("TEXT","IMAGE","AUDIO").contains(type)) {
|
||||
System.out.print("❌ Invalid. Try (TEXT / IMAGE / AUDIO): ");
|
||||
type = sc.nextLine().trim().toUpperCase();
|
||||
}
|
||||
|
||||
System.out.print("Text (optional for media; empty = no caption): ");
|
||||
String text = sc.nextLine();
|
||||
|
||||
if ("TEXT".equals(type)) {
|
||||
sendTextMessage(receiverId, receiverType, text);
|
||||
} else {
|
||||
System.out.print("File path: ");
|
||||
String path = sc.nextLine().trim();
|
||||
File f = new File(path);
|
||||
if (!f.isFile()) {
|
||||
System.out.println("❌ File not found");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sendMediaMessage(receiverId, receiverType, type, f, text);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("❌ Media send failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendTextMessage(UUID receiverId, String receiverType, String content) {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "send_message")
|
||||
.put("receiver_type", receiverType)
|
||||
.put("receiver_id", receiverId.toString())
|
||||
.put("message_type", "TEXT")
|
||||
.put("content", content == null ? "" : content);
|
||||
|
||||
JSONObject resp = sendWithResponse(req);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
System.out.println("✅ Sent. id=" + resp.optJSONObject("data").optString("message_id",""));
|
||||
} else {
|
||||
System.out.println("❌ Failed: " + (resp != null ? resp.optString("message") : "no response"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sendMediaMessage(UUID receiverId, String receiverType, String type /* IMAGE/AUDIO */, File file, String caption) {
|
||||
if (file == null) {
|
||||
System.out.println("❌ File is null");
|
||||
return;
|
||||
}
|
||||
if (!file.exists()) {
|
||||
System.out.println("❌ File not found: " + file.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
System.out.println("❌ Path is a directory, expected a file: " + file.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID messageId = UUID.randomUUID();
|
||||
|
||||
try {
|
||||
String mime = detectMime(file, type.toUpperCase());
|
||||
if (mime == null) mime = type.equalsIgnoreCase("IMAGE") ? "image/*" : "audio/*";
|
||||
|
||||
JSONObject header = new JSONObject()
|
||||
.put("message_id", messageId.toString())
|
||||
.put("sender_id", TelegramClient.loggedInUserId.toString())
|
||||
.put("receiver_type", receiverType) // private|group|channel
|
||||
.put("receiver_id", receiverId.toString())
|
||||
.put("message_type", type.toUpperCase()) // IMAGE | AUDIO
|
||||
.put("file_name", file.getName())
|
||||
.put("mime_type", mime)
|
||||
.put("text", caption == null ? "" : caption);
|
||||
|
||||
byte[] headerBytes = header.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
long contentLen = file.length();
|
||||
|
||||
BlockingQueue<JSONObject> q = new LinkedBlockingQueue<>(1);
|
||||
TelegramClient.pendingResponses.put(messageId.toString(), q);
|
||||
|
||||
try {
|
||||
|
||||
outBin.write("MEDIA\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
|
||||
outBin.flush();
|
||||
|
||||
// 2) binary frame: magic + headerLen + header + contentLen + content
|
||||
outBin.writeInt(0x4D444D31); // "MDM1"
|
||||
outBin.writeInt(headerBytes.length); // headerLen (int)
|
||||
outBin.write(headerBytes); // header
|
||||
outBin.writeLong(contentLen); // contentLen (long)
|
||||
|
||||
try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) {
|
||||
byte[] buf = new byte[8192];
|
||||
int n;
|
||||
while ((n = fis.read(buf)) != -1) {
|
||||
outBin.write(buf, 0, n);
|
||||
}
|
||||
}
|
||||
outBin.flush();
|
||||
|
||||
JSONObject ack = q.poll(20, java.util.concurrent.TimeUnit.SECONDS);
|
||||
if (ack == null) {
|
||||
System.out.println("❌ Media ACK timeout for " + messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
String status = ack.optString("status", "error");
|
||||
if ("success".equalsIgnoreCase(status)) {
|
||||
System.out.println("✅ Media sent. id=" + ack.optString("message_id") +
|
||||
" url=" + ack.optString("file_url"));
|
||||
} else {
|
||||
System.out.println("❌ Media failed: " + ack.optString("message"));
|
||||
}
|
||||
|
||||
} finally {
|
||||
TelegramClient.pendingResponses.remove(messageId.toString());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("❌ sendMediaMessage error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String detectMime(File f, String typeUpper /* IMAGE or AUDIO */) {
|
||||
try {
|
||||
String m = java.nio.file.Files.probeContentType(f.toPath());
|
||||
if (m != null) return m;
|
||||
} catch (Exception ignored) {}
|
||||
String name = f.getName().toLowerCase();
|
||||
if (name.endsWith(".png")) return "image/png";
|
||||
if (name.endsWith(".jpg") || name.endsWith(".jpeg")) return "image/jpeg";
|
||||
if (name.endsWith(".gif")) return "image/gif";
|
||||
if (name.endsWith(".mp3")) return "audio/mpeg";
|
||||
if (name.endsWith(".wav")) return "audio/wav";
|
||||
if (name.endsWith(".ogg")) return "audio/ogg";
|
||||
return typeUpper.equals("IMAGE") ? "image/*" : "audio/*";
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static String safeName(String s) {
|
||||
if (s == null) return "unknown";
|
||||
s = s.replace("\\", "/");
|
||||
if (s.contains("/")) s = s.substring(s.lastIndexOf('/') + 1);
|
||||
s = s.replaceAll("[\\\\/:*?\"<>|]", "_").trim();
|
||||
if (s.isEmpty() || s.equals(".") || s.equals("..")) s = "unknown";
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String accountFolderName() {
|
||||
JSONObject me = Session.currentUser;
|
||||
String acc = me.optString("username",
|
||||
me.optString("user_id",
|
||||
me.optString("profile_name",
|
||||
me.optString("internal_uuid", "me"))));
|
||||
return safeName(acc);
|
||||
}
|
||||
|
||||
private static String chatFolderName(ChatEntry chat) {
|
||||
String name = chat.getName();
|
||||
if (name == null || name.isBlank()) {
|
||||
name = chat.getDisplayId() != null && !chat.getDisplayId().isBlank()
|
||||
? chat.getDisplayId()
|
||||
: String.valueOf(chat.getId());
|
||||
}
|
||||
return safeName(name);
|
||||
}
|
||||
|
||||
|
||||
public void uploadAvatar(File file) {
|
||||
if (file == null || !file.exists() || !file.isFile()) {
|
||||
System.out.println("❌ Invalid file");
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID requestId = UUID.randomUUID();
|
||||
|
||||
try {
|
||||
String mime = detectMime(file, "IMAGE");
|
||||
if (mime == null) mime = "image/*";
|
||||
|
||||
// header مثل مدیا، ولی برای آواتار
|
||||
JSONObject header = new JSONObject()
|
||||
.put("message_id", requestId.toString()) // برای مچ ACK
|
||||
.put("target_type", "user") // یا channel/group
|
||||
// .put("target_id", "…") // اگر channel/group بود
|
||||
.put("file_name", file.getName())
|
||||
.put("mime_type", mime);
|
||||
|
||||
byte[] headerBytes = header.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
long contentLen = file.length();
|
||||
|
||||
// صف پاسخ (مثل sendMediaMessage)
|
||||
BlockingQueue<JSONObject> q = new LinkedBlockingQueue<>(1);
|
||||
TelegramClient.pendingResponses.put(requestId.toString(), q);
|
||||
|
||||
try {
|
||||
// 🔹 سوئیچ به حالت آواتار (مثل "MEDIA\n")
|
||||
outBin.write("AVATAR\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
|
||||
outBin.flush();
|
||||
|
||||
// 🔹 فریم باینری: مجیک + headerLen + header + contentLen + content
|
||||
outBin.writeInt(0x41565431); // "AVT1"
|
||||
outBin.writeInt(headerBytes.length);
|
||||
outBin.write(headerBytes);
|
||||
outBin.writeLong(contentLen);
|
||||
|
||||
try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) {
|
||||
byte[] buf = new byte[8192]; int n;
|
||||
while ((n = fis.read(buf)) != -1) outBin.write(buf, 0, n);
|
||||
}
|
||||
outBin.flush();
|
||||
|
||||
// 🔹 انتظار ACK (مثل مدیا)
|
||||
JSONObject ack = q.poll(20, java.util.concurrent.TimeUnit.SECONDS);
|
||||
if (ack == null) {
|
||||
System.out.println("❌ Avatar ACK timeout for " + requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("success".equalsIgnoreCase(ack.optString("status"))) {
|
||||
String url = ack.optString("display_url", null);
|
||||
System.out.println("✅ Avatar uploaded. url=" + url);
|
||||
// (اختیاری) رفرش UI + شکستن کش:
|
||||
// MainController.getInstance().refreshMyAvatar(url + "?v=" + System.currentTimeMillis());
|
||||
} else {
|
||||
System.out.println("❌ Avatar failed: " + ack.optString("message"));
|
||||
}
|
||||
} finally {
|
||||
TelegramClient.pendingResponses.remove(requestId.toString());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("❌ uploadAvatar error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final Path UPLOADS_ROOT =
|
||||
Paths.get(System.getProperty("app.uploads.root", "uploads"))
|
||||
.toAbsolutePath().normalize();
|
||||
|
||||
private String prepareAvatar(String input) {
|
||||
if (input == null || input.isBlank()) return null;
|
||||
|
||||
// اگر URL کامل است، دست نزن
|
||||
if (input.startsWith("http://") || input.startsWith("https://") || input.startsWith("file:")) {
|
||||
return input;
|
||||
}
|
||||
|
||||
try {
|
||||
Path src = Paths.get(input).toAbsolutePath().normalize();
|
||||
if (!Files.exists(src)) {
|
||||
System.out.println("⚠️ Image file not found: " + src);
|
||||
return null;
|
||||
}
|
||||
|
||||
String ext = getExt(src.getFileName().toString());
|
||||
String day = LocalDate.now().toString();
|
||||
String fileName = java.util.UUID.randomUUID() + (ext.isEmpty() ? ".jpg" : ext);
|
||||
|
||||
Path destDir = UPLOADS_ROOT.resolve("avatars").resolve(day);
|
||||
Files.createDirectories(destDir);
|
||||
Path dest = destDir.resolve(fileName);
|
||||
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// مقدار نسبی که سرور/کلاینتهای دیگر هم میفهمند
|
||||
return "/avatars/" + day + "/" + fileName;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void uploadAvatarFor(String targetType, UUID targetId, File file) {
|
||||
if (file == null || !file.exists() || !file.isFile()) {
|
||||
System.out.println("❌ Invalid file");
|
||||
return;
|
||||
}
|
||||
|
||||
final UUID reqId = UUID.randomUUID();
|
||||
|
||||
String lastStatus;
|
||||
String lastMessage;
|
||||
try {
|
||||
String mime = detectMime(file, "IMAGE");
|
||||
if (mime == null) mime = "image/*";
|
||||
|
||||
JSONObject header = new JSONObject()
|
||||
.put("request_id", reqId.toString()) // ← هماهنگ با pendingResponses
|
||||
.put("target_type", targetType.toLowerCase()); // user | group | channel
|
||||
if (targetId != null) header.put("target_id", targetId.toString());
|
||||
header.put("file_name", file.getName())
|
||||
.put("mime_type", mime);
|
||||
|
||||
byte[] headerBytes = header.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
long contentLen = file.length();
|
||||
|
||||
BlockingQueue<JSONObject> q = new LinkedBlockingQueue<>(1);
|
||||
// رجیستر با هر دو کلید تا هر نوع ACK روت شود
|
||||
TelegramClient.pendingResponses.put(reqId.toString(), q);
|
||||
TelegramClient.pendingResponses.put(header.optString("message_id", reqId.toString()), q);
|
||||
|
||||
try {
|
||||
TelegramClient.mediaBusy.set(true); // ← حین باینری JSON نخونیم
|
||||
|
||||
// سوئیچ پروتکل
|
||||
TelegramClient.getInstance().getOutBin()
|
||||
.write("AVATAR\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
|
||||
|
||||
DataOutputStream outBin = TelegramClient.getInstance().getOutBin();
|
||||
outBin.flush();
|
||||
|
||||
// فریم: MAGIC + headerLen + header + contentLen + content
|
||||
outBin.writeInt(0x41565431); // "AVT1"
|
||||
outBin.writeInt(headerBytes.length);
|
||||
outBin.write(headerBytes);
|
||||
outBin.writeLong(contentLen);
|
||||
|
||||
try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) {
|
||||
byte[] buf = new byte[8192];
|
||||
int n;
|
||||
while ((n = fis.read(buf)) != -1) outBin.write(buf, 0, n);
|
||||
}
|
||||
outBin.flush();
|
||||
} finally {
|
||||
TelegramClient.mediaBusy.set(false);
|
||||
}
|
||||
|
||||
// انتظار ACK (هر کدوم از کلیدها بیاد، Listener میفرسته به صف q)
|
||||
JSONObject ack = q.poll(20, java.util.concurrent.TimeUnit.SECONDS);
|
||||
if (ack == null) {
|
||||
System.out.println("❌ Avatar ACK timeout for " + reqId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ("success".equalsIgnoreCase(ack.optString("status"))) {
|
||||
String url = ack.optString("display_url", null);
|
||||
lastStatus = "success";
|
||||
lastMessage = url != null ? url : "";
|
||||
|
||||
if (url != null && targetId != null) {
|
||||
String busted = url + (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis();
|
||||
|
||||
// بهروزرسانی State و UI: چتلیست + هدر
|
||||
Platform.runLater(() -> {
|
||||
var mc = org.to.telegramfinalproject.UI.MainController.getInstance();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
System.out.println("❌ Avatar failed: " + ack.optString("message"));
|
||||
lastStatus = "error";
|
||||
lastMessage = ack.optString("message", "failed");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("❌ uploadAvatarFor error: " + e.getMessage());
|
||||
lastStatus = "error";
|
||||
lastMessage = e.getMessage();
|
||||
} finally {
|
||||
TelegramClient.pendingResponses.remove(reqId.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getExt(String name) {
|
||||
int i = name.lastIndexOf('.');
|
||||
return (i >= 0) ? name.substring(i) : "";
|
||||
}
|
||||
|
||||
private void updateChatAvatarLocally(String targetType, UUID chatId, String newUrl) {
|
||||
try {
|
||||
|
||||
java.util.function.Consumer<java.util.List<org.to.telegramfinalproject.Models.ChatEntry>> upd =
|
||||
list -> list.stream()
|
||||
.filter(c -> c.getId().equals(chatId))
|
||||
.findFirst()
|
||||
.ifPresent(c -> c.setImageUrl(newUrl));
|
||||
|
||||
upd.accept(Session.chatList);
|
||||
upd.accept(Session.activeChats);
|
||||
upd.accept(Session.archivedChats);
|
||||
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -36,34 +36,43 @@ public class IncomingMessageListener implements Runnable {
|
||||
try {
|
||||
System.out.println("👂 Real-Time Listener started.");
|
||||
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
while (true) {
|
||||
|
||||
if (TelegramClient.mediaBusy.get()) {
|
||||
try { Thread.sleep(15); } catch (InterruptedException ignored) {}
|
||||
continue;
|
||||
}
|
||||
|
||||
String line = in.readLine();
|
||||
if (line == null) break;
|
||||
if (line.isBlank()) continue;
|
||||
|
||||
final JSONObject response;
|
||||
try {
|
||||
response = new JSONObject(line);
|
||||
} catch (Exception badJson) {
|
||||
System.out.println("⚠️ [Listener] Non-JSON line ignored: " + line);
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject response = new JSONObject(line);
|
||||
System.out.println("📥 Received raw line: " + line);
|
||||
|
||||
//if it has reqID answer
|
||||
// 1) اول message_id را روت کن (برای ACK نهایی مدیا)
|
||||
String mid = response.optString("message_id", "");
|
||||
if (!mid.isEmpty()) {
|
||||
BlockingQueue<JSONObject> q = TelegramClient.pendingResponses.get(mid);
|
||||
if (q != null) {
|
||||
q.put(response);
|
||||
continue; // این پیام مصرف شد
|
||||
continue; // مصرف شد
|
||||
}
|
||||
}
|
||||
|
||||
// 2) بعد request_id را روت کن (برای INIT و بقیه درخواستها)
|
||||
if (response.has("request_id")) {
|
||||
String requestId = response.getString("request_id");
|
||||
System.out.println("📬 Response with request_id: " + requestId);
|
||||
System.out.println("📬 Full response: " + response.toString(2));
|
||||
|
||||
|
||||
BlockingQueue<JSONObject> queue = TelegramClient.pendingResponses.get(requestId);
|
||||
if (queue != null) {
|
||||
queue.put(response);
|
||||
@@ -71,13 +80,10 @@ public class IncomingMessageListener implements Runnable {
|
||||
System.out.println("⚠️ No pending queue for request_id = " + requestId + ". Putting in responseQueue...");
|
||||
TelegramClient.responseQueue.put(response);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//if it has action check it
|
||||
// 3) رویدادهای real-time
|
||||
if (response.has("action")) {
|
||||
String action = response.getString("action");
|
||||
System.out.println("🎯 [Listener] Action received: " + response.toString(2));
|
||||
@@ -88,9 +94,12 @@ public class IncomingMessageListener implements Runnable {
|
||||
} else {
|
||||
TelegramClient.responseQueue.put(response);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
} else if (response.has("status") && response.has("message")) {
|
||||
TelegramClient.responseQueue.put(response); // general answer
|
||||
// 4) سایر پاسخهای عمومی
|
||||
if (response.has("status") && response.has("message")) {
|
||||
TelegramClient.responseQueue.put(response);
|
||||
} else {
|
||||
TelegramClient.responseQueue.put(response); // fallback
|
||||
}
|
||||
|
||||
@@ -222,9 +222,6 @@ public class TelegramClient {
|
||||
if (socket != null && socket.isConnected() && !socket.isClosed()) return;
|
||||
|
||||
socket = new Socket(SERVER_HOST, SERVER_PORT);
|
||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
|
||||
out = new PrintWriter(socket.getOutputStream(), true);
|
||||
|
||||
InputStream rawIn = socket.getInputStream();
|
||||
OutputStream rawOut = socket.getOutputStream();
|
||||
|
||||
@@ -256,14 +253,24 @@ public class TelegramClient {
|
||||
// }
|
||||
|
||||
|
||||
// private void startListenerOnce(IncomingMessageListener.UIMode mode) {
|
||||
// if (listenerStarted) return;
|
||||
// listenerStarted = true;
|
||||
//
|
||||
// Thread listenerThread = new Thread(
|
||||
// new IncomingMessageListener(in, mode),
|
||||
// "socket-listener"
|
||||
// );
|
||||
// listenerThread.setDaemon(true);
|
||||
// listenerThread.start();
|
||||
// }
|
||||
|
||||
private void startListenerOnce(IncomingMessageListener.UIMode mode) {
|
||||
if (listenerStarted) return;
|
||||
listenerStarted = true;
|
||||
|
||||
Thread listenerThread = new Thread(
|
||||
new IncomingMessageListener(in, mode),
|
||||
"socket-listener"
|
||||
);
|
||||
listener = new IncomingMessageListener(in, mode);
|
||||
Thread listenerThread = new Thread(listener, "socket-listener");
|
||||
listenerThread.setDaemon(true);
|
||||
listenerThread.start();
|
||||
}
|
||||
|
||||
@@ -223,16 +223,58 @@ public class ChannelDatabase {
|
||||
}
|
||||
|
||||
|
||||
public static boolean insertChannel(UUID internalUUID, String channelId, String channelName, UUID creatorId, String imageUrl, LocalDateTime createdAt) {
|
||||
String sql = "INSERT INTO channels (internal_uuid, channel_id, channel_name, creator_id, image_url, created_at) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
// public static boolean insertChannel(UUID internalUUID, String channelId, String channelName, UUID creatorId, String imageUrl, LocalDateTime createdAt) {
|
||||
// String sql = "INSERT INTO channels (internal_uuid, channel_id, channel_name, creator_id, image_url, created_at) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
//
|
||||
// try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
// stmt.setObject(1, internalUUID);
|
||||
// stmt.setString(2, channelId);
|
||||
// stmt.setString(3, channelName);
|
||||
// stmt.setObject(4, creatorId);
|
||||
// stmt.setString(5, imageUrl);
|
||||
// stmt.setObject(6, createdAt);
|
||||
// stmt.executeUpdate();
|
||||
// return true;
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
public static boolean insertChannel(UUID internalUUID,
|
||||
String channelId,
|
||||
String channelName,
|
||||
UUID creatorId,
|
||||
String imageUrl,
|
||||
String description,
|
||||
LocalDateTime createdAt) {
|
||||
String sql = "INSERT INTO channels " +
|
||||
"(internal_uuid, channel_id, channel_name, creator_id, image_url, description, created_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, internalUUID);
|
||||
stmt.setString(2, channelId);
|
||||
stmt.setString(3, channelName);
|
||||
stmt.setObject(4, creatorId);
|
||||
|
||||
if (imageUrl != null && !imageUrl.isBlank()) {
|
||||
stmt.setString(5, imageUrl);
|
||||
stmt.setObject(6, createdAt);
|
||||
} else {
|
||||
stmt.setNull(5, java.sql.Types.VARCHAR);
|
||||
}
|
||||
|
||||
if (description != null && !description.isBlank()) {
|
||||
// اگر میخوای مطمئن بشی 255 نشکنه:
|
||||
stmt.setString(6, description.length() > 255 ? description.substring(0, 255) : description);
|
||||
} else {
|
||||
stmt.setNull(6, java.sql.Types.VARCHAR);
|
||||
}
|
||||
|
||||
stmt.setObject(7, createdAt);
|
||||
|
||||
stmt.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
|
||||
@@ -7,11 +7,11 @@ import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChannelService {
|
||||
public static boolean createChannel(String channelId, String channelName, UUID creatorUUID, String imageUrl) {
|
||||
public static boolean createChannel(String channelId, String channelName, UUID creatorUUID, String imageUrl,String description) {
|
||||
UUID internalUUID = UUID.randomUUID();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
boolean inserted = ChannelDatabase.insertChannel(internalUUID, channelId, channelName, creatorUUID, imageUrl, now);
|
||||
boolean inserted = ChannelDatabase.insertChannel(internalUUID, channelId, channelName, creatorUUID, imageUrl, description,now);
|
||||
|
||||
if (inserted) {
|
||||
ChannelDatabase.addSubscriber(internalUUID, creatorUUID,"owner");
|
||||
|
||||
@@ -970,8 +970,8 @@ public class ClientHandler implements Runnable {
|
||||
UUID creatorUUID = UUID.fromString(userIdStr);
|
||||
|
||||
// اگر سرویسات ورودی توضیح را میپذیرد، از متد اورلودشده استفاده کن:
|
||||
// boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl, description);
|
||||
boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl);
|
||||
boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl, description);
|
||||
//boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl);
|
||||
|
||||
if (created) {
|
||||
Channel createdChannel = ChannelDatabase.findByChannelId(channelId);
|
||||
@@ -3319,6 +3319,10 @@ public class ClientHandler implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasManagePermission(UUID currentUserId, String targetType, UUID targetId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String err(String m) { return new JSONObject().put("status","error").put("message",m).toString(); }
|
||||
|
||||
private static boolean isAllowedImageMime(String mime) {
|
||||
@@ -3344,8 +3348,8 @@ public class ClientHandler implements Runnable {
|
||||
private boolean updateProfileImageUrl(String targetType, UUID targetId, String url) {
|
||||
String sql = switch (targetType) {
|
||||
case "user" -> "UPDATE users SET image_url=? WHERE internal_uuid=?";
|
||||
case "channel" -> "UPDATE channels SET image_url=? WHERE id=?";
|
||||
case "group" -> "UPDATE groups SET image_url=? WHERE id=?";
|
||||
case "channel" -> "UPDATE channels SET image_url=? WHERE internal_uuid=?";
|
||||
case "group" -> "UPDATE groups SET image_url=? WHERE internal_uuid=?";
|
||||
default -> null;
|
||||
};
|
||||
if (sql == null) return false;
|
||||
|
||||
@@ -117,4 +117,28 @@ public class ChatItemController {
|
||||
unreadCount.setManaged(show);
|
||||
if (show) unreadCount.setText(String.valueOf(unread));
|
||||
}
|
||||
|
||||
// ChatItemController.java
|
||||
@FXML private ImageView avatarImage;
|
||||
// اگر نوع چت نیاز داری برای دیفالتها (private|group|channel)
|
||||
private String chatType; // اگر داری از setChatData بگیرش و نگه دار
|
||||
|
||||
public void updateAvatar(String url) {
|
||||
try {
|
||||
if (url == null || url.isBlank()) {
|
||||
String def = switch (chatType == null ? "" : chatType.toLowerCase()) {
|
||||
case "group" -> "/org/to/telegramfinalproject/Avatars/default_group_profile.png";
|
||||
case "channel" -> "/org/to/telegramfinalproject/Avatars/default_channel_profile.png";
|
||||
default -> "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
};
|
||||
avatarImage.setImage(new Image(Objects.requireNonNull(
|
||||
getClass().getResourceAsStream(def))));
|
||||
} else {
|
||||
avatarImage.setImage(new Image(url, true)); // true = لود غیرهمزمان
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
import static org.to.telegramfinalproject.Client.Session.currentChatId;
|
||||
|
||||
public class ChatPageController {
|
||||
|
||||
// ===== messages area =====
|
||||
@@ -2354,6 +2356,39 @@ private void addBubble(
|
||||
}
|
||||
}
|
||||
|
||||
// ChatPageController.java
|
||||
|
||||
public void onChatAvatarUpdated(UUID chatId, String newUrl) {
|
||||
// اگه چت فعلی چیز دیگریه، کاری نکن
|
||||
if (currentChat == null || chatId == null || newUrl == null || newUrl.isBlank()) return;
|
||||
if (!currentChat.getId().equals(chatId)) return;
|
||||
|
||||
// اگر از نخ غیر JavaFX صدا زده شد، امنش کنیم
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
Platform.runLater(() -> onChatAvatarUpdated(chatId, newUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
// State داخلی entry را هم آپدیت کن
|
||||
currentChat.setImageUrl(newUrl);
|
||||
|
||||
// آواتار هدر را ست کن (با resolver خودت)
|
||||
try {
|
||||
Image im = org.to.telegramfinalproject.Client.AvatarLocalResolver.load(newUrl);
|
||||
if (im != null) {
|
||||
userAvatar.setImage(im);
|
||||
} else {
|
||||
// fallback اگر لود نشد
|
||||
setDefaultHeaderAvatarByType(currentChat.getType());
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
setDefaultHeaderAvatarByType(currentChat.getType());
|
||||
}
|
||||
|
||||
// مطمئن شو همچنان دایرهایه
|
||||
try { AvatarFX.circleClip(userAvatar, 36); } catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static final class ForwardTarget {
|
||||
|
||||
@@ -437,6 +437,9 @@ public class MainController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ChatPageController controller = loader.getController();
|
||||
controller.showChat(chat);
|
||||
|
||||
|
||||
@@ -118,28 +118,34 @@ public class NewChannelController {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: اگر آپلود تصویر داری، فایل را آپلود کن و URL نهایی را اینجا بفرست
|
||||
final String imageUrl = null;
|
||||
|
||||
// در مرحله ساخت، image_url را خالی بفرست؛ بعداً آپلود میکنیم
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "create_channel")
|
||||
.put("channel_id", dispId) // آیدی نمایشی/عمومی
|
||||
.put("channel_id", dispId)
|
||||
.put("channel_name", name)
|
||||
.put("user_id", me) // internal_uuid سازنده
|
||||
.put("image_url", imageUrl) // اختیاری
|
||||
.put("description", description); // اختیاری (سرور اگر ساپورت نکند نادیده میگیرد)
|
||||
.put("user_id", me)
|
||||
.put("image_url", (Object) null)
|
||||
.put("description", description);
|
||||
|
||||
createButton.setDisable(true);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> showToast("Create failed: " +
|
||||
(resp == null ? "no response" : resp.optString("message",""))));
|
||||
Platform.runLater(() -> {
|
||||
createButton.setDisable(false);
|
||||
showToast("Create failed: " + (resp == null ? "no response" : resp.optString("message","")));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
if (data == null) {
|
||||
Platform.runLater(() -> showToast("Create failed: empty data."));
|
||||
Platform.runLater(() -> {
|
||||
createButton.setDisable(false);
|
||||
showToast("Create failed: empty data.");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,27 +154,34 @@ public class NewChannelController {
|
||||
String returnedDisp = data.optString("id", dispId);
|
||||
String returnedImg = data.optString("image_url", "");
|
||||
|
||||
// 1) چت کانال تازهساخته را به سایدبار اضافه کن و انتخاب کن
|
||||
// ✅ اگر کاربر عکس انتخاب کرده بود: الان آپلود کن (target_type=channel)
|
||||
if (channelImageFile != null) {
|
||||
ActionHandler.instance.uploadAvatarFor("channel", internalId, channelImageFile);
|
||||
if (ActionHandler.instance.wasSuccess()) {
|
||||
String url = ActionHandler.instance.getLastMessage(); // display_url
|
||||
if (url != null && !url.isBlank()) {
|
||||
returnedImg = url;
|
||||
}
|
||||
} else {
|
||||
// اختیاری: پیام خطا را نشان بده
|
||||
System.out.println("Channel avatar upload failed: " + ActionHandler.instance.getLastMessage());
|
||||
}
|
||||
}
|
||||
|
||||
String finalImg = returnedImg;
|
||||
Platform.runLater(() -> {
|
||||
// 1) کانال را به سایدبار اضافه و انتخاب کن
|
||||
ChatEntry entry = ChatEntry.fromServer(
|
||||
internalId,
|
||||
"channel",
|
||||
returnedName,
|
||||
returnedDisp,
|
||||
returnedImg,
|
||||
/*isOwner*/ true,
|
||||
/*isAdmin*/ true
|
||||
internalId, "channel", returnedName, returnedDisp, finalImg,
|
||||
/*isOwner*/ true, /*isAdmin*/ true
|
||||
);
|
||||
MainController.getInstance().addChatAndSelect(entry);
|
||||
});
|
||||
|
||||
// 2) اُورلی افزودن سابسکرایبر را باز کن و internal_id را پاس بده
|
||||
Platform.runLater(() -> {
|
||||
// 2) باز کردن Overlay افزودن سابسکرایبر و بستن این Overlay
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_subscriber.fxml"));
|
||||
StackPane addSubsOverlay = loader.load();
|
||||
|
||||
AddSubscriberController controller = loader.getController();
|
||||
controller.setChannelInfo(internalId, returnedName, returnedDisp, channelImageFile, description);
|
||||
|
||||
@@ -178,10 +191,20 @@ public class NewChannelController {
|
||||
ex.printStackTrace();
|
||||
showToast("Failed to open Add Subscribers.");
|
||||
}
|
||||
|
||||
createButton.setDisable(false);
|
||||
});
|
||||
|
||||
} catch (Exception ex) {
|
||||
Platform.runLater(() -> {
|
||||
createButton.setDisable(false);
|
||||
showToast("Create failed: " + ex.getMessage());
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private void showToast(String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||
a.initOwner(overlayRoot.getScene().getWindow());
|
||||
|
||||
@@ -14,12 +14,16 @@ import javafx.scene.layout.Pane;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Client.TelegramClient;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static org.to.telegramfinalproject.Client.ActionHandler.detectMime;
|
||||
|
||||
public class NewGroupController {
|
||||
|
||||
@@ -83,34 +87,25 @@ public class NewGroupController {
|
||||
String groupId = groupIdField.getText() == null ? "" : groupIdField.getText().trim();
|
||||
|
||||
boolean ok = true;
|
||||
if (groupName.isEmpty()) {
|
||||
groupNameField.getStyleClass().add("error");
|
||||
groupNameLabel.getStyleClass().add("error");
|
||||
ok = false;
|
||||
}
|
||||
if (groupId.isEmpty()) {
|
||||
groupIdField.getStyleClass().add("error");
|
||||
groupIdLabel.getStyleClass().add("error");
|
||||
ok = false;
|
||||
}
|
||||
if (groupName.isEmpty()) { groupNameField.getStyleClass().add("error"); groupNameLabel.getStyleClass().add("error"); ok = false; }
|
||||
if (groupId.isEmpty()) { groupIdField.getStyleClass().add("error"); groupIdLabel.getStyleClass().add("error"); ok = false; }
|
||||
if (!ok) return;
|
||||
|
||||
// ساخت گروه روی سرور
|
||||
final String me = Session.getUserUUID(); // internal_uuid
|
||||
final String me = Session.getUserUUID();
|
||||
if (me == null || me.isBlank()) {
|
||||
showToast("Cannot create group: current user UUID missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
// اگر آپلود تصویر داری، اینجا عکس رو آپلود کن و imageUrl واقعی بفرست (TODO)
|
||||
// در مرحلهٔ ساخت، image_url را خالی بفرست (بعداً آپلود میکنیم)
|
||||
final String imageUrl = null;
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "create_group")
|
||||
.put("group_id", groupId)
|
||||
.put("group_name", groupName)
|
||||
.put("user_id", me) // ← internal_uuid سازنده
|
||||
.put("image_url", imageUrl); // ← اختیاری
|
||||
.put("user_id", me)
|
||||
.put("image_url", imageUrl);
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
@@ -131,21 +126,37 @@ public class NewGroupController {
|
||||
String returnedDisp = data.optString("id", groupId);
|
||||
String returnedImg = data.optString("image_url", "");
|
||||
|
||||
// 1) بهصورت لوکال یک ChatEntry بساز و به سایدبار اضافه و سِلکت کن
|
||||
// اگر عکس انتخاب شده، حالا آپلود کن (target_type=group, target_id=internalId)
|
||||
String finalImageUrl = returnedImg;
|
||||
if (groupImageFile != null) {
|
||||
ActionHandler.instance.uploadAvatarFor("group", internalId, groupImageFile);
|
||||
if (ActionHandler.instance.wasSuccess()) {
|
||||
String url = ActionHandler.instance.getLastMessage(); // display_url
|
||||
if (url != null && !url.isBlank()) {
|
||||
finalImageUrl = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1) به سایدبار اضافه و انتخاب کن
|
||||
String imageUrlToUse = finalImageUrl; // برای استفاده داخل lambda
|
||||
Platform.runLater(() -> {
|
||||
ChatEntry entry = ChatEntry.fromServer(
|
||||
internalId,
|
||||
"group",
|
||||
returnedName,
|
||||
returnedDisp,
|
||||
returnedImg,
|
||||
imageUrlToUse,
|
||||
/*isOwner*/ true,
|
||||
/*isAdmin*/ true
|
||||
);
|
||||
MainController.getInstance().addChatAndSelect(entry);
|
||||
|
||||
// اگر URL جدید داریم، میتونی یک bust-cache بزنی:
|
||||
// MainController.getInstance().refreshChatAvatar(internalId, imageUrlToUse + "?v=" + System.currentTimeMillis());
|
||||
});
|
||||
|
||||
// 2) پنجرهی Add Members را باز کن و internal_id گروه را پاس بده
|
||||
// 2) پنجره Add Members
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
@@ -154,10 +165,7 @@ public class NewGroupController {
|
||||
AddMembersController controller = loader.getController();
|
||||
controller.setGroupInfo(internalId, returnedName, returnedDisp, groupImageFile);
|
||||
|
||||
// این اُورلی را روی UI نشان بده
|
||||
MainController.getInstance().showOverlay(addMembersOverlay);
|
||||
|
||||
// این اُورلی NewGroup را ببند
|
||||
MainController.getInstance().closeOverlay(overlayRoot);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
@@ -167,10 +175,14 @@ public class NewGroupController {
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private void showToast(String msg) {
|
||||
// جایگزینش کن با سیستم نوتی شما
|
||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||
a.initOwner(overlayRoot.getScene().getWindow());
|
||||
a.show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user