From 3412648c7f3edc2988a8f90e69bafd8725924a62 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Fri, 15 Aug 2025 00:24:55 +0330 Subject: [PATCH] Work on download files(channels) --- .../Client/ActionHandler.java | 44 +++---- .../Client/DownloadIndexRegistry.java | 2 +- .../Client/DownloadsIndex.java | 1 + .../Client/SocketMediaDownloader.java | 2 +- .../Database/MessageDatabase.java | 3 +- .../telegramfinalproject/Models/MediaRow.java | 2 + .../Server/ClientHandler.java | 107 +++++++++++++++++- .../Utils/ChannelPermissionUtil.java | 25 ++++ 8 files changed, 159 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 7d65dc0..2fef192 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -3823,43 +3823,44 @@ public class ActionHandler { String fileName = sanitizeFileName(rawName); long declaredSize = att.optLong("file_size", 0L); - // ~/Downloads/TeleSock// - String folderName = (chat.getDisplayId() != null && !chat.getDisplayId().isBlank()) - ? chat.getDisplayId() : chat.getId().toString(); - Path saveDir = Paths.get(System.getProperty("user.home"), "Downloads", "TeleSock", folderName); + // ~/Downloads/TeleSock/// + String accFolder = accountFolderName(); // ← از یوزر لاگین‌شده + String chatFolder = chatFolderName(chat); // ← برای پرایوت: اسم طرف مقابل + Path saveDir = Paths.get(System.getProperty("user.home"), + "Downloads", "TeleSock", accFolder, chatFolder); - try { Files.createDirectories(saveDir); } catch (IOException e) { + // دیباگ اختیاری + System.out.println("👤 AccountFolder = " + accFolder); + System.out.println("💬 ChatFolder = " + chatFolder); + System.out.println("📁 SaveDir = " + saveDir); + + try { Files.createDirectories(saveDir); } + catch (IOException e) { System.out.println("❌ Cannot create folder: " + saveDir + " -> " + e.getMessage()); return; } - // اگر قبلاً دانلود شده (و فایل واقعاً وجود دارد) - try { - Path existing = Session.downloadsIndex.find(mediaKey); + // اگر قبلاً دانلود شده (و فایل واقعاً وجود دارد) — ایندکس مخصوص همین اکانت + DownloadsIndex di = Session.downloadsIndex; + if (di != null) { + Path existing = di.find(mediaKey); if (existing != null) { System.out.println("✅ Already downloaded: " + existing); return; } - } catch (IllegalStateException notInit) { - System.out.println("⚠️ DownloadsIndex not initialized. Call DownloadsIndex.init() after login."); - // ادامه می‌دهیم؛ فقط کش نمی‌شود. } - // جلوگیری از overwrite با انتخاب نام یکتا + // جلوگیری از overwrite Path target = uniquePath(saveDir, fileName); - - // دانلود روی همان سوکت: Listener را موقتاً متوقف کن + // دانلود روی همان سوکت TelegramClient.mediaBusy.set(true); try { - Path saved = TelegramClient.getDownloader().download(mediaKey, saveDir, target.getFileName().toString()); + Path saved = TelegramClient.getDownloader() + .download(mediaKey, saveDir, target.getFileName().toString()); long sizeToRecord = declaredSize > 0 ? declaredSize : Files.size(saved); - try { - Session.downloadsIndex.put(mediaKey, saved, sizeToRecord); - } catch (IllegalStateException notInit) { - // اگر init نشده بود، تنها کش نمی‌کنیم - } + if (di != null) di.put(mediaKey, saved, sizeToRecord); System.out.println("✅ Saved to: " + saved + " (" + humanSize(sizeToRecord) + ")"); } catch (Exception ex) { @@ -3869,6 +3870,9 @@ public class ActionHandler { } } + + + // نام یکتا اگر فایل موجود است: name.png -> name (1).png private static Path uniquePath(Path dir, String fileName) { Path p = dir.resolve(fileName); diff --git a/src/main/java/org/to/telegramfinalproject/Client/DownloadIndexRegistry.java b/src/main/java/org/to/telegramfinalproject/Client/DownloadIndexRegistry.java index 2c29b72..f89bd8f 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/DownloadIndexRegistry.java +++ b/src/main/java/org/to/telegramfinalproject/Client/DownloadIndexRegistry.java @@ -27,4 +27,4 @@ public final class DownloadIndexRegistry { })); HOOK_REGISTERED = true; } -} +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Client/DownloadsIndex.java b/src/main/java/org/to/telegramfinalproject/Client/DownloadsIndex.java index 12e1944..b3a0f06 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/DownloadsIndex.java +++ b/src/main/java/org/to/telegramfinalproject/Client/DownloadsIndex.java @@ -1,3 +1,4 @@ + package org.to.telegramfinalproject.Client; import org.json.JSONObject; diff --git a/src/main/java/org/to/telegramfinalproject/Client/SocketMediaDownloader.java b/src/main/java/org/to/telegramfinalproject/Client/SocketMediaDownloader.java index 0a128ac..ee72ed2 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SocketMediaDownloader.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SocketMediaDownloader.java @@ -75,4 +75,4 @@ public final class SocketMediaDownloader { while (java.nio.file.Files.exists(dir.resolve(base + " (" + i + ")" + ext))) i++; return dir.resolve(base + " (" + i + ")" + ext); } -} +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 4de0c20..ca5011b 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -3,6 +3,7 @@ package org.to.telegramfinalproject.Database; import org.to.telegramfinalproject.Models.FileAttachment; import org.to.telegramfinalproject.Models.MediaRow; import org.to.telegramfinalproject.Models.Message; +import org.to.telegramfinalproject.Utils.ChannelPermissionUtil; import java.sql.*; import java.time.LocalDateTime; @@ -1355,7 +1356,7 @@ public class MessageDatabase { return GroupDatabase.isMember(mr.receiverId, requester); case "channel": - return ChannelDatabase.isUserInChannel(mr.receiverId, requester); + return ChannelPermissionUtil.isUserInChannel(requester, mr.receiverId); default: return false; diff --git a/src/main/java/org/to/telegramfinalproject/Models/MediaRow.java b/src/main/java/org/to/telegramfinalproject/Models/MediaRow.java index 0d89c82..0332ff3 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/MediaRow.java +++ b/src/main/java/org/to/telegramfinalproject/Models/MediaRow.java @@ -19,4 +19,6 @@ public class MediaRow { public Integer durationSeconds; //for audio only public String thumbnailUrl; public String fileUrl; //display link + public String chatType; + public UUID chatId; } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 8dce818..d29953d 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -22,6 +22,15 @@ public class ClientHandler implements Runnable { private final AuthService authService = new AuthService(); private User currentUser; + // ClientHandler.java + private static void log(String msg) { + System.out.println(java.time.LocalDateTime.now() + " [ClientHandler] " + msg); + } + private static void logf(String fmt, Object... args) { + log(String.format(fmt, args)); + } + + public ClientHandler(Socket socket) { this.socket = socket; @@ -72,12 +81,17 @@ public class ClientHandler implements Runnable { continue; } + if ("MEDIA_DL".equalsIgnoreCase(line)) { - if (this.currentUser.getInternal_uuid() == null) { + UUID cu = (currentUser == null ? null : currentUser.getInternal_uuid()); + logf("MEDIA_DL received. currentUser.internal_uuid=%s", cu); + + if (cu == null) { + log("MEDIA_DL rejected: currentUser is null or no internal_uuid"); sendDlErr(dos, "not authorized"); continue; } - handleMediaDownload(dis, dos, this.currentUser.getInternal_uuid()); + handleMediaDownload(dis, dos, cu); continue; } @@ -2925,8 +2939,68 @@ public class ClientHandler implements Runnable { private static final int MAGIC_DL = 0x4D444D32; // "MDM2" +// private void handleMediaDownload(DataInputStream inBin, DataOutputStream outBin, UUID requesterId) { +// try { +// int magic = inBin.readInt(); +// if (magic != MAGIC_DL) { sendDlErr(outBin, "bad magic"); return; } +// +// int hlen = inBin.readInt(); +// if (hlen <= 0 || hlen > 64 * 1024) { sendDlErr(outBin, "bad header length"); return; } +// +// byte[] hb = inBin.readNBytes(hlen); +// if (hb.length != hlen) { sendDlErr(outBin, "header truncated"); return; } +// +// JSONObject hdr = new JSONObject(new String(hb, java.nio.charset.StandardCharsets.UTF_8)); +// if (!"download".equalsIgnoreCase(hdr.optString("op"))) { sendDlErr(outBin, "bad op"); return; } +// +// UUID mediaKey = UUID.fromString(hdr.getString("media_key")); +// long offset = Math.max(0L, hdr.optLong("offset", 0L)); +// +// MediaRow mr = MessageDatabase.findMediaByKey(mediaKey); +// if (mr == null) { sendDlErr(outBin, "not found"); return; } +// if (!MessageDatabase.canAccess(requesterId, mr)) { sendDlErr(outBin, "not authorized"); return; } +// +// java.nio.file.Path path = java.nio.file.Paths.get(mr.storagePath).normalize(); +// long size = java.nio.file.Files.size(path); +// if (offset > size) offset = 0L; +// +// JSONObject ok = new JSONObject() +// .put("status","success") +// .put("media_key", mediaKey.toString()) +// .put("file_name", mr.fileName) +// .put("mime_type", mr.mimeType) +// .put("file_size", size); +// +// byte[] okb = ok.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); +// +// outBin.writeInt(MAGIC_DL); +// outBin.writeInt(okb.length); +// outBin.write(okb); +// outBin.writeLong(size - offset); +// +// try (java.io.InputStream fis = new java.io.BufferedInputStream(java.nio.file.Files.newInputStream(path))) { +// if (offset > 0) fis.skipNBytes(offset); +// byte[] buf = new byte[8192]; +// long remain = size - offset; +// while (remain > 0) { +// int n = fis.read(buf, 0, (int) Math.min(buf.length, remain)); +// if (n == -1) break; +// outBin.write(buf, 0, n); +// remain -= n; +// } +// } +// outBin.flush(); +// +// } catch (Exception e) { +// e.printStackTrace(); +// try { sendDlErr(outBin, "exception"); } catch (Exception ignored) {} +// } +// } + private void handleMediaDownload(DataInputStream inBin, DataOutputStream outBin, UUID requesterId) { try { + logf("MEDIA_DL start. requester=%s", requesterId); + int magic = inBin.readInt(); if (magic != MAGIC_DL) { sendDlErr(outBin, "bad magic"); return; } @@ -2936,15 +3010,38 @@ public class ClientHandler implements Runnable { byte[] hb = inBin.readNBytes(hlen); if (hb.length != hlen) { sendDlErr(outBin, "header truncated"); return; } - JSONObject hdr = new JSONObject(new String(hb, java.nio.charset.StandardCharsets.UTF_8)); + String hdrStr = new String(hb, java.nio.charset.StandardCharsets.UTF_8); + logf("MEDIA_DL header: %s", hdrStr); + + JSONObject hdr = new JSONObject(hdrStr); if (!"download".equalsIgnoreCase(hdr.optString("op"))) { sendDlErr(outBin, "bad op"); return; } UUID mediaKey = UUID.fromString(hdr.getString("media_key")); long offset = Math.max(0L, hdr.optLong("offset", 0L)); + logf("Parsed mediaKey=%s offset=%d", mediaKey, offset); MediaRow mr = MessageDatabase.findMediaByKey(mediaKey); if (mr == null) { sendDlErr(outBin, "not found"); return; } - if (!MessageDatabase.canAccess(requesterId, mr)) { sendDlErr(outBin, "not authorized"); return; } + + logf("MediaRow: chatType=%s chatId=%s sender=%s receiver=%s storage=%s", + mr.chatType, mr.chatId, mr.senderId, mr.receiverId, mr.storagePath); + + // تست مستقیم دیتابیس (موقت برای دیباگ) + try (java.sql.Connection c = ConnectionDb.connect(); + java.sql.PreparedStatement st = c.prepareStatement( + "SELECT 1 FROM channel_subscribers WHERE channel_id = ? AND user_id = ? LIMIT 1")) { + st.setObject(1, mr.chatId, java.sql.Types.OTHER); + st.setObject(2, requesterId, java.sql.Types.OTHER); + boolean direct; + try (java.sql.ResultSet r = st.executeQuery()) { direct = r.next(); } + logf("[DL] direct channel membership ch=%s user=%s => %s", mr.chatId, requesterId, direct); + } catch (Exception e) { + logf("[DL] direct membership check ERROR: %s", e.toString()); + } + + boolean allowed = MessageDatabase.canAccess(requesterId, mr); + logf("canAccess(..) -> %s", allowed); + if (!allowed) { sendDlErr(outBin, "not authorized"); return; } java.nio.file.Path path = java.nio.file.Paths.get(mr.storagePath).normalize(); long size = java.nio.file.Files.size(path); @@ -2963,6 +3060,7 @@ public class ClientHandler implements Runnable { outBin.writeInt(okb.length); outBin.write(okb); outBin.writeLong(size - offset); + logf("Sending OK header. file=%s size=%d offset=%d", mr.fileName, size, offset); try (java.io.InputStream fis = new java.io.BufferedInputStream(java.nio.file.Files.newInputStream(path))) { if (offset > 0) fis.skipNBytes(offset); @@ -2976,6 +3074,7 @@ public class ClientHandler implements Runnable { } } outBin.flush(); + log("MEDIA_DL done."); } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java b/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java index 709fee2..0b82779 100644 --- a/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java +++ b/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java @@ -73,4 +73,29 @@ public class ChannelPermissionUtil { } return false; } + + + + public static boolean isUserInChannel(UUID userId, UUID channelId) { + final String SQL = "SELECT 1 FROM channel_subscribers WHERE channel_id = ? AND user_id = ? LIMIT 1"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(SQL)) { + + ps.setObject(1, channelId, java.sql.Types.OTHER); // 👈 مهم برای Postgres UUID + ps.setObject(2, userId, java.sql.Types.OTHER); + + System.out.println("[SQL] isUserInChannel ch=" + channelId + " user=" + userId + + " db=" + conn.getMetaData().getURL()); + + try (ResultSet rs = ps.executeQuery()) { + boolean ok = rs.next(); + System.out.println("[SQL] isUserInChannel -> " + ok); + return ok; + } + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + }