[update] Add Times New Roman font dependency in pom.xml, downgrade project language level to JDK 21, and update run configurations for BSK Client and BSK Server. Enhance database backup functionality in Server class to include WAL and SHM files, and improve search functionality in ServerHandler for Vietnamese names with wildcard support. Update print forms for better layout and clarity.

This commit is contained in:
Lê Thành Đạt committed 2025-12-06 01:21:36 -05:00
1 parent 3b3fe8ad3c
commit 2ba360ff54
18 files changed
+579 -47

No files matched your search

+10
View File
@@ -250,3 +250,13 @@ image/checkup_media/8/IMG_8_20250807_001010.jpg
image/checkup_media/8/IMG_8_20250807_001050.jpg
image/checkup_media/8/ultrasound_8_20250807_001232_940.jpg
image/checkup_media/8/ultrasound_8_20250807_001253_394.jpg
database/BSK.db
database/BSK.db
database/BSK.db-shm
database/BSK.db-wal
*.jar
*.db
*.db-shm
*.db-wal
database/*.db
*.old
+1 -1
View File
@@ -8,5 +8,5 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_24" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK" />
</project>
+6 -6
View File
@@ -1,10 +1,10 @@
<configuration name="BSK Client" type="Application" factoryName="Application">
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BSK Client" type="Application" factoryName="Application">
<option name="ALTERNATIVE_JRE_PATH" value="corretto-21" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="BsK.client.Client" />
<module name="BSK-All-in-One-Clinic-Management-System" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="ALLOW_RUNNING_IN_PARALLEL" value="true" />
<option name="SINGLE_INSTANCE_ONLY" value="false" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</configuration>
</component>
+1 -1
View File
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BSK Server" type="Application" factoryName="Application" singleton="true">
<option name="ALTERNATIVE_JRE_PATH" value="corretto-22" />
<option name="ALTERNATIVE_JRE_PATH" value="corretto-21" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="BsK.server.Server" />
<module name="ldS_BsK" />
+304
View File
@@ -0,0 +1,304 @@
# 🗄️ BSK Clinic Database Backup Guide
## ⚠️ **CRITICAL: Understanding SQLite WAL Mode**
Your database uses **WAL (Write-Ahead Logging)** mode for better concurrent access. This creates **3 files**:
```
database/
├── BSK.db ← Main database (updated periodically via "checkpoint")
├── BSK.db-wal ← Write-Ahead Log (contains RECENT uncommitted data)
└── BSK.db-shm ← Shared Memory (coordination file)
```
### **The Problem You Experienced:**
```
❌ WRONG - What Happened:
1. You copied ONLY BSK.db (old data from last checkpoint)
2. You LOST BSK.db-wal (contained checkups 184-1978)
3. Result: 1795 checkups disappeared!
✅ CORRECT - What You Should Do:
1. Stop the server OR checkpoint first
2. Copy the main BSK.db file (now contains all data)
3. Restore it
```
---
## 📋 **Backup Methods**
### **Method 1: Using Built-in Google Drive Backup (RECOMMENDED) ✅**
This is now **COMPLETELY SAFE** - backs up ALL 3 database files!
**Steps:**
1. Open **Server Dashboard**
2. Click **"Sao lưu CSDL lên Drive"** button
3. Watch the progress:
- "Đang kiểm tra các tệp cơ sở dữ liệu..."
- Shows size of each file (BSK.db, BSK.db-wal, BSK.db-shm)
- "Đang tải lên tất cả các tệp database..."
- "✅ Sao lưu hoàn tất!"
4. ✅ Done! All files backed up to a timestamped folder.
**What happens behind the scenes:**
```java
// server.jar automatically does this:
1. Checks all 3 files: BSK.db, BSK.db-wal, BSK.db-shm
2. Creates timestamped folder: "_Database_Backups/Backup_2025-01-15_14-30-00/"
3. Uploads ALL files to that folder
4. No data loss possible!
```
**Google Drive Structure:**
```
BSK_Clinic_Patient_Files/
└── _Database_Backups/
├── Backup_2025-01-15_14-30-00/
│ ├── BSK.db (main database)
│ ├── BSK.db-wal (uncommitted changes)
│ └── BSK.db-shm (shared memory)
└── Backup_2025-01-15_09-15-00/
└── ... (older backup)
```
---
### **Method 2: Manual Backup with PowerShell Script**
**For manual backups when server is running:**
```powershell
# Step 1: Checkpoint the database
.\checkpoint_database.ps1
# Step 2: Copy the database
Copy-Item database\BSK.db "D:\Backups\BSK_backup_$(Get-Date -Format 'yyyy-MM-dd_HH-mm-ss').db"
```
**For manual backups when server is stopped:**
```powershell
# Just copy all 3 files
Copy-Item database\BSK.db* "D:\Backups\"
```
---
### **Method 3: Using SQLite Command Line**
```bash
# Option A: Checkpoint first (if server is running)
sqlite3 database/BSK.db "PRAGMA wal_checkpoint(TRUNCATE);"
# Option B: Create a proper backup
sqlite3 database/BSK.db ".backup 'backup.db'"
```
---
## 🔄 **Restoring from Backup**
### **Method 1: Restore Complete Backup from Google Drive**
**Steps:**
1. **Stop the server** completely
2. Download the **entire backup folder** from Google Drive
3. Navigate to `_Database_Backups/Backup_YYYY-MM-DD_HH-MM-SS/`
4. Download all 3 files:
- `BSK.db`
- `BSK.db-wal` (if present)
- `BSK.db-shm` (if present)
5. **Delete or backup your current database files**:
```powershell
# Backup current database
Move-Item database\BSK.db* backup_old\
```
6. **Copy downloaded files** to `database/` folder
7. **Restart the server**
8. ✅ **Done!** All data restored including uncommitted transactions
### **Method 2: Restore Only Main DB (Data Loss Risk!)**
⚠️ **WARNING:** Only use this if no WAL file exists in the backup!
**Steps:**
1. Stop the server
2. Download only `BSK.db` from backup
3. Copy to `database/` folder
4. Restart server
5. ⚠️ Any data in the WAL file at backup time is **lost**
---
## 🔧 **When to Checkpoint**
SQLite automatically checkpoints when:
- WAL file reaches **1000 pages** (~4MB)
- Database is closed gracefully
- Connection pool is shut down
**Manual checkpoint is needed when:**
- ⚠️ Server has been running for days without restart
- ⚠️ You need to backup while server is running
- ⚠️ WAL file is very large (>10MB)
**Check WAL status:**
```powershell
.\checkpoint_database.ps1 -CheckOnly
```
---
## 🚨 **Emergency: Recovering from Bad Backup**
If you already uploaded a backup that's missing data:
### **Option 1: Check if WAL files still exist at hospital**
```powershell
# At the hospital server, check:
Get-ChildItem database\BSK.db*
# If BSK.db-wal exists:
sqlite3 database\BSK.db "PRAGMA wal_checkpoint(TRUNCATE);"
# Now backup this file - it has all data!
```
### **Option 2: Restore from older backup + replay transactions**
If WAL is gone, data is **permanently lost**. You'll need:
1. Use the latest backup before modification
2. Manually re-enter lost data
3. Use filesystem folder IDs to identify missing checkups:
```powershell
# Find checkup folders
Get-ChildItem image\checkup_media -Directory |
Where-Object { $_.Name -match '^\d+$' } |
Sort-Object { [int]$_.Name }
# Compare with database
sqlite3 database\BSK.db "SELECT checkup_id FROM Checkup ORDER BY checkup_id"
```
---
## 📊 **Best Practices**
### ✅ **DO:**
- ✅ Use the built-in Google Drive backup button
- ✅ Stop the server before manual file copies
- ✅ Test your backups regularly
- ✅ Keep multiple backup versions
- ✅ Backup before schema changes
- ✅ Document any manual database modifications
### ❌ **DON'T:**
- ❌ Copy only BSK.db while server is running
- ❌ Modify the database on your machine while hospital uses it
- ❌ Ignore WAL files during backup
- ❌ Restore old backups without checking timestamps
- ❌ Edit production database without testing first
---
## 🛠️ **Schema Change Workflow**
When you need to add columns or modify the database:
```
CORRECT Process:
──────────────────────────────────────────────────
1. STOP the hospital server completely
2. Backup using: .\checkpoint_database.ps1
3. Download the backup
4. Test changes on your machine
5. Create migration script (ALTER TABLE commands)
6. Send ONLY the migration script (not the whole DB)
7. Run migration script at hospital
8. Restart server
9. Verify everything works
NEVER:
──────────────────────────────────────────────────
❌ Download → Modify → Upload entire database
(This WILL cause data loss!)
```
### **Migration Script Example:**
```sql
-- migration_add_doctor_columns.sql
BEGIN TRANSACTION;
-- Add new column
ALTER TABLE Doctor ADD COLUMN specialty TEXT;
-- Update existing data if needed
UPDATE Doctor SET specialty = 'General' WHERE specialty IS NULL;
COMMIT;
```
**Apply migration:**
```bash
sqlite3 database/BSK.db < migration_add_doctor_columns.sql
```
---
## 🔍 **Troubleshooting**
### **Problem: WAL file is huge (>100MB)**
```powershell
# Force checkpoint
sqlite3 database\BSK.db "PRAGMA wal_checkpoint(TRUNCATE);"
# If it fails, connections are open:
# Stop the server, then retry
```
### **Problem: "database is locked"**
- Server is running → stop it first
- Old connections → restart server
- Check: `database\BSK.db-shm` (if exists, connections are active)
### **Problem: Backup seems too small**
```powershell
# Check WAL size
Get-Item database\BSK.db-wal
# If WAL is large, you forgot to checkpoint!
.\checkpoint_database.ps1
```
---
## 📞 **Support**
If you encounter data loss:
1. **DON'T PANIC**
2. **DON'T overwrite any files**
3. Check if old backups exist on Google Drive
4. Contact: [Your Support Contact]
---
## 📚 **Further Reading**
- [SQLite WAL Mode Documentation](https://www.sqlite.org/wal.html)
- [SQLite Backup Documentation](https://www.sqlite.org/backup.html)
- [Understanding WAL Checkpoints](https://www.sqlite.org/wal.html#ckpt)
---
**Last Updated:** $(Get-Date -Format 'yyyy-MM-dd')
**Version:** 1.0
**Author:** BSK Development Team
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7
View File
@@ -241,5 +241,12 @@
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>com.yourcompany</groupId>
<artifactId>jasper-fonts-timesnewroman</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/fonts/timesnewroman-fonts.jar</systemPath>
</dependency>
</dependencies>
</project>
@@ -12,7 +12,7 @@ import BsK.common.packet.res.DeleteCheckupResponse;
import BsK.common.packet.res.GetCheckupDataResponse;
import BsK.common.util.network.NetworkUtil;
import BsK.client.ui.component.CheckUpPage.CheckUpPage;
import BsK.common.entity.CheckupData;
// import BsK.common.entity.CheckupData;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
@@ -997,10 +997,10 @@ private void setupDebounceSearch() {
JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchPanel.setBorder(BorderFactory.createTitledBorder("Tìm kiếm bệnh nhân"));
//searchPanel.add(new JLabel("Tên BN:"));
searchPanel.add(new JLabel("Họ tên:"));
searchNameField = new JTextField(15);
//searchPanel.add(searchNameField);
searchNameField.setToolTipText("Tìm kiếm theo họ tên (không cần dấu)");
searchPanel.add(searchNameField);
searchPanel.add(new JLabel("SĐT:"));
searchPhoneField = new JTextField(10);
+29 -6
View File
@@ -332,9 +332,13 @@ public class Server {
}
/**
* Initiates the backup of the main database file to Google Drive.
* This is intended to be called from the ServerDashboard.
* @throws IOException if the file cannot be found or the upload fails.
* Backs up the complete database set (BSK.db + WAL files) to Google Drive.
* Creates a timestamped folder and uploads all database files.
*
* This method backs up ALL database files (BSK.db, BSK.db-wal, BSK.db-shm)
* to prevent data loss from uncommitted WAL transactions.
*
* @throws IOException if the backup fails
*/
public static void backupDatabaseToDrive() throws IOException {
ServerDashboard dashboard = ServerDashboard.getInstance();
@@ -344,7 +348,7 @@ public class Server {
throw new IOException("Dịch vụ Google Drive không khả dụng.");
}
// Define the path to the database
// Verify main database file exists
String dbPath = "database/BSK.db";
java.io.File dbFile = new java.io.File(dbPath);
@@ -353,7 +357,26 @@ public class Server {
throw new IOException("Không tìm thấy tệp cơ sở dữ liệu: " + dbPath);
}
// Call the service to perform the backup
googleDriveService.backupDatabaseFile(dbFile);
// Check for WAL files
java.io.File walFile = new java.io.File(dbPath + "-wal");
java.io.File shmFile = new java.io.File(dbPath + "-shm");
dashboard.addLog("Đang kiểm tra các tệp cơ sở dữ liệu...");
dashboard.addLog(" • BSK.db: " + (dbFile.length() / (1024 * 1024)) + " MB");
if (walFile.exists() && walFile.length() > 0) {
dashboard.addLog(" • BSK.db-wal: " + (walFile.length() / (1024 * 1024)) + " MB (chứa dữ liệu chưa commit)");
}
if (shmFile.exists()) {
dashboard.addLog(" • BSK.db-shm: " + (shmFile.length() / 1024) + " KB");
}
// Backup all files to a timestamped folder
dashboard.addLog("Đang tải lên tất cả các tệp database...");
String folderId = googleDriveService.backupCompleteDatabaseSet();
dashboard.addLog("✅ Sao lưu hoàn tất! Tất cả tệp đã được lưu vào Google Drive.");
log.info("Database backup completed successfully. Folder ID: {}", folderId);
}
}
@@ -47,4 +47,69 @@ public class DatabaseManager {
log.info("Database connection pool closed.");
}
}
/**
* Performs a WAL checkpoint to merge all WAL data into the main database file.
* This ensures the main .db file contains all recent data.
*
* IMPORTANT: Call this before backing up the database!
*
* Checkpoint modes:
* - PASSIVE: Tries to checkpoint without blocking (default)
* - FULL: Waits for all transactions to complete, then checkpoints
* - RESTART: Like FULL, then restarts the WAL
* - TRUNCATE: Like RESTART, then truncates WAL file to 0 bytes
*
* @param mode Checkpoint mode: "PASSIVE", "FULL", "RESTART", or "TRUNCATE"
* @return true if checkpoint succeeded, false otherwise
*/
public static boolean checkpointWAL(String mode) {
log.info("Starting WAL checkpoint with mode: {}", mode);
try (Connection conn = getConnection();
var stmt = conn.createStatement()) {
// Execute checkpoint
var rs = stmt.executeQuery("PRAGMA wal_checkpoint(" + mode + ")");
if (rs.next()) {
int busy = rs.getInt(1); // 0 if successful, 1 if blocked
int walPages = rs.getInt(2); // Number of modified pages in WAL
int checkpointed = rs.getInt(3); // Number of pages checkpointed
if (busy == 0) {
log.info("✅ WAL checkpoint succeeded: {} pages checkpointed out of {} WAL pages",
checkpointed, walPages);
return true;
} else {
log.warn("⚠️ WAL checkpoint blocked by active transactions. " +
"Close all connections and try again.");
return false;
}
}
} catch (SQLException e) {
log.error("❌ Failed to checkpoint WAL", e);
return false;
}
return false;
}
/**
* Performs a FULL WAL checkpoint (recommended before backup).
* This ensures all data is written to the main database file.
*
* Usage example:
* <pre>
* // Before backing up database:
* DatabaseManager.checkpointWAL();
* // Now safe to copy BSK.db
* </pre>
*
* @return true if checkpoint succeeded, false otherwise
*/
public static boolean checkpointWAL() {
return checkpointWAL("TRUNCATE");
}
}
@@ -385,10 +385,22 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
boolean hasNameSearch = getRecentPatientRequest.getSearchName() != null && !getRecentPatientRequest.getSearchName().trim().isEmpty();
boolean hasPhoneSearch = getRecentPatientRequest.getSearchPhone() != null && !getRecentPatientRequest.getSearchPhone().trim().isEmpty();
// Prepare wildcard search pattern and accent-stripped term for precise filtering
String wildcardSearchPattern = null;
String accentStrippedSearchName = null;
if (hasNameSearch) {
String searchInput = getRecentPatientRequest.getSearchName().toLowerCase().trim();
// First remove accents from user input (so "võ" becomes "vo")
accentStrippedSearchName = TextUtils.removeAccents(searchInput);
// Then convert to wildcard pattern: replace Vietnamese vowels with _ to match any accented variant
// "vo" -> "v_" which matches "võ", "vò", "ve", "va", etc.
wildcardSearchPattern = createVietnameseWildcardPattern(accentStrippedSearchName);
}
if (hasNameSearch || hasPhoneSearch) {
queryBuilder.append(" WHERE ");
if (hasNameSearch) {
// SỬA LỖI SQL: Dùng || thay vì CONCAT cho SQLite
// Use wildcard pattern that matches accented variants
queryBuilder.append("(LOWER(customer_first_name) LIKE ? OR LOWER(customer_last_name) LIKE ? OR ")
.append("LOWER(customer_last_name || ' ' || customer_first_name) LIKE ?)");
}
@@ -412,7 +424,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
try (PreparedStatement countStmt = conn.prepareStatement(countQuery)) {
int paramIndex = 1;
if (hasNameSearch) {
String searchName = "%" + getRecentPatientRequest.getSearchName().toLowerCase().trim() + "%";
String searchName = "%" + wildcardSearchPattern + "%";
countStmt.setString(paramIndex++, searchName);
countStmt.setString(paramIndex++, searchName);
countStmt.setString(paramIndex++, searchName);
@@ -441,7 +453,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
try (PreparedStatement stmt = conn.prepareStatement(queryBuilder.toString())) {
int paramIndex = 1;
if (hasNameSearch) {
String searchName = "%" + getRecentPatientRequest.getSearchName().toLowerCase().trim() + "%";
String searchName = "%" + wildcardSearchPattern + "%";
stmt.setString(paramIndex++, searchName);
stmt.setString(paramIndex++, searchName);
stmt.setString(paramIndex++, searchName);
@@ -451,10 +463,10 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
stmt.setString(paramIndex++, searchPhone);
}
stmt.setInt(paramIndex++, pageSize);
stmt.setInt(paramIndex, offset); // Sửa lại: Dùng paramIndex thay vì paramIndex++
stmt.setInt(paramIndex, offset);
try (ResultSet rs = stmt.executeQuery()) {
// Xử lý kết quả (phần này không thay đổi)
// Xử lý kết quả với wildcard-based matching (no filtering needed - wildcards handle it)
ArrayList<String> resultList = new ArrayList<>();
while (rs.next()) {
String customerId = rs.getString("customer_id");
@@ -466,6 +478,9 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
String customerAddress = rs.getString("customer_address");
String customerGender = rs.getString("customer_gender");
String cccdDdcn = rs.getString("cccd_ddcn");
// No additional Java filtering - SQL wildcards handle everything
// "v_" matches "võ", "ve", "va", etc. - all are valid matches
String result = String.join("|", customerId, customerLastName + " " + customerFirstName,
year, customerNumber, customerAddress, customerGender, customerDob, cccdDdcn);
@@ -478,8 +493,10 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
resultArray[i] = resultString[i].split("\\|");
}
// Update total count based on actual filtered results
int actualCount = resultArray.length;
UserUtil.sendPacket(currentUser.getSessionId(), new GetRecentPatientResponse(resultArray, totalCount, currentPage, totalPages, pageSize));
UserUtil.sendPacket(currentUser.getSessionId(), new GetRecentPatientResponse(resultArray, actualCount, currentPage, totalPages, pageSize));
}
}
} catch (SQLException e) {
@@ -2340,6 +2357,43 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
UserUtil.sendPacket(sessionId, new ErrorResponse(Error.SQL_EXCEPTION));
}
}
/**
* Creates a wildcard pattern for Vietnamese name search.
* Replaces Vietnamese vowels with SQL wildcards (_) to match any accented variant.
*
* Examples:
* "vo" -> "v_" (matches võ, vò, vó, vô, vơ, etc.)
* "nguyen" -> "n_u__n" (matches nguyễn, nguyen, etc.)
* "dat" -> "d_t" (matches đạt, dat, đát, etc.)
*
* @param input The search input (already lowercased and accent-stripped)
* @return SQL LIKE pattern with wildcards for accented characters
*/
private String createVietnameseWildcardPattern(String input) {
StringBuilder pattern = new StringBuilder();
for (char c : input.toCharArray()) {
// Check if character is a Vietnamese vowel (including đ/d)
if (isVietnameseVowelOrD(c)) {
pattern.append('_'); // SQL wildcard for any single character
} else {
pattern.append(c);
}
}
return pattern.toString();
}
/**
* Checks if a character is a Vietnamese vowel or 'd/đ' that can have accents.
* Vietnamese vowels: a, ă, â, e, ê, i, o, ô, ơ, u, ư, y
* Also includes 'd' which can be 'đ'
*/
private boolean isVietnameseVowelOrD(char c) {
return "aăâeêioôơuưyd".indexOf(c) >= 0;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof IOException) {
@@ -503,7 +503,9 @@ public class GoogleDriveServiceOAuth {
* @param dbFile The local database file to upload.
* @return The ID of the uploaded file in Google Drive.
* @throws IOException if the upload fails.
* @deprecated Use {@link #backupCompleteDatabaseSet()} instead to backup all WAL files
*/
@Deprecated
public String backupDatabaseFile(java.io.File dbFile) throws IOException {
// 1. Define the dedicated, private backup folder name
String backupFolderName = "_Database_Backups"; // Using underscore to sort it to the top
@@ -524,6 +526,79 @@ public class GoogleDriveServiceOAuth {
return fileId;
}
/**
* Backs up the complete database set (BSK.db, BSK.db-wal, BSK.db-shm) to Google Drive.
* Creates a timestamped folder and uploads all database files to prevent data loss.
*
* IMPORTANT: This method backs up ALL database files including WAL files to ensure
* no data is lost during backup.
*
* @return The folder ID of the backup folder containing all database files
* @throws IOException if any file upload fails
*/
public String backupCompleteDatabaseSet() throws IOException {
// 1. Define the main backup folder name
String backupFolderName = "_Database_Backups";
// 2. Get or create the main backup folder
String mainBackupFolderId = findOrCreateFolder(this.rootFolderId, backupFolderName);
log.info("Using main database backup folder: {} (ID: {})", backupFolderName, mainBackupFolderId);
// 3. Create a timestamped subfolder for this backup session
String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new java.util.Date());
String sessionFolderName = "Backup_" + timestamp;
String sessionFolderId = findOrCreateFolder(mainBackupFolderId, sessionFolderName);
log.info("Created backup session folder: {} (ID: {})", sessionFolderName, sessionFolderId);
// 4. Define all database files
String dbBasePath = "database/BSK.db";
java.io.File mainDbFile = new java.io.File(dbBasePath);
java.io.File walFile = new java.io.File(dbBasePath + "-wal");
java.io.File shmFile = new java.io.File(dbBasePath + "-shm");
int filesUploaded = 0;
// 5. Upload main database file (required)
if (!mainDbFile.exists()) {
throw new IOException("Main database file not found: " + dbBasePath);
}
log.info("Uploading main database file: BSK.db ({} MB)", mainDbFile.length() / (1024 * 1024));
uploadFileToFolder(sessionFolderId, mainDbFile, "BSK.db");
filesUploaded++;
log.info("✅ Uploaded BSK.db");
// 6. Upload WAL file (if exists)
if (walFile.exists()) {
long walSizeMB = walFile.length() / (1024 * 1024);
log.info("Uploading WAL file: BSK.db-wal ({} MB)", walSizeMB);
if (walSizeMB > 0) {
uploadFileToFolder(sessionFolderId, walFile, "BSK.db-wal");
filesUploaded++;
log.info("✅ Uploaded BSK.db-wal");
} else {
log.info("⏭️ Skipping empty WAL file");
}
} else {
log.info("ℹ️ No WAL file found (database may have been checkpointed)");
}
// 7. Upload SHM file (if exists)
if (shmFile.exists()) {
log.info("Uploading shared memory file: BSK.db-shm");
uploadFileToFolder(sessionFolderId, shmFile, "BSK.db-shm");
filesUploaded++;
log.info("✅ Uploaded BSK.db-shm");
} else {
log.info("ℹ️ No shared memory file found");
}
log.info("🎉 Database backup complete! {} file(s) uploaded to folder: {}", filesUploaded, sessionFolderName);
return sessionFolderId;
}
/**
* Deletes a file from Google Drive by its file ID.
* @param fileId The ID of the file to delete.
@@ -60,36 +60,30 @@
<element kind="staticText" uuid="0a9ca0c8-f2bd-4ade-a025-a6d79c13613f" x="1" y="84" width="80" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Ngày sinh:]]></text>
</element>
<element kind="staticText" uuid="21917ab8-ae07-44c8-ba4e-bc6c4c54b440" x="131" y="84" width="80" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<element kind="staticText" uuid="21917ab8-ae07-44c8-ba4e-bc6c4c54b440" x="309" y="69" width="80" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Giới tính:]]></text>
</element>
<element kind="staticText" uuid="bcbb614e-7a4d-48a3-83c2-198e4fb62c5f" x="1" y="100" width="49" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Địa chỉ:]]></text>
</element>
<element kind="staticText" uuid="45fe4be1-9e21-459b-aeee-2efc8354f372" x="216" y="69" width="80" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<element kind="staticText" uuid="45fe4be1-9e21-459b-aeee-2efc8354f372" x="131" y="83" width="80" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[BS chỉ định:]]></text>
</element>
<element kind="staticText" uuid="f2ff4b17-63cc-4e8c-a3b0-2523f534f21a" x="217" y="84" width="70" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Ngày khám:]]></text>
</element>
<element kind="textField" uuid="1511f53a-b282-41b1-b233-612b6106c07a" x="57" y="69" width="160" height="15" fontName="Times New Roman" fontSize="12.0">
<element kind="textField" uuid="1511f53a-b282-41b1-b233-612b6106c07a" x="57" y="69" width="252" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{patientName}]]></expression>
</element>
<element kind="textField" uuid="5bdac267-523d-4931-926b-76233ce9b84d" x="57" y="84" width="74" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{patientDOB}]]></expression>
</element>
<element kind="textField" uuid="f4df20b4-920c-491e-882b-6e0733cb4866" x="182" y="84" width="36" height="15" fontName="Times New Roman" fontSize="12.0">
<element kind="textField" uuid="f4df20b4-920c-491e-882b-6e0733cb4866" x="360" y="69" width="36" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{patientGender}]]></expression>
</element>
<element kind="textField" uuid="3b8ac40e-85aa-4ab8-8445-64bcddb8b267" x="57" y="100" width="341" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{patientAddress}]]></expression>
</element>
<element kind="textField" uuid="db40cdb0-2850-4d66-8430-addc54f90321" x="281" y="69" width="116" height="15" fontName="Times New Roman" fontSize="12.0">
<element kind="textField" uuid="db40cdb0-2850-4d66-8430-addc54f90321" x="196" y="83" width="201" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{doctorName}]]></expression>
</element>
<element kind="textField" uuid="eb9cf54a-3b50-43c5-8696-cfc1e21bddfe" x="281" y="84" width="116" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{checkupDate}]]></expression>
</element>
<element kind="staticText" uuid="9f7de81c-4c38-41a2-9fea-a165eff19431" x="1" y="116" width="69" height="16" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Chẩn đoán:]]></text>
</element>
@@ -252,9 +246,12 @@
<element kind="image" uuid="14220495-3e07-4322-9c5f-ebf98a8083c8" x="-50" y="3" width="48" height="48">
<expression><![CDATA[$P{driveURL}]]></expression>
</element>
<element kind="staticText" uuid="fbffa5e9-b68c-4bd9-be86-34d3537fbfa5" x="2" y="538" width="196" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" italic="true" hTextAlign="Left">
<element kind="staticText" uuid="fbffa5e9-b68c-4bd9-be86-34d3537fbfa5" x="2" y="538" width="196" height="15" fontName="Times New Roman" fontSize="12.0" italic="true" bold="true" hTextAlign="Left">
<text><![CDATA[Lưu ý: Mang phiếu khi tái khám]]></text>
</element>
<element kind="textField" uuid="eb9cf54a-3b50-43c5-8696-cfc1e21bddfe" x="242" y="522" width="116" height="15" fontName="Times New Roman" fontSize="10.0" hTextAlign="Center">
<expression><![CDATA[$P{checkupDate}]]></expression>
</element>
</element>
</band>
</detail>
@@ -65,34 +65,31 @@
<element kind="staticText" uuid="0a9ca0c8-f2bd-4ade-a025-a6d79c13613f" x="1" y="83" width="80" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Ngày sinh:]]></text>
</element>
<element kind="staticText" uuid="21917ab8-ae07-44c8-ba4e-bc6c4c54b440" x="130" y="84" width="80" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<element kind="staticText" uuid="21917ab8-ae07-44c8-ba4e-bc6c4c54b440" x="357" y="67" width="89" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Giới tính:]]></text>
</element>
<element kind="staticText" uuid="bcbb614e-7a4d-48a3-83c2-198e4fb62c5f" x="1" y="101" width="49" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Địa chỉ:]]></text>
</element>
<element kind="staticText" uuid="45fe4be1-9e21-459b-aeee-2efc8354f372" x="247" y="67" width="89" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<element kind="staticText" uuid="45fe4be1-9e21-459b-aeee-2efc8354f372" x="131" y="83" width="89" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Bác sĩ chỉ định:]]></text>
</element>
<element kind="staticText" uuid="f2ff4b17-63cc-4e8c-a3b0-2523f534f21a" x="221" y="83" width="69" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
<text><![CDATA[Ngày khám:]]></text>
</element>
<element kind="textField" uuid="1511f53a-b282-41b1-b233-612b6106c07a" x="56" y="67" width="194" height="15" fontName="Times New Roman" fontSize="12.0">
<element kind="textField" uuid="1511f53a-b282-41b1-b233-612b6106c07a" x="59" y="67" width="307" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{patientName}]]></expression>
</element>
<element kind="textField" uuid="5bdac267-523d-4931-926b-76233ce9b84d" x="60" y="83" width="70" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{patientDOB}]]></expression>
</element>
<element kind="textField" uuid="f4df20b4-920c-491e-882b-6e0733cb4866" x="185" y="84" width="36" height="15" fontName="Times New Roman" fontSize="12.0">
<element kind="textField" uuid="f4df20b4-920c-491e-882b-6e0733cb4866" x="412" y="67" width="45" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{patientGender}]]></expression>
</element>
<element kind="textField" uuid="3b8ac40e-85aa-4ab8-8445-64bcddb8b267" x="50" y="101" width="350" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{patientAddress}]]></expression>
</element>
<element kind="textField" uuid="db40cdb0-2850-4d66-8430-addc54f90321" x="326" y="67" width="180" height="15" fontName="Times New Roman" fontSize="12.0">
<element kind="textField" uuid="db40cdb0-2850-4d66-8430-addc54f90321" x="210" y="83" width="180" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{doctorName}]]></expression>
</element>
<element kind="textField" uuid="eb9cf54a-3b50-43c5-8696-cfc1e21bddfe" x="290" y="83" width="110" height="15" fontName="Times New Roman" fontSize="12.0">
<element kind="textField" uuid="eb9cf54a-3b50-43c5-8696-cfc1e21bddfe" x="391" y="750" width="110" height="15" fontName="Times New Roman" fontSize="12.0" hTextAlign="Center">
<expression><![CDATA[$P{checkupDate}]]></expression>
</element>
<element kind="staticText" uuid="9f7de81c-4c38-41a2-9fea-a165eff19431" x="1" y="116" width="69" height="16" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Left">
@@ -170,7 +167,7 @@
<element kind="staticText" uuid="d1d33b8f-b79a-45a1-b33b-1eecb656f49a" x="400" y="87" width="96" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" hTextAlign="Center">
<text><![CDATA[Bác sĩ siêu âm]]></text>
</element>
<element kind="textField" uuid="de0d077c-f1d6-4d0c-8bbd-fb08c19b07c6" x="347" y="165" width="200" height="21" fontName="Times New Roman" fontSize="14.0" bold="true" hTextAlign="Center">
<element kind="textField" uuid="de0d077c-f1d6-4d0c-8bbd-fb08c19b07c6" x="336" y="165" width="214" height="21" fontName="Times New Roman" fontSize="14.0" bold="true" hTextAlign="Center">
<expression><![CDATA[$P{ultrasoundDoctorName}]]></expression>
</element>
<element kind="staticText" uuid="0b4ca7da-4831-4ca6-8272-94af19fd3089" x="0" y="155" width="110" height="14" forecolor="#FF0905" fontName="Times New Roman" fontSize="10.0" bold="true">
@@ -185,7 +182,7 @@
<element kind="textField" uuid="4d8aeb33-ab41-4a5b-a5d1-52bab4043246" x="48" y="155" width="150" height="15" fontName="Times New Roman" fontSize="12.0">
<expression><![CDATA[$P{reCheckupDate}]]></expression>
</element>
<element kind="staticText" uuid="f1223127-533c-4ecf-a950-7324b8aae0fa" x="0" y="170" width="196" height="15" fontName="Times New Roman" fontSize="12.0" bold="true" italic="true" hTextAlign="Left">
<element kind="staticText" uuid="f1223127-533c-4ecf-a950-7324b8aae0fa" x="0" y="170" width="196" height="15" fontName="Times New Roman" fontSize="12.0" italic="true" bold="true" hTextAlign="Left">
<text><![CDATA[Lưu ý: Mang phiếu khi tái khám]]></text>
</element>
</summary>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

After

Width:  |  Height:  |  Size: 98 B