From: Mathieu Baudier Date: Mon, 4 Dec 2023 15:57:10 +0000 (+0100) Subject: Adapt to changes in Argeo TP X-Git-Tag: v2.3.20~8 X-Git-Url: https://git.argeo.org/?p=gpl%2Fargeo-suite.git;a=commitdiff_plain;h=d7da00bfd0ae230b97abccb2cc9d091e34c11632 Adapt to changes in Argeo TP --- diff --git a/Makefile b/Makefile index ad5bd8b..e724f9b 100644 --- a/Makefile +++ b/Makefile @@ -27,8 +27,11 @@ swt/org.argeo.app.ui \ DEP_CATEGORIES = \ org.argeo.tp \ org.argeo.tp.httpd \ +org.argeo.tp.sys \ +org.argeo.tp.sdk \ org.argeo.tp.jcr \ org.argeo.tp.utils \ +org.argeo.tp.img \ org.argeo.tp.publish \ org.argeo.tp.math \ org.argeo.tp.earth \ diff --git a/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java b/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java index 2104145..e1fb4fe 100644 --- a/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java +++ b/org.argeo.app.core/src/org/argeo/app/core/SuiteMaintenance.java @@ -1,14 +1,10 @@ package org.argeo.app.core; -import java.net.MalformedURLException; -import java.net.URL; - import javax.measure.Quantity; import javax.measure.quantity.Area; -import org.argeo.api.acr.spi.ContentNamespace; import org.argeo.api.acr.spi.ProvidedRepository; -import org.geotools.gml3.v3_2.GML; +//import org.geotools.gml3.v3_2.GML; import si.uom.SI; import tech.units.indriya.quantity.Quantities; @@ -31,27 +27,27 @@ public class SuiteMaintenance { // } // GML schema import fails because of xlinks issues - getContentRepository().registerTypes(new ContentNamespace() { - - @Override - public URL getSchemaResource() { - try { - return new URL(GML.getInstance().getSchemaLocation()); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public String getNamespaceURI() { - return GML.getInstance().getNamespaceURI(); - } - - @Override - public String getDefaultPrefix() { - return "gml"; - } - }); +// getContentRepository().registerTypes(new ContentNamespace() { +// +// @Override +// public URL getSchemaResource() { +// try { +// return new URL(GML.getInstance().getSchemaLocation()); +// } catch (MalformedURLException e) { +// throw new IllegalArgumentException(e); +// } +// } +// +// @Override +// public String getNamespaceURI() { +// return GML.getInstance().getNamespaceURI(); +// } +// +// @Override +// public String getDefaultPrefix() { +// return "gml"; +// } +// }); } diff --git a/org.argeo.app.core/src/org/argeo/app/mail/EmailMigration.java b/org.argeo.app.core/src/org/argeo/app/mail/EmailMigration.java deleted file mode 100644 index 8c899c2..0000000 --- a/org.argeo.app.core/src/org/argeo/app/mail/EmailMigration.java +++ /dev/null @@ -1,524 +0,0 @@ -package org.argeo.app.mail; - -import static java.lang.System.Logger.Level.DEBUG; -import static java.lang.System.Logger.Level.ERROR; -import static org.argeo.app.mail.EmailUtils.describe; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.System.Logger; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.time.Instant; -import java.util.Date; -import java.util.Enumeration; -import java.util.Properties; - -import javax.mail.FetchProfile; -import javax.mail.Folder; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.Store; -import javax.mail.URLName; -import javax.mail.internet.InternetHeaders; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.search.HeaderTerm; -import javax.mail.util.SharedFileInputStream; - -import com.sun.mail.imap.IMAPFolder; -import com.sun.mail.mbox.MboxFolder; -import com.sun.mail.mbox.MboxMessage; - -/** Migrates emails from one storage to the another one. */ -public class EmailMigration { - private final static Logger logger = System.getLogger(EmailMigration.class.getName()); - -// private String targetBaseDir; - - private String sourceServer; - private String sourceUsername; - private String sourcePassword; - - private String targetServer; - private String targetUsername; - private String targetPassword; - - private boolean targetSupportDualTypeFolders = true; - - public void process() throws MessagingException, IOException { -// Path baseDir = Paths.get(targetBaseDir).resolve(sourceUsername).resolve("mbox"); - - Store sourceStore = null; - try { - Properties sourceProperties = System.getProperties(); - sourceProperties.setProperty("mail.store.protocol", "imaps"); - - Session sourceSession = Session.getInstance(sourceProperties, null); - // session.setDebug(true); - sourceStore = sourceSession.getStore("imaps"); - sourceStore.connect(sourceServer, sourceUsername, sourcePassword); - - Folder defaultFolder = sourceStore.getDefaultFolder(); -// migrateFolders(baseDir, defaultFolder); - - // Always start with Inbox -// Folder inboxFolder = sourceStore.getFolder(EmailUtils.INBOX); -// migrateFolder(baseDir, inboxFolder); - - Properties targetProperties = System.getProperties(); - targetProperties.setProperty("mail.imap.starttls.enable", "true"); - targetProperties.setProperty("mail.imap.auth", "true"); - - Session targetSession = Session.getInstance(targetProperties, null); - // session.setDebug(true); - Store targetStore = targetSession.getStore("imap"); - targetStore.connect(targetServer, targetUsername, targetPassword); - -// Folder targetFolder = targetStore.getFolder(EmailUtils.INBOX); -// logger.log(DEBUG, "Source message count " + inboxFolder.getMessageCount()); -// logger.log(DEBUG, "Target message count " + targetFolder.getMessageCount()); - - migrateFolders(defaultFolder, targetStore); - } finally { - if (sourceStore != null) - sourceStore.close(); - - } - } - - protected void migrateFolders(Folder sourceParentFolder, Store targetStore) throws MessagingException, IOException { - folders: for (Folder sourceFolder : sourceParentFolder.list()) { - String sourceFolderName = sourceFolder.getName(); - - String sourceFolderFullName = sourceFolder.getFullName(); - char sourceFolderSeparator = sourceParentFolder.getSeparator(); - char targetFolderSeparator = targetStore.getDefaultFolder().getSeparator(); - String targetFolderFullName = sourceFolderFullName.replace(sourceFolderSeparator, targetFolderSeparator); - - // GMail specific - if (sourceFolderFullName.equals("[Gmail]")) { - migrateFolders(sourceFolder, targetStore); - continue folders; - } - if (sourceFolderFullName.startsWith("[Gmail]")) { - String subFolderName = null; - // Make it configurable - switch (sourceFolderName) { - case "All Mail": - case "Important": - case "Spam": - continue folders; - case "Sent Mail": - subFolderName = "Sent"; - default: - // does nothing - } - targetFolderFullName = subFolderName == null ? sourceFolder.getName() : subFolderName; - } - - // nature of the source folder - int messageCount = (sourceFolder.getType() & Folder.HOLDS_MESSAGES) != 0 ? sourceFolder.getMessageCount() - : 0; - boolean hasSubFolders = (sourceFolder.getType() & Folder.HOLDS_FOLDERS) != 0 - ? sourceFolder.list().length != 0 - : false; - - Folder targetFolder; - if (targetSupportDualTypeFolders) { - targetFolder = targetStore.getFolder(targetFolderFullName); - if (!targetFolder.exists()) { - targetFolder.create(Folder.HOLDS_FOLDERS | Folder.HOLDS_MESSAGES); - logger.log(DEBUG, "Created HOLDS_FOLDERS | HOLDS_MESSAGES folder " + targetFolder.getFullName()); - } - - } else { - if (hasSubFolders) {// has sub-folders - if (messageCount == 0) { - targetFolder = targetStore.getFolder(targetFolderFullName); - if (!targetFolder.exists()) { - targetFolder.create(Folder.HOLDS_FOLDERS); - logger.log(DEBUG, "Created HOLDS_FOLDERS folder " + targetFolder.getFullName()); - } - } else {// also has messages - Folder parentFolder = targetStore.getFolder(targetFolderFullName); - if (!parentFolder.exists()) { - parentFolder.create(Folder.HOLDS_FOLDERS); - logger.log(DEBUG, "Created HOLDS_FOLDERS folder " + parentFolder.getFullName()); - } - String miscFullName = targetFolderFullName + targetFolderSeparator + "_Misc"; - targetFolder = targetStore.getFolder(miscFullName); - if (!targetFolder.exists()) { - targetFolder.create(Folder.HOLDS_MESSAGES); - logger.log(DEBUG, "Created HOLDS_MESSAGES folder " + targetFolder.getFullName()); - } - } - } else {// no sub-folders - if (messageCount == 0) { // empty - logger.log(DEBUG, "Skip empty folder " + targetFolderFullName); - continue folders; - } - targetFolder = targetStore.getFolder(targetFolderFullName); - if (!targetFolder.exists()) { - targetFolder.create(Folder.HOLDS_MESSAGES); - logger.log(DEBUG, "Created HOLDS_MESSAGES folder " + targetFolder.getFullName()); - } - } - } - - if (messageCount != 0) { - - targetFolder.open(Folder.READ_WRITE); - try { - long begin = System.currentTimeMillis(); - sourceFolder.open(Folder.READ_ONLY); - migrateFolder(sourceFolder, targetFolder); - long duration = System.currentTimeMillis() - begin; - logger.log(DEBUG, targetFolderFullName + " - Migration of " + messageCount + " messages took " - + (duration / 1000) + " s (" + (duration / messageCount) + " ms per message)"); - } finally { - sourceFolder.close(); - targetFolder.close(); - } - } - - // recursive - if (hasSubFolders) { - migrateFolders(sourceFolder, targetStore); - } - } - } - - protected void migrateFoldersToFs(Path baseDir, Folder sourceFolder) throws MessagingException, IOException { - folders: for (Folder folder : sourceFolder.list()) { - String folderName = folder.getName(); - - if ((folder.getType() & Folder.HOLDS_MESSAGES) != 0) { - // Make it configurable - switch (folderName) { - case "All Mail": - case "Important": - continue folders; - default: - // doe nothing - } - migrateFolderToFs(baseDir, folder); - } - if ((folder.getType() & Folder.HOLDS_FOLDERS) != 0) { - migrateFoldersToFs(baseDir.resolve(folder.getName()), folder); - } - } - } - - protected void migrateFolderToFs(Path baseDir, Folder sourceFolder) throws MessagingException, IOException { - - String folderName = sourceFolder.getName(); - sourceFolder.open(Folder.READ_ONLY); - - Folder targetFolder = null; - try { - int messageCount = sourceFolder.getMessageCount(); - logger.log(DEBUG, folderName + " - Message count : " + messageCount); - if (messageCount == 0) - return; -// logger.log(DEBUG, folderName + " - Unread Messages : " + sourceFolder.getUnreadMessageCount()); - - boolean saveAsFiles = false; - - if (saveAsFiles) { - Message messages[] = sourceFolder.getMessages(); - - for (int i = 0; i < messages.length; ++i) { -// logger.log(DEBUG, "MESSAGE #" + (i + 1) + ":"); - Message msg = messages[i]; -// String from = "unknown"; -// if (msg.getReplyTo().length >= 1) { -// from = msg.getReplyTo()[0].toString(); -// } else if (msg.getFrom().length >= 1) { -// from = msg.getFrom()[0].toString(); -// } - String subject = msg.getSubject(); - Instant sentDate = msg.getSentDate().toInstant(); -// logger.log(DEBUG, "Saving ... " + subject + " from " + from + " (" + sentDate + ")"); - String fileName = sentDate + " " + subject; - Path file = baseDir.resolve(fileName); - savePartsAsFiles(msg.getContent(), file); - } - } else { - long begin = System.currentTimeMillis(); - targetFolder = openMboxTargetFolder(sourceFolder, baseDir); - migrateFolder(sourceFolder, targetFolder); - long duration = System.currentTimeMillis() - begin; - logger.log(DEBUG, folderName + " - Migration of " + messageCount + " messages took " + (duration / 1000) - + " s (" + (duration / messageCount) + " ms per message)"); - } - } finally { - sourceFolder.close(); - if (targetFolder != null) - targetFolder.close(); - } - } - - protected Folder migrateFolder(Folder sourceFolder, Folder targetFolder) throws MessagingException, IOException { - String folderName = targetFolder.getName(); - - int lastSourceNumber; - int currentTargetMessageCount = targetFolder.getMessageCount(); - if (currentTargetMessageCount != 0) { - MimeMessage lastTargetMessage = (MimeMessage) targetFolder.getMessage(currentTargetMessageCount); - logger.log(DEBUG, folderName + " - Last target message " + describe(lastTargetMessage)); - Date lastTargetSent = lastTargetMessage.getReceivedDate(); - Message[] lastSourceMessage = sourceFolder - .search(new HeaderTerm(EmailUtils.MESSAGE_ID, lastTargetMessage.getMessageID())); - if (lastSourceMessage.length == 0) - throw new IllegalStateException("No message found with message ID " + lastTargetMessage.getMessageID()); - if (lastSourceMessage.length != 1) { - for (Message msg : lastSourceMessage) { - logger.log(ERROR, "Message " + describe(msg)); - - } - throw new IllegalStateException( - lastSourceMessage.length + " messages found with received date " + lastTargetSent.toInstant()); - } - lastSourceNumber = lastSourceMessage[0].getMessageNumber(); - } else { - lastSourceNumber = 0; - } - logger.log(DEBUG, folderName + " - Last source message number " + lastSourceNumber); - - int countToRetrieve = sourceFolder.getMessageCount() - lastSourceNumber; - - FetchProfile fetchProfile = new FetchProfile(); - fetchProfile.add(FetchProfile.Item.FLAGS); - fetchProfile.add(FetchProfile.Item.ENVELOPE); - fetchProfile.add(FetchProfile.Item.CONTENT_INFO); - fetchProfile.add(FetchProfile.Item.SIZE); - if (sourceFolder instanceof IMAPFolder) { - // IMAPFolder sourceImapFolder = (IMAPFolder) sourceFolder; - fetchProfile.add(IMAPFolder.FetchProfileItem.HEADERS); - fetchProfile.add(IMAPFolder.FetchProfileItem.MESSAGE); - } - - int batchSize = 100; - int batchCount = countToRetrieve / batchSize; - if (countToRetrieve % batchSize != 0) - batchCount = batchCount + 1; - // int batchCount = 2; // for testing - for (int i = 0; i < batchCount; i++) { - long begin = System.currentTimeMillis(); - - int start = lastSourceNumber + i * batchSize + 1; - int end = lastSourceNumber + (i + 1) * batchSize; - if (end >= (lastSourceNumber + countToRetrieve + 1)) - end = lastSourceNumber + countToRetrieve; - Message[] sourceMessages = sourceFolder.getMessages(start, end); - sourceFolder.fetch(sourceMessages, fetchProfile); - // targetFolder.appendMessages(sourceMessages); - // sourceFolder.copyMessages(sourceMessages,targetFolder); - - copyMessages(sourceMessages, targetFolder); -// copyMessagesToMbox(sourceMessages, targetFolder); - - String describeLast = describe(sourceMessages[sourceMessages.length - 1]); - -// if (i % 10 == 9) { - // free memory from fetched messages - sourceFolder.close(); - targetFolder.close(); - - sourceFolder.open(Folder.READ_ONLY); - targetFolder.open(Folder.READ_WRITE); -// logger.log(DEBUG, "Open/close folder in order to free memory"); -// } - - long duration = System.currentTimeMillis() - begin; - logger.log(DEBUG, folderName + " - batch " + i + " took " + (duration / 1000) + " s, " - + (duration / (end - start + 1)) + " ms per message. Last message " + describeLast); - } - - return targetFolder; - } - - protected Folder openMboxTargetFolder(Folder sourceFolder, Path baseDir) throws MessagingException, IOException { - String folderName = sourceFolder.getName(); - if (sourceFolder.getName().equals(EmailUtils.INBOX_UPPER_CASE)) - folderName = EmailUtils.INBOX;// Inbox - - Path targetDir = baseDir;// .resolve("mbox"); - Files.createDirectories(targetDir); - Path targetPath; - if (((sourceFolder.getType() & Folder.HOLDS_FOLDERS) != 0) && sourceFolder.list().length != 0) { - Path dir = targetDir.resolve(folderName); - Files.createDirectories(dir); - targetPath = dir.resolve("_Misc"); - } else { - targetPath = targetDir.resolve(folderName); - } - if (!Files.exists(targetPath)) - Files.createFile(targetPath); - URLName targetUrlName = new URLName("mbox:" + targetPath.toString()); - Properties targetProperties = new Properties(); - // targetProperties.setProperty("mail.mime.address.strict", "false"); - Session targetSession = Session.getDefaultInstance(targetProperties); - Folder targetFolder = targetSession.getFolder(targetUrlName); - targetFolder.open(Folder.READ_WRITE); - - return targetFolder; - } - - protected void copyMessages(Message[] sourceMessages, Folder targetFolder) throws MessagingException { - targetFolder.appendMessages(sourceMessages); - } - - protected void copyMessagesToMbox(Message[] sourceMessages, Folder targetFolder) - throws MessagingException, IOException { - Message[] targetMessages = new Message[sourceMessages.length]; - for (int j = 0; j < sourceMessages.length; j++) { - MimeMessage sourceMm = (MimeMessage) sourceMessages[j]; - InternetHeaders ih = new InternetHeaders(); - for (Enumeration e = sourceMm.getAllHeaderLines(); e.hasMoreElements();) { - ih.addHeaderLine(e.nextElement()); - } - Path tmpFileSource = Files.createTempFile("argeo-mbox-source", ".txt"); - Path tmpFileTarget = Files.createTempFile("argeo-mbox-target", ".txt"); - Files.copy(sourceMm.getRawInputStream(), tmpFileSource, StandardCopyOption.REPLACE_EXISTING); - - // we use ISO_8859_1 because it is more robust than US_ASCII with regard to - // missing characters - try (BufferedReader reader = Files.newBufferedReader(tmpFileSource, StandardCharsets.ISO_8859_1); - BufferedWriter writer = Files.newBufferedWriter(tmpFileTarget, StandardCharsets.ISO_8859_1);) { - int lineNumber = 0; - String line = null; - try { - while ((line = reader.readLine()) != null) { - lineNumber++; - if (line.startsWith("From ")) { - writer.write(">" + line); - logger.log(DEBUG, - "Fix line " + lineNumber + " in " + EmailUtils.describe(sourceMm) + ": " + line); - } else { - writer.write(line); - } - writer.newLine(); - } - } catch (IOException e) { - logger.log(ERROR, "Error around line " + lineNumber + " of " + tmpFileSource); - throw e; - } - } - - MboxMessage mboxMessage = new MboxMessage((MboxFolder) targetFolder, ih, - new SharedFileInputStream(tmpFileTarget.toFile()), sourceMm.getMessageNumber(), - EmailUtils.getUnixFrom(sourceMm), true); - targetMessages[j] = mboxMessage; - - // clean up - Files.delete(tmpFileSource); - Files.delete(tmpFileTarget); - } - targetFolder.appendMessages(targetMessages); - - } - - /** Save body parts and attachments as plain files. */ - protected void savePartsAsFiles(Object content, Path fileBase) throws IOException, MessagingException { - OutputStream out = null; - InputStream in = null; - try { - if (content instanceof Multipart) { - Multipart multi = ((Multipart) content); - int parts = multi.getCount(); - for (int j = 0; j < parts; ++j) { - MimeBodyPart part = (MimeBodyPart) multi.getBodyPart(j); - if (part.getContent() instanceof Multipart) { - // part-within-a-part, do some recursion... - savePartsAsFiles(part.getContent(), fileBase); - } else { - String extension = ""; - if (part.isMimeType("text/html")) { - extension = "html"; - } else { - if (part.isMimeType("text/plain")) { - extension = "txt"; - } else { - // Try to get the name of the attachment - extension = part.getDataHandler().getName(); - } - } - String filename = fileBase + "." + extension; - System.out.println("... " + filename); - out = new FileOutputStream(new File(filename)); - in = part.getInputStream(); - int k; - while ((k = in.read()) != -1) { - out.write(k); - } - } - } - } - } finally { - if (in != null) { - in.close(); - } - if (out != null) { - out.flush(); - out.close(); - } - } - } - - public void setSourceServer(String sourceServer) { - this.sourceServer = sourceServer; - } - - public void setSourceUsername(String sourceUsername) { - this.sourceUsername = sourceUsername; - } - - public void setSourcePassword(String sourcePassword) { - this.sourcePassword = sourcePassword; - } - - public void setTargetServer(String targetServer) { - this.targetServer = targetServer; - } - - public void setTargetUsername(String targetUsername) { - this.targetUsername = targetUsername; - } - - public void setTargetPassword(String targetPassword) { - this.targetPassword = targetPassword; - } - - public static void main(String args[]) throws Exception { - if (args.length < 6) - throw new IllegalArgumentException( - "usage: "); - String sourceServer = args[0]; - String sourceUsername = args[1]; - String sourcePassword = args[2]; - String targetServer = args[3]; - String targetUsername = args[4]; - String targetPassword = args[5]; - - EmailMigration emailMigration = new EmailMigration(); - emailMigration.setSourceServer(sourceServer); - emailMigration.setSourceUsername(sourceUsername); - emailMigration.setSourcePassword(sourcePassword); - emailMigration.setTargetServer(targetServer); - emailMigration.setTargetUsername(targetUsername); - emailMigration.setTargetPassword(targetPassword); - - emailMigration.process(); - } -} diff --git a/org.argeo.app.core/src/org/argeo/app/mail/EmailUtils.java b/org.argeo.app.core/src/org/argeo/app/mail/EmailUtils.java deleted file mode 100644 index 694c17c..0000000 --- a/org.argeo.app.core/src/org/argeo/app/mail/EmailUtils.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.argeo.app.mail; - -import java.util.Date; - -import javax.mail.Address; -import javax.mail.Flags; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; - -/** Utilities around emails. */ -public class EmailUtils { - public final static String INBOX = "Inbox"; - public final static String INBOX_UPPER_CASE = "INBOX"; - public final static String MESSAGE_ID = "Message-ID"; - - public static String getMessageId(Message msg) { - try { - return msg instanceof MimeMessage ? ((MimeMessage) msg).getMessageID() : ""; - } catch (MessagingException e) { - throw new IllegalStateException("Cannot extract message id from " + msg, e); - } - } - - public static String describe(Message msg) { - try { - return "Message " + msg.getMessageNumber() + " " + msg.getSentDate().toInstant() + " " + getMessageId(msg); - } catch (MessagingException e) { - throw new IllegalStateException("Cannot describe " + msg, e); - } - } - - static void setHeadersFromFlags(MimeMessage msg, Flags flags) { - try { - StringBuilder status = new StringBuilder(); - if (flags.contains(Flags.Flag.SEEN)) - status.append('R'); - if (!flags.contains(Flags.Flag.RECENT)) - status.append('O'); - if (status.length() > 0) - msg.setHeader("Status", status.toString()); - else - msg.removeHeader("Status"); - - boolean sims = false; - String s = msg.getHeader("X-Status", null); - // is it a SIMS 2.0 format X-Status header? - sims = s != null && s.length() == 4 && s.indexOf('$') >= 0; - //status.setLength(0); - if (flags.contains(Flags.Flag.DELETED)) - status.append('D'); - else if (sims) - status.append('$'); - if (flags.contains(Flags.Flag.FLAGGED)) - status.append('F'); - else if (sims) - status.append('$'); - if (flags.contains(Flags.Flag.ANSWERED)) - status.append('A'); - else if (sims) - status.append('$'); - if (flags.contains(Flags.Flag.DRAFT)) - status.append('T'); - else if (sims) - status.append('$'); - if (status.length() > 0) - msg.setHeader("X-Status", status.toString()); - else - msg.removeHeader("X-Status"); - - String[] userFlags = flags.getUserFlags(); - if (userFlags.length > 0) { - status.setLength(0); - for (int i = 0; i < userFlags.length; i++) - status.append(userFlags[i]).append(' '); - status.setLength(status.length() - 1); // smash trailing space - msg.setHeader("X-Keywords", status.toString()); - } - if (flags.contains(Flags.Flag.DELETED)) { - s = msg.getHeader("X-Dt-Delete-Time", null); - if (s == null) - // XXX - should be time - msg.setHeader("X-Dt-Delete-Time", "1"); - } - } catch (MessagingException e) { - // ignore it - } - } - - protected static String getUnixFrom(MimeMessage msg) { - Address[] afrom; - String from; - Date ddate; - String date; - try { - if ((afrom = msg.getFrom()) == null || - !(afrom[0] instanceof InternetAddress) || - (from = ((InternetAddress)afrom[0]).getAddress()) == null) - from = "UNKNOWN"; - if ((ddate = msg.getReceivedDate()) == null || - (ddate = msg.getSentDate()) == null) - ddate = new Date(); - } catch (MessagingException e) { - from = "UNKNOWN"; - ddate = new Date(); - } - date = ddate.toString(); - // date is of the form "Sat Aug 12 02:30:00 PDT 1995" - // need to strip out the timezone - return "From " + from + " " + - date.substring(0, 20) + date.substring(24); - } - - /** Singleton. */ - private EmailUtils() { - } -} diff --git a/org.argeo.app.theme.default/OSGI-INF/cmsTheme.xml b/org.argeo.app.theme.default/OSGI-INF/cmsTheme.xml index bcb5ecb..28ef00f 100644 --- a/org.argeo.app.theme.default/OSGI-INF/cmsTheme.xml +++ b/org.argeo.app.theme.default/OSGI-INF/cmsTheme.xml @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.app.theme.default/bnd.bnd b/org.argeo.app.theme.default/bnd.bnd index 4743076..48ad7e2 100644 --- a/org.argeo.app.theme.default/bnd.bnd +++ b/org.argeo.app.theme.default/bnd.bnd @@ -6,6 +6,6 @@ Private-Package: * Export-Package: !* Import-Package:\ -org.argeo.cms.swt.osgi;resolution:="optional",\ +org.argeo.app.swt.osgi;resolution:="optional",\ javax.servlet.*;version="[3,5)",\ * \ No newline at end of file diff --git a/swt/org.argeo.app.swt/bnd.bnd b/swt/org.argeo.app.swt/bnd.bnd index fac182d..21c1d25 100644 --- a/swt/org.argeo.app.swt/bnd.bnd +++ b/swt/org.argeo.app.swt/bnd.bnd @@ -3,4 +3,5 @@ org.eclipse.swt,\ org.argeo.api.cms.ux,\ org.argeo.cms.ux.acr,\ org.argeo.app.api,\ +org.argeo.cms.osgi,\ * \ No newline at end of file diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/osgi/BundleSvgTheme.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/osgi/BundleSvgTheme.java new file mode 100644 index 0000000..15a18ae --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/osgi/BundleSvgTheme.java @@ -0,0 +1,131 @@ +package org.argeo.app.swt.osgi; + +import java.awt.Color; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.apache.batik.transcoder.TranscoderException; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.ImageTranscoder; +import org.apache.batik.transcoder.image.PNGTranscoder; +import org.argeo.cms.swt.osgi.BundleCmsSwtTheme; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.widgets.Display; +import org.osgi.framework.BundleContext; + +/** Theme which can dynamically create icons from SVG data. */ +public class BundleSvgTheme extends BundleCmsSwtTheme { + private final static Logger logger = System.getLogger(BundleSvgTheme.class.getName()); + + private Map> imageDataCache = Collections.synchronizedMap(new HashMap<>()); + + private Map transcoders = Collections.synchronizedMap(new HashMap<>()); + + private final static String IMAGE_CACHE_KEY = BundleSvgTheme.class.getName() + ".imageCache"; + + @Override + public Image getIcon(String name, Integer preferredSize) { + String path = "icons/types/svg/" + name + ".svg"; + return createImageFromSvg(path, preferredSize); + } + + @SuppressWarnings("unchecked") + protected Image createImageFromSvg(String path, Integer preferredSize) { + Display display = Display.getCurrent(); + Objects.requireNonNull(display, "Not a user interface thread"); + + Map> imageCache = (Map>) display + .getData(IMAGE_CACHE_KEY); + if (imageCache == null) + display.setData(IMAGE_CACHE_KEY, new HashMap>()); + imageCache = (Map>) display.getData(IMAGE_CACHE_KEY); + + Image image = null; + if (imageCache.containsKey(path)) { + image = imageCache.get(path).get(preferredSize); + } + if (image != null) + return image; + ImageData imageData = loadFromSvg(path, preferredSize); + image = new Image(display, imageData); + if (!imageCache.containsKey(path)) + imageCache.put(path, Collections.synchronizedMap(new HashMap<>())); + imageCache.get(path).put(preferredSize, image); + return image; + } + + protected ImageData loadFromSvg(String path, int size) { + ImageData imageData = null; + if (imageDataCache.containsKey(path)) + imageData = imageDataCache.get(path).get(size); + if (imageData != null) + return imageData; + + ImageTranscoder transcoder = null; + synchronized (this) { + transcoder = transcoders.get(size); + if (transcoder == null) { + transcoder = new PNGTranscoder(); + transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) size); + transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) size); + transcoder.addTranscodingHint(PNGTranscoder.KEY_BACKGROUND_COLOR, new Color(255, 255, 255, 0)); + transcoders.put(size, transcoder); + } + } + try (InputStream in = getResourceAsStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream();) { + if (in == null) + throw new IllegalArgumentException(path + " not found"); + TranscoderInput input = new TranscoderInput(in); + TranscoderOutput output = new TranscoderOutput(out); + transcoder.transcode(input, output); + try (InputStream imageIn = new ByteArrayInputStream(out.toByteArray())) { + imageData = new ImageData(imageIn); + } + logger.log(Level.DEBUG, () -> "Generated " + size + "x" + size + " PNG icon from " + path); + } catch (IOException | TranscoderException e) { + throw new RuntimeException("Cannot transcode SVG " + path, e); + } + + // cache it + if (!imageDataCache.containsKey(path)) + imageDataCache.put(path, Collections.synchronizedMap(new HashMap<>())); + imageDataCache.get(path).put(size, imageData); + + return imageData; + } + + @Override + public void init(BundleContext bundleContext, Map properties) { + super.init(bundleContext, properties); + + // preload all icons +// paths: for (String p : getImagesPaths()) { +// if (!p.endsWith(".svg")) +// continue paths; +// createImageFromSvg(p, getDefaultIconSize()); +// } + } + +// @Override +// public void destroy(BundleContext bundleContext, Map properties) { +// Display display = Display.getDefault(); +// if (display != null) +// for (String path : imageCache.keySet()) { +// for (Image image : imageCache.get(path).values()) { +// display.syncExec(() -> image.dispose()); +// } +// } +// super.destroy(bundleContext, properties); +// } + +} diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/osgi/SvgToPng.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/osgi/SvgToPng.java new file mode 100644 index 0000000..236dbc6 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/osgi/SvgToPng.java @@ -0,0 +1,64 @@ +package org.argeo.app.swt.osgi; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.batik.transcoder.TranscoderException; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.ImageTranscoder; +import org.apache.batik.transcoder.image.PNGTranscoder; + +public class SvgToPng { + + public void convertSvgDir(Path sourceDir, Path targetDir, int width) { + System.out.println("##\n## " + width + "px - " + sourceDir + "\n##"); + try { + if (targetDir == null) + targetDir = sourceDir.getParent().resolve(Integer.toString(width)); + Files.createDirectories(targetDir); + + PNGTranscoder transcoder = new PNGTranscoder(); + // transcoder.addTranscodingHint(ImageTranscoder.KEY_BACKGROUND_COLOR, + // Color.WHITE); + transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width); + transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) width); + + for (Path source : Files.newDirectoryStream(sourceDir, "*.svg")) { + // FIXME extract base name + String baseName = null; // = FilenameUtils.getBaseName(source.toString()); + Path target = targetDir.resolve(baseName + ".png"); + convertSvgFile(transcoder, source, target); + } + } catch (IOException | TranscoderException e) { + throw new IllegalStateException("Cannot convert from " + sourceDir + " to " + targetDir, e); + } + + } + + protected void convertSvgFile(ImageTranscoder transcoder, Path source, Path target) + throws IOException, TranscoderException { + try (Reader reader = Files.newBufferedReader(source); OutputStream out = Files.newOutputStream(target);) { + TranscoderInput input = new TranscoderInput(reader); +// BufferedImage image = transcoder.createImage(32, 32); + TranscoderOutput output = new TranscoderOutput(out); + transcoder.transcode(input, output); + System.out.println(source.getFileName() + " -> " + target); + } + } + + public static void main(String[] args) throws Exception { + + Path path = Paths.get(args[0]); + + SvgToPng svgToPng = new SvgToPng(); + svgToPng.convertSvgDir(path, null, 16); + svgToPng.convertSvgDir(path, null, 32); + svgToPng.convertSvgDir(path, null, 64); + svgToPng.convertSvgDir(path, null, 96); + } +}