feat: initial commit — OST to PST/MBOX converter with Docker support
Some checks failed
Build & Push Multi-Arch Image / build-and-push (push) Has been cancelled
Some checks failed
Build & Push Multi-Arch Image / build-and-push (push) Has been cancelled
This commit is contained in:
332
Convert.java
Normal file
332
Convert.java
Normal file
@@ -0,0 +1,332 @@
|
||||
import com.aspose.email.FileFormatVersion;
|
||||
import com.aspose.email.FolderInfo;
|
||||
import com.aspose.email.MapiMessage;
|
||||
import com.aspose.email.MessageInfo;
|
||||
import com.aspose.email.PersonalStorage;
|
||||
import com.aspose.email.MailMessage;
|
||||
import com.aspose.email.MailConversionOptions;
|
||||
import com.aspose.email.MboxrdStorageWriter;
|
||||
import com.aspose.email.MapiContact;
|
||||
import com.aspose.email.MapiItemType;
|
||||
import com.aspose.email.ContactSaveFormat;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class Convert {
|
||||
private static PersonalStorage sourceStorage;
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
System.err.println("Usage: java -cp <classpath> Convert <input.ost> <output.pst/zip> [format]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String inputPath = args[0];
|
||||
String outputPath = args[1];
|
||||
String format = (args.length >= 3) ? args[2].toLowerCase() : "pst";
|
||||
boolean combineVcf = (args.length >= 4) && args[3].equalsIgnoreCase("true");
|
||||
|
||||
System.out.println("Starting manual copy conversion of OST: " + inputPath + " to format: " + format.toUpperCase());
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
// Delete existing output file if it exists
|
||||
File outFile = new File(outputPath);
|
||||
if (outFile.exists()) {
|
||||
System.out.println("Destination file already exists. Deleting " + outputPath + "...");
|
||||
if (outFile.delete()) {
|
||||
System.out.println("Deleted existing file successfully.");
|
||||
} else {
|
||||
System.err.println("Warning: Failed to delete existing file.");
|
||||
}
|
||||
}
|
||||
|
||||
// Load the source OST file
|
||||
System.out.println("Loading OST file...");
|
||||
sourceStorage = PersonalStorage.fromFile(inputPath);
|
||||
|
||||
if (format.equals("mbox")) {
|
||||
// Export folders to a temporary directory of .mbox files, then zip them
|
||||
File tempDir = new File("mbox_temp_" + System.currentTimeMillis());
|
||||
if (!tempDir.exists()) {
|
||||
tempDir.mkdirs();
|
||||
}
|
||||
|
||||
System.out.println("Exporting folder structure, email messages, and contacts...");
|
||||
exportFoldersToMbox(sourceStorage.getRootFolder(), "", tempDir, combineVcf);
|
||||
|
||||
// If combineVcf was requested, merge all individual VCFs in the contacts subdirs
|
||||
if (combineVcf) {
|
||||
System.out.println("Merging contact VCF files into a single contacts.vcf...");
|
||||
mergeVcfFiles(tempDir);
|
||||
}
|
||||
|
||||
System.out.println("Packaging MBOX files and VCards into ZIP archive...");
|
||||
zipDirectory(tempDir, outputPath);
|
||||
|
||||
System.out.println("Cleaning up temporary MBOX/VCard files...");
|
||||
cleanDirectory(tempDir);
|
||||
tempDir.delete();
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
System.out.println("Conversion completed successfully in " + (duration / 1000.0) + " seconds.");
|
||||
} else {
|
||||
// Default PST format
|
||||
System.out.println("Creating destination PST file...");
|
||||
try (PersonalStorage destPst = PersonalStorage.create(outputPath, FileFormatVersion.Unicode)) {
|
||||
System.out.println("Copying folder structure and messages...");
|
||||
copyFolders(sourceStorage.getRootFolder(), destPst.getRootFolder());
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
System.out.println("Conversion completed successfully in " + (duration / 1000.0) + " seconds.");
|
||||
}
|
||||
}
|
||||
|
||||
sourceStorage.close();
|
||||
System.exit(0);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error during conversion: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
if (sourceStorage != null) {
|
||||
try {
|
||||
sourceStorage.close();
|
||||
} catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyFolders(FolderInfo sourceFolder, FolderInfo destParentFolder) {
|
||||
for (FolderInfo subFolder : sourceFolder.getSubFolders()) {
|
||||
String folderName = subFolder.getDisplayName();
|
||||
System.out.println("Processing Folder: " + folderName);
|
||||
|
||||
// Recreate folder in destination
|
||||
FolderInfo newDestFolder = destParentFolder.addSubFolder(folderName);
|
||||
|
||||
// Copy messages
|
||||
int copiedCount = 0;
|
||||
for (MessageInfo msgInfo : subFolder.enumerateMessages()) {
|
||||
try {
|
||||
MapiMessage msg = sourceStorage.extractMessage(msgInfo);
|
||||
newDestFolder.addMessage(msg);
|
||||
copiedCount++;
|
||||
if (copiedCount % 100 == 0) {
|
||||
System.out.println(" Copied " + copiedCount + " messages...");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
System.err.println(" Warning: Failed to copy message '" + msgInfo.getSubject() + "': " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
if (copiedCount > 0) {
|
||||
System.out.println(" Successfully copied " + copiedCount + " messages to folder: " + folderName);
|
||||
}
|
||||
|
||||
// Recursively copy subfolders
|
||||
copyFolders(subFolder, newDestFolder);
|
||||
}
|
||||
}
|
||||
|
||||
private static void exportFoldersToMbox(FolderInfo sourceFolder, String currentPath, File tempDir, boolean combineVcf) {
|
||||
for (FolderInfo subFolder : sourceFolder.getSubFolders()) {
|
||||
String folderName = subFolder.getDisplayName();
|
||||
String fullPath = currentPath.isEmpty() ? folderName : currentPath + " - " + folderName;
|
||||
System.out.println("Processing Folder: " + folderName);
|
||||
|
||||
boolean isContactsFolder = folderName.toLowerCase().contains("contacts");
|
||||
|
||||
// Skip other non-email folders
|
||||
if (folderName.toLowerCase().contains("calendrier") ||
|
||||
folderName.toLowerCase().contains("journal") ||
|
||||
folderName.toLowerCase().contains("tâches") ||
|
||||
folderName.toLowerCase().contains("notes")) {
|
||||
System.out.println(" Skipping non-email/non-contact folder: " + folderName);
|
||||
|
||||
// Still process children in case there are nested folders
|
||||
exportFoldersToMbox(subFolder, fullPath, tempDir, combineVcf);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isContactsFolder) {
|
||||
System.out.println(" Processing contacts in folder: " + folderName);
|
||||
String safeSubDirName = "Contacts - " + folderName.replaceAll("[\\\\/:*?\"<>|]", "_");
|
||||
File contactsDir = new File(tempDir, safeSubDirName);
|
||||
if (!contactsDir.exists()) {
|
||||
contactsDir.mkdirs();
|
||||
}
|
||||
|
||||
int contactCount = 0;
|
||||
for (MessageInfo msgInfo : subFolder.enumerateMessages()) {
|
||||
try {
|
||||
MapiMessage msg = sourceStorage.extractMessage(msgInfo);
|
||||
if (msg.getSupportedType() == MapiItemType.Contact) {
|
||||
MapiContact contact = (MapiContact) msg.toMapiMessageItem();
|
||||
|
||||
// Determine a suitable name for the .vcf file
|
||||
String displayName = null;
|
||||
if (contact.getNameInfo() != null) {
|
||||
displayName = contact.getNameInfo().getDisplayName();
|
||||
}
|
||||
if (displayName == null || displayName.trim().isEmpty()) {
|
||||
if (contact.getElectronicAddresses() != null &&
|
||||
contact.getElectronicAddresses().getEmail1() != null) {
|
||||
displayName = contact.getElectronicAddresses().getEmail1().getEmailAddress();
|
||||
}
|
||||
}
|
||||
if (displayName == null || displayName.trim().isEmpty()) {
|
||||
displayName = "Contact_" + (contactCount + 1);
|
||||
}
|
||||
|
||||
String safeContactName = displayName.replaceAll("[\\\\/:*?\"<>|]", "_") + ".vcf";
|
||||
File vcfFile = new File(contactsDir, safeContactName);
|
||||
|
||||
// Check for duplicates
|
||||
int index = 1;
|
||||
while (vcfFile.exists()) {
|
||||
String nameWithIndex = displayName.replaceAll("[\\\\/:*?\"<>|]", "_") + "_" + index + ".vcf";
|
||||
vcfFile = new File(contactsDir, nameWithIndex);
|
||||
index++;
|
||||
}
|
||||
|
||||
contact.save(vcfFile.getAbsolutePath(), ContactSaveFormat.VCard);
|
||||
contactCount++;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
System.err.println(" Warning: Failed to export contact: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (contactCount > 0) {
|
||||
System.out.println(" Successfully exported " + contactCount + " contacts to folder: " + folderName);
|
||||
} else {
|
||||
contactsDir.delete();
|
||||
}
|
||||
|
||||
// Recursively walk subfolders
|
||||
exportFoldersToMbox(subFolder, fullPath, tempDir, combineVcf);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, it's an email folder. Export to MBOX...
|
||||
int copiedCount = 0;
|
||||
String safeFileName = fullPath.replaceAll("[\\\\/:*?\"<>|]", "_") + ".mbox";
|
||||
File mboxFile = new File(tempDir, safeFileName);
|
||||
|
||||
MailConversionOptions options = new MailConversionOptions();
|
||||
MboxrdStorageWriter writer = null;
|
||||
|
||||
for (MessageInfo msgInfo : subFolder.enumerateMessages()) {
|
||||
try {
|
||||
if (writer == null) {
|
||||
writer = new MboxrdStorageWriter(mboxFile.getAbsolutePath(), false);
|
||||
}
|
||||
MapiMessage msg = sourceStorage.extractMessage(msgInfo);
|
||||
MailMessage mailMsg = msg.toMailMessage(options);
|
||||
writer.writeMessage(mailMsg);
|
||||
copiedCount++;
|
||||
if (copiedCount % 100 == 0) {
|
||||
System.out.println(" Exported " + copiedCount + " messages...");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
System.err.println(" Warning: Failed to export message '" + msgInfo.getSubject() + "': " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (copiedCount > 0) {
|
||||
System.out.println(" Successfully copied " + copiedCount + " messages to folder: " + folderName);
|
||||
} else {
|
||||
// Delete empty mbox file if created
|
||||
if (mboxFile.exists()) {
|
||||
mboxFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively walk subfolders
|
||||
exportFoldersToMbox(subFolder, fullPath, tempDir, combineVcf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk all contact subdirectories, read each .vcf, and write them all
|
||||
* into a single contacts.vcf at the root of tempDir.
|
||||
* Then delete the now-merged individual files and empty subdirs.
|
||||
*/
|
||||
private static void mergeVcfFiles(File tempDir) throws Exception {
|
||||
File combinedVcf = new File(tempDir, "contacts.vcf");
|
||||
try (FileOutputStream fos = new FileOutputStream(combinedVcf)) {
|
||||
for (File child : tempDir.listFiles()) {
|
||||
if (child.isDirectory() && child.getName().startsWith("Contacts")) {
|
||||
for (File vcf : child.listFiles()) {
|
||||
if (vcf.isFile() && vcf.getName().endsWith(".vcf")) {
|
||||
byte[] data = java.nio.file.Files.readAllBytes(vcf.toPath());
|
||||
fos.write(data);
|
||||
// Ensure each vCard ends with a newline separator
|
||||
if (data.length > 0 && data[data.length - 1] != '\n') {
|
||||
fos.write('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove the now-merged individual VCF directory
|
||||
cleanDirectory(child);
|
||||
child.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println(" Merged VCF written: " + combinedVcf.getName());
|
||||
}
|
||||
|
||||
private static void zipDirectory(File dir, String zipFilePath) throws Exception {
|
||||
try (FileOutputStream fos = new FileOutputStream(zipFilePath);
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
zipFolder(dir, dir, zos);
|
||||
}
|
||||
}
|
||||
|
||||
private static void zipFolder(File rootDir, File currentDir, ZipOutputStream zos) throws Exception {
|
||||
File[] files = currentDir.listFiles();
|
||||
if (files != null) {
|
||||
byte[] buffer = new byte[8192];
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
zipFolder(rootDir, file, zos);
|
||||
} else {
|
||||
String relativePath = rootDir.toURI().relativize(file.toURI()).getPath();
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
ZipEntry ze = new ZipEntry(relativePath);
|
||||
zos.putNextEntry(ze);
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void cleanDirectory(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
cleanDirectory(file);
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user