Work on sending files
This commit is contained in:
@@ -1313,7 +1313,7 @@ public class ActionHandler {
|
|||||||
JSONObject m = messages.getJSONObject(i);
|
JSONObject m = messages.getJSONObject(i);
|
||||||
String senderId = m.getString("sender_id");
|
String senderId = m.getString("sender_id");
|
||||||
String senderName = m.optString("sender_name", "Other");
|
String senderName = m.optString("sender_name", "Other");
|
||||||
String content = m.getString("content");
|
String content = m.optString("content", "");
|
||||||
String time = m.getString("send_at");
|
String time = m.getString("send_at");
|
||||||
|
|
||||||
String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : senderName;
|
String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : senderName;
|
||||||
|
|||||||
@@ -84,25 +84,54 @@ public class MessageDatabase {
|
|||||||
|
|
||||||
public static boolean insertAttachmentsTx(Connection conn, UUID messageId, List<FileAttachment> attachments) throws SQLException {
|
public static boolean insertAttachmentsTx(Connection conn, UUID messageId, List<FileAttachment> attachments) throws SQLException {
|
||||||
if (attachments == null || attachments.isEmpty()) return true;
|
if (attachments == null || attachments.isEmpty()) return true;
|
||||||
String sql = """
|
|
||||||
INSERT INTO message_attachments
|
final String sql = """
|
||||||
(attachment_id, message_id, file_url, file_type, file_name, file_size, mime_type, width, height, duration_seconds, thumbnail_url)
|
INSERT INTO message_attachments(
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
attachment_id, message_id,
|
||||||
|
file_url, file_type, file_name, file_size, mime_type,
|
||||||
|
width, height, duration_seconds, thumbnail_url,
|
||||||
|
media_key, storage_path
|
||||||
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
for (FileAttachment att : attachments) {
|
for (FileAttachment att : attachments) {
|
||||||
ps.setObject(1, UUID.randomUUID());
|
if (att == null) throw new IllegalArgumentException("Attachment is null");
|
||||||
ps.setObject(2, messageId);
|
UUID attachmentId = att.getAttachmentId() != null ? att.getAttachmentId() : UUID.randomUUID();
|
||||||
ps.setString(3, att.getFileUrl());
|
UUID mediaKey = att.getMediaKey() != null ? att.getMediaKey() : attachmentId; // سادهترین حالت
|
||||||
ps.setString(4, att.getFileType()); // IMAGE/VIDEO/AUDIO/FILE/GIF/STICKER
|
|
||||||
ps.setString(5, att.getFileName());
|
String ft = att.getFileType();
|
||||||
if (att.getFileSize() != null) ps.setLong(6, att.getFileSize()); else ps.setNull(6, java.sql.Types.BIGINT);
|
if (!"IMAGE".equalsIgnoreCase(ft) && !"AUDIO".equalsIgnoreCase(ft)) {
|
||||||
ps.setString(7, att.getMimeType());
|
throw new IllegalArgumentException("file_type must be IMAGE or AUDIO");
|
||||||
if (att.getWidth() != null) ps.setInt(8, att.getWidth()); else ps.setNull(8, java.sql.Types.INTEGER);
|
}
|
||||||
if (att.getHeight() != null) ps.setInt(9, att.getHeight()); else ps.setNull(9, java.sql.Types.INTEGER);
|
if (att.getStoragePath() == null || att.getStoragePath().isBlank()) {
|
||||||
if (att.getDurationSeconds() != null) ps.setInt(10, att.getDurationSeconds()); else ps.setNull(10, java.sql.Types.INTEGER);
|
throw new IllegalArgumentException("storage_path is required for socket downloads");
|
||||||
ps.setString(11, att.getThumbnailUrl());
|
}
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
ps.setObject(i++, attachmentId);
|
||||||
|
ps.setObject(i++, messageId);
|
||||||
|
//file url (display link)
|
||||||
|
if (att.getFileUrl() == null || att.getFileUrl().isBlank()) ps.setNull(i++, java.sql.Types.VARCHAR);
|
||||||
|
else ps.setString(i++, att.getFileUrl());
|
||||||
|
|
||||||
|
ps.setString(i++, ft.toUpperCase());
|
||||||
|
ps.setString(i++, att.getFileName());
|
||||||
|
if (att.getFileSize() == null) ps.setNull(i++, java.sql.Types.BIGINT); else ps.setLong(i++, att.getFileSize());
|
||||||
|
if (att.getMimeType() == null) ps.setNull(i++, java.sql.Types.VARCHAR); else ps.setString(i++, att.getMimeType());
|
||||||
|
if (att.getWidth() == null) ps.setNull(i++, java.sql.Types.INTEGER); else ps.setInt(i++, att.getWidth());
|
||||||
|
if (att.getHeight() == null) ps.setNull(i++, java.sql.Types.INTEGER); else ps.setInt(i++, att.getHeight());
|
||||||
|
if (att.getDurationSeconds() == null) ps.setNull(i++, java.sql.Types.INTEGER); else ps.setInt(i++, att.getDurationSeconds());
|
||||||
|
if (att.getThumbnailUrl() == null || att.getThumbnailUrl().isBlank()) ps.setNull(i++, java.sql.Types.VARCHAR);
|
||||||
|
else ps.setString(i++, att.getThumbnailUrl());
|
||||||
|
|
||||||
|
ps.setObject(i++, mediaKey);
|
||||||
|
ps.setString(i++, att.getStoragePath());
|
||||||
|
|
||||||
ps.addBatch();
|
ps.addBatch();
|
||||||
|
|
||||||
|
att.setAttachmentId(attachmentId);
|
||||||
|
att.setMediaKey(mediaKey);
|
||||||
}
|
}
|
||||||
ps.executeBatch();
|
ps.executeBatch();
|
||||||
return true;
|
return true;
|
||||||
@@ -110,15 +139,24 @@ public class MessageDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean saveMessageWithOptionalAttachments(UUID messageId, UUID senderId, UUID receiverId,
|
|
||||||
|
public static boolean saveMessageWithOptionalAttachments(
|
||||||
|
UUID messageId, UUID senderId, UUID receiverId,
|
||||||
String receiverType, String content, String messageType,
|
String receiverType, String content, String messageType,
|
||||||
List<FileAttachment> attachments) {
|
List<FileAttachment> attachments
|
||||||
|
) {
|
||||||
Connection conn = null;
|
Connection conn = null;
|
||||||
try {
|
try {
|
||||||
conn = ConnectionDb.connect();
|
conn = ConnectionDb.connect();
|
||||||
conn.setAutoCommit(false);
|
conn.setAutoCommit(false);
|
||||||
|
|
||||||
boolean isText = "TEXT".equalsIgnoreCase(messageType);
|
boolean isText = "TEXT".equalsIgnoreCase(messageType);
|
||||||
|
boolean isImage = "IMAGE".equalsIgnoreCase(messageType);
|
||||||
|
boolean isAudio = "AUDIO".equalsIgnoreCase(messageType);
|
||||||
|
if (!isText && !isImage && !isAudio) {
|
||||||
|
throw new IllegalArgumentException("messageType must be TEXT, IMAGE, or AUDIO");
|
||||||
|
}
|
||||||
|
|
||||||
if (isText) {
|
if (isText) {
|
||||||
if (attachments != null && !attachments.isEmpty())
|
if (attachments != null && !attachments.isEmpty())
|
||||||
throw new IllegalArgumentException("TEXT must not have attachments");
|
throw new IllegalArgumentException("TEXT must not have attachments");
|
||||||
@@ -127,9 +165,18 @@ public class MessageDatabase {
|
|||||||
} else {
|
} else {
|
||||||
if (attachments == null || attachments.isEmpty())
|
if (attachments == null || attachments.isEmpty())
|
||||||
throw new IllegalArgumentException("Non-TEXT must have at least one attachment");
|
throw new IllegalArgumentException("Non-TEXT must have at least one attachment");
|
||||||
|
|
||||||
|
for (FileAttachment a : attachments) {
|
||||||
|
if (a == null) throw new IllegalArgumentException("Attachment is null");
|
||||||
|
String ft = a.getFileType();
|
||||||
|
if (isImage && !"IMAGE".equalsIgnoreCase(ft))
|
||||||
|
throw new IllegalArgumentException("All attachments must be IMAGE for messageType=IMAGE");
|
||||||
|
if (isAudio && !"AUDIO".equalsIgnoreCase(ft))
|
||||||
|
throw new IllegalArgumentException("All attachments must be AUDIO for messageType=AUDIO");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insertMessageTx(conn, messageId, senderId, receiverId, receiverType, content, messageType);
|
insertMessageTx(conn, messageId, senderId, receiverId, receiverType, content, messageType.toUpperCase());
|
||||||
if (!isText) insertAttachmentsTx(conn, messageId, attachments);
|
if (!isText) insertAttachmentsTx(conn, messageId, attachments);
|
||||||
|
|
||||||
conn.commit();
|
conn.commit();
|
||||||
|
|||||||
@@ -2,18 +2,22 @@ package org.to.telegramfinalproject.Models;
|
|||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class FileAttachment {
|
public class FileAttachment {
|
||||||
|
|
||||||
private final String fileUrl;
|
private UUID attachmentId; // اختیاری؛ اگر null بود، تولید میکنیم
|
||||||
private final String fileType; // IMAGE, VIDEO, AUDIO, FILE, GIF, STICKER
|
private UUID mediaKey;
|
||||||
private final String fileName;
|
private String fileUrl;
|
||||||
private final Long fileSize;
|
private String fileType; // IMAGE, VIDEO, AUDIO, FILE, GIF, STICKER
|
||||||
private final String mimeType; // e.g., image/png
|
private String fileName;
|
||||||
private final Integer width;
|
private Long fileSize;
|
||||||
private final Integer height;
|
private String mimeType; // e.g., image/png
|
||||||
private final Integer durationSeconds; // for audio/video
|
private Integer width;
|
||||||
private final String thumbnailUrl;
|
private Integer height;
|
||||||
|
private Integer durationSeconds; // for audio/video
|
||||||
|
private String thumbnailUrl;
|
||||||
|
private String storagePath;
|
||||||
|
|
||||||
public FileAttachment(String fileUrl,
|
public FileAttachment(String fileUrl,
|
||||||
String fileType,
|
String fileType,
|
||||||
@@ -39,6 +43,10 @@ public class FileAttachment {
|
|||||||
this(fileUrl, fileType, null, null, null, null, null, null, null);
|
this(fileUrl, fileType, null, null, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FileAttachment() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ساخت از JSON /upload
|
// ساخت از JSON /upload
|
||||||
public static FileAttachment fromUploadJson(JSONObject j) {
|
public static FileAttachment fromUploadJson(JSONObject j) {
|
||||||
return new FileAttachment(
|
return new FileAttachment(
|
||||||
@@ -54,7 +62,6 @@ public class FileAttachment {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// خروجی JSON برای RT/کلاینت
|
|
||||||
public JSONObject toJson() {
|
public JSONObject toJson() {
|
||||||
JSONObject out = new JSONObject()
|
JSONObject out = new JSONObject()
|
||||||
.put("file_url", fileUrl)
|
.put("file_url", fileUrl)
|
||||||
@@ -123,4 +130,33 @@ public class FileAttachment {
|
|||||||
", thumbnailUrl='" + thumbnailUrl + '\'' +
|
", thumbnailUrl='" + thumbnailUrl + '\'' +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UUID getAttachmentId() {return attachmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getMediaKey() {return mediaKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStoragePath() {return storagePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttachmentId(UUID attachmentId) {this.attachmentId = attachmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediaKey(UUID mediaKey) {this.mediaKey = mediaKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setFileUrl(String fileUrl) {this.fileUrl = fileUrl;
|
||||||
|
}
|
||||||
|
public void setFileType(String fileType){this.fileType = fileType;}
|
||||||
|
public void setFileName(String fileName){this.fileName = fileName;}
|
||||||
|
public void setFileSize(Long fileSize){this.fileSize = fileSize;}
|
||||||
|
public void setMimeType(String mimeType){this.mimeType = mimeType;}
|
||||||
|
public void setWidth(int width){this.width = width;}
|
||||||
|
public void setHeight(int height){this.height = height;}
|
||||||
|
public void setDurationSeconds(Integer durationSeconds){this.durationSeconds = durationSeconds;}
|
||||||
|
public void setThumbnailUrl(String thumbnailUrl){this.thumbnailUrl = thumbnailUrl;}
|
||||||
|
public void setStoragePath(String storagePath) {this.storagePath = storagePath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import org.to.telegramfinalproject.Utils.GroupPermissionUtil;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -2609,68 +2612,177 @@ public class ClientHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// private void handleMediaFrame(DataInputStream dis, PrintWriter out) {
|
||||||
|
// try {
|
||||||
|
// // MAGIC = "MDM1"
|
||||||
|
// final int MAGIC_EXPECTED = 0x4D444D31;
|
||||||
|
// int magic = dis.readInt();
|
||||||
|
// if (magic != MAGIC_EXPECTED) {
|
||||||
|
// out.println(new JSONObject().put("status","error").put("message","bad magic").toString());
|
||||||
|
// out.flush();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// int headerLen = dis.readInt();
|
||||||
|
// if (headerLen <= 0 || headerLen > (64 * 1024)) {
|
||||||
|
// out.println(new JSONObject().put("status","error").put("message","bad header length").toString());
|
||||||
|
// out.flush();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// byte[] headerBytes = dis.readNBytes(headerLen);
|
||||||
|
// if (headerBytes.length != headerLen) {
|
||||||
|
// out.println(new JSONObject().put("status","error").put("message","header truncated").toString());
|
||||||
|
// out.flush();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// JSONObject h = new JSONObject(new String(headerBytes, java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
//
|
||||||
|
// long contentLen = dis.readLong();
|
||||||
|
// long MAX_MEDIA = 25L * 1024 * 1024;
|
||||||
|
// if (contentLen <= 0 || contentLen > MAX_MEDIA) {
|
||||||
|
// skip(dis, contentLen);
|
||||||
|
// out.println(new JSONObject().put("status","error").put("message","file too large/invalid").toString());
|
||||||
|
// out.flush();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// UUID messageId = UUID.fromString(h.getString("message_id"));
|
||||||
|
// UUID senderId = UUID.fromString(h.getString("sender_id"));
|
||||||
|
// String rType = h.getString("receiver_type"); // private/group/channel
|
||||||
|
// UUID receiverId = UUID.fromString(h.getString("receiver_id"));
|
||||||
|
// String messageType = h.getString("message_type"); // IMAGE | AUDIO
|
||||||
|
//
|
||||||
|
// if (!"IMAGE".equalsIgnoreCase(messageType) && !"AUDIO".equalsIgnoreCase(messageType)) {
|
||||||
|
// skip(dis, contentLen);
|
||||||
|
// out.println(new JSONObject().put("status","error").put("message","unsupported message_type").toString());
|
||||||
|
// out.flush();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// String fileName = h.optString("file_name", "file.bin");
|
||||||
|
// String mimeType = h.optString("mime_type", "application/octet-stream");
|
||||||
|
// String text = h.optString("text", "");
|
||||||
|
//
|
||||||
|
// Integer width = h.has("width") && !h.isNull("width") ? h.getInt("width") : null;
|
||||||
|
// Integer height = h.has("height") && !h.isNull("height") ? h.getInt("height") : null;
|
||||||
|
//
|
||||||
|
// if (fileName.length() > 200) fileName = fileName.substring(0, 200);
|
||||||
|
//
|
||||||
|
// // مسیر ذخیره
|
||||||
|
// java.nio.file.Path baseDir = java.nio.file.Paths.get("uploads").toAbsolutePath().normalize();
|
||||||
|
// java.nio.file.Files.createDirectories(baseDir);
|
||||||
|
// String kind = "IMAGE".equalsIgnoreCase(messageType) ? "images" : "audios";
|
||||||
|
// String subdir = kind + "/" + java.time.LocalDate.now();
|
||||||
|
// java.nio.file.Path dir = baseDir.resolve(subdir).normalize();
|
||||||
|
// java.nio.file.Files.createDirectories(dir);
|
||||||
|
//
|
||||||
|
// String ext = guessExt(fileName, mimeType);
|
||||||
|
// String storedName = java.util.UUID.randomUUID() + ext;
|
||||||
|
// java.nio.file.Path target = dir.resolve(storedName).normalize();
|
||||||
|
//
|
||||||
|
// // دریافت بایتهای فایل
|
||||||
|
// try (OutputStream fos = new BufferedOutputStream(java.nio.file.Files.newOutputStream(
|
||||||
|
// target, java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.TRUNCATE_EXISTING))) {
|
||||||
|
// long remaining = contentLen;
|
||||||
|
// byte[] buf = new byte[8192];
|
||||||
|
// while (remaining > 0) {
|
||||||
|
// int toRead = (int) Math.min(buf.length, remaining);
|
||||||
|
// int n = dis.read(buf, 0, toRead);
|
||||||
|
// if (n == -1) throw new EOFException("stream ended early");
|
||||||
|
// fos.write(buf, 0, n);
|
||||||
|
// remaining -= n;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// long fileSize = java.nio.file.Files.size(target);
|
||||||
|
// String fileUrl = "/" + subdir.replace('\\','/') + "/" + storedName;
|
||||||
|
//
|
||||||
|
// FileAttachment att = new FileAttachment(
|
||||||
|
// fileUrl,
|
||||||
|
// messageType.toUpperCase(), // IMAGE/AUDIO
|
||||||
|
// fileName,
|
||||||
|
// fileSize,
|
||||||
|
// mimeType,
|
||||||
|
// width,
|
||||||
|
// height,
|
||||||
|
// null, // durationSeconds
|
||||||
|
// null // thumbnailUrl
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// boolean ok = MessageDatabase.saveMessageWithOptionalAttachments(
|
||||||
|
// messageId, senderId, receiverId, rType, text, messageType.toUpperCase(), java.util.List.of(att)
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// JSONObject ack = new JSONObject()
|
||||||
|
// .put("status", ok ? "success" : "error")
|
||||||
|
// .put("message_id", messageId.toString())
|
||||||
|
// .put("file_url", fileUrl)
|
||||||
|
// .put("file_size", fileSize)
|
||||||
|
// .put("mime_type", mimeType);
|
||||||
|
//
|
||||||
|
// out.println(ack.toString());
|
||||||
|
// out.flush();
|
||||||
|
//
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// out.println(new JSONObject().put("status","error").put("message","exception").toString());
|
||||||
|
// out.flush();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
private void handleMediaFrame(DataInputStream dis, PrintWriter out) {
|
private void handleMediaFrame(DataInputStream dis, PrintWriter out) {
|
||||||
try {
|
try {
|
||||||
// MAGIC = "MDM1"
|
final int MAGIC_EXPECTED = 0x4D444D31; // "MDM1"
|
||||||
final int MAGIC_EXPECTED = 0x4D444D31;
|
|
||||||
int magic = dis.readInt();
|
int magic = dis.readInt();
|
||||||
if (magic != MAGIC_EXPECTED) {
|
if (magic != MAGIC_EXPECTED) {
|
||||||
out.println(new JSONObject().put("status","error").put("message","bad magic").toString());
|
out.println(new JSONObject().put("status","error").put("message","bad magic").toString()); out.flush(); return;
|
||||||
out.flush();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int headerLen = dis.readInt();
|
int headerLen = dis.readInt();
|
||||||
if (headerLen <= 0 || headerLen > (64 * 1024)) {
|
if (headerLen <= 0 || headerLen > 64 * 1024) {
|
||||||
out.println(new JSONObject().put("status","error").put("message","bad header length").toString());
|
out.println(new JSONObject().put("status","error").put("message","bad header length").toString()); out.flush(); return;
|
||||||
out.flush();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] headerBytes = dis.readNBytes(headerLen);
|
byte[] headerBytes = dis.readNBytes(headerLen);
|
||||||
if (headerBytes.length != headerLen) {
|
if (headerBytes.length != headerLen) {
|
||||||
out.println(new JSONObject().put("status","error").put("message","header truncated").toString());
|
out.println(new JSONObject().put("status","error").put("message","header truncated").toString()); out.flush(); return;
|
||||||
out.flush();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject h = new JSONObject(new String(headerBytes, java.nio.charset.StandardCharsets.UTF_8));
|
JSONObject h = new JSONObject(new String(headerBytes, java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
|
||||||
long contentLen = dis.readLong();
|
long contentLen = dis.readLong();
|
||||||
long MAX_MEDIA = 25L * 1024 * 1024;
|
long MAX_MEDIA = 25L * 1024 * 1024;
|
||||||
if (contentLen <= 0 || contentLen > MAX_MEDIA) {
|
if (contentLen <= 0 || contentLen > MAX_MEDIA) {
|
||||||
skip(dis, contentLen);
|
skip(dis, contentLen);
|
||||||
out.println(new JSONObject().put("status","error").put("message","file too large/invalid").toString());
|
out.println(new JSONObject().put("status","error").put("message","file too large/invalid").toString()); out.flush(); return;
|
||||||
out.flush();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// الزامیها
|
|
||||||
UUID messageId = UUID.fromString(h.getString("message_id"));
|
UUID messageId = UUID.fromString(h.getString("message_id"));
|
||||||
UUID senderId = UUID.fromString(h.getString("sender_id"));
|
UUID senderId = UUID.fromString(h.getString("sender_id"));
|
||||||
String rType = h.getString("receiver_type"); // private/group/channel
|
String rType = h.getString("receiver_type"); // private/group/channel
|
||||||
UUID receiverId = UUID.fromString(h.getString("receiver_id"));
|
UUID receiverId = UUID.fromString(h.getString("receiver_id"));
|
||||||
String messageType = h.getString("message_type"); // IMAGE | AUDIO
|
String messageType = h.getString("message_type").toUpperCase(); // IMAGE | AUDIO
|
||||||
|
|
||||||
if (!"IMAGE".equalsIgnoreCase(messageType) && !"AUDIO".equalsIgnoreCase(messageType)) {
|
if (!"IMAGE".equals(messageType) && !"AUDIO".equals(messageType)) {
|
||||||
skip(dis, contentLen);
|
skip(dis, contentLen);
|
||||||
out.println(new JSONObject().put("status","error").put("message","unsupported message_type").toString());
|
out.println(new JSONObject().put("status","error").put("message","unsupported message_type").toString()); out.flush(); return;
|
||||||
out.flush();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String fileName = h.optString("file_name", "file.bin");
|
String fileName = h.optString("file_name", "file.bin");
|
||||||
String mimeType = h.optString("mime_type", "application/octet-stream");
|
String mimeType = h.optString("mime_type", "application/octet-stream");
|
||||||
String text = h.optString("text", "");
|
String text = h.optString("text", ""); // کپشن اختیاری
|
||||||
|
|
||||||
Integer width = h.has("width") && !h.isNull("width") ? h.getInt("width") : null;
|
Integer width = h.has("width") && !h.isNull("width") ? h.getInt("width") : null;
|
||||||
Integer height = h.has("height") && !h.isNull("height") ? h.getInt("height") : null;
|
Integer height = h.has("height") && !h.isNull("height") ? h.getInt("height") : null;
|
||||||
|
|
||||||
if (fileName.length() > 200) fileName = fileName.substring(0, 200);
|
if (fileName.length() > 200) fileName = fileName.substring(0, 200);
|
||||||
|
|
||||||
// مسیر ذخیره
|
// مسیر ذخیره (فیزیکی)
|
||||||
java.nio.file.Path baseDir = java.nio.file.Paths.get("uploads").toAbsolutePath().normalize();
|
java.nio.file.Path baseDir = java.nio.file.Paths.get("uploads").toAbsolutePath().normalize();
|
||||||
java.nio.file.Files.createDirectories(baseDir);
|
java.nio.file.Files.createDirectories(baseDir);
|
||||||
String kind = "IMAGE".equalsIgnoreCase(messageType) ? "images" : "audios";
|
String kind = "IMAGE".equals(messageType) ? "images" : "audios";
|
||||||
String subdir = kind + "/" + java.time.LocalDate.now();
|
String subdir = kind + "/" + java.time.LocalDate.now();
|
||||||
java.nio.file.Path dir = baseDir.resolve(subdir).normalize();
|
java.nio.file.Path dir = baseDir.resolve(subdir).normalize();
|
||||||
java.nio.file.Files.createDirectories(dir);
|
java.nio.file.Files.createDirectories(dir);
|
||||||
@@ -2679,7 +2791,7 @@ public class ClientHandler implements Runnable {
|
|||||||
String storedName = java.util.UUID.randomUUID() + ext;
|
String storedName = java.util.UUID.randomUUID() + ext;
|
||||||
java.nio.file.Path target = dir.resolve(storedName).normalize();
|
java.nio.file.Path target = dir.resolve(storedName).normalize();
|
||||||
|
|
||||||
// دریافت بایتهای فایل
|
// دریافت باینری فایل
|
||||||
try (OutputStream fos = new BufferedOutputStream(java.nio.file.Files.newOutputStream(
|
try (OutputStream fos = new BufferedOutputStream(java.nio.file.Files.newOutputStream(
|
||||||
target, java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.TRUNCATE_EXISTING))) {
|
target, java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.TRUNCATE_EXISTING))) {
|
||||||
long remaining = contentLen;
|
long remaining = contentLen;
|
||||||
@@ -2694,30 +2806,52 @@ public class ClientHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long fileSize = java.nio.file.Files.size(target);
|
long fileSize = java.nio.file.Files.size(target);
|
||||||
String fileUrl = "/" + subdir.replace('\\','/') + "/" + storedName;
|
|
||||||
|
|
||||||
FileAttachment att = new FileAttachment(
|
String storagePath = target.toString(); // فقط سرور استفاده کنه
|
||||||
fileUrl,
|
String fileUrl = "/" + subdir.replace('\\','/') + "/" + storedName; // اختیاری/نمایشی (HTTP لازم نیست)
|
||||||
messageType.toUpperCase(), // IMAGE/AUDIO
|
String mt = messageType; // "IMAGE" یا "AUDIO"
|
||||||
fileName,
|
int safeWidth = ("IMAGE".equals(mt) && width != null) ? width : 0;
|
||||||
fileSize,
|
int safeHeight = ("IMAGE".equals(mt) && height != null) ? height : 0;
|
||||||
mimeType,
|
FileAttachment att = new FileAttachment();
|
||||||
width,
|
att.setFileUrl(fileUrl); // اختیاری
|
||||||
height,
|
att.setFileType(messageType); // IMAGE/AUDIO
|
||||||
null, // durationSeconds
|
att.setFileName(fileName);
|
||||||
null // thumbnailUrl
|
att.setFileSize(fileSize);
|
||||||
);
|
att.setMimeType(mimeType);
|
||||||
|
att.setWidth(safeWidth);
|
||||||
|
att.setHeight(safeHeight);
|
||||||
|
att.setDurationSeconds(0);
|
||||||
|
att.setThumbnailUrl(null);
|
||||||
|
att.setStoragePath(storagePath); // اجباری برای سوکت
|
||||||
|
// اجازه بده insertAttachmentsTx برایش mediaKey و attachmentId بسازد
|
||||||
|
java.util.List<FileAttachment> atts = java.util.List.of(att);
|
||||||
|
|
||||||
boolean ok = MessageDatabase.saveMessageWithOptionalAttachments(
|
boolean ok = MessageDatabase.saveMessageWithOptionalAttachments(
|
||||||
messageId, senderId, receiverId, rType, text, messageType.toUpperCase(), java.util.List.of(att)
|
messageId, senderId, receiverId, rType, text, messageType, atts
|
||||||
);
|
);
|
||||||
|
|
||||||
|
UUID mediaKey = null;
|
||||||
|
try (PreparedStatement q = ConnectionDb.connect().prepareStatement(
|
||||||
|
"SELECT media_key FROM message_attachments WHERE message_id = ? AND storage_path = ? LIMIT 1"
|
||||||
|
)) {
|
||||||
|
q.setObject(1, messageId);
|
||||||
|
q.setString(2, storagePath);
|
||||||
|
try (ResultSet rs = q.executeQuery()) {
|
||||||
|
if (rs.next()) mediaKey = (UUID) rs.getObject(1);
|
||||||
|
}
|
||||||
|
} catch (SQLException sqle) {
|
||||||
|
// در بدترین حالت بدون media_key ACK میدیم، ولی بهتره خطا رو لاگ کنیم
|
||||||
|
sqle.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
JSONObject ack = new JSONObject()
|
JSONObject ack = new JSONObject()
|
||||||
.put("status", ok ? "success" : "error")
|
.put("status", ok ? "success" : "error")
|
||||||
.put("message_id", messageId.toString())
|
.put("message_id", messageId.toString())
|
||||||
.put("file_url", fileUrl)
|
.put("media_key", mediaKey != null ? mediaKey.toString() : JSONObject.NULL) // برای دانلود سوکتی
|
||||||
|
.put("file_name", fileName)
|
||||||
.put("file_size", fileSize)
|
.put("file_size", fileSize)
|
||||||
.put("mime_type", mimeType);
|
.put("mime_type", mimeType)
|
||||||
|
.put("display_path", fileUrl); // صرفاً نمایشی
|
||||||
|
|
||||||
out.println(ack.toString());
|
out.println(ack.toString());
|
||||||
out.flush();
|
out.flush();
|
||||||
@@ -2729,6 +2863,7 @@ public class ClientHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void skip(DataInputStream dis, long n) throws IOException {
|
private static void skip(DataInputStream dis, long n) throws IOException {
|
||||||
if (n <= 0) return;
|
if (n <= 0) return;
|
||||||
byte[] buf = new byte[8192];
|
byte[] buf = new byte[8192];
|
||||||
@@ -2997,7 +3132,6 @@ public class ClientHandler implements Runnable {
|
|||||||
.put("data", chatUpdate);
|
.put("data", chatUpdate);
|
||||||
for (UUID u : allMembers) RealTimeEventDispatcher.sendToUser(u, chatPayload);
|
for (UUID u : allMembers) RealTimeEventDispatcher.sendToUser(u, chatPayload);
|
||||||
|
|
||||||
// حالا new_message را فقط به غیر از sender
|
|
||||||
List<UUID> others = new ArrayList<>(allMembers);
|
List<UUID> others = new ArrayList<>(allMembers);
|
||||||
others.remove(senderId);
|
others.remove(senderId);
|
||||||
RealTimeEventDispatcher.broadcastToUsers(others, payload);
|
RealTimeEventDispatcher.broadcastToUsers(others, payload);
|
||||||
|
|||||||
Reference in New Issue
Block a user