mirror of
https://github.com/tiennm99/BSK-All-in-One-Clinic-Management-System.git
synced 2026-10-17 04:14:24 +00:00
[update] Remove DebugFileWalk class and enhance CheckUpPage with image deletion functionality. Implement ping/pong mechanism for connection stability in ClientHandler and Server, and improve timeout management with IdleStateHandler adjustments. Update binary files for database and enhance Google Drive integration for image deletion.
This commit is contained in:
1 parent
700c90733b
commit
ccfc9eaaed
17 files changed
+368
-87
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -1,51 +0,0 @@
|
||||
package BsK.client.ui.component.CheckUpPage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DebugFileWalk {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// ▼▼▼ IMPORTANT ▼▼▼
|
||||
// Change this path to the EXACT folder you are trying to scan.
|
||||
// Use double backslashes "\\" for Windows paths.
|
||||
// Example: "C:\\Users\\YourName\\Pictures\\MyEvent"
|
||||
Path mediaPath = Paths.get("ANH SIEU AM");
|
||||
// ▲▲▲ IMPORTANT ▲▲▲
|
||||
|
||||
|
||||
System.out.println("--- Starting Scan ---");
|
||||
System.out.println("Scanning Path: " + mediaPath.toAbsolutePath());
|
||||
System.out.println("Does path exist? " + Files.exists(mediaPath));
|
||||
System.out.println("--------------------------------\n");
|
||||
|
||||
System.out.println(">>> ALL files and folders found by Files.walk():");
|
||||
try (Stream<Path> pathStream = Files.walk(mediaPath)) {
|
||||
pathStream.forEach(path -> {
|
||||
System.out.println(" -> Found: " + path);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
System.err.println("An ERROR occurred during the scan:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("\n--------------------------------");
|
||||
System.out.println(">>> Files that match the '.jpg' filter:");
|
||||
try (Stream<Path> pathStream = Files.walk(mediaPath)) {
|
||||
pathStream
|
||||
.filter(path -> !Files.isDirectory(path))
|
||||
.filter(path -> path.toString().toLowerCase().endsWith(".jpg"))
|
||||
.forEach(path -> {
|
||||
System.out.println(" -> Matched: " + path);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
System.err.println("An ERROR occurred during the filtered scan:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("\n--- Scan Finished ---");
|
||||
}
|
||||
}
|
||||
@@ -189,9 +189,10 @@ public class Client {
|
||||
true,
|
||||
new DefaultHttpHeaders(),
|
||||
50 * 1024 * 1024))
|
||||
// Add IdleStateHandler with longer timeouts for network resilience
|
||||
// Read timeout: 60 seconds, Write timeout: 30 seconds
|
||||
.addLast(new IdleStateHandler(60, 30, 0))
|
||||
// Add IdleStateHandler for keep-alive and timeout detection
|
||||
// Read timeout: 5 minutes (300s) - disconnect if no data from server
|
||||
// Write timeout: 10 seconds - send ping if no data sent to server
|
||||
.addLast(new IdleStateHandler(300, 10, 0))
|
||||
.addLast("ws", ClientHandler.INSTANCE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,7 +6,9 @@ import BsK.client.ui.handler.UIHandler;
|
||||
import BsK.common.entity.DoctorItem;
|
||||
import BsK.common.packet.Packet;
|
||||
import BsK.common.packet.PacketSerializer;
|
||||
import BsK.common.packet.req.PingRequest;
|
||||
import BsK.common.packet.res.*;
|
||||
import BsK.common.util.network.NetworkUtil;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.websocketx.*;
|
||||
@@ -35,6 +37,12 @@ public class ClientHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
private static final long NETWORK_ISSUE_DIALOG_COOLDOWN = 30000; // 30 seconds
|
||||
private static volatile boolean dialogCurrentlyShowing = false;
|
||||
|
||||
// Ping-pong tracking
|
||||
private static volatile int missedPongCount = 0;
|
||||
private static final int MAX_MISSED_PONGS = 3;
|
||||
private static volatile long lastPingRequestTime = 0;
|
||||
private static volatile boolean awaitingPongResponse = false;
|
||||
|
||||
// Disconnection type tracking
|
||||
public enum DisconnectionType {
|
||||
TIMEOUT, // Server not responding (READER_IDLE)
|
||||
@@ -135,6 +143,19 @@ public class ClientHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
LocalStorage.provinceToId = provinceResponse.getProvinceToId();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle PongResponse
|
||||
if (packet instanceof PongResponse pongResponse) {
|
||||
long roundTripTime = System.currentTimeMillis() - pongResponse.getTimestamp();
|
||||
log.debug("Received PongResponse (round trip: {}ms)", roundTripTime);
|
||||
|
||||
// Reset missed pong counter on successful pong
|
||||
missedPongCount = 0;
|
||||
awaitingPongResponse = false;
|
||||
wasTimeoutDetected = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch to registered listeners
|
||||
List<ResponseListener<?>> responseListeners = listeners.get(packet.getClass());
|
||||
@@ -156,33 +177,40 @@ public class ClientHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent) evt;
|
||||
if (event.state() == IdleState.WRITER_IDLE) {
|
||||
// Send ping to keep connection alive
|
||||
log.debug("Sending ping to server");
|
||||
lastPingTime = System.currentTimeMillis();
|
||||
awaitingPong = true;
|
||||
ctx.writeAndFlush(new PingWebSocketFrame());
|
||||
// Send PingRequest packet to keep connection alive
|
||||
if (awaitingPongResponse) {
|
||||
// Previous ping not answered - increment missed counter
|
||||
missedPongCount++;
|
||||
log.warn("Missed pong #{} - no response to previous ping", missedPongCount);
|
||||
|
||||
if (missedPongCount >= MAX_MISSED_PONGS) {
|
||||
log.error("Missed {} consecutive pongs - disconnecting", missedPongCount);
|
||||
wasTimeoutDetected = true;
|
||||
lastDisconnectionType = DisconnectionType.TIMEOUT;
|
||||
ctx.close(); // Force disconnect
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Send new ping packet
|
||||
log.debug("Sending PingRequest to server (missed count: {})", missedPongCount);
|
||||
lastPingRequestTime = System.currentTimeMillis();
|
||||
awaitingPongResponse = true;
|
||||
|
||||
PingRequest pingRequest = new PingRequest();
|
||||
NetworkUtil.sendPacket(ctx.channel(), pingRequest);
|
||||
|
||||
} else if (event.state() == IdleState.READER_IDLE) {
|
||||
// Server hasn't responded in a while - this is a timeout scenario
|
||||
log.warn("Server not responding - timeout detected");
|
||||
// Server hasn't sent any data (including pong) for 5 minutes
|
||||
log.warn("Server not responding - no data received for 5 minutes (READER_IDLE)");
|
||||
wasTimeoutDetected = true;
|
||||
lastDisconnectionType = DisconnectionType.TIMEOUT;
|
||||
// Just set the flag, don't show dialog yet - let channelInactive handle it
|
||||
// Try to send a ping to test connection
|
||||
lastPingTime = System.currentTimeMillis();
|
||||
awaitingPong = true;
|
||||
ctx.writeAndFlush(new PingWebSocketFrame());
|
||||
}
|
||||
} else if (evt instanceof WebSocketFrame) {
|
||||
// Handle pong responses
|
||||
if (evt instanceof PongWebSocketFrame) {
|
||||
lastPongTime = System.currentTimeMillis();
|
||||
long pingRoundTrip = lastPongTime - lastPingTime;
|
||||
log.debug("Received pong from server (round trip: {}ms)", pingRoundTrip);
|
||||
awaitingPong = false;
|
||||
// Reset timeout flag since we got a response
|
||||
wasTimeoutDetected = false;
|
||||
// Let channelInactive handle the disconnect
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: Call super to ensure event propagation continues
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -223,10 +251,10 @@ public class ClientHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
|
||||
// Determine disconnection type if not already set
|
||||
if (lastDisconnectionType == DisconnectionType.UNKNOWN) {
|
||||
if (wasTimeoutDetected) {
|
||||
if (wasTimeoutDetected || missedPongCount >= MAX_MISSED_PONGS) {
|
||||
lastDisconnectionType = DisconnectionType.TIMEOUT;
|
||||
log.info("Channel inactive due to timeout");
|
||||
} else if (awaitingPong && (System.currentTimeMillis() - lastPingTime) > 60000) {
|
||||
log.info("Channel inactive due to timeout (missed {} pongs)", missedPongCount);
|
||||
} else if (awaitingPongResponse && (System.currentTimeMillis() - lastPingRequestTime) > 30000) {
|
||||
lastDisconnectionType = DisconnectionType.TIMEOUT;
|
||||
log.info("Channel inactive - server stopped responding to pings");
|
||||
} else {
|
||||
@@ -262,6 +290,9 @@ public class ClientHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
awaitingPong = false;
|
||||
lastPingTime = 0;
|
||||
lastPongTime = 0;
|
||||
missedPongCount = 0;
|
||||
awaitingPongResponse = false;
|
||||
lastPingRequestTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -186,6 +186,7 @@ public class CheckUpPage extends JPanel {
|
||||
private final ResponseListener<UploadCheckupPdfResponse> uploadPdfResponseListener = this::handleUploadPdfResponse;
|
||||
private final ResponseListener<SyncCheckupImagesResponse> syncImagesResponseListener = this::handleSyncImagesResponse;
|
||||
private final ResponseListener<GetCheckupImageResponse> getImageResponseListener = this::handleGetCheckupImageResponse;
|
||||
private final ResponseListener<DeleteCheckupImageResponse> deleteImageResponseListener = this::handleDeleteImageResponse;
|
||||
private JTextField checkupIdField, customerLastNameField, customerFirstNameField,customerAddressField, customerPhoneField, customerIdField, customerCccdDdcnField;
|
||||
private JTextArea suggestionField, diagnosisField, conclusionField; // Changed symptomsField to suggestionField
|
||||
private JTextPane notesField;
|
||||
@@ -365,6 +366,7 @@ public class CheckUpPage extends JPanel {
|
||||
ClientHandler.addResponseListener(UploadCheckupPdfResponse.class, uploadPdfResponseListener);
|
||||
ClientHandler.addResponseListener(SyncCheckupImagesResponse.class, syncImagesResponseListener);
|
||||
ClientHandler.addResponseListener(GetCheckupImageResponse.class, getImageResponseListener);
|
||||
ClientHandler.addResponseListener(DeleteCheckupImageResponse.class, deleteImageResponseListener);
|
||||
|
||||
// Instantiate the new queue page but don't show it yet
|
||||
queueManagementPage = new QueueManagementPage();
|
||||
@@ -3652,6 +3654,45 @@ public class CheckUpPage extends JPanel {
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteImage(File imageFile) {
|
||||
if (imageFile == null || !imageFile.exists()) {
|
||||
log.warn("Cannot delete image - file does not exist or is null");
|
||||
return;
|
||||
}
|
||||
|
||||
String fileName = imageFile.getName();
|
||||
String checkupId = currentCheckupIdForMedia;
|
||||
|
||||
if (checkupId == null || checkupId.trim().isEmpty()) {
|
||||
log.error("Cannot delete image - no checkup ID available");
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Không thể xóa ảnh - không có mã khám.",
|
||||
"Lỗi",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send delete request to server
|
||||
DeleteCheckupImageRequest request = new DeleteCheckupImageRequest(checkupId, fileName);
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request);
|
||||
log.info("Sent DeleteCheckupImageRequest for file: {} in checkup: {}", fileName, checkupId);
|
||||
|
||||
// Delete local file immediately
|
||||
try {
|
||||
if (imageFile.delete()) {
|
||||
log.info("Successfully deleted local image file: {}", fileName);
|
||||
// Refresh the gallery
|
||||
if (currentCheckupMediaPath != null && Files.exists(currentCheckupMediaPath)) {
|
||||
loadAndDisplayImages(currentCheckupMediaPath);
|
||||
}
|
||||
} else {
|
||||
log.warn("Failed to delete local image file: {}", fileName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error deleting local image file: {}", fileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUploadImageResponse(UploadCheckupImageResponse response) {
|
||||
String fileName = response.getFileName();
|
||||
|
||||
@@ -3672,6 +3713,29 @@ public class CheckUpPage extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeleteImageResponse(DeleteCheckupImageResponse response) {
|
||||
String fileName = response.getFileName();
|
||||
|
||||
if (response.isSuccess()) {
|
||||
log.info("Successfully deleted image from server: {} - {}", fileName, response.getMessage());
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
// Image already deleted locally, just log success
|
||||
if (callingStatusLabel != null) {
|
||||
callingStatusLabel.setText("Đã xóa ảnh: " + fileName);
|
||||
callingStatusLabel.setForeground(new Color(0, 150, 0)); // Green color
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.error("Failed to delete image from server: {} - {}", fileName, response.getMessage());
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Lỗi khi xóa ảnh trên server: " + response.getMessage(),
|
||||
"Lỗi Xóa Ảnh",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUploadPdfResponse(UploadCheckupPdfResponse response) {
|
||||
if (response.isSuccess()) {
|
||||
log.info("Successfully uploaded PDF: {} - {}", response.getPdfType(), response.getMessage());
|
||||
@@ -4091,11 +4155,33 @@ public class CheckUpPage extends JPanel {
|
||||
layeredPane.setBorder(BorderFactory.createLineBorder(Color.GRAY));
|
||||
layeredPane.setCursor(new Cursor(Cursor.HAND_CURSOR)); // Set the hand cursor
|
||||
|
||||
// Create popup menu for right-click
|
||||
JPopupMenu imagePopupMenu = new JPopupMenu();
|
||||
JMenuItem deleteMenuItem = new JMenuItem("Xoá");
|
||||
deleteMenuItem.setIcon(UIManager.getIcon("FileView.fileIcon")); // Optional icon
|
||||
deleteMenuItem.addActionListener(ev -> {
|
||||
int confirm = JOptionPane.showConfirmDialog(
|
||||
CheckUpPage.this,
|
||||
"Bạn có chắc chắn muốn xoá ảnh này không?\n" + imageFile.getName(),
|
||||
"Xác nhận xoá",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
|
||||
if (confirm == JOptionPane.YES_OPTION) {
|
||||
deleteImage(imageFile);
|
||||
}
|
||||
});
|
||||
imagePopupMenu.add(deleteMenuItem);
|
||||
|
||||
// Add the click listener directly to the image label
|
||||
imageLabel.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
showFullImageDialog(imageFile);
|
||||
if (SwingUtilities.isRightMouseButton(e)) {
|
||||
imagePopupMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
} else if (SwingUtilities.isLeftMouseButton(e)) {
|
||||
showFullImageDialog(imageFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4300,6 +4386,20 @@ public class CheckUpPage extends JPanel {
|
||||
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(queueTableModel);
|
||||
queueTable.setRowSorter(sorter);
|
||||
|
||||
// Set custom comparator for STT column to sort as integers
|
||||
sorter.setComparator(0, new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
try {
|
||||
Integer i1 = Integer.parseInt(s1);
|
||||
Integer i2 = Integer.parseInt(s2);
|
||||
return i1.compareTo(i2);
|
||||
} catch (NumberFormatException e) {
|
||||
return s1.compareTo(s2); // Fallback to string comparison
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by "STT" (queue number) column in ascending order
|
||||
List<RowSorter.SortKey> sortKeys = new ArrayList<>();
|
||||
sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package BsK.common.packet.req;
|
||||
|
||||
import BsK.common.packet.Packet;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeleteCheckupImageRequest implements Packet {
|
||||
private String checkupId;
|
||||
private String fileName;
|
||||
|
||||
public DeleteCheckupImageRequest(String checkupId, String fileName) {
|
||||
this.checkupId = checkupId;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package BsK.common.packet.req;
|
||||
|
||||
import BsK.common.packet.Packet;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class PingRequest implements Packet {}
|
||||
public class PingRequest implements Packet {
|
||||
private long timestamp;
|
||||
|
||||
public PingRequest() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package BsK.common.packet.res;
|
||||
|
||||
import BsK.common.packet.Packet;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeleteCheckupImageResponse implements Packet {
|
||||
private boolean success;
|
||||
private String message;
|
||||
private String fileName;
|
||||
|
||||
public DeleteCheckupImageResponse(boolean success, String message, String fileName) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package BsK.common.packet.res;
|
||||
|
||||
import BsK.common.packet.Packet;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PongResponse implements Packet {
|
||||
private long timestamp;
|
||||
|
||||
public PongResponse() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public PongResponse(long clientTimestamp) {
|
||||
this.timestamp = clientTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,9 +294,10 @@ public class Server {
|
||||
.addLast(new ChunkedWriteHandler())
|
||||
.addLast(new HttpObjectAggregator(50 * 1024 * 1024))
|
||||
.addLast(new WebSocketServerProtocolHandler("/", null, true, 50 * 1024 * 1024))
|
||||
// Add IdleStateHandler with longer timeout to be more tolerant of network issues
|
||||
// Reader timeout: 90 seconds (longer than client's 60s to allow for network delays)
|
||||
.addLast(new IdleStateHandler(90, 0, 0))
|
||||
// Add IdleStateHandler to detect inactive clients
|
||||
// Reader timeout: 40 seconds - disconnect if no data from client (ping should arrive every 10s)
|
||||
// Allow up to 4 missed pings (10s * 4 = 40s) before disconnecting
|
||||
.addLast(new IdleStateHandler(40, 0, 0))
|
||||
.addLast(new ServerHandler());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1217,6 +1217,59 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
}
|
||||
}
|
||||
|
||||
if (packet instanceof DeleteCheckupImageRequest deleteCheckupImageRequest) {
|
||||
log.info("Received DeleteCheckupImageRequest for checkupId: {}, fileName: {}",
|
||||
deleteCheckupImageRequest.getCheckupId(), deleteCheckupImageRequest.getFileName());
|
||||
|
||||
String checkupId = deleteCheckupImageRequest.getCheckupId();
|
||||
String fileName = deleteCheckupImageRequest.getFileName();
|
||||
|
||||
if (checkupId == null || checkupId.trim().isEmpty()) {
|
||||
UserUtil.sendPacket(currentUser.getSessionId(),
|
||||
new DeleteCheckupImageResponse(false, "CheckupID không hợp lệ.", fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileName == null || fileName.trim().isEmpty()) {
|
||||
UserUtil.sendPacket(currentUser.getSessionId(),
|
||||
new DeleteCheckupImageResponse(false, "Tên file không hợp lệ.", fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Define the local file path
|
||||
Path storageDir = Paths.get(Server.imageDbPath, checkupId.trim());
|
||||
Path filePath = storageDir.resolve(fileName);
|
||||
|
||||
// 2. Delete the local file if it exists
|
||||
boolean localDeleted = false;
|
||||
if (Files.exists(filePath)) {
|
||||
Files.delete(filePath);
|
||||
localDeleted = true;
|
||||
log.info("Successfully deleted local image: {}", filePath);
|
||||
} else {
|
||||
log.warn("Local file not found: {}", filePath);
|
||||
}
|
||||
|
||||
// 3. Send success response to client immediately
|
||||
UserUtil.sendPacket(currentUser.getSessionId(),
|
||||
new DeleteCheckupImageResponse(true, "Xóa ảnh thành công.", fileName));
|
||||
|
||||
// 4. Asynchronously delete from Google Drive
|
||||
deleteCheckupImageFromGoogleDriveAsync(checkupId, fileName);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to delete image for checkupId {}: {}", checkupId, e.getMessage());
|
||||
UserUtil.sendPacket(currentUser.getSessionId(),
|
||||
new DeleteCheckupImageResponse(false, "Lỗi máy chủ khi xóa ảnh: " + e.getMessage(), fileName));
|
||||
} catch (Exception e) {
|
||||
log.error("An unexpected error occurred during image deletion for checkupId {}: {}",
|
||||
checkupId, e.getMessage());
|
||||
UserUtil.sendPacket(currentUser.getSessionId(),
|
||||
new DeleteCheckupImageResponse(false, "Lỗi không xác định: " + e.getMessage(), fileName));
|
||||
}
|
||||
}
|
||||
|
||||
if (packet instanceof DeleteCheckupRequest deleteCheckupRequest) {
|
||||
log.info("Recieved delete checkup request for checkupId: {}", deleteCheckupRequest.getCheckupId());
|
||||
|
||||
@@ -1616,6 +1669,12 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
UserUtil.sendPacket(currentUser.getSessionId(), new GetCheckupDataResponse(new String[0][0], 0, 1, 0, getCheckupDataRequest.getPageSize()));
|
||||
}
|
||||
}
|
||||
if (packet instanceof PingRequest pingRequest) {
|
||||
// Respond immediately to ping with pong
|
||||
log.debug("Received PingRequest from session {}, responding with PongResponse", currentUser.getSessionId());
|
||||
UserUtil.sendPacket(currentUser.getSessionId(), new PongResponse(pingRequest.getTimestamp()));
|
||||
}
|
||||
|
||||
if (packet instanceof SimpleMessageRequest simpleMessageRequest) {
|
||||
log.info("Received SimpleMessageRequest from {}", simpleMessageRequest.getSenderName());
|
||||
SimpleMessageResponse response = new SimpleMessageResponse(simpleMessageRequest.getSenderName(), simpleMessageRequest.getMessage());
|
||||
@@ -2416,6 +2475,80 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an image from Google Drive asynchronously.
|
||||
*
|
||||
* @param checkupId ID of the checkup (as String).
|
||||
* @param fileName Name of the image file to delete.
|
||||
*/
|
||||
private void deleteCheckupImageFromGoogleDriveAsync(String checkupId, String fileName) {
|
||||
log.info("deleteCheckupImageFromGoogleDriveAsync - Checkup: {}, File: {}", checkupId, fileName);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
if (!Server.isGoogleDriveConnected()) {
|
||||
log.info("Google Drive not connected - skipping image deletion for checkup {}", checkupId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the checkup's Google Drive folder ID from database
|
||||
String checkupDriveFolderId = null;
|
||||
String sql = "SELECT drive_folder_id FROM Checkup WHERE checkup_id = ?";
|
||||
|
||||
try (Connection conn = DatabaseManager.getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setInt(1, Integer.parseInt(checkupId));
|
||||
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
checkupDriveFolderId = rs.getString("drive_folder_id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checkupDriveFolderId == null || checkupDriveFolderId.trim().isEmpty()) {
|
||||
log.warn("Checkup {} has no Google Drive folder ID - cannot delete image", checkupId);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Deleting image {} from Google Drive for checkup {}", fileName, checkupId);
|
||||
|
||||
// Find and delete the file from Google Drive
|
||||
List<com.google.api.services.drive.model.File> files =
|
||||
Server.getGoogleDriveService().findFilesByName(checkupDriveFolderId, fileName);
|
||||
|
||||
if (files.isEmpty()) {
|
||||
log.warn("File {} not found in Google Drive folder for checkup {}", fileName, checkupId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete all matching files (there should typically be only one)
|
||||
for (com.google.api.services.drive.model.File file : files) {
|
||||
Server.getGoogleDriveService().deleteFile(file.getId());
|
||||
log.info("Successfully deleted file {} (ID: {}) from Google Drive for checkup {}",
|
||||
fileName, file.getId(), checkupId);
|
||||
|
||||
if (ServerDashboard.getInstance() != null) {
|
||||
ServerDashboard.getInstance().addLog(
|
||||
String.format("Deleted image %s from Google Drive for checkup %s", fileName, checkupId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("Invalid checkupId format '{}' for Google Drive deletion.", checkupId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to delete image from Google Drive for checkup {}: {}", checkupId, e.getMessage(), e);
|
||||
if (ServerDashboard.getInstance() != null) {
|
||||
ServerDashboard.getInstance().addLog(
|
||||
String.format("Failed to delete image %s from Google Drive for checkup %s: %s",
|
||||
fileName, checkupId, e.getMessage())
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void broadcastQueueUpdate() {
|
||||
// SỬA ĐỔI: Sử dụng một kết nối duy nhất, an toàn cho tất cả các truy vấn bên trong
|
||||
|
||||
@@ -451,7 +451,7 @@ public class GoogleDriveServiceOAuth {
|
||||
* @return A list of File objects matching the name.
|
||||
* @throws IOException if the Google Drive API call fails.
|
||||
*/
|
||||
private List<File> findFilesByName(String parentFolderId, String fileName) throws IOException {
|
||||
public List<File> findFilesByName(String parentFolderId, String fileName) throws IOException {
|
||||
String query = String.format("name = '%s' and '%s' in parents and trashed = false",
|
||||
fileName.replace("'", "\\'"), // Escape single quotes in filename
|
||||
parentFolderId);
|
||||
@@ -523,4 +523,14 @@ public class GoogleDriveServiceOAuth {
|
||||
|
||||
return fileId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a file from Google Drive by its file ID.
|
||||
* @param fileId The ID of the file to delete.
|
||||
* @throws IOException if the deletion fails.
|
||||
*/
|
||||
public void deleteFile(String fileId) throws IOException {
|
||||
driveService.files().delete(fileId).execute();
|
||||
log.info("Successfully deleted file with ID: {}", fileId);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user