work on send file

This commit is contained in:
2025-08-11 15:59:54 +03:30
parent 17366f5ea6
commit b3941108b5
12 changed files with 629 additions and 211 deletions
@@ -9,6 +9,7 @@ import org.to.telegramfinalproject.Utils.GroupPermissionUtil;
import java.io.*;
import java.net.Socket;
import java.sql.Connection;
import java.time.LocalDateTime;
import java.util.*;
@@ -111,6 +112,8 @@ public class ClientHandler implements Runnable {
JSONObject c = new JSONObject();
c.put("user_id", contact.getUser_id().toString());
c.put("contact_id", contact.getContact_id().toString());
User Contact = userDatabase.findByInternalUUID(contact.getContact_id());
c.put("contact_displayId", Contact.getUser_id());
c.put("is_blocked", contact.getIs_blocked());
c.put("profile_name", target.getProfile_name());
@@ -2469,8 +2472,87 @@ public class ClientHandler implements Runnable {
}
private ResponseModel handleSendMessage(JSONObject json) {
// private ResponseModel handleSendMessage(JSONObject json) {
//
// try {
// if (currentUser == null)
// return new ResponseModel("error", "Unauthorized. Please login first.");
//
// UUID messageId = UUID.randomUUID();
// UUID senderId = currentUser.getInternal_uuid();
// String receiverType = json.getString("receiver_type");
// UUID receiverId;
// receiverId = UUID.fromString(json.getString("receiver_id"));
//
// if(Objects.equals(receiverType, "private")){
// PrivateChatDatabase.clearDeletedFlag(senderId, receiverId);
// UUID other = PrivateChatDatabase.getOtherParticipant(receiverId, senderId);
// if (other == null) {
// return new ResponseModel("error", "Invalid private chat.");
// }
// if (ContactDatabase.isBlocked(senderId, other) || ContactDatabase.isBlocked(other, senderId)) {
// return new ResponseModel("error", "You can't message this user (blocked).");
// }
// }
//
//
// String content = json.optString("content", "");
// String messageType = json.optString("message_type", "TEXT");
//
// boolean inserted = MessageDatabase.insertMessage(messageId, senderId, receiverId, receiverType, content, messageType);
// if (!inserted)
// return new ResponseModel("error", "Failed to insert message.");
//
// if (json.has("attachments")) {
// JSONArray attachmentsArray = json.getJSONArray("attachments");
// List<FileAttachment> attachments = new ArrayList<>();
//
// for (int i = 0; i < attachmentsArray.length(); i++) {
// JSONObject attJson = attachmentsArray.getJSONObject(i);
// attachments.add(new FileAttachment(
// attJson.getString("file_url"),
// attJson.getString("file_type")
// ));
// }
//
// boolean attInserted = MessageDatabase.insertAttachments(messageId, attachments);
// if (!attInserted)
// return new ResponseModel("error", "Message inserted but failed to attach files.");
// }
//
// // Send real-time message
// Message msg = new Message(messageId, senderId, receiverId, receiverType, content, messageType, LocalDateTime.now());
// List<UUID> receivers = getReceiversForChat(receiverId, receiverType);
// receivers.remove(senderId);
// RealTimeEventDispatcher.sendNewMessage(msg, receivers);
//
// // Update chat list (last_message_time)
// JSONObject chatUpdate = new JSONObject();
// chatUpdate.put("chat_id", receiverId.toString());
// chatUpdate.put("chat_type", receiverType);
// chatUpdate.put("last_message_time", LocalDateTime.now().toString());
//
// JSONObject chatPayload = new JSONObject();
// chatPayload.put("action", "chat_updated");
// chatPayload.put("data", chatUpdate);
//
// for (UUID receiver : receivers) {
// RealTimeEventDispatcher.sendToUser(receiver, chatPayload);
// }
//
// JSONObject data = new JSONObject();
// data.put("message_id", messageId.toString());
// return new ResponseModel("success", "Message sent successfully.", data);
//
// } catch (Exception e) {
// e.printStackTrace();
// return new ResponseModel("error", "Exception occurred while sending message.");
// }
// }
private ResponseModel handleSendMessage(JSONObject json) {
try {
if (currentUser == null)
return new ResponseModel("error", "Unauthorized. Please login first.");
@@ -2478,68 +2560,124 @@ public class ClientHandler implements Runnable {
UUID messageId = UUID.randomUUID();
UUID senderId = currentUser.getInternal_uuid();
String receiverType = json.getString("receiver_type");
UUID receiverId;
receiverId = UUID.fromString(json.getString("receiver_id"));
if(Objects.equals(receiverType, "private")){
PrivateChatDatabase.clearDeletedFlag(senderId, receiverId);
UUID other = PrivateChatDatabase.getOtherParticipant(receiverId, senderId);
if (other == null) {
return new ResponseModel("error", "Invalid private chat.");
}
if (ContactDatabase.isBlocked(senderId, other) || ContactDatabase.isBlocked(other, senderId)) {
return new ResponseModel("error", "You can't message this user (blocked).");
}
}
UUID receiverId = UUID.fromString(json.getString("receiver_id"));
// private validations...
// ...
String content = json.optString("content", "");
String messageType = json.optString("message_type", "TEXT");
boolean inserted = MessageDatabase.insertMessage(messageId, senderId, receiverId, receiverType, content, messageType);
if (!inserted)
return new ResponseModel("error", "Failed to insert message.");
// Parse attachments
List<FileAttachment> attachments = new ArrayList<>();
if (json.has("attachments")) {
JSONArray attachmentsArray = json.getJSONArray("attachments");
List<FileAttachment> attachments = new ArrayList<>();
for (int i = 0; i < attachmentsArray.length(); i++) {
JSONObject attJson = attachmentsArray.getJSONObject(i);
JSONArray arr = json.getJSONArray("attachments");
for (int i = 0; i < arr.length(); i++) {
JSONObject a = arr.getJSONObject(i);
attachments.add(new FileAttachment(
attJson.getString("file_url"),
attJson.getString("file_type")
a.optString("file_url",""),
a.optString("file_type","FILE"),
a.optString("file_name",""),
a.has("file_size") && !a.isNull("file_size") ? a.getLong("file_size") : null,
a.optString("mime_type", null),
a.has("width") && !a.isNull("width") ? a.getInt("width") : null,
a.has("height") && !a.isNull("height") ? a.getInt("height") : null,
a.has("duration_seconds") && !a.isNull("duration_seconds") ? a.getInt("duration_seconds") : null,
a.isNull("thumbnail_url") ? null : a.optString("thumbnail_url", null)
));
}
boolean attInserted = MessageDatabase.insertAttachments(messageId, attachments);
if (!attInserted)
return new ResponseModel("error", "Message inserted but failed to attach files.");
}
// Send real-time message
if ((content == null || content.isBlank()) && attachments.isEmpty()) {
return new ResponseModel("error", "Empty message: no content or attachment.");
}
// Harmonize message_type
if (!attachments.isEmpty()) {
String firstType = attachments.get(0).getFileType();
if ("TEXT".equalsIgnoreCase(messageType)) {
messageType = firstType;
} else if (!messageType.equalsIgnoreCase(firstType) && !messageType.equalsIgnoreCase("FILE")) {
return new ResponseModel("error", "message_type and attachment.file_type mismatch.");
}
}
// DB transaction
try (Connection conn = ConnectionDb.connect()) {
conn.setAutoCommit(false);
boolean inserted = MessageDatabase.insertMessageTx(conn, messageId, senderId, receiverId, receiverType, content, messageType);
if (!inserted) {
conn.rollback();
return new ResponseModel("error", "Failed to insert message.");
}
if (!attachments.isEmpty()) {
boolean attInserted = MessageDatabase.insertAttachmentsTx(conn, messageId, attachments);
if (!attInserted) {
conn.rollback();
return new ResponseModel("error", "Message inserted but failed to attach files.");
}
}
conn.commit();
}
// Real-Time
Message msg = new Message(messageId, senderId, receiverId, receiverType, content, messageType, LocalDateTime.now());
// رویداد با پیوست‌ها
JSONObject payload = new JSONObject();
payload.put("action", "new_message");
JSONObject data = new JSONObject();
data.put("id", messageId.toString());
data.put("sender_id", senderId.toString());
data.put("receiver_id", receiverId.toString());
data.put("receiver_type", receiverType);
data.put("content", content);
data.put("message_type", messageType);
data.put("send_at", msg.getSend_at().toString());
if (!attachments.isEmpty()) {
JSONArray out = new JSONArray();
for (FileAttachment a : attachments) {
JSONObject ao = new JSONObject()
.put("file_url", a.getFileUrl())
.put("file_type", a.getFileType())
.put("file_name", a.getFileName() == null ? JSONObject.NULL : a.getFileName())
.put("file_size", a.getFileSize() == null ? JSONObject.NULL : a.getFileSize())
.put("mime_type", a.getMimeType() == null ? JSONObject.NULL : a.getMimeType())
.put("width", a.getWidth() == null ? JSONObject.NULL : a.getWidth())
.put("height", a.getHeight() == null ? JSONObject.NULL : a.getHeight())
.put("duration_seconds", a.getDurationSeconds() == null ? JSONObject.NULL : a.getDurationSeconds())
.put("thumbnail_url", a.getThumbnailUrl() == null ? JSONObject.NULL : a.getThumbnailUrl());
out.put(ao);
}
data.put("attachments", out);
}
User sender = userDatabase.findByInternalUUID(senderId);
if (sender != null) data.put("sender_name", sender.getProfile_name());
payload.put("data", data);
List<UUID> receivers = getReceiversForChat(receiverId, receiverType);
receivers.remove(senderId);
RealTimeEventDispatcher.sendNewMessage(msg, receivers);
RealTimeEventDispatcher.broadcastToUsers(receivers, payload);
// Update chat list (last_message_time)
JSONObject chatUpdate = new JSONObject();
chatUpdate.put("chat_id", receiverId.toString());
chatUpdate.put("chat_type", receiverType);
chatUpdate.put("last_message_time", LocalDateTime.now().toString());
// chat_updated
JSONObject chatUpdate = new JSONObject()
.put("chat_id", receiverId.toString())
.put("chat_type", receiverType)
.put("last_message_time", LocalDateTime.now().toString());
JSONObject chatPayload = new JSONObject();
chatPayload.put("action", "chat_updated");
chatPayload.put("data", chatUpdate);
JSONObject chatPayload = new JSONObject()
.put("action", "chat_updated")
.put("data", chatUpdate);
for (UUID receiver : receivers) {
RealTimeEventDispatcher.sendToUser(receiver, chatPayload);
}
for (UUID r : receivers) RealTimeEventDispatcher.sendToUser(r, chatPayload);
JSONObject data = new JSONObject();
data.put("message_id", messageId.toString());
return new ResponseModel("success", "Message sent successfully.", data);
JSONObject respData = new JSONObject().put("message_id", messageId.toString());
return new ResponseModel("success", "Message sent successfully.", respData);
} catch (Exception e) {
e.printStackTrace();
@@ -2548,6 +2686,7 @@ public class ClientHandler implements Runnable {
}
private List<UUID> getReceiversForChat(UUID receiverId, String receiverType) {
switch (receiverType) {
case "private":
@@ -9,6 +9,7 @@ import java.net.Socket;
public class MainServer {
private static final int PORT = 8000;
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Server started on port " + PORT);
@@ -0,0 +1,57 @@
package org.to.telegramfinalproject.Server;
import org.to.telegramfinalproject.Database.userDatabase;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestServer {
private static final int SOCKET_PORT = 8000; // سرور سوکت
private static final int HTTP_PORT = 8080; // سرور آپلود
private static final String UPLOAD_BASE_DIR = "uploads"; // پوشه‌ی ذخیره فایل‌ها
public static void main(String[] args) {
// 1) استارت HTTP Upload در ترد جدا
Thread httpThread = new Thread(() -> {
try {
UploadHttp.start(HTTP_PORT, UPLOAD_BASE_DIR);
} catch (IOException e) {
System.err.println("Upload HTTP failed to start: " + e.getMessage());
e.printStackTrace();
}
}, "upload-http");
httpThread.setDaemon(true);
httpThread.start();
// 2) سرور سوکت با Thread Pool
ExecutorService pool = Executors.newCachedThreadPool();
try (ServerSocket serverSocket = new ServerSocket(SOCKET_PORT)) {
System.out.println("Socket server started on port " + SOCKET_PORT);
userDatabase.setAllUsersOffline();
// 3) Shutdown Hook برای خاموشی تمیز
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("\nShutting down...");
try { serverSocket.close(); } catch (IOException ignore) {}
pool.shutdownNow();
userDatabase.setAllUsersOffline();
System.out.println("Goodbye.");
}));
// 4) حلقه پذیرش اتصال‌ها
while (!serverSocket.isClosed()) {
Socket clientSocket = serverSocket.accept();
clientSocket.setTcpNoDelay(true);
System.out.println("New client connected: " + clientSocket.getInetAddress());
pool.submit(new ClientHandler(clientSocket));
}
} catch (IOException e) {
System.err.println("Socket server error: " + e.getMessage());
e.printStackTrace();
}
}
}
@@ -1,35 +1,20 @@
package org.to.telegramfinalproject.Server;
import static spark.Spark.*;
import javax.servlet.MultipartConfigElement;
import javax.servlet.http.Part;
import java.io.IOException;
import java.nio.file.*;
import java.io.InputStream;
import org.json.JSONObject;
import javax.imageio.ImageIO;
import javax.servlet.MultipartConfigElement;
import javax.servlet.http.Part;
import java.awt.image.BufferedImage;
import java.nio.file.*;
import java.io.InputStream;
import java.io.IOException;
import java.nio.file.*;
import java.time.LocalDate;
import javax.sound.sampled.*; // برای WAV
// برای ویدیو (MP4 و )
import org.jcodec.api.FrameGrab;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.model.Picture;
import org.jcodec.scale.AWTUtil;
//import org.jcodec.containers.mp4.MP4Demuxer;
//import org.jcodec.containers.mp4.MP4DemuxerTrack;
// برای MP3
import org.json.JSONObject;
import com.mpatric.mp3agic.Mp3File;
public class UploadHttp {
@@ -67,7 +52,7 @@ public class UploadHttp {
String original = filePart.getSubmittedFileName();
String ext = guessExt(original, mime);
String day = LocalDate.now().toString();
String typeDir = subdirFor(mime); // images/videos/audios/files
String typeDir = subdirFor(mime); // images/audios/files
String subdir = typeDir + "/" + day;
String name = java.util.UUID.randomUUID() + ext;
@@ -84,39 +69,15 @@ public class UploadHttp {
String fileUrl = "/" + subdir.replace('\\', '/') + "/" + name;
String fileType = mapToFileType(mime);
// متادیتا
//Meta deta only for audio and image
Integer width = null, height = null, durationSeconds = null;
String thumbnailUrl = null;
if ("IMAGE".equals(fileType) || "GIF".equals(fileType)) {
int[] wh = imageSize(target);
if (wh != null) { width = wh[0]; height = wh[1]; }
} else if ("VIDEO".equals(fileType)) {
// تلاش برای استخراج width/height/duration با JCodec
VideoMeta vm = videoMeta(target);
if (vm != null) {
width = vm.width;
height = vm.height;
durationSeconds = vm.durationSeconds;
}
// ساخت thumbnail (اختیاری)
try {
String thumbName = name.replace(ext, "") + "_thumb.jpg";
Path thumbDir = basePath.resolve("thumbs/" + day).normalize();
Files.createDirectories(thumbDir);
Path thumbTarget = thumbDir.resolve(thumbName).normalize();
if (makeVideoThumbnail(target, thumbTarget)) {
thumbnailUrl = "/thumbs/" + day + "/" + thumbName;
}
} catch (Exception ignore) {}
} else if ("AUDIO".equals(fileType)) {
// اگر MP3 بود، مدت را با mp3agic بگیر
if ("audio/mpeg".equalsIgnoreCase(mime) || ext.equalsIgnoreCase(".mp3")) {
try {
Mp3File mp3 = new Mp3File(target.toFile());
durationSeconds = (int) mp3.getLengthInSeconds();
} catch (Exception ignore) {}
}
durationSeconds = audioDurationSeconds(target, mime, ext);
}
res.status(200);
@@ -129,11 +90,11 @@ public class UploadHttp {
.put("width", width == null ? JSONObject.NULL : width)
.put("height", height == null ? JSONObject.NULL : height)
.put("duration_seconds", durationSeconds == null ? JSONObject.NULL : durationSeconds)
.put("thumbnail_url", thumbnailUrl == null ? JSONObject.NULL : thumbnailUrl)
.put("thumbnail_url", JSONObject.NULL)
.toString();
} catch (Exception e) {
e.printStackTrace(); // لوکال
e.printStackTrace();
res.status(500);
return jsonError("internal error");
}
@@ -153,7 +114,6 @@ public class UploadHttp {
private static String subdirFor(String mime) {
String m = mime.toLowerCase();
if (m.startsWith("image/")) return "images";
if (m.startsWith("video/")) return "videos";
if (m.startsWith("audio/")) return "audios";
return "files";
}
@@ -164,7 +124,6 @@ public class UploadHttp {
if (m.contains("gif")) return "GIF";
return "IMAGE";
}
if (m.startsWith("video/")) return "VIDEO";
if (m.startsWith("audio/")) return "AUDIO";
return "FILE";
}
@@ -177,14 +136,13 @@ public class UploadHttp {
if ("image/png".equalsIgnoreCase(mime)) return ".png";
if ("image/jpeg".equalsIgnoreCase(mime)) return ".jpg";
if ("image/gif".equalsIgnoreCase(mime)) return ".gif";
if ("video/mp4".equalsIgnoreCase(mime)) return ".mp4";
if ("audio/mpeg".equalsIgnoreCase(mime)) return ".mp3";
if ("audio/wav".equalsIgnoreCase(mime) || "audio/x-wav".equalsIgnoreCase(mime)) return ".wav";
if ("application/pdf".equalsIgnoreCase(mime)) return ".pdf";
return "";
}
private static String safeName(String name) {
// پاک‌سازی خیلی ساده برای خروجی
return name.replace("\"", "").replace("\n", "").replace("\r", "");
}
@@ -196,49 +154,30 @@ public class UploadHttp {
return null;
}
// --- Video meta via JCodec ---
private static class VideoMeta {
final Integer width, height, durationSeconds;
VideoMeta(Integer w, Integer h, Integer d) { this.width = w; this.height = h; this.durationSeconds = d; }
}
private static VideoMeta videoMeta(Path file) {
//only audio
private static Integer audioDurationSeconds(Path file, String mime, String ext) {
try {
// Width/Height از طریق اولین فریم
BufferedImage first = null;
try {
FrameGrab grab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(file.toFile()));
Picture p = grab.getNativeFrame();
if (p != null) first = AWTUtil.toBufferedImage(p);
} catch (Exception ignore) {}
if ("audio/mpeg".equalsIgnoreCase(mime) || ".mp3".equalsIgnoreCase(ext)) {
Mp3File mp3 = new Mp3File(file.toFile());
return (int) mp3.getLengthInSeconds();
}
Integer w = null, h = null;
if (first != null) { w = first.getWidth(); h = first.getHeight(); }
// Duration از Demuxer (فقط MP4ها عالی جواب میده)
Integer dur = null;
// try {
// MP4Demuxer demuxer = new MP4Demuxer(NIOUtils.readableChannel(file.toFile()));
// MP4DemuxerTrack vt = (MP4DemuxerTrack) demuxer.getVideoTrack();
// double seconds = vt.getMeta().getTotalDuration();
// dur = (int) Math.round(seconds);
// } catch (Exception ignore) {}
if (w != null || h != null || dur != null) return new VideoMeta(w, h, dur);
} catch (Exception ignore) {}
// WAV با javax.sound.sampled
if ("audio/wav".equalsIgnoreCase(mime) || "audio/x-wav".equalsIgnoreCase(mime) || ".wav".equalsIgnoreCase(ext)) {
try (AudioInputStream ais = AudioSystem.getAudioInputStream(file.toFile())) {
AudioFormat format = ais.getFormat();
long frames = ais.getFrameLength();
if (frames > 0 && format.getFrameRate() > 0) {
double seconds = frames / format.getFrameRate();
return (int)Math.round(seconds);
}
}
}
} catch (UnsupportedAudioFileException | IOException ignore) {
// فرمت صوتی پشتیبانی نشده برای AudioSystem
} catch (Exception ignore) {
// mp3agic یا سایر استثناها
}
return null;
}
private static boolean makeVideoThumbnail(Path videoFile, Path thumbTarget) {
try {
FrameGrab grab = FrameGrab.createFrameGrab(NIOUtils.readableChannel(videoFile.toFile()));
Picture p = grab.getNativeFrame();
if (p == null) return false;
BufferedImage bi = AWTUtil.toBufferedImage(p);
Files.createDirectories(thumbTarget.getParent());
return ImageIO.write(bi, "jpg", thumbTarget.toFile());
} catch (Exception e) {
return false;
}
}
}