mirror of
https://github.com/moparisthebest/mailiverse
synced 2025-01-30 22:50:14 -05:00
added more files
This commit is contained in:
parent
e332e79f6d
commit
20d47dbe50
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
lib/
|
||||||
|
|
||||||
|
# Compiled source #
|
||||||
|
###################
|
||||||
|
*.com
|
||||||
|
*.class
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
*.d
|
||||||
|
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
|
||||||
|
# Packages #
|
||||||
|
############
|
||||||
|
# it's better to unpack these files and commit the raw source
|
||||||
|
# git has its own built in compression methods
|
||||||
|
*.7z
|
||||||
|
*.dmg
|
||||||
|
*.gz
|
||||||
|
*.iso
|
||||||
|
*.rar
|
||||||
|
*.tar
|
||||||
|
*.zip
|
||||||
|
|
||||||
|
# Logs and databases #
|
||||||
|
######################
|
||||||
|
*.log
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# OS generated files #
|
||||||
|
######################
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
Icon?
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
24
java/core/.classpath
Normal file
24
java/core/.classpath
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||||
|
<classpathentry combineaccessrules="false" kind="src" path="/Mailiverse.Ext.BouncyCastle"/>
|
||||||
|
<classpathentry combineaccessrules="false" kind="src" path="/Mailiverse.Ext.JordanZimmerman"/>
|
||||||
|
<classpathentry combineaccessrules="false" kind="src" path="/Mailiverse.Ext.Json"/>
|
||||||
|
<classpathentry kind="lib" path="lib/dropbox-java-sdk-1.3.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/aws-java-sdk.jar" sourcepath="/Users/tprepscius/.m2/repository/com/amazonaws/aws-java-sdk/1.3.27/aws-java-sdk-1.3.27-sources.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/javamail-1.4.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/activation-1.1.1.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/slf4j-api-1.3.1.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/slf4j-simple-1.3.1.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/mysql-connector-java-3.1.14-bin.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/mina-core-2.0.4.jar"/>
|
||||||
|
<classpathentry combineaccessrules="false" kind="src" path="/Mailiverse.Ext.Apache"/>
|
||||||
|
<classpathentry kind="lib" path="lib/commons-codec-1.4.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/commons-logging-1.1.1.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/httpclient-4.1.1.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/httpcore-4.1.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/JavaPNS_2.2.jar"/>
|
||||||
|
<classpathentry kind="lib" path="lib/log4j-1.2.15.jar"/>
|
||||||
|
<classpathentry kind="output" path="bin"/>
|
||||||
|
</classpath>
|
17
java/core/.project
Normal file
17
java/core/.project
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>Mailiverse.Core</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
170
java/core/src/core/app/AppConstants.java
Normal file
170
java/core/src/core/app/AppConstants.java
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
|
||||||
|
package core.app;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import core.util.Streams;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
|
||||||
|
public class AppConstants
|
||||||
|
{
|
||||||
|
public static String
|
||||||
|
USER_HOME,
|
||||||
|
DOCUMENTS,
|
||||||
|
AUTORUNS,
|
||||||
|
RUNNING_JAR,
|
||||||
|
RUNNING_JAR_FILE,
|
||||||
|
RUNNING_JAR_DIRECTORY,
|
||||||
|
RUNNING_APP,
|
||||||
|
RUNNING_APP_PARENT,
|
||||||
|
PROGRAM_FILES,
|
||||||
|
DOCUMENTS_CLIENT,
|
||||||
|
DOCUMENTS_CLIENT_REDIRECT,
|
||||||
|
AUTORUN_SCRIPT,
|
||||||
|
OS;
|
||||||
|
|
||||||
|
static final String CLIENT_JAR = "Mailiverse.jar";
|
||||||
|
static final String SETUP_JAR = "Setup.jar";
|
||||||
|
|
||||||
|
static final String INTEGRATE_MACMAIL_SCRIPT = "Mailiverse-Integrate-MacMail.script";
|
||||||
|
static final String QUERY_DIRECTORIES_VBSCRIPT = "QueryDirectories.vbs";
|
||||||
|
|
||||||
|
static final String AUTORUN_MAC = "Mailiverse-AutoLaunch.plist";
|
||||||
|
static final String AUTORUN_WINDOWS = "Mailiverse-AutoRun.vbs";
|
||||||
|
|
||||||
|
public static final String HELP_PAGE = "http://www.mailiverse.com/help.html";
|
||||||
|
public static final String MANUAL_S3 = "http://www.mailiverse.com//manual-s3.html";
|
||||||
|
public static final String MANUAL_DROPBOX = "http://www.mailiverse.com/manual-s3.html";
|
||||||
|
public static final String MANUAL_RSA = "http://www.mailiverse.com/manual-rsa.html";
|
||||||
|
|
||||||
|
|
||||||
|
static {
|
||||||
|
queryDirectories();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toOS (String app)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (OS.startsWith("Windows"))
|
||||||
|
{
|
||||||
|
if (Pattern.matches("^/[a-zA-Z]:.*", app))
|
||||||
|
app = app.substring(1);
|
||||||
|
|
||||||
|
app = "\"" + URLDecoder.decode(app, "UTF-8").replace("/", "\\") + "\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void queryDirectories ()
|
||||||
|
{
|
||||||
|
OS = System.getProperty ("os.name");
|
||||||
|
|
||||||
|
USER_HOME = System.getProperty("user.home");
|
||||||
|
RUNNING_JAR = UserUtil.class.getProtectionDomain().getCodeSource().getLocation().getPath();
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern pattern = Pattern.compile("(.+)/(.+?)");
|
||||||
|
Matcher matcher = pattern.matcher(RUNNING_JAR);
|
||||||
|
if (matcher.matches())
|
||||||
|
{
|
||||||
|
RUNNING_JAR_DIRECTORY = matcher.group(1);
|
||||||
|
RUNNING_JAR_FILE = matcher.group(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OS.startsWith("Mac"))
|
||||||
|
{
|
||||||
|
PROGRAM_FILES = "/Applications";
|
||||||
|
DOCUMENTS = USER_HOME + "/Documents";
|
||||||
|
AUTORUNS = USER_HOME + "/Library/LaunchAgents";
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern pattern = Pattern.compile("(.+)/Contents/Resources/Java/.+?");
|
||||||
|
Matcher matcher = pattern.matcher(RUNNING_JAR);
|
||||||
|
if (matcher.matches())
|
||||||
|
RUNNING_APP = matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern pattern = Pattern.compile("(.+)/Contents/.+?/Contents/Resources/Java/.+?");
|
||||||
|
Matcher matcher = pattern.matcher(RUNNING_JAR);
|
||||||
|
if (matcher.matches())
|
||||||
|
RUNNING_APP_PARENT = matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTORUN_SCRIPT = AUTORUN_MAC;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (OS.startsWith("Windows"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process p = Runtime.getRuntime().exec(
|
||||||
|
new String[] { "cscript", "//Nologo", toOS(RUNNING_JAR_DIRECTORY + "/" + QUERY_DIRECTORIES_VBSCRIPT) }
|
||||||
|
);
|
||||||
|
String output = Streams.readFullyString(p.getInputStream(), "UTF-8");
|
||||||
|
String[] lines = Strings.splitLines(output);
|
||||||
|
|
||||||
|
DOCUMENTS = lines[0];
|
||||||
|
AUTORUNS = lines[1];
|
||||||
|
PROGRAM_FILES = lines[2];
|
||||||
|
|
||||||
|
RUNNING_APP = RUNNING_JAR;
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTORUN_SCRIPT = AUTORUN_WINDOWS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// linux
|
||||||
|
PROGRAM_FILES = "/usr/local/bin";
|
||||||
|
DOCUMENTS = USER_HOME + "/Documents";
|
||||||
|
AUTORUNS = null;
|
||||||
|
RUNNING_APP = RUNNING_JAR;
|
||||||
|
AUTORUN_SCRIPT = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCUMENTS_CLIENT = DOCUMENTS + "/Mailiverse";
|
||||||
|
DOCUMENTS_CLIENT_REDIRECT = DOCUMENTS + "/Mailiverse.redirect";
|
||||||
|
|
||||||
|
/*
|
||||||
|
String[] strings = {
|
||||||
|
USER_HOME,
|
||||||
|
DOCUMENTS,
|
||||||
|
AUTORUNS,
|
||||||
|
RUNNING_JAR,
|
||||||
|
RUNNING_JAR_FILE,
|
||||||
|
RUNNING_JAR_DIRECTORY,
|
||||||
|
RUNNING_APP,
|
||||||
|
RUNNING_APP_PARENT,
|
||||||
|
PROGRAM_FILES,
|
||||||
|
DOCUMENTS_CLIENT,
|
||||||
|
DOCUMENTS_CLIENT_REDIRECT,
|
||||||
|
AUTORUN_SCRIPT,
|
||||||
|
OS
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String string : strings)
|
||||||
|
System.out.println(string);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
58
java/core/src/core/app/MailViewUtil.java
Normal file
58
java/core/src/core/app/MailViewUtil.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
|
||||||
|
package core.app;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.Streams;
|
||||||
|
|
||||||
|
|
||||||
|
public class MailViewUtil
|
||||||
|
{
|
||||||
|
public static File createTempDirectory()
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
final File temp;
|
||||||
|
|
||||||
|
temp = File.createTempFile("temp", Long.toString(System.nanoTime()));
|
||||||
|
|
||||||
|
if(!(temp.delete()))
|
||||||
|
{
|
||||||
|
throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!(temp.mkdir()))
|
||||||
|
{
|
||||||
|
throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void integrateOSXMail (String name) throws Exception
|
||||||
|
{
|
||||||
|
String script =
|
||||||
|
Streams.readFullyString(
|
||||||
|
MailViewUtil.class.getResourceAsStream(AppConstants.INTEGRATE_MACMAIL_SCRIPT),
|
||||||
|
"UTF-8"
|
||||||
|
).replace("#NAME#", name);
|
||||||
|
|
||||||
|
File temporary = new File(createTempDirectory().toString() + "/" + AppConstants.INTEGRATE_MACMAIL_SCRIPT);
|
||||||
|
|
||||||
|
FileWriter writer = new FileWriter(temporary);
|
||||||
|
writer.write(script);
|
||||||
|
writer.close();
|
||||||
|
|
||||||
|
if (!temporary.exists())
|
||||||
|
throw new Exception("Failed to write " + AppConstants.INTEGRATE_MACMAIL_SCRIPT);
|
||||||
|
|
||||||
|
Runtime.getRuntime().exec( new String[] { "osascript", temporary.getAbsolutePath() } );
|
||||||
|
}
|
||||||
|
}
|
18
java/core/src/core/app/Mailiverse-AutoLaunch.plist
Normal file
18
java/core/src/core/app/Mailiverse-AutoLaunch.plist
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>Mailiverse</string>
|
||||||
|
<key>Program</key>
|
||||||
|
<string>#TARGET#</string>
|
||||||
|
<key>LimitLoadToSessionType</key>
|
||||||
|
<string>Aqua</string>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/dev/null</string>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>/dev/null</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
1
java/core/src/core/app/Mailiverse-AutoRun.vbs
Normal file
1
java/core/src/core/app/Mailiverse-AutoRun.vbs
Normal file
@ -0,0 +1 @@
|
|||||||
|
WScript.CreateObject( "WScript.Shell" ).Run("#TARGET#")
|
13
java/core/src/core/app/Mailiverse-Integrate-MacMail.script
Normal file
13
java/core/src/core/app/Mailiverse-Integrate-MacMail.script
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
tell application "Mail"
|
||||||
|
|
||||||
|
set _password to ""
|
||||||
|
set _name to "#NAME#"
|
||||||
|
set _email to "#NAME#@mailiverse.com"
|
||||||
|
set _fullname to "#NAME#"
|
||||||
|
|
||||||
|
set newacct to make new pop account with properties {name:"Mailiverse.#NAME#", user name:_name, password:_password, uses ssl:false, server name:"localhost", port:3110, full name:_fullname, email addresses:{_email}}
|
||||||
|
set addsmtp to make new smtp server with properties {name:"Mailiverse.#NAME#", server name:"localhost", uses ssl:false, port:3025, user name:_name, password:"", authentication:("axct" as constant)}
|
||||||
|
|
||||||
|
set smtp server of newacct to addsmtp
|
||||||
|
|
||||||
|
end tell
|
65
java/core/src/core/app/Updater.java
Normal file
65
java/core/src/core/app/Updater.java
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
|
||||||
|
package core.app;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
import core.constants.ConstantsClient;
|
||||||
|
import core.constants.ConstantsVersion;
|
||||||
|
import core.util.Streams;
|
||||||
|
|
||||||
|
|
||||||
|
public class Updater extends Thread
|
||||||
|
{
|
||||||
|
static public final String VERSION_URL = "http://www.mailiverse.com/version/client";
|
||||||
|
static public final String DESCRIPTION_URL = "http://www.mailiverse.com/version/client.description.txt";
|
||||||
|
|
||||||
|
public void run ()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
URL url = new URL(VERSION_URL);
|
||||||
|
URLConnection c = url.openConnection();
|
||||||
|
String response = Streams.readFullyString(c.getInputStream(), "UTF-8");
|
||||||
|
|
||||||
|
response = response.replace("\n", " ");
|
||||||
|
Pattern pattern = Pattern.compile ("^(.+?),(.*)$");
|
||||||
|
Matcher matcher = pattern.matcher(response);
|
||||||
|
|
||||||
|
if (matcher.matches())
|
||||||
|
{
|
||||||
|
String currentVersion = matcher.group(1);
|
||||||
|
String downloadURL = matcher.group(2);
|
||||||
|
|
||||||
|
if (!currentVersion.equals(ConstantsVersion.CLIENT))
|
||||||
|
{
|
||||||
|
String description = Streams.readFullyString(new URL(DESCRIPTION_URL).openConnection().getInputStream(), "UTF-8");
|
||||||
|
UpdaterGUI.run(description, downloadURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Thread execute ()
|
||||||
|
{
|
||||||
|
Thread thread = new Updater();
|
||||||
|
thread.start();
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main (String[] args) throws InterruptedException
|
||||||
|
{
|
||||||
|
execute().join();
|
||||||
|
}
|
||||||
|
}
|
103
java/core/src/core/app/UpdaterGUI.java
Normal file
103
java/core/src/core/app/UpdaterGUI.java
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
|
||||||
|
package core.app;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.SpringLayout;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
|
public class UpdaterGUI extends JFrame
|
||||||
|
{
|
||||||
|
|
||||||
|
private JPanel contentPane;
|
||||||
|
String description;
|
||||||
|
String url;
|
||||||
|
private JTextArea txtrDescription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the application.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
run("None", "None");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void run (final String description, final String url)
|
||||||
|
{
|
||||||
|
EventQueue.invokeLater(new Runnable() {
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UpdaterGUI frame = new UpdaterGUI();
|
||||||
|
frame.bind(description, url);
|
||||||
|
frame.setVisible(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the frame.
|
||||||
|
*/
|
||||||
|
public UpdaterGUI()
|
||||||
|
{
|
||||||
|
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||||
|
setBounds(100, 100, 450, 300);
|
||||||
|
contentPane = new JPanel();
|
||||||
|
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
|
setContentPane(contentPane);
|
||||||
|
SpringLayout sl_contentPane = new SpringLayout();
|
||||||
|
contentPane.setLayout(sl_contentPane);
|
||||||
|
|
||||||
|
JButton btnOpenDownloadsPage = new JButton("Open Downloads Page");
|
||||||
|
btnOpenDownloadsPage.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent arg0) {
|
||||||
|
onOpen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sl_contentPane.putConstraint(SpringLayout.HORIZONTAL_CENTER, btnOpenDownloadsPage, 0, SpringLayout.HORIZONTAL_CENTER, contentPane);
|
||||||
|
sl_contentPane.putConstraint(SpringLayout.SOUTH, btnOpenDownloadsPage, -5, SpringLayout.SOUTH, contentPane);
|
||||||
|
contentPane.add(btnOpenDownloadsPage);
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane();
|
||||||
|
sl_contentPane.putConstraint(SpringLayout.NORTH, scrollPane, 5, SpringLayout.NORTH, contentPane);
|
||||||
|
sl_contentPane.putConstraint(SpringLayout.WEST, scrollPane, 5, SpringLayout.WEST, contentPane);
|
||||||
|
sl_contentPane.putConstraint(SpringLayout.EAST, scrollPane, -5, SpringLayout.EAST, contentPane);
|
||||||
|
sl_contentPane.putConstraint(SpringLayout.SOUTH, scrollPane, -5, SpringLayout.NORTH, btnOpenDownloadsPage);
|
||||||
|
contentPane.add(scrollPane);
|
||||||
|
|
||||||
|
txtrDescription = new JTextArea();
|
||||||
|
txtrDescription.setText("Description");
|
||||||
|
scrollPane.setViewportView(txtrDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind (String description, String url)
|
||||||
|
{
|
||||||
|
this.description = description;
|
||||||
|
txtrDescription.setText(description);
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onOpen ()
|
||||||
|
{
|
||||||
|
UserUtil.openPageInDefaultBrowser(url);
|
||||||
|
}
|
||||||
|
}
|
43
java/core/src/core/app/User.java
Normal file
43
java/core/src/core/app/User.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
|
||||||
|
package core.app;
|
||||||
|
|
||||||
|
import core.constants.ConstantsClient;
|
||||||
|
import core.constants.ConstantsEnvironmentKeys;
|
||||||
|
import core.constants.ConstantsServer;
|
||||||
|
import core.crypt.CryptorRSA;
|
||||||
|
import core.crypt.CryptorRSAAES;
|
||||||
|
import core.crypt.CryptorRSAFactoryEnvironment;
|
||||||
|
import core.exceptions.CryptoException;
|
||||||
|
import core.util.Environment;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public User (Environment environment) throws Exception
|
||||||
|
{
|
||||||
|
this.environment = environment;
|
||||||
|
this.clientEnvironment = environment.childEnvironment(ConstantsEnvironmentKeys.CLIENT_ENVIRONMENT);
|
||||||
|
this.name = clientEnvironment.checkGet(ConstantsEnvironmentKeys.CLIENT_NAME);
|
||||||
|
this.email = name + ConstantsClient.ATHOST;
|
||||||
|
this.password = clientEnvironment.checkGet(ConstantsEnvironmentKeys.CLIENT_PASSWORD);
|
||||||
|
this.cryptor = new CryptorRSAAES(CryptorRSAFactoryEnvironment.create(clientEnvironment));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name;
|
||||||
|
public String email;
|
||||||
|
public String password;
|
||||||
|
public Environment environment;
|
||||||
|
public Environment clientEnvironment;
|
||||||
|
public CryptorRSAAES cryptor;
|
||||||
|
|
||||||
|
public boolean alreadyEnsuredDirectories = false;
|
||||||
|
|
||||||
|
public void authenticate (String name, String password) throws Exception
|
||||||
|
{
|
||||||
|
if (!this.name.equals(name) || !this.password.equals(password) )
|
||||||
|
throw new Exception ("Authentication failure");
|
||||||
|
}
|
||||||
|
}
|
209
java/core/src/core/app/UserUtil.java
Normal file
209
java/core/src/core/app/UserUtil.java
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
|
||||||
|
package core.app;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import core.util.Streams;
|
||||||
|
|
||||||
|
|
||||||
|
public class UserUtil
|
||||||
|
{
|
||||||
|
static public String getConfigurationDirectory () throws Exception
|
||||||
|
{
|
||||||
|
File redirect = new File(AppConstants.DOCUMENTS_CLIENT_REDIRECT);
|
||||||
|
if (redirect.exists() && redirect.isFile())
|
||||||
|
return Streams.readFullyString(new FileInputStream(redirect.getPath()), "UTF-8").trim();
|
||||||
|
|
||||||
|
return AppConstants.DOCUMENTS_CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public boolean hasConfigurationDirectory () throws Exception
|
||||||
|
{
|
||||||
|
File configurationDirectory = new File(getConfigurationDirectory());
|
||||||
|
return configurationDirectory.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static public void writeConfiguration (String user, String password, Environment e) throws Exception
|
||||||
|
{
|
||||||
|
PBE pbe = new PBE(password.toCharArray(), PBE.DEFAULT_SALT_2, PBE.DEFAULT_ITERATIONS, PBE.DEFAULT_KEYLENGTH);
|
||||||
|
|
||||||
|
File configurationDirectory = new File(getConfigurationDirectory());
|
||||||
|
|
||||||
|
if (!configurationDirectory.exists())
|
||||||
|
{
|
||||||
|
if (!configurationDirectory.mkdirs())
|
||||||
|
throw new Exception ("Unable to create directory for Client configuration.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment.toStore(new EncryptedZipFileConnector(pbe, getConfigurationDirectory() + "/" + user + ".mv1"), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Environment readConfiguration (String user, String password)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PBE pbe = new PBE(password.toCharArray(), PBE.DEFAULT_SALT_2, PBE.DEFAULT_ITERATIONS, PBE.DEFAULT_KEYLENGTH);
|
||||||
|
|
||||||
|
Environment e = Environment.fromStore(
|
||||||
|
new EncryptedZipFileConnector(pbe, getConfigurationDirectory() + "/" + user + ".mv1")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!e.containsKey(ConstantsEnvironmentKeys.VERSION))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static void openPageInDefaultBrowser(String url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
java.awt.Desktop.getDesktop().browse(java.net.URI.create(url));
|
||||||
|
}
|
||||||
|
catch (java.io.IOException e)
|
||||||
|
{
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startClientFromSetup ()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (AppConstants.OS.startsWith("Mac"))
|
||||||
|
{
|
||||||
|
String app = AppConstants.toOS(AppConstants.RUNNING_APP_PARENT);
|
||||||
|
System.out.println("Attempting to start " + app);
|
||||||
|
Runtime.getRuntime().exec( new String[] { "open", app, "--args", "--install-autorun" } );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String app = AppConstants.toOS(AppConstants.RUNNING_JAR_DIRECTORY + "/" + AppConstants.CLIENT_JAR);
|
||||||
|
System.out.println("Attempting to start " + app);
|
||||||
|
Runtime.getRuntime().exec( new String[] { "java", "-jar", app, "--install-autorun" } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startSetupFromClient ()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (AppConstants.OS.startsWith("Mac"))
|
||||||
|
{
|
||||||
|
String app = AppConstants.toOS(AppConstants.RUNNING_APP + "/Contents/Setup.app");
|
||||||
|
System.out.println("Attempting to start " + app);
|
||||||
|
Runtime.getRuntime().exec( new String[] { "open", app } );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String app = AppConstants.toOS(AppConstants.RUNNING_JAR_DIRECTORY + "/" + AppConstants.SETUP_JAR);
|
||||||
|
System.out.println("Attempting to start " + app);
|
||||||
|
Runtime.getRuntime().exec( new String[] { "java", "-jar", app } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void enableAutoLaunch (String app, boolean enable) throws IOException
|
||||||
|
{
|
||||||
|
if (AppConstants.OS.startsWith("Linux"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
String launchFileName = AppConstants.AUTORUNS + "/" + AppConstants.AUTORUN_SCRIPT;
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
System.out.println("Attempting to create autorun " + launchFileName + " pointing to " + app);
|
||||||
|
|
||||||
|
File directory = new File(AppConstants.AUTORUNS);
|
||||||
|
if (!directory.exists())
|
||||||
|
directory.mkdirs();
|
||||||
|
|
||||||
|
String launchString =
|
||||||
|
Streams.readFullyString(
|
||||||
|
UserUtil.class.getResourceAsStream(AppConstants.AUTORUN_SCRIPT),
|
||||||
|
"UTF-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (AppConstants.OS.startsWith("Mac"))
|
||||||
|
app = app + "/Contents/MacOS/JavaApplicationStub";
|
||||||
|
if (AppConstants.OS.startsWith("Windows"))
|
||||||
|
app = app.replace("\\", "\\\\").replace("\"", "\"\"");
|
||||||
|
|
||||||
|
launchString = launchString.replace("#TARGET#", app);
|
||||||
|
|
||||||
|
FileOutputStream fos = new FileOutputStream(launchFileName);
|
||||||
|
PrintWriter writer = new PrintWriter(fos);
|
||||||
|
writer.write(launchString);
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.println("Attempting to delete autorun " + launchFileName);
|
||||||
|
|
||||||
|
File f = new File (launchFileName);
|
||||||
|
if (f.exists())
|
||||||
|
f.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAutoLaunchEnabled ()
|
||||||
|
{
|
||||||
|
if (AppConstants.OS.startsWith("Linux"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String launchFileName = AppConstants.AUTORUNS + "/" + AppConstants.AUTORUN_SCRIPT;
|
||||||
|
|
||||||
|
File f = new File (launchFileName);
|
||||||
|
return f.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bringApplicationToFront ()
|
||||||
|
{
|
||||||
|
if (AppConstants.OS.startsWith("Mac"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String application = "Mailiverse";
|
||||||
|
System.out.println("Attempting to bring application to front via applescript.");
|
||||||
|
|
||||||
|
Runtime.getRuntime().exec (
|
||||||
|
new String[] {
|
||||||
|
"osascript",
|
||||||
|
"-e",
|
||||||
|
"tell application \"" + application + "\" to activate"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
java/core/src/core/constants/ConstantsClient.java
Normal file
38
java/core/src/core/constants/ConstantsClient.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsClient
|
||||||
|
{
|
||||||
|
public static final String
|
||||||
|
HOST = ConstantsClientPlatform.HOST,
|
||||||
|
TOMCAT_HOST = ConstantsClientPlatform.TOMCAT_HOST,
|
||||||
|
AUTH_HOST = ConstantsClientPlatform.AUTH_HOST,
|
||||||
|
WEB_HOST = ConstantsClientPlatform.WEB_HOST;
|
||||||
|
|
||||||
|
public static final String ATHOST = "@" + HOST;
|
||||||
|
public static final String WEB_SERVER_URL = "https://" + WEB_HOST;
|
||||||
|
|
||||||
|
public static final String SERVER_TOMCAT = TOMCAT_HOST + "/Mailiverse/";
|
||||||
|
public static final String WEB_SERVER_TOMCAT = "https://" + SERVER_TOMCAT;
|
||||||
|
|
||||||
|
public static final String KEY_AUTH_HOST = AUTH_HOST;
|
||||||
|
public static final int KEY_AUTH_PORT = 7000;
|
||||||
|
|
||||||
|
public static final String MAIL_AUTH_HOST = AUTH_HOST;
|
||||||
|
public static final int MAIL_AUTH_PORT = 7001;
|
||||||
|
|
||||||
|
public static final String MAIL_SERVER_WEBSOCKET = "wss://" + SERVER_TOMCAT + "MailServer";
|
||||||
|
public static final String KEY_SERVER_WEBSOCKET = "wss://" + SERVER_TOMCAT + "KeyServer";
|
||||||
|
|
||||||
|
// Mailiverse
|
||||||
|
public static final String DROPBOX_APPKEY = "YOUR_APPKEY";
|
||||||
|
public static final String DROPBOX_APPSECRET = "YOUR_APPSECRET";
|
||||||
|
|
||||||
|
public static final int MAXIMUM_MAIL_SIZE = 1024 * 1024 * 1;
|
||||||
|
public static final String WEB_AUTHORIZED_URL = WEB_SERVER_URL + "/DropboxAuthorized.html";
|
||||||
|
|
||||||
|
public static final String DROPBOX_AUTH_URL =
|
||||||
|
"https://www.dropbox.com/1/oauth/authorize" +
|
||||||
|
"?oauth_token=REQUEST_TOKEN_KEY" +
|
||||||
|
"&oauth_callback=" + WEB_AUTHORIZED_URL +
|
||||||
|
"&locale=en";
|
||||||
|
}
|
26
java/core/src/core/constants/ConstantsClientPlatform.java
Normal file
26
java/core/src/core/constants/ConstantsClientPlatform.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsClientPlatform
|
||||||
|
{
|
||||||
|
public static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
public static final String HOST, AUTH_HOST, TOMCAT_HOST, WEB_HOST;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
if (DEBUG)
|
||||||
|
{
|
||||||
|
HOST = "mailiverse.com";
|
||||||
|
AUTH_HOST = "red";
|
||||||
|
TOMCAT_HOST = "YOUR_DEV_TOMCAT:8080";
|
||||||
|
WEB_HOST = "YOUR_DEV_WEB:8000";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HOST = "mailiverse.com";
|
||||||
|
AUTH_HOST = "mail.mailiverse.com";
|
||||||
|
TOMCAT_HOST = "mail.mailiverse.com";
|
||||||
|
WEB_HOST = "www.mailiverse.com";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
java/core/src/core/constants/ConstantsDropbox.java
Normal file
11
java/core/src/core/constants/ConstantsDropbox.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsDropbox {
|
||||||
|
|
||||||
|
public static final String
|
||||||
|
DropboxAppKey = "DropboxAppKey",
|
||||||
|
DropboxAppSecret = "DropboxAppSecret",
|
||||||
|
DropboxTokenKey = "DropboxTokenKey",
|
||||||
|
DropboxTokenSecret = "DropboxTokenSecret",
|
||||||
|
DropboxUserPrefix = "DropboxUserPrefix";
|
||||||
|
}
|
16
java/core/src/core/constants/ConstantsEnvironmentKeys.java
Normal file
16
java/core/src/core/constants/ConstantsEnvironmentKeys.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsEnvironmentKeys
|
||||||
|
{
|
||||||
|
public static String HANDLER = "handler";
|
||||||
|
public static String IDENTITY = "identity";
|
||||||
|
public static String SMTP_PASSWORD = "smtpPassword";
|
||||||
|
public static final String PUBLIC_ENCRYPTION_KEY = "RSA-PublicKey";
|
||||||
|
public static final String PRIVATE_DECRYPTION_KEY = "RSA-PrivateKey";
|
||||||
|
public static final String CLIENT_NAME = "name";
|
||||||
|
public static final String CLIENT_PASSWORD = "password";
|
||||||
|
public static final String CONFIGURATION_VERSION = "version";
|
||||||
|
public static final String VERSION = "version";
|
||||||
|
public static final String CLIENT_ENVIRONMENT = "client";
|
||||||
|
public static final String SERVER_ENVIRONMENT = "server";
|
||||||
|
}
|
34
java/core/src/core/constants/ConstantsMailJson.java
Normal file
34
java/core/src/core/constants/ConstantsMailJson.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsMailJson
|
||||||
|
{
|
||||||
|
public static final String
|
||||||
|
Original = "original",
|
||||||
|
|
||||||
|
To = "to",
|
||||||
|
Cc = "cc",
|
||||||
|
Bcc = "bcc",
|
||||||
|
From = "from",
|
||||||
|
ReplyTo = "reply-to",
|
||||||
|
UIDL = "uidl",
|
||||||
|
|
||||||
|
Class = "class",
|
||||||
|
Value = "value",
|
||||||
|
Headers = "headers",
|
||||||
|
Addresses = "addresses",
|
||||||
|
Content = "content",
|
||||||
|
|
||||||
|
String = "string",
|
||||||
|
Bytes = "bytes",
|
||||||
|
MultiPart = "part",
|
||||||
|
Unknown = "unknown",
|
||||||
|
|
||||||
|
Dates = "dates",
|
||||||
|
Received = "received",
|
||||||
|
Sent= "sent",
|
||||||
|
Written = "written",
|
||||||
|
|
||||||
|
Name = "name",
|
||||||
|
Email = "email",
|
||||||
|
EmbeddedCryptors = "cryptors";
|
||||||
|
}
|
10
java/core/src/core/constants/ConstantsPushNotifications.java
Normal file
10
java/core/src/core/constants/ConstantsPushNotifications.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsPushNotifications
|
||||||
|
{
|
||||||
|
public static final String
|
||||||
|
USER = "user",
|
||||||
|
NOTIFICATION_TYPE = "notification_type",
|
||||||
|
DEVICE_TYPE = "device_type",
|
||||||
|
DEVICE_ID = "device_id";
|
||||||
|
}
|
10
java/core/src/core/constants/ConstantsS3.java
Normal file
10
java/core/src/core/constants/ConstantsS3.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsS3 {
|
||||||
|
|
||||||
|
public static final String
|
||||||
|
AWSAccessKeyId = "AWSAccessKeyId",
|
||||||
|
AWSSecretKey = "AWSSecretKey",
|
||||||
|
AWSBucketName = "AWSBucketName",
|
||||||
|
AWSBucketRegion = "AWSBucketRegion";
|
||||||
|
}
|
46
java/core/src/core/constants/ConstantsServer.java
Normal file
46
java/core/src/core/constants/ConstantsServer.java
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsServer
|
||||||
|
{
|
||||||
|
public static final boolean DEBUG = System.getenv("PRODUCTION")==null;
|
||||||
|
|
||||||
|
public static final String LOCAL_MAIL_SERVER, DBCONNECTION_PREFIX;
|
||||||
|
public static final String KEY_SERVER;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
if (DEBUG)
|
||||||
|
{
|
||||||
|
System.out.println("Running DEBUG Mode");
|
||||||
|
|
||||||
|
LOCAL_MAIL_SERVER = "red";
|
||||||
|
DBCONNECTION_PREFIX = "jdbc:mysql://red/";
|
||||||
|
KEY_SERVER = "red";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.println("Running PRODUCTION Mode");
|
||||||
|
|
||||||
|
KEY_SERVER = "localhost";
|
||||||
|
|
||||||
|
// the mail server has to be the full name, or else the SSL certificate fails
|
||||||
|
LOCAL_MAIL_SERVER = "mail.mailiverse.com";
|
||||||
|
DBCONNECTION_PREFIX = "jdbc:mysql://localhost/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String SMTP_HOST = LOCAL_MAIL_SERVER;
|
||||||
|
public static final int SMTP_PORT = 25;
|
||||||
|
|
||||||
|
public static final String LOCAL_SMTP_HOST = "YOUR_LOCAL_SMTP_HOST";
|
||||||
|
public static final String LOCAL_SMTP_PORT = "10025";
|
||||||
|
|
||||||
|
public static final String KEY_AUTH_HOST = KEY_SERVER;
|
||||||
|
public static final int KEY_AUTH_PORT = 7000;
|
||||||
|
|
||||||
|
public static final String MAIL_AUTH_HOST = KEY_SERVER;
|
||||||
|
public static final int MAIL_AUTH_PORT = 7001;
|
||||||
|
public static final int MAXIMUM_MAIL_SIZE = 1024 * 1024 * 1;
|
||||||
|
|
||||||
|
public static final int AUTH_TIMEOUT = 45;
|
||||||
|
}
|
9
java/core/src/core/constants/ConstantsSettings.java
Normal file
9
java/core/src/core/constants/ConstantsSettings.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsSettings
|
||||||
|
{
|
||||||
|
public static final String
|
||||||
|
USERNAME = "name",
|
||||||
|
SIGNATURE = "signature";
|
||||||
|
|
||||||
|
}
|
31
java/core/src/core/constants/ConstantsStorage.java
Normal file
31
java/core/src/core/constants/ConstantsStorage.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsStorage
|
||||||
|
{
|
||||||
|
public final static String
|
||||||
|
HANDLER_DROPBOX = "DB",
|
||||||
|
HANDLER_S3 = "S3";
|
||||||
|
|
||||||
|
public final static String
|
||||||
|
IN = "In",
|
||||||
|
OUT = "Out",
|
||||||
|
JSON = "_Json",
|
||||||
|
|
||||||
|
NEW = "Mail",
|
||||||
|
NEW_IN = NEW + "/" + IN,
|
||||||
|
NEW_OUT = NEW + "/" + OUT,
|
||||||
|
|
||||||
|
NEW_IN_JSON = NEW_IN + JSON,
|
||||||
|
NEW_OUT_JSON = NEW_OUT + JSON,
|
||||||
|
|
||||||
|
CACHE = "Cache",
|
||||||
|
CACHE_PREFIX = CACHE + "/";
|
||||||
|
|
||||||
|
public static int LARGE_MESSAGE_SIZE = 20 * 1024;
|
||||||
|
|
||||||
|
public static final int FLUSH_LOCK_TIME_SECONDS = 10;
|
||||||
|
public static final int FLUSH_LOCK_TIME_ALLOWED_BEFORE_RELOCK_SECONDS = 5;
|
||||||
|
|
||||||
|
public static final int MAIL_CHECK_LOCK_TIME_SECONDS = 120;
|
||||||
|
public static final int MAIL_CHECK_LOCK_TIME_ALLOWED_BEFORE_RELOCK_SECONDS = 60;
|
||||||
|
}
|
10
java/core/src/core/constants/ConstantsVersion.java
Normal file
10
java/core/src/core/constants/ConstantsVersion.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package core.constants;
|
||||||
|
|
||||||
|
public class ConstantsVersion
|
||||||
|
{
|
||||||
|
static public final String
|
||||||
|
APP_NAME = "Mailiverse",
|
||||||
|
LOGIN = "2.0",
|
||||||
|
CONFIGURATION = "2.0",
|
||||||
|
CLIENT = "0.3";
|
||||||
|
}
|
31
java/core/src/core/key/auth/GetKeyServerEnvironment.java
Normal file
31
java/core/src/core/key/auth/GetKeyServerEnvironment.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.auth;
|
||||||
|
|
||||||
|
import java.io.FileWriter;
|
||||||
|
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.JSONSerializer;
|
||||||
|
|
||||||
|
public class GetKeyServerEnvironment
|
||||||
|
{
|
||||||
|
public static void main (String[] args) throws Exception
|
||||||
|
{
|
||||||
|
KeyServerAuthenticatorSync auth = new KeyServerAuthenticatorSync();
|
||||||
|
|
||||||
|
if (args.length != 3)
|
||||||
|
{
|
||||||
|
System.out.println("Arguments: name password outFile");
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment e = auth.get(args[0], args[1], null);
|
||||||
|
|
||||||
|
FileWriter writer = new FileWriter(args[2]);
|
||||||
|
writer.write(new String(JSONSerializer.serialize(e)));
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
}
|
34
java/core/src/core/key/auth/KeyServerAuthTest.java
Normal file
34
java/core/src/core/key/auth/KeyServerAuthTest.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.auth;
|
||||||
|
|
||||||
|
import core.constants.ConstantsEnvironmentKeys;
|
||||||
|
import core.crypt.CryptorRSA;
|
||||||
|
import core.crypt.CryptorRSAAES;
|
||||||
|
import core.crypt.CryptorRSAFactory;
|
||||||
|
import core.crypt.CryptorRSAFactoryEnvironment;
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
public class KeyServerAuthTest
|
||||||
|
{
|
||||||
|
public static void main (String[] args) throws Exception
|
||||||
|
{
|
||||||
|
KeyServerAuthenticatorSync key = new KeyServerAuthenticatorSync();
|
||||||
|
|
||||||
|
Environment e = key.get("USERXXXX@mailiverse.com", "PASSWORD", null);
|
||||||
|
for (String k : e.keySet())
|
||||||
|
{
|
||||||
|
System.out.format("%s -> %s\n", k, e.get(k));
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment client = e.childEnvironment(ConstantsEnvironmentKeys.CLIENT_ENVIRONMENT);
|
||||||
|
CryptorRSA cryptorRSA = CryptorRSAFactoryEnvironment.create (client);
|
||||||
|
CryptorRSAAES cryptorRSAAES = new CryptorRSAAES(cryptorRSA);
|
||||||
|
|
||||||
|
byte[] bytes = cryptorRSAAES.encrypt(Strings.toBytes("This message was encrypted and decrypted?"));
|
||||||
|
System.out.println(Strings.toString(cryptorRSAAES.decrypt(bytes)));
|
||||||
|
}
|
||||||
|
}
|
137
java/core/src/core/key/auth/KeyServerAuthenticator.java
Normal file
137
java/core/src/core/key/auth/KeyServerAuthenticator.java
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.auth;
|
||||||
|
|
||||||
|
import core.client.ClientUserSession;
|
||||||
|
import core.client.messages.Get;
|
||||||
|
import core.client.messages.Put;
|
||||||
|
import core.client.messages.Response;
|
||||||
|
import core.constants.ConstantsClient;
|
||||||
|
import core.constants.ConstantsServer;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import core.io.IoChain;
|
||||||
|
import core.io.IoChainBase64;
|
||||||
|
import core.io.IoChainNewLinePackets;
|
||||||
|
import core.io.IoChainSocket;
|
||||||
|
import core.io.IoChainThread;
|
||||||
|
import core.crypt.KeyPairFromPasswordCryptor;
|
||||||
|
import core.crypt.KeyPairFromPassword;
|
||||||
|
import core.srp.client.SRPClientListener;
|
||||||
|
import core.srp.client.SRPClientUserSession;
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.JSONSerializer;
|
||||||
|
import core.util.Streams;
|
||||||
|
import core.util.Zip;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callbacks.IoStop;
|
||||||
|
|
||||||
|
public class KeyServerAuthenticator
|
||||||
|
{
|
||||||
|
public static final int DEFAULT_PORT = ConstantsClient.KEY_AUTH_PORT;
|
||||||
|
public static final String DEFAULT_HOST = ConstantsClient.KEY_AUTH_HOST;
|
||||||
|
|
||||||
|
String host;
|
||||||
|
int port;
|
||||||
|
|
||||||
|
KeyPairFromPassword keyPair = null;
|
||||||
|
Cryptor cryptor;
|
||||||
|
Thread running = null;
|
||||||
|
|
||||||
|
public KeyServerAuthenticator(String host, int port)
|
||||||
|
{
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyServerAuthenticator ()
|
||||||
|
{
|
||||||
|
this(DEFAULT_HOST, DEFAULT_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread get (String user, String password, Callback callback, SRPClientListener listener) throws Exception
|
||||||
|
{
|
||||||
|
keyPair = new KeyPairFromPassword (password);
|
||||||
|
keyPair.generate();
|
||||||
|
|
||||||
|
cryptor = new KeyPairFromPasswordCryptor (keyPair);
|
||||||
|
|
||||||
|
ClientUserSession session = new ClientUserSession(
|
||||||
|
new Get(),
|
||||||
|
getFinished_().addCallback(callback),
|
||||||
|
new SRPClientUserSession (
|
||||||
|
user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
new IoChainSocket(host, port)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
running = new IoChainThread(session);
|
||||||
|
running.start();
|
||||||
|
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback getFinished_ ()
|
||||||
|
{
|
||||||
|
return new CallbackDefault () {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object... args) throws Exception
|
||||||
|
{
|
||||||
|
running = null;
|
||||||
|
|
||||||
|
IoChain io = (IoChain)args[0];
|
||||||
|
io.stop();
|
||||||
|
|
||||||
|
Object arg = args[1];
|
||||||
|
if (arg instanceof Exception)
|
||||||
|
throw (Exception)arg;
|
||||||
|
|
||||||
|
byte[] block = cryptor.decrypt((byte[])arg);
|
||||||
|
block = Zip.inflate(block);
|
||||||
|
Environment e = JSONSerializer.deserialize(block);
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread put (String user, String password, Environment environment, Callback callback, SRPClientListener listener) throws Exception
|
||||||
|
{
|
||||||
|
keyPair = new KeyPairFromPassword (password);
|
||||||
|
keyPair.generate();
|
||||||
|
|
||||||
|
cryptor = new KeyPairFromPasswordCryptor (keyPair);
|
||||||
|
|
||||||
|
byte[] block = cryptor.encrypt(Zip.deflate(JSONSerializer.serialize(environment)));
|
||||||
|
|
||||||
|
ClientUserSession session = new ClientUserSession(
|
||||||
|
new Put(block),
|
||||||
|
putFinished_().addCallback(callback),
|
||||||
|
new SRPClientUserSession (user, keyPair,
|
||||||
|
new IoChainBase64 (
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
new IoChainSocket(host, port)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
running = new IoChainThread(session);
|
||||||
|
running.start();
|
||||||
|
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback putFinished_ ()
|
||||||
|
{
|
||||||
|
return new IoStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.auth;
|
||||||
|
|
||||||
|
import core.client.ClientUserSession;
|
||||||
|
import core.client.messages.Get;
|
||||||
|
import core.client.messages.Put;
|
||||||
|
import core.crypt.KeyPairFromPasswordCryptor;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import core.crypt.KeyPairFromPassword;
|
||||||
|
import core.io.IoChain;
|
||||||
|
import core.io.IoChainBase64;
|
||||||
|
import core.io.IoChainNewLinePackets;
|
||||||
|
import core.srp.client.SRPClientListener;
|
||||||
|
import core.srp.client.SRPClientUserSession;
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.JSONSerializer;
|
||||||
|
import core.util.Zip;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callbacks.*;
|
||||||
|
|
||||||
|
public class KeyServerAuthenticatorNoThread
|
||||||
|
{
|
||||||
|
public static Callback getFinished_ (Cryptor cryptor)
|
||||||
|
{
|
||||||
|
return new IoStop()
|
||||||
|
.addCallback(cryptor.decrypt_())
|
||||||
|
.addCallback(Zip.inflate_())
|
||||||
|
.addCallback(new JSONDeserialize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback putFinished_ ()
|
||||||
|
{
|
||||||
|
return new IoStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback get_ (String user, KeyPairFromPassword keyPair, IoChain sender, SRPClientListener listener) throws Exception
|
||||||
|
{
|
||||||
|
return new CallbackDefault(user, keyPair, sender, listener) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
String user = V(0);
|
||||||
|
KeyPairFromPassword keyPair = V(1);
|
||||||
|
IoChain sender = V(2);
|
||||||
|
SRPClientListener listener = V(3);
|
||||||
|
|
||||||
|
new ClientUserSession(
|
||||||
|
new Get(),
|
||||||
|
getFinished_(new KeyPairFromPasswordCryptor(keyPair))
|
||||||
|
.setReturn(callback),
|
||||||
|
new SRPClientUserSession (user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
).run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback put_ (String user, KeyPairFromPassword keyPair, Environment environment, IoChain sender, SRPClientListener listener)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new JSONSerialize()
|
||||||
|
.addCallback(Zip.deflate_())
|
||||||
|
.addCallback(new KeyPairFromPasswordCryptor (keyPair).encrypt_())
|
||||||
|
.addCallback(new CallbackDefault(user, keyPair, sender, listener) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
byte[] block = (byte[])arguments[0];
|
||||||
|
String user = (String)V(0);
|
||||||
|
KeyPairFromPassword keyPair = (KeyPairFromPassword)V(1);
|
||||||
|
IoChain sender = (IoChain)V(2);
|
||||||
|
SRPClientListener listener = V(3);
|
||||||
|
new ClientUserSession(
|
||||||
|
new Put(block),
|
||||||
|
putFinished_().setReturn(callback),
|
||||||
|
new SRPClientUserSession (user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
).run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
58
java/core/src/core/key/auth/KeyServerAuthenticatorSync.java
Normal file
58
java/core/src/core/key/auth/KeyServerAuthenticatorSync.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.auth;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.srp.client.SRPClientListener;
|
||||||
|
import core.util.Environment;
|
||||||
|
|
||||||
|
|
||||||
|
public class KeyServerAuthenticatorSync
|
||||||
|
{
|
||||||
|
KeyServerAuthenticator authenticator;
|
||||||
|
Object[] result;
|
||||||
|
|
||||||
|
private class ResultSetter extends Callback
|
||||||
|
{
|
||||||
|
public void invoke (Object... args)
|
||||||
|
{
|
||||||
|
result = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyServerAuthenticatorSync (String server, int port)
|
||||||
|
{
|
||||||
|
authenticator = new KeyServerAuthenticator(server, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyServerAuthenticatorSync ()
|
||||||
|
{
|
||||||
|
authenticator = new KeyServerAuthenticator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment get (String user, String password, SRPClientListener listener) throws Exception
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
Thread thread = authenticator.get(user, password, new ResultSetter(), listener);
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
if (result[0] instanceof Exception)
|
||||||
|
throw (Exception)result[0];
|
||||||
|
|
||||||
|
return (Environment)result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment put (String user, String password, Environment environment, SRPClientListener listener) throws Exception
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
Thread thread = authenticator.put(user, password, environment, new ResultSetter(), listener);
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
if (result[0] instanceof Exception)
|
||||||
|
throw (Exception)result[0];
|
||||||
|
|
||||||
|
return (Environment)result[0];
|
||||||
|
}
|
||||||
|
}
|
29
java/core/src/core/key/auth/PutKeyServerEnvironment.java
Normal file
29
java/core/src/core/key/auth/PutKeyServerEnvironment.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.auth;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.JSONSerializer;
|
||||||
|
import core.util.Streams;
|
||||||
|
|
||||||
|
public class PutKeyServerEnvironment
|
||||||
|
{
|
||||||
|
public static void main (String[] args) throws Exception
|
||||||
|
{
|
||||||
|
KeyServerAuthenticatorSync auth = new KeyServerAuthenticatorSync();
|
||||||
|
|
||||||
|
if (args.length != 4)
|
||||||
|
{
|
||||||
|
System.out.println("Arguments: name password outFile");
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream reader = new FileInputStream(args[2]);
|
||||||
|
Environment e = JSONSerializer.deserialize(Streams.readFullyString(reader, "UTF-8").getBytes("UTF-8"));
|
||||||
|
auth.put(args[0], args[1], e, null);
|
||||||
|
}
|
||||||
|
}
|
71
java/core/src/core/key/server/KeyServerSessionDb.java
Normal file
71
java/core/src/core/key/server/KeyServerSessionDb.java
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.server;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import key.streamserver.SRPProtocolHandler;
|
||||||
|
|
||||||
|
import core.exceptions.PublicMessageException;
|
||||||
|
import core.srp.server.SRPServerUserSessionDb;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Triple;
|
||||||
|
|
||||||
|
import core.server.srp.db.UserDb;
|
||||||
|
|
||||||
|
public class KeyServerSessionDb implements SRPServerUserSessionDb
|
||||||
|
{
|
||||||
|
static LogOut log = new LogOut(SRPProtocolHandler.class);
|
||||||
|
UserDb db;
|
||||||
|
|
||||||
|
public KeyServerSessionDb (UserDb db)
|
||||||
|
{
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlock (String userName, byte[] bytes) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("setBlock", userName);
|
||||||
|
db.setBlock(userName, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBlock (String userName) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("getBlock", userName);
|
||||||
|
return db.getBlock(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Triple<String, BigInteger, BigInteger> getUserVVS(String userName) throws Exception
|
||||||
|
{
|
||||||
|
return db.getVVS(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createUser(String version, String userName, BigInteger v, BigInteger s, byte[] extra) throws Exception
|
||||||
|
{
|
||||||
|
throw new PublicMessageException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rateLimitFailure(String userName) throws Exception
|
||||||
|
{
|
||||||
|
// db.rateLimitFailure(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void markFailure(String userName) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("markFailure", userName);
|
||||||
|
db.markFailure(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void testCreate(String version, String userName) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("testCreate", version, userName);
|
||||||
|
db.testCreateUser(version, userName);
|
||||||
|
}
|
||||||
|
}
|
58
java/core/src/core/key/server/KeyServerUserSession.java
Normal file
58
java/core/src/core/key/server/KeyServerUserSession.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.server;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import core.client.messages.Get;
|
||||||
|
import core.client.messages.Message;
|
||||||
|
import core.client.messages.Put;
|
||||||
|
import core.client.messages.Response;
|
||||||
|
import core.io.IoChain;
|
||||||
|
import core.srp.server.SRPServerUserSession;
|
||||||
|
import core.util.SimpleSerializer;
|
||||||
|
import core.server.captcha.Captcha;
|
||||||
|
import core.server.mailextra.MailExtraDb;
|
||||||
|
|
||||||
|
public class KeyServerUserSession extends IoChain
|
||||||
|
{
|
||||||
|
String userName;
|
||||||
|
KeyServerSessionDb db;
|
||||||
|
Captcha captcha;
|
||||||
|
|
||||||
|
public KeyServerUserSession (KeyServerSessionDb db, SRPServerUserSession sender) throws Exception
|
||||||
|
{
|
||||||
|
super(sender);
|
||||||
|
|
||||||
|
this.captcha = new Captcha();
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void open ()
|
||||||
|
{
|
||||||
|
userName = ((SRPServerUserSession)sender).getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive (byte[] bytes) throws Exception
|
||||||
|
{
|
||||||
|
Message message = SimpleSerializer.deserialize(bytes);
|
||||||
|
|
||||||
|
if (message instanceof Put)
|
||||||
|
{
|
||||||
|
db.setBlock(userName, ((Put)message).getBlock());
|
||||||
|
send(SimpleSerializer.serialize(new Response(db.getBlock(userName))));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (message instanceof Get)
|
||||||
|
{
|
||||||
|
sender.send(SimpleSerializer.serialize(new Response(db.getBlock(userName))));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Exception("Unknown message type");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
java/core/src/core/key/server/sql/KeyUserDb.java
Normal file
29
java/core/src/core/key/server/sql/KeyUserDb.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.server.sql;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import core.server.srp.db.UserDb;
|
||||||
|
import core.server.srp.db.sql.Catalog;
|
||||||
|
|
||||||
|
public class KeyUserDb extends UserDb
|
||||||
|
{
|
||||||
|
public KeyUserDb ()
|
||||||
|
{
|
||||||
|
super (new Catalog());
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBlock (String userName) throws IOException, SQLException
|
||||||
|
{
|
||||||
|
return super.getKeyBlock(userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] setBlock(String userName, byte[] block) throws IOException, SQLException
|
||||||
|
{
|
||||||
|
return super.setKeyBlock(userName, block);
|
||||||
|
}
|
||||||
|
}
|
146
java/core/src/core/key/streamserver/BogusSslContextFactory.java
Normal file
146
java/core/src/core/key/streamserver/BogusSslContextFactory.java
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package key.streamserver;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to create a bogus SSLContext.
|
||||||
|
*
|
||||||
|
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
|
||||||
|
*/
|
||||||
|
public class BogusSslContextFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protocol to use.
|
||||||
|
*/
|
||||||
|
private static final String PROTOCOL = "TLS";
|
||||||
|
|
||||||
|
private static final String KEY_MANAGER_FACTORY_ALGORITHM;
|
||||||
|
|
||||||
|
static {
|
||||||
|
String algorithm = Security
|
||||||
|
.getProperty("ssl.KeyManagerFactory.algorithm");
|
||||||
|
if (algorithm == null) {
|
||||||
|
algorithm = KeyManagerFactory.getDefaultAlgorithm();
|
||||||
|
}
|
||||||
|
|
||||||
|
KEY_MANAGER_FACTORY_ALGORITHM = algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bougus Server certificate keystore file name.
|
||||||
|
*/
|
||||||
|
private static final String BOGUS_KEYSTORE = "bogus.cert";
|
||||||
|
|
||||||
|
// NOTE: The keystore was generated using keytool:
|
||||||
|
// keytool -genkey -alias bogus -keysize 512 -validity 3650
|
||||||
|
// -keyalg RSA -dname "CN=bogus.com, OU=XXX CA,
|
||||||
|
// O=Bogus Inc, L=Stockholm, S=Stockholm, C=SE"
|
||||||
|
// -keypass boguspw -storepass boguspw -keystore bogus.cert
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bougus keystore password.
|
||||||
|
*/
|
||||||
|
private static final char[] BOGUS_PW = { 'b', 'o', 'g', 'u', 's', 'p', 'w' };
|
||||||
|
|
||||||
|
private static SSLContext serverInstance = null;
|
||||||
|
|
||||||
|
private static SSLContext clientInstance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get SSLContext singleton.
|
||||||
|
*
|
||||||
|
* @return SSLContext
|
||||||
|
* @throws java.security.GeneralSecurityException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static SSLContext getInstance(boolean server)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
SSLContext retInstance = null;
|
||||||
|
if (server) {
|
||||||
|
synchronized(BogusSslContextFactory.class) {
|
||||||
|
if (serverInstance == null) {
|
||||||
|
try {
|
||||||
|
serverInstance = createBougusServerSslContext();
|
||||||
|
} catch (Exception ioe) {
|
||||||
|
throw new GeneralSecurityException(
|
||||||
|
"Can't create Server SSLContext:" + ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retInstance = serverInstance;
|
||||||
|
} else {
|
||||||
|
synchronized (BogusSslContextFactory.class) {
|
||||||
|
if (clientInstance == null) {
|
||||||
|
clientInstance = createBougusClientSslContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retInstance = clientInstance;
|
||||||
|
}
|
||||||
|
return retInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SSLContext createBougusServerSslContext()
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
|
// Create keystore
|
||||||
|
KeyStore ks = KeyStore.getInstance("JKS");
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
in = BogusSslContextFactory.class
|
||||||
|
.getResourceAsStream(BOGUS_KEYSTORE);
|
||||||
|
ks.load(in, BOGUS_PW);
|
||||||
|
} finally {
|
||||||
|
if (in != null) {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up key manager factory to use our key store
|
||||||
|
KeyManagerFactory kmf = KeyManagerFactory
|
||||||
|
.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
|
||||||
|
kmf.init(ks, BOGUS_PW);
|
||||||
|
|
||||||
|
// Initialize the SSLContext to work with our key managers.
|
||||||
|
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
|
||||||
|
sslContext.init(kmf.getKeyManagers(),
|
||||||
|
BogusTrustManagerFactory.X509_MANAGERS, null);
|
||||||
|
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SSLContext createBougusClientSslContext()
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
SSLContext context = SSLContext.getInstance(PROTOCOL);
|
||||||
|
context.init(null, BogusTrustManagerFactory.X509_MANAGERS, null);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package key.streamserver;
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import javax.net.ssl.ManagerFactoryParameters;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactorySpi;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bogus trust manager factory. Creates BogusX509TrustManager
|
||||||
|
*
|
||||||
|
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
|
||||||
|
*/
|
||||||
|
class BogusTrustManagerFactory extends TrustManagerFactorySpi {
|
||||||
|
|
||||||
|
static final X509TrustManager X509 = new X509TrustManager() {
|
||||||
|
public void checkClientTrusted(X509Certificate[] x509Certificates,
|
||||||
|
String s) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkServerTrusted(X509Certificate[] x509Certificates,
|
||||||
|
String s) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new X509Certificate[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static final TrustManager[] X509_MANAGERS = new TrustManager[] { X509 };
|
||||||
|
|
||||||
|
public BogusTrustManagerFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TrustManager[] engineGetTrustManagers() {
|
||||||
|
return X509_MANAGERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(KeyStore keystore) throws KeyStoreException {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
|
||||||
|
throws InvalidAlgorithmParameterException {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
67
java/core/src/core/key/streamserver/KeyStreamServerMain.java
Normal file
67
java/core/src/core/key/streamserver/KeyStreamServerMain.java
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.streamserver;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import key.server.sql.KeyUserDb;
|
||||||
|
|
||||||
|
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
|
||||||
|
import org.apache.mina.filter.codec.ProtocolCodecFilter;
|
||||||
|
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
|
||||||
|
import org.apache.mina.filter.logging.LoggingFilter;
|
||||||
|
import org.apache.mina.filter.ssl.SslFilter;
|
||||||
|
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
|
||||||
|
import core.constants.ConstantsServer;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (<b>Entry point</b>) NetCat client. NetCat client connects to the specified
|
||||||
|
* endpoint and prints out received data. NetCat client disconnects
|
||||||
|
* automatically when no data is read for 10 seconds.
|
||||||
|
*
|
||||||
|
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
|
||||||
|
*/
|
||||||
|
public class KeyStreamServerMain
|
||||||
|
{
|
||||||
|
public static void main(String[] args) throws Exception
|
||||||
|
{
|
||||||
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
|
KeyUserDb userDb = new KeyUserDb();
|
||||||
|
userDb.ensureTables();
|
||||||
|
|
||||||
|
// Create TCP/IP connector.
|
||||||
|
NioSocketAcceptor acceptor = new NioSocketAcceptor();
|
||||||
|
|
||||||
|
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
|
||||||
|
|
||||||
|
// Start communication.
|
||||||
|
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
|
||||||
|
TextLineCodecFactory textLineCodec = new TextLineCodecFactory(Charset.forName("UTF-8"));
|
||||||
|
textLineCodec.setDecoderMaxLineLength(50 * 1000);
|
||||||
|
textLineCodec.setEncoderMaxLineLength(50 * 1000);
|
||||||
|
acceptor.getFilterChain().addLast(
|
||||||
|
"codec",
|
||||||
|
new ProtocolCodecFilter(textLineCodec)
|
||||||
|
);
|
||||||
|
|
||||||
|
acceptor.setHandler(new SRPProtocolHandler());
|
||||||
|
acceptor.bind(new InetSocketAddress(ConstantsServer.KEY_AUTH_PORT));
|
||||||
|
|
||||||
|
System.out.println("Listening on port " + ConstantsServer.KEY_AUTH_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addSSLSupport(DefaultIoFilterChainBuilder chain) throws Exception
|
||||||
|
{
|
||||||
|
SSLContextGenerator sslContextGenerator = new SSLContextGenerator();
|
||||||
|
SslFilter sslFilter = new SslFilter(sslContextGenerator.getSslContext());
|
||||||
|
sslFilter.setUseClientMode(false);
|
||||||
|
sslFilter.setWantClientAuth(true);
|
||||||
|
|
||||||
|
chain.addLast("sslFilter", sslFilter);
|
||||||
|
System.out.println("SSL ON");
|
||||||
|
}
|
||||||
|
}
|
161
java/core/src/core/key/streamserver/SRPProtocolHandler.java
Normal file
161
java/core/src/core/key/streamserver/SRPProtocolHandler.java
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.streamserver;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import key.server.KeyServerSessionDb;
|
||||||
|
import key.server.KeyServerUserSession;
|
||||||
|
import key.server.sql.KeyUserDb;
|
||||||
|
import mail.streamserver.MailServerSessionDb;
|
||||||
|
|
||||||
|
import org.apache.mina.core.buffer.IoBuffer;
|
||||||
|
import org.apache.mina.core.service.IoHandler;
|
||||||
|
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||||
|
import org.apache.mina.core.session.IdleStatus;
|
||||||
|
import org.apache.mina.core.session.IoSession;
|
||||||
|
|
||||||
|
import core.constants.ConstantsServer;
|
||||||
|
import core.crypt.CryptorRSAAES;
|
||||||
|
import core.crypt.CryptorRSAJCE;
|
||||||
|
import core.io.IoChainBase64;
|
||||||
|
import core.io.IoChainFinishedException;
|
||||||
|
import core.io.IoChainNewLinePackets;
|
||||||
|
import core.io.IoChainAccumulator;
|
||||||
|
import core.srp.server.SRPServerUserSession;
|
||||||
|
import core.util.ExternalResource;
|
||||||
|
import core.util.InternalResource;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link IoHandler} implementation for NetCat client. This class extended
|
||||||
|
* {@link IoHandlerAdapter} for convenience.
|
||||||
|
*
|
||||||
|
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
|
||||||
|
*/
|
||||||
|
public class SRPProtocolHandler extends IoHandlerAdapter
|
||||||
|
{
|
||||||
|
static final int TIMEOUT_SECONDS = ConstantsServer.AUTH_TIMEOUT;
|
||||||
|
static LogOut log = new LogOut(SRPProtocolHandler.class);
|
||||||
|
|
||||||
|
CryptorRSAAES cryptorRSA;
|
||||||
|
KeyUserDb db;
|
||||||
|
Map<IoSession, KeyServerUserSession> sessions = new HashMap<IoSession, KeyServerUserSession>();
|
||||||
|
|
||||||
|
public SRPProtocolHandler () throws Exception
|
||||||
|
{
|
||||||
|
db = new KeyUserDb();
|
||||||
|
cryptorRSA = new CryptorRSAAES(new CryptorRSAJCE(ExternalResource.getResourceAsStream(getClass(), "keystore.jks"), null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(IoSession session, Throwable cause)
|
||||||
|
{
|
||||||
|
log.debug("exceptionCaught", cause);
|
||||||
|
session.close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sessionOpened(IoSession session)
|
||||||
|
{
|
||||||
|
log.debug("sessionOpened",session);
|
||||||
|
|
||||||
|
// Set reader idle time to 10 seconds.
|
||||||
|
// sessionIdle(...) method will be invoked when no data is read
|
||||||
|
// for 10 seconds.
|
||||||
|
session.getConfig().setIdleTime(IdleStatus.READER_IDLE, TIMEOUT_SECONDS);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
KeyServerSessionDb sessionDb = new KeyServerSessionDb(db);
|
||||||
|
KeyServerUserSession userSession =
|
||||||
|
new KeyServerUserSession(
|
||||||
|
sessionDb,
|
||||||
|
new SRPServerUserSession(cryptorRSA, sessionDb,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
new IoChainAccumulator()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
userSession.run();
|
||||||
|
sessions.put(session, userSession);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.debug("sessionOpened caught", e);
|
||||||
|
session.close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sessionClosed(IoSession session) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("sessionClosed ", session," Total ",session.getReadBytes()," byte(s)");
|
||||||
|
KeyServerUserSession userSession = sessions.get(session);
|
||||||
|
sessions.remove(session);
|
||||||
|
|
||||||
|
if (userSession != null)
|
||||||
|
userSession.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sessionIdle(IoSession session, IdleStatus status) {
|
||||||
|
// Close the connection if reader is idle.
|
||||||
|
if (status == IdleStatus.READER_IDLE) {
|
||||||
|
session.close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write (IoSession session, byte[] data)
|
||||||
|
{
|
||||||
|
log.debug("writing");
|
||||||
|
|
||||||
|
byte[] bytes = data;
|
||||||
|
IoBuffer out = IoBuffer.allocate(bytes.length);
|
||||||
|
out.setAutoExpand(true);
|
||||||
|
out.put (bytes);
|
||||||
|
out.flip();
|
||||||
|
|
||||||
|
session.write(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(IoSession session, Object message) {
|
||||||
|
log.debug("messageReceived");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IoChainAccumulator userSession =
|
||||||
|
(IoChainAccumulator)sessions.get(session).getFinalSender();
|
||||||
|
|
||||||
|
// add back the new line
|
||||||
|
String str = message.toString() + "\n";
|
||||||
|
userSession.receive(str.getBytes());
|
||||||
|
|
||||||
|
List<byte[]> packets = userSession.getAndClearPackets();
|
||||||
|
for(byte[] packet: packets)
|
||||||
|
write(session, packet);
|
||||||
|
|
||||||
|
Exception e = userSession.getAndClearException();
|
||||||
|
if (e != null)
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
if (userSession.isClosed())
|
||||||
|
throw new IoChainFinishedException();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.debug("messageReceived caught", e);
|
||||||
|
session.close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
java/core/src/core/key/streamserver/SSLContextGenerator.java
Normal file
42
java/core/src/core/key/streamserver/SSLContextGenerator.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package key.streamserver;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import org.apache.mina.filter.ssl.KeyStoreFactory;
|
||||||
|
import org.apache.mina.filter.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
import core.util.Streams;
|
||||||
|
|
||||||
|
|
||||||
|
public class SSLContextGenerator
|
||||||
|
{
|
||||||
|
public SSLContext getSslContext() throws Exception
|
||||||
|
{
|
||||||
|
SSLContext sslContext = null;
|
||||||
|
|
||||||
|
final KeyStoreFactory keyStoreFactory = new KeyStoreFactory();
|
||||||
|
keyStoreFactory.setData(Streams.readFullyBytes(this.getClass().getResourceAsStream("keystore.jks")));
|
||||||
|
keyStoreFactory.setPassword("password");
|
||||||
|
|
||||||
|
final KeyStoreFactory trustStoreFactory = new KeyStoreFactory();
|
||||||
|
trustStoreFactory.setData(Streams.readFullyBytes(this.getClass().getResourceAsStream("truststore.jks")));
|
||||||
|
trustStoreFactory.setPassword("password");
|
||||||
|
|
||||||
|
final SslContextFactory sslContextFactory = new SslContextFactory();
|
||||||
|
final KeyStore keyStore = keyStoreFactory.newInstance();
|
||||||
|
sslContextFactory.setKeyManagerFactoryKeyStore(keyStore);
|
||||||
|
|
||||||
|
final KeyStore trustStore = trustStoreFactory.newInstance();
|
||||||
|
sslContextFactory.setTrustManagerFactoryKeyStore(trustStore);
|
||||||
|
sslContextFactory.setKeyManagerFactoryKeyStorePassword("password");
|
||||||
|
sslContext = sslContextFactory.newInstance();
|
||||||
|
System.out.println("SSL provider is: " + sslContext.getProvider());
|
||||||
|
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
}
|
31
java/core/src/core/mail/auth/GetMailServerEnvironment.java
Normal file
31
java/core/src/core/mail/auth/GetMailServerEnvironment.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.auth;
|
||||||
|
|
||||||
|
import java.io.FileWriter;
|
||||||
|
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.JSONSerializer;
|
||||||
|
|
||||||
|
public class GetMailServerEnvironment
|
||||||
|
{
|
||||||
|
public static void main (String[] args) throws Exception
|
||||||
|
{
|
||||||
|
MailServerAuthenticatorSync auth = new MailServerAuthenticatorSync();
|
||||||
|
|
||||||
|
if (args.length != 3)
|
||||||
|
{
|
||||||
|
System.out.println("Arguments: name password outFile");
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment e = auth.get(args[0], args[1]);
|
||||||
|
|
||||||
|
FileWriter writer = new FileWriter(args[2]);
|
||||||
|
writer.write(new String(JSONSerializer.serialize(e)));
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
}
|
53
java/core/src/core/mail/auth/MailServerAuthTest.java
Normal file
53
java/core/src/core/mail/auth/MailServerAuthTest.java
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.auth;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
|
||||||
|
import core.constants.ConstantsEnvironmentKeys;
|
||||||
|
import core.util.Environment;
|
||||||
|
|
||||||
|
|
||||||
|
public class MailServerAuthTest
|
||||||
|
{
|
||||||
|
public static void main (String[] args) throws Exception
|
||||||
|
{
|
||||||
|
Random random = new Random();
|
||||||
|
MailServerAuthenticatorSync auth = new MailServerAuthenticatorSync();
|
||||||
|
|
||||||
|
String user = "test-" + BigInteger.valueOf(Math.abs(random.nextLong())).toString(32);
|
||||||
|
user = user + "@mailiverse.com";
|
||||||
|
|
||||||
|
String password = "password-" + BigInteger.valueOf(Math.abs(random.nextLong())).toString(32);
|
||||||
|
String token = "3ee1vpbih0cl";
|
||||||
|
|
||||||
|
System.out.println("user " + user);
|
||||||
|
|
||||||
|
System.out.println("testCreate");
|
||||||
|
auth.test(user);
|
||||||
|
|
||||||
|
System.out.println("create");
|
||||||
|
auth.create(user, password, token);
|
||||||
|
|
||||||
|
Environment e = new Environment();
|
||||||
|
e.put(ConstantsEnvironmentKeys.SMTP_PASSWORD, "abcdef");
|
||||||
|
|
||||||
|
System.out.println("put");
|
||||||
|
e.put("hi", "there");
|
||||||
|
auth.put(user, password, e);
|
||||||
|
|
||||||
|
System.out.println("get");
|
||||||
|
e = auth.get(user, password);
|
||||||
|
for (String k : e.keySet())
|
||||||
|
{
|
||||||
|
System.out.format("%s -> %s\n", k, e.get(k));
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("delete");
|
||||||
|
auth.delete(user, password);
|
||||||
|
}
|
||||||
|
}
|
273
java/core/src/core/mail/auth/MailServerAuthenticator.java
Normal file
273
java/core/src/core/mail/auth/MailServerAuthenticator.java
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.auth;
|
||||||
|
|
||||||
|
import core.client.ClientCreateSession;
|
||||||
|
import core.client.ClientTestCreateSession;
|
||||||
|
import core.client.ClientUserSession;
|
||||||
|
import core.client.messages.Delete;
|
||||||
|
import core.client.messages.Get;
|
||||||
|
import core.client.messages.Put;
|
||||||
|
import core.client.messages.Response;
|
||||||
|
import core.constants.ConstantsClient;
|
||||||
|
import core.constants.ConstantsServer;
|
||||||
|
import core.crypt.CryptorRSAAES;
|
||||||
|
import core.crypt.CryptorRSAFactory;
|
||||||
|
import core.crypt.CryptorRSAJCE;
|
||||||
|
import core.exceptions.PublicMessageException;
|
||||||
|
import core.io.IoChain;
|
||||||
|
import core.io.IoChainBase64;
|
||||||
|
import core.io.IoChainNewLinePackets;
|
||||||
|
import core.io.IoChainSocket;
|
||||||
|
import core.io.IoChainThread;
|
||||||
|
import core.crypt.KeyPairFromPassword;
|
||||||
|
import core.srp.SRPPackets;
|
||||||
|
import core.srp.client.SRPClientUserSession;
|
||||||
|
import core.callback.CallbackWithVariables;
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callbacks.IoStop;
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.ExternalResource;
|
||||||
|
import core.util.InternalResource;
|
||||||
|
import core.util.JSONSerializer;
|
||||||
|
import core.util.SimpleSerializer;
|
||||||
|
|
||||||
|
public class MailServerAuthenticator
|
||||||
|
{
|
||||||
|
public static final int DEFAULT_PORT = ConstantsClient.MAIL_AUTH_PORT;
|
||||||
|
public static final String DEFAULT_HOST = ConstantsClient.MAIL_AUTH_HOST;
|
||||||
|
|
||||||
|
String host;
|
||||||
|
int port;
|
||||||
|
|
||||||
|
KeyPairFromPassword keyPair = null;
|
||||||
|
Thread running = null;
|
||||||
|
|
||||||
|
public MailServerAuthenticator (String host, int port)
|
||||||
|
{
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailServerAuthenticator ()
|
||||||
|
{
|
||||||
|
this(DEFAULT_HOST, DEFAULT_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread testCreate (String user, Callback callback) throws Exception
|
||||||
|
{
|
||||||
|
CryptorRSAAES cryptor = new CryptorRSAAES(CryptorRSAFactory.fromResources(null, ExternalResource.getResourceAsStream(MailServerAuthenticator.class, "truststore.jks")));
|
||||||
|
|
||||||
|
ClientTestCreateSession session = new ClientTestCreateSession(
|
||||||
|
cryptor, user,
|
||||||
|
new CallbackWithVariables (callback) {
|
||||||
|
public void invoke (Object... args)
|
||||||
|
{
|
||||||
|
testCreateFinished((Callback)V(0), args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
new IoChainSocket(host, port)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
running = new IoChainThread(session);
|
||||||
|
running.start();
|
||||||
|
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreateFinished (Callback callback, Object[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
running = null;
|
||||||
|
|
||||||
|
IoChain io = (IoChain)args[0];
|
||||||
|
io.stop();
|
||||||
|
|
||||||
|
Object arg = args[1];
|
||||||
|
if (arg instanceof Exception)
|
||||||
|
throw (Exception)arg;
|
||||||
|
|
||||||
|
SRPPackets.PacketInit_ServerResponse response = SimpleSerializer.deserialize((byte[])arg);
|
||||||
|
if (!response.succeeded)
|
||||||
|
throw new PublicMessageException(response.reason);
|
||||||
|
|
||||||
|
callback.invoke(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
callback.invoke(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Thread create (String user, String password, String token, Callback callback) throws Exception
|
||||||
|
{
|
||||||
|
keyPair = new KeyPairFromPassword (password);
|
||||||
|
keyPair.generate();
|
||||||
|
|
||||||
|
CryptorRSAAES cryptor = new CryptorRSAAES(CryptorRSAFactory.fromResources(null, ExternalResource.getResourceAsStream(MailServerAuthenticator.class, "truststore.jks")));
|
||||||
|
|
||||||
|
ClientCreateSession session = new ClientCreateSession(
|
||||||
|
cryptor, user, keyPair, SimpleSerializer.serialize(token),
|
||||||
|
new CallbackWithVariables (callback) {
|
||||||
|
public void invoke (Object... args)
|
||||||
|
{
|
||||||
|
createFinished((Callback)V(0), args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
new IoChainSocket(host, port)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
running = new IoChainThread(session);
|
||||||
|
running.start();
|
||||||
|
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createFinished (Callback callback, Object[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
running = null;
|
||||||
|
|
||||||
|
IoChain io = (IoChain)args[0];
|
||||||
|
io.stop();
|
||||||
|
|
||||||
|
Object arg = args[1];
|
||||||
|
if (arg instanceof Exception)
|
||||||
|
throw (Exception)arg;
|
||||||
|
|
||||||
|
SRPPackets.PacketInit_ServerResponse response = SimpleSerializer.deserialize((byte[])arg);
|
||||||
|
if (!response.succeeded)
|
||||||
|
throw new PublicMessageException(response.reason);
|
||||||
|
|
||||||
|
callback.invoke(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
callback.invoke(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread delete (String user, String password, Callback callback) throws Exception
|
||||||
|
{
|
||||||
|
keyPair = new KeyPairFromPassword (password);
|
||||||
|
keyPair.generate();
|
||||||
|
|
||||||
|
ClientUserSession session = new ClientUserSession(
|
||||||
|
new Delete(),
|
||||||
|
new IoStop().setReturn(callback),
|
||||||
|
new SRPClientUserSession(
|
||||||
|
user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
new IoChainSocket(host, port)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
running = new IoChainThread(session);
|
||||||
|
running.start();
|
||||||
|
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Thread get (String user, String password, Callback callback) throws Exception
|
||||||
|
{
|
||||||
|
keyPair = new KeyPairFromPassword (password);
|
||||||
|
keyPair.generate();
|
||||||
|
|
||||||
|
ClientUserSession session = new ClientUserSession(
|
||||||
|
new Get(),
|
||||||
|
new CallbackWithVariables (callback) {
|
||||||
|
public void invoke (Object... args)
|
||||||
|
{
|
||||||
|
getOrPutFinished((Callback)V(0), args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new SRPClientUserSession(
|
||||||
|
user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
new IoChainSocket(host, port)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
running = new IoChainThread(session);
|
||||||
|
running.start();
|
||||||
|
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getOrPutFinished (Callback callback, Object[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
running = null;
|
||||||
|
|
||||||
|
IoChain io = (IoChain)args[0];
|
||||||
|
io.stop();
|
||||||
|
|
||||||
|
Object arg = args[1];
|
||||||
|
if (arg instanceof Exception)
|
||||||
|
throw (Exception)arg;
|
||||||
|
|
||||||
|
Response response = (Response)arg;
|
||||||
|
Environment e = JSONSerializer.deserialize(response.getBlock());
|
||||||
|
callback.invoke(e);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
callback.invoke(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread put (String user, String password, Environment environment, Callback callback) throws Exception
|
||||||
|
{
|
||||||
|
keyPair = new KeyPairFromPassword (password);
|
||||||
|
keyPair.generate();
|
||||||
|
|
||||||
|
byte[] block = JSONSerializer.serialize(environment);
|
||||||
|
|
||||||
|
ClientUserSession session = new ClientUserSession(
|
||||||
|
new Put(block),
|
||||||
|
new CallbackWithVariables (callback) {
|
||||||
|
public void invoke (Object... args)
|
||||||
|
{
|
||||||
|
getOrPutFinished((Callback)V(0), args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new SRPClientUserSession(
|
||||||
|
user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
new IoChainSocket(host, port)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
running = new IoChainThread(session);
|
||||||
|
running.start();
|
||||||
|
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.auth;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callbacks.IoStop;
|
||||||
|
import core.callbacks.JSONDeserialize;
|
||||||
|
import core.callbacks.JSONSerialize;
|
||||||
|
import core.client.ClientCreateSession;
|
||||||
|
import core.client.ClientTestCreateSession;
|
||||||
|
import core.client.ClientUserSession;
|
||||||
|
import core.client.messages.Delete;
|
||||||
|
import core.client.messages.Get;
|
||||||
|
import core.client.messages.Put;
|
||||||
|
import core.crypt.CryptorRSAAES;
|
||||||
|
import core.crypt.CryptorRSAFactory;
|
||||||
|
import core.crypt.KeyPairFromPassword;
|
||||||
|
import core.exceptions.PublicMessageException;
|
||||||
|
import core.io.IoChain;
|
||||||
|
import core.io.IoChainBase64;
|
||||||
|
import core.io.IoChainNewLinePackets;
|
||||||
|
import core.srp.SRPPackets;
|
||||||
|
import core.srp.client.SRPClientListener;
|
||||||
|
import core.srp.client.SRPClientUserSession;
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.InternalResource;
|
||||||
|
import core.util.SimpleSerializer;
|
||||||
|
|
||||||
|
public class MailServerAuthenticatorNoThread
|
||||||
|
{
|
||||||
|
public static Callback testCreate_ (String user, IoChain sender)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(user, sender) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
String user = V(0);
|
||||||
|
IoChain sender = V(1);
|
||||||
|
CryptorRSAAES cryptor = new CryptorRSAAES(CryptorRSAFactory.fromResources(null, InternalResource.getResourceAsStream(MailServerAuthenticatorNoThread.class, "truststore.jks")));
|
||||||
|
|
||||||
|
new ClientTestCreateSession(
|
||||||
|
cryptor, user,
|
||||||
|
createFinished_().setReturn(callback),
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback create_ (String user, KeyPairFromPassword keyPair, String token, IoChain sender)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(user, keyPair, token, sender) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
String user = V(0);
|
||||||
|
KeyPairFromPassword keyPair = V(1);
|
||||||
|
String token = V(2);
|
||||||
|
IoChain sender = V(3);
|
||||||
|
CryptorRSAAES cryptor = new CryptorRSAAES(CryptorRSAFactory.fromResources(null, InternalResource.getResourceAsStream(MailServerAuthenticatorNoThread.class, "truststore.jks")));
|
||||||
|
|
||||||
|
new ClientCreateSession(
|
||||||
|
cryptor, user, keyPair, SimpleSerializer.serialize(token),
|
||||||
|
createFinished_().setReturn(callback),
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback createFinished_ ()
|
||||||
|
{
|
||||||
|
return new IoStop()
|
||||||
|
.addCallback(new JSONDeserialize())
|
||||||
|
.addCallback(new CallbackDefault(){
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
SRPPackets.PacketInit_ServerResponse response = (SRPPackets.PacketInit_ServerResponse)arguments[0];
|
||||||
|
if (!response.succeeded)
|
||||||
|
throw new PublicMessageException(response.reason);
|
||||||
|
|
||||||
|
next(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback get_ (String user, KeyPairFromPassword keyPair, IoChain sender, SRPClientListener listener) throws Exception
|
||||||
|
{
|
||||||
|
return new CallbackDefault(user, keyPair, sender, listener) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
String user = V(0);
|
||||||
|
KeyPairFromPassword keyPair = V(1);
|
||||||
|
IoChain sender = V(2);
|
||||||
|
SRPClientListener listener = V(3);
|
||||||
|
|
||||||
|
new ClientUserSession(
|
||||||
|
new Get(),
|
||||||
|
getFinished_().setReturn(callback),
|
||||||
|
new SRPClientUserSession (user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
).run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback put_ (String user, KeyPairFromPassword keyPair, Environment environment, IoChain sender, SRPClientListener listener)
|
||||||
|
{
|
||||||
|
return new JSONSerialize()
|
||||||
|
.addCallback(new CallbackDefault(user, sender, keyPair, listener) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
byte[] block = (byte[])arguments[0];
|
||||||
|
String user = (String)V(0);
|
||||||
|
IoChain sender = (IoChain)V(1);
|
||||||
|
KeyPairFromPassword keyPair = (KeyPairFromPassword)V(2);
|
||||||
|
SRPClientListener listener = V(3);
|
||||||
|
|
||||||
|
new ClientUserSession(
|
||||||
|
new Put(block),
|
||||||
|
putFinished_().setReturn(callback),
|
||||||
|
new SRPClientUserSession (user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
).run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback delete_ (String user, KeyPairFromPassword keyPair, IoChain sender, SRPClientListener listener)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new CallbackDefault(user, sender, keyPair, listener) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
String user = (String)V(0);
|
||||||
|
IoChain sender = (IoChain)V(1);
|
||||||
|
KeyPairFromPassword keyPair = (KeyPairFromPassword)V(2);
|
||||||
|
SRPClientListener listener = V(3);
|
||||||
|
|
||||||
|
new ClientUserSession(
|
||||||
|
new Delete(),
|
||||||
|
deleteFinished_().setReturn(callback),
|
||||||
|
new SRPClientUserSession (user, keyPair,
|
||||||
|
new IoChainBase64(
|
||||||
|
new IoChainNewLinePackets(
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listener
|
||||||
|
)
|
||||||
|
).run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback deleteFinished_ ()
|
||||||
|
{
|
||||||
|
return new IoStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback getFinished_ ()
|
||||||
|
{
|
||||||
|
return new IoStop().addCallback(new JSONDeserialize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Callback putFinished_ ()
|
||||||
|
{
|
||||||
|
return new IoStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.auth;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.util.Environment;
|
||||||
|
|
||||||
|
|
||||||
|
public class MailServerAuthenticatorSync
|
||||||
|
{
|
||||||
|
MailServerAuthenticator authenticator;
|
||||||
|
Object[] result;
|
||||||
|
|
||||||
|
private class ResultSetter extends Callback
|
||||||
|
{
|
||||||
|
public void invoke (Object... args)
|
||||||
|
{
|
||||||
|
result = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailServerAuthenticatorSync ()
|
||||||
|
{
|
||||||
|
authenticator = new MailServerAuthenticator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void create (String user, String password, String token) throws Exception
|
||||||
|
{
|
||||||
|
Thread thread = authenticator.create(user, password, token, new ResultSetter());
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
if (result[0] instanceof Exception)
|
||||||
|
throw (Exception)result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test (String user) throws Exception
|
||||||
|
{
|
||||||
|
Thread thread = authenticator.testCreate(user, new ResultSetter());
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
if (result[0] instanceof Exception)
|
||||||
|
throw (Exception)result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment get (String user, String password) throws Exception
|
||||||
|
{
|
||||||
|
Thread thread = authenticator.get(user, password, new ResultSetter());
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
if (result[0] instanceof Exception)
|
||||||
|
throw (Exception)result[0];
|
||||||
|
|
||||||
|
return (Environment)result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment put (String user, String password, Environment environment) throws Exception
|
||||||
|
{
|
||||||
|
Thread thread = authenticator.put(user, password, environment, new ResultSetter());
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
if (result[0] instanceof Exception)
|
||||||
|
throw (Exception)result[0];
|
||||||
|
|
||||||
|
return (Environment)result[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(String user, String password) throws Exception
|
||||||
|
{
|
||||||
|
Thread thread = authenticator.delete(user, password, new ResultSetter());
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
if (result[0] instanceof Exception)
|
||||||
|
throw (Exception)result[0];
|
||||||
|
}
|
||||||
|
}
|
29
java/core/src/core/mail/auth/PutMailServerEnvironment.java
Normal file
29
java/core/src/core/mail/auth/PutMailServerEnvironment.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.auth;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.JSONSerializer;
|
||||||
|
import core.util.Streams;
|
||||||
|
|
||||||
|
public class PutMailServerEnvironment
|
||||||
|
{
|
||||||
|
public static void main (String[] args) throws Exception
|
||||||
|
{
|
||||||
|
MailServerAuthenticatorSync auth = new MailServerAuthenticatorSync();
|
||||||
|
|
||||||
|
if (args.length != 3)
|
||||||
|
{
|
||||||
|
System.out.println("Arguments: name password outFile");
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream reader = new FileInputStream(args[2]);
|
||||||
|
Environment e = JSONSerializer.deserialize(Streams.readFullyString(reader, "UTF-8").getBytes("UTF-8"));
|
||||||
|
auth.put(args[0], args[1], e);
|
||||||
|
}
|
||||||
|
}
|
170
java/core/src/core/mail/client/Actions.java
Normal file
170
java/core/src/core/mail/client/Actions.java
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import mail.client.model.Attachments;
|
||||||
|
import mail.client.model.Body;
|
||||||
|
import mail.client.model.Conversation;
|
||||||
|
import mail.client.model.Header;
|
||||||
|
import mail.client.model.Mail;
|
||||||
|
import mail.client.model.Recipients;
|
||||||
|
import mail.client.model.TransportState;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.constants.ConstantsSettings;
|
||||||
|
import core.constants.ConstantsEnvironmentKeys;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.Pair;
|
||||||
|
|
||||||
|
|
||||||
|
public class Actions extends Servent<Master>
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Actions.class);
|
||||||
|
|
||||||
|
public Body calculateSignaturedReplyBody (Mail mail)
|
||||||
|
{
|
||||||
|
String signature = getMaster().getCacheManager().getSettings().get(ConstantsSettings.SIGNATURE, "");
|
||||||
|
return mail.calculateReply(signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<Conversation,Mail> newMail () throws Exception
|
||||||
|
{
|
||||||
|
Mail mail = master.getCacheManager().newMail (
|
||||||
|
new Header (
|
||||||
|
null, // external key
|
||||||
|
null, // original key
|
||||||
|
null,
|
||||||
|
master.getIdentity(),
|
||||||
|
new Recipients(),
|
||||||
|
null,
|
||||||
|
new Date(),
|
||||||
|
TransportState.fromList(TransportState.DRAFT),
|
||||||
|
null
|
||||||
|
),
|
||||||
|
new Body (),
|
||||||
|
new Attachments ()
|
||||||
|
);
|
||||||
|
|
||||||
|
Conversation conversation = master.getIndexer().newMail(mail);
|
||||||
|
return new Pair<Conversation,Mail>(conversation,mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveMail (Conversation conversation, Mail mail)
|
||||||
|
{
|
||||||
|
mail.getHeader().setDate(new Date());
|
||||||
|
mail.markDirty();
|
||||||
|
|
||||||
|
conversation.itemChanged(mail);
|
||||||
|
master.getIndexer().conversationChanged(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteMail (Conversation conversation, Mail mail)
|
||||||
|
{
|
||||||
|
conversation.removeItem(mail);
|
||||||
|
master.getCacheManager().deleteMail(mail);
|
||||||
|
master.getStore().deleteMail(mail);
|
||||||
|
|
||||||
|
if (conversation.getNumItems()==0)
|
||||||
|
{
|
||||||
|
master.getIndexer().removeConversation(conversation);
|
||||||
|
master.getCacheManager().deleteConversation(conversation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
master.getIndexer().conversationChanged(conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback deleteMail_ (Conversation conversation, Mail mail)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(conversation, mail)
|
||||||
|
{
|
||||||
|
public void onSuccess(Object... arguments) throws Exception
|
||||||
|
{
|
||||||
|
Conversation conversation = V(0);
|
||||||
|
Mail mail = V(1);
|
||||||
|
deleteMail(conversation, mail);
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteConversation (Conversation conversation)
|
||||||
|
{
|
||||||
|
Mail mails[] = conversation.getItems().toArray(new Mail[0]);
|
||||||
|
|
||||||
|
for (Mail mail : mails)
|
||||||
|
mail.apply(deleteMail_(conversation, mail));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mail replyToAll (Conversation conversation, Mail mail) throws Exception
|
||||||
|
{
|
||||||
|
return reply (
|
||||||
|
new Recipients (mail.getHeader().calculateReplyAll(getMaster()), null, null, null),
|
||||||
|
conversation, mail, calculateSignaturedReplyBody(mail)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mail replyTo (Conversation conversation, Mail mail) throws Exception
|
||||||
|
{
|
||||||
|
return reply (
|
||||||
|
new Recipients (mail.getHeader().calculateReplyTo(getMaster()), null, null, null),
|
||||||
|
conversation, mail, calculateSignaturedReplyBody(mail)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mail forward (Conversation conversation, Mail mail) throws Exception
|
||||||
|
{
|
||||||
|
return reply (
|
||||||
|
new Recipients (null,null,null,null),
|
||||||
|
conversation, mail, new Body(mail.getBody())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMail (Conversation conversation, Mail mail)
|
||||||
|
{
|
||||||
|
log.debug("Actions.sendMail");
|
||||||
|
|
||||||
|
Mailer sendMail = master.getMailer();
|
||||||
|
|
||||||
|
sendMail.sendMail(
|
||||||
|
master.getEnvironment().get(ConstantsEnvironmentKeys.SMTP_PASSWORD),
|
||||||
|
conversation,
|
||||||
|
mail
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reindexConversation (Conversation conversation)
|
||||||
|
{
|
||||||
|
master.getIndexer().conversationChanged(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mail reply (Recipients recipients, Conversation conversation, Mail mail, Body body) throws Exception
|
||||||
|
{
|
||||||
|
Mail reply = master.getCacheManager().newMail (
|
||||||
|
new Header (
|
||||||
|
null, // external key
|
||||||
|
null, // original key
|
||||||
|
null,
|
||||||
|
master.getIdentity(),
|
||||||
|
recipients,
|
||||||
|
mail.getHeader().getSubject(),
|
||||||
|
new Date(),
|
||||||
|
TransportState.fromList(TransportState.DRAFT),
|
||||||
|
null
|
||||||
|
),
|
||||||
|
body != null ? body : new Body(),
|
||||||
|
new Attachments ()
|
||||||
|
);
|
||||||
|
|
||||||
|
conversation.addItem(reply);
|
||||||
|
master.getIndexer().replyMail(conversation, reply);
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
16
java/core/src/core/mail/client/ArrivalsMonitor.java
Normal file
16
java/core/src/core/mail/client/ArrivalsMonitor.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
public abstract class ArrivalsMonitor extends Servent<Master>
|
||||||
|
{
|
||||||
|
public ArrivalsMonitor ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void check ();
|
||||||
|
|
||||||
|
public abstract boolean isChecking ();
|
||||||
|
}
|
185
java/core/src/core/mail/client/ArrivalsMonitorDefault.java
Normal file
185
java/core/src/core/mail/client/ArrivalsMonitorDefault.java
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import mail.client.model.Direction;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callback.CallbackWithVariables;
|
||||||
|
import core.connector.FileInfo;
|
||||||
|
import core.connector.async.AsyncStoreConnector;
|
||||||
|
import core.connector.async.AsyncStoreConnectorBase64;
|
||||||
|
import core.connector.async.AsyncStoreConnectorEncrypted;
|
||||||
|
import core.connector.async.Lock;
|
||||||
|
import core.constants.ConstantsStorage;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import core.util.LogNull;
|
||||||
|
|
||||||
|
public class ArrivalsMonitorDefault extends ArrivalsMonitor
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(ArrivalsMonitorDefault.class);
|
||||||
|
static final int NUM_FILES_BEFORE_CACHE = 10;
|
||||||
|
static final int NUM_FILES_IN_CHECK = 15;
|
||||||
|
AsyncStoreConnector store;
|
||||||
|
|
||||||
|
boolean checking = false;
|
||||||
|
Object in,out;
|
||||||
|
List<FileInfo> files;
|
||||||
|
int totalFilesFound=0;
|
||||||
|
int numFilesWillCheck=0;
|
||||||
|
Lock checkMailLock;
|
||||||
|
|
||||||
|
public ArrivalsMonitorDefault (Cryptor cryptor, AsyncStoreConnector connector)
|
||||||
|
{
|
||||||
|
this.store = new AsyncStoreConnectorEncrypted(cryptor, connector);
|
||||||
|
checkMailLock =
|
||||||
|
new Lock(
|
||||||
|
new AsyncStoreConnectorBase64(connector),
|
||||||
|
ConstantsStorage.NEW_IN_JSON + "/checkMail.lock",
|
||||||
|
ConstantsStorage.MAIL_CHECK_LOCK_TIME_SECONDS,
|
||||||
|
ConstantsStorage.MAIL_CHECK_LOCK_TIME_ALLOWED_BEFORE_RELOCK_SECONDS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChecking ()
|
||||||
|
{
|
||||||
|
return checking;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void check ()
|
||||||
|
{
|
||||||
|
if (checking)
|
||||||
|
return;
|
||||||
|
|
||||||
|
log.debug("check");
|
||||||
|
|
||||||
|
in = null;
|
||||||
|
out = null;
|
||||||
|
totalFilesFound = numFilesWillCheck = 0;
|
||||||
|
|
||||||
|
CallbackChain callbackChain = new CallbackChain()
|
||||||
|
.addCallback(master.getEventPropagator().signal_(Events.CheckBegin, (Object[])null))
|
||||||
|
.addCallback(master.getCacheManager().update_())
|
||||||
|
.addCallback(checkMailLock.lock_())
|
||||||
|
.addCallback(check_directory_(ConstantsStorage.NEW_IN_JSON, Direction.IN))
|
||||||
|
.addCallback(check_directory_(ConstantsStorage.NEW_OUT_JSON, Direction.OUT))
|
||||||
|
.addCallback(check_combine_())
|
||||||
|
.addCallback(new CyclicalFileCheck(this))
|
||||||
|
.addCallback(check_end_());
|
||||||
|
|
||||||
|
// we do not unlock the checkMailLock, because I think this throws
|
||||||
|
// too many variables into the equation
|
||||||
|
|
||||||
|
checking = true;
|
||||||
|
callbackChain.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback check_end_()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new CallbackDefault() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
checking = false;
|
||||||
|
|
||||||
|
master.getEventPropagator().signal(Events.CheckSuccess, (Object[])null);
|
||||||
|
master.getEventPropagator().signal(Events.CheckEnd, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
checking = false;
|
||||||
|
|
||||||
|
master.getEventPropagator().signal(Events.CheckFailure, "Failed");
|
||||||
|
master.getEventPropagator().signal(Events.CheckEnd, (Object[])null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback check_directory_ (String path, Direction direction)
|
||||||
|
{
|
||||||
|
log.debug("check directory", path);
|
||||||
|
|
||||||
|
return store.list_(path + "/").addCallback(check_accumulate_(direction));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback check_accumulate_ (Direction direction)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(direction) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
Direction direction = V(0);
|
||||||
|
if (direction == Direction.IN)
|
||||||
|
in = arguments[0];
|
||||||
|
else
|
||||||
|
out = arguments[0];
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback check_combine_ ()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object...arguments) throws Exception {
|
||||||
|
|
||||||
|
log.debug("check_combine");
|
||||||
|
checkMailLock.testLock((List<FileInfo>)in);
|
||||||
|
|
||||||
|
files = new ArrayList<FileInfo>();
|
||||||
|
if (in instanceof List)
|
||||||
|
{
|
||||||
|
List<FileInfo> lin = (List<FileInfo>)in;
|
||||||
|
log.debug("check_combine","in", lin.size());
|
||||||
|
|
||||||
|
for (FileInfo file : lin)
|
||||||
|
{
|
||||||
|
if (file.path.endsWith(".lock"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (master.getArrivalsProcessor().alreadyProcessed(file.path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
log.debug("found file",file);
|
||||||
|
|
||||||
|
files.add(file);
|
||||||
|
file.user = Direction.IN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out instanceof List)
|
||||||
|
{
|
||||||
|
List<FileInfo> lout = (List<FileInfo>)out;
|
||||||
|
log.debug("check_combine","out", lout.size());
|
||||||
|
for (FileInfo file : lout)
|
||||||
|
{
|
||||||
|
if (master.getArrivalsProcessor().alreadyProcessed(file.path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
files.add(file);
|
||||||
|
file.user = Direction.OUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalFilesFound = files.size();
|
||||||
|
|
||||||
|
Collections.sort(files, new FileInfo.SortByDateAscending());
|
||||||
|
List<FileInfo> segment = files.subList(0, Math.min(files.size(), NUM_FILES_IN_CHECK));
|
||||||
|
files = segment;
|
||||||
|
|
||||||
|
numFilesWillCheck = files.size();
|
||||||
|
|
||||||
|
log.debug("check_combine","final", files.size());
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
357
java/core/src/core/mail/client/ArrivalsProcessor.java
Normal file
357
java/core/src/core/mail/client/ArrivalsProcessor.java
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/*
|
||||||
|
import javax.mail.Address;
|
||||||
|
import javax.mail.BodyPart;
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
import javax.mail.Session;
|
||||||
|
import javax.mail.internet.InternetAddress;
|
||||||
|
import javax.mail.internet.MimeMessage;
|
||||||
|
import javax.mail.internet.MimeMultipart;
|
||||||
|
import javax.mail.internet.MimeMessage.RecipientType;
|
||||||
|
*/
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callbacks.Single;
|
||||||
|
import core.constants.ConstantsMailJson;
|
||||||
|
import core.crypt.CryptorAES;
|
||||||
|
import core.util.Base64;
|
||||||
|
import core.util.JSON_;
|
||||||
|
import core.util.JSON_.JSONException;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
import mail.client.cache.ID;
|
||||||
|
import mail.client.model.Attachment;
|
||||||
|
import mail.client.model.Attachments;
|
||||||
|
import mail.client.model.Body;
|
||||||
|
import mail.client.model.Dictionary;
|
||||||
|
import mail.client.model.Direction;
|
||||||
|
import mail.client.model.Header;
|
||||||
|
import mail.client.model.Identity;
|
||||||
|
import mail.client.model.Mail;
|
||||||
|
import mail.client.model.Recipients;
|
||||||
|
import mail.client.model.TransportState;
|
||||||
|
import mail.client.model.UnregisteredIdentity;
|
||||||
|
|
||||||
|
|
||||||
|
public class ArrivalsProcessor extends Servent<Master>
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(ArrivalsProcessor.class);
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
static class DuplicateMailException extends Exception {};
|
||||||
|
|
||||||
|
|
||||||
|
public void processSuccess (Direction direction, String externalKey, Date date, byte[] inputStream) throws Exception
|
||||||
|
{
|
||||||
|
Indexer indexer = master.getIndexer();
|
||||||
|
|
||||||
|
log.debug("processSuccess");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Mail mail = processStream (direction, externalKey, date, inputStream);
|
||||||
|
|
||||||
|
indexer.addMail(mail);
|
||||||
|
}
|
||||||
|
catch (DuplicateMailException e)
|
||||||
|
{
|
||||||
|
log.debug("Marking duplicate");
|
||||||
|
indexer.addDuplicate(externalKey, date);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decode (String s)
|
||||||
|
{
|
||||||
|
return Strings.toString(Base64.decode(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFirstHeader(Object message, String s, String def) throws JSONException
|
||||||
|
{
|
||||||
|
Object a = JSON_.getArray(message, "headers");
|
||||||
|
|
||||||
|
if (a != null)
|
||||||
|
{
|
||||||
|
s = s.toLowerCase();
|
||||||
|
for (int i=0; i<JSON_.size(a); ++i)
|
||||||
|
{
|
||||||
|
Object h = JSON_.getArray(a, i);
|
||||||
|
if (s.equals(JSON_.getString(h, 0).toLowerCase()))
|
||||||
|
return decode(JSON_.getString(h, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback setExternalKeys_(String externalKey, String originalKey)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(externalKey,originalKey) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
log.debug("mail has finally loaded, setting external key");
|
||||||
|
|
||||||
|
String externalKey = V(0);
|
||||||
|
String originalKey = V(1);
|
||||||
|
Mail mail = (Mail)arguments[0];
|
||||||
|
|
||||||
|
if (mail.getHeader().getExternalKey()==null)
|
||||||
|
{
|
||||||
|
mail.getHeader().setOriginalKey(originalKey);
|
||||||
|
mail.getHeader().setExternalKey(externalKey);
|
||||||
|
mail.markDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Mail processStream (Direction direction, String externalKey, Date date, byte[] bytes) throws Exception
|
||||||
|
{
|
||||||
|
String string = Strings.toString(bytes);
|
||||||
|
Object message = JSON_.parse(string);
|
||||||
|
String originalKey = JSON_.getString(message, ConstantsMailJson.Original);
|
||||||
|
Object content = JSON_.getObject(message,ConstantsMailJson.Content);
|
||||||
|
// CryptorAES embeddedCryptor = null;
|
||||||
|
|
||||||
|
/*
|
||||||
|
// ok, if there are embedded cryptors we take a look at them and find ours
|
||||||
|
if (JSON_.has(content, ConstantsMailJson.EmbeddedCryptors))
|
||||||
|
{
|
||||||
|
Object cryptors = JSON_.getArray(content, ConstantsMailJson.EmbeddedCryptors);
|
||||||
|
for (int i=0; i<JSON_.size(cryptors); ++i)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String cryptor= JSON_.getString(cryptors, i);
|
||||||
|
byte[] aesKey = getMaster().getCryptor().decrypt(Base64.decode(cryptor));
|
||||||
|
embeddedCryptor = new CryptorAES(aesKey);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.debug("embedded cryptor " + i + " failed to decode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
String uidl = externalKey;
|
||||||
|
|
||||||
|
if (JSON_.has(message, ConstantsMailJson.UIDL))
|
||||||
|
{
|
||||||
|
uidl = JSON_.getString(message, ConstantsMailJson.UIDL);
|
||||||
|
if (master.getIndexer().containsUIDL(uidl))
|
||||||
|
{
|
||||||
|
log.debug("contains UIDL");
|
||||||
|
if (direction == Direction.OUT)
|
||||||
|
{
|
||||||
|
log.debug("mail was sent");
|
||||||
|
|
||||||
|
String id = uidl.replaceAll("<(.*)@.*>", "$1");
|
||||||
|
log.debug("found embedded id", id);
|
||||||
|
|
||||||
|
if (id != null)
|
||||||
|
{
|
||||||
|
CacheManager cacheManager = master.getCacheManager();
|
||||||
|
|
||||||
|
Mail mail = cacheManager.getMail(ID.fromString(id));
|
||||||
|
if (mail!= null)
|
||||||
|
{
|
||||||
|
log.debug("cache has the mail");
|
||||||
|
mail.apply(setExternalKeys_(externalKey, originalKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new DuplicateMailException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String subject = getFirstHeader(content, "subject", "");
|
||||||
|
|
||||||
|
Identity author = null;
|
||||||
|
Recipients recipients = new Recipients ();
|
||||||
|
|
||||||
|
if (JSON_.has(message, ConstantsMailJson.Addresses))
|
||||||
|
{
|
||||||
|
Object addresses = JSON_.getObject(message, ConstantsMailJson.Addresses);
|
||||||
|
if (JSON_.has(addresses, ConstantsMailJson.From))
|
||||||
|
{
|
||||||
|
Object jAddresses = JSON_.getArray(addresses, ConstantsMailJson.From);
|
||||||
|
if (JSON_.size(jAddresses) > 0)
|
||||||
|
{
|
||||||
|
Object jia = JSON_.getObject(jAddresses, 0);
|
||||||
|
author = master.getAddressBook().getIdentity(
|
||||||
|
new UnregisteredIdentity(
|
||||||
|
JSON_.has(jia,ConstantsMailJson.Name) ?
|
||||||
|
decode(JSON_.getString(jia,ConstantsMailJson.Name)) : null,
|
||||||
|
JSON_.has(jia, ConstantsMailJson.Email) ?
|
||||||
|
decode(JSON_.getString(jia, ConstantsMailJson.Email)) : null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] buckets = { ConstantsMailJson.To, ConstantsMailJson.Cc, ConstantsMailJson.Bcc, ConstantsMailJson.ReplyTo };
|
||||||
|
|
||||||
|
for (String bucket : buckets)
|
||||||
|
{
|
||||||
|
if (JSON_.has(addresses,bucket))
|
||||||
|
{
|
||||||
|
Object jAddresses = JSON_.getArray(addresses, bucket);
|
||||||
|
for (int i=0; i<JSON_.size(jAddresses); ++i)
|
||||||
|
{
|
||||||
|
Object jia = JSON_.getObject(jAddresses, i);
|
||||||
|
recipients.get(bucket).add(master.getAddressBook().getIdentity(
|
||||||
|
new UnregisteredIdentity(
|
||||||
|
JSON_.has(jia,ConstantsMailJson.Name) ?
|
||||||
|
decode(JSON_.getString(jia,ConstantsMailJson.Name)) : null,
|
||||||
|
JSON_.has(jia, ConstantsMailJson.Email) ?
|
||||||
|
decode(JSON_.getString(jia, ConstantsMailJson.Email)) : null
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (author == null)
|
||||||
|
author = master.getAddressBook().getIdentity(new UnregisteredIdentity("<Unknown>"));
|
||||||
|
|
||||||
|
Body body = new Body();
|
||||||
|
Attachments attachments = new Attachments();
|
||||||
|
|
||||||
|
List<Object> contents = new ArrayList<Object>();
|
||||||
|
if (content != null)
|
||||||
|
contents.add(content);
|
||||||
|
|
||||||
|
while (contents.size() > 0)
|
||||||
|
{
|
||||||
|
Object c = contents.get(0);
|
||||||
|
contents.remove(0);
|
||||||
|
|
||||||
|
String clazz = JSON_.getString(c, ConstantsMailJson.Class);
|
||||||
|
Object value = JSON_.has(c, ConstantsMailJson.Value) ?
|
||||||
|
JSON_.get(c, ConstantsMailJson.Value) : null;
|
||||||
|
String contentType = getFirstHeader(c, "Content-Type", "text/plain");
|
||||||
|
|
||||||
|
if (clazz.equals(ConstantsMailJson.String))
|
||||||
|
{
|
||||||
|
String valueString = JSON_.asString(value);
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (contentType.startsWith("encrypted/block"))
|
||||||
|
{
|
||||||
|
String jsonBlock = Strings.toString(embeddedCryptor.decrypt(Base64.decode(valueString)));
|
||||||
|
Object json = JSON_.parse(jsonBlock);
|
||||||
|
|
||||||
|
subject = JSON_.has(json, "subject") ? JSON_.getString(json, "subject") : null;
|
||||||
|
contents.add(JSON_.getObject(json, "content"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*/
|
||||||
|
if (contentType.startsWith("text/html"))
|
||||||
|
{
|
||||||
|
if (body.getHTML() == null)
|
||||||
|
body.setHTML(valueString);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (contentType.startsWith("text/plain"))
|
||||||
|
{
|
||||||
|
if (body.getText() == null)
|
||||||
|
body.setText(valueString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (clazz.equals(ConstantsMailJson.MultiPart))
|
||||||
|
{
|
||||||
|
Object valueParts = (Object)value;
|
||||||
|
for (int i=0; i<JSON_.size(valueParts); ++i)
|
||||||
|
{
|
||||||
|
Object valueContent = JSON_.getObject(valueParts,i);
|
||||||
|
contents.add(valueContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (clazz.equals(ConstantsMailJson.Bytes))
|
||||||
|
{
|
||||||
|
String contentDisposition = getFirstHeader(c, "Content-Disposition", "None");
|
||||||
|
String contentId = getFirstHeader(c, "Content-Id", "None");
|
||||||
|
|
||||||
|
String attachmentId = Attachment.getAttachmentId(contentDisposition, contentId);
|
||||||
|
if (attachmentId != null)
|
||||||
|
{
|
||||||
|
attachments.addAttachment(
|
||||||
|
new Attachment (attachmentId, contentDisposition, contentType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Date markDate = null;
|
||||||
|
|
||||||
|
if (JSON_.has(message, ConstantsMailJson.Dates))
|
||||||
|
{
|
||||||
|
Object dates = JSON_.getObject(message, ConstantsMailJson.Dates);
|
||||||
|
|
||||||
|
// use a date from somewhere
|
||||||
|
markDate =
|
||||||
|
JSON_.has(dates, ConstantsMailJson.Sent) ?
|
||||||
|
new Date(Long.parseLong(JSON_.getString(dates, ConstantsMailJson.Sent))) : null;
|
||||||
|
|
||||||
|
if (markDate == null)
|
||||||
|
markDate =
|
||||||
|
JSON_.has(dates, ConstantsMailJson.Received) ?
|
||||||
|
new Date(Long.parseLong(JSON_.getString(dates, ConstantsMailJson.Received))) : null;
|
||||||
|
|
||||||
|
if (markDate == null)
|
||||||
|
markDate =
|
||||||
|
JSON_.has(dates, ConstantsMailJson.Written) ?
|
||||||
|
new Date(Long.parseLong(JSON_.getString(dates, ConstantsMailJson.Written))) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markDate == null)
|
||||||
|
markDate = date;
|
||||||
|
|
||||||
|
// received or sent
|
||||||
|
TransportState transportState =
|
||||||
|
direction == Direction.IN ?
|
||||||
|
TransportState.fromList(TransportState.RECEIVED) :
|
||||||
|
TransportState.fromList(TransportState.SENT);
|
||||||
|
|
||||||
|
Header header = new Header(externalKey, originalKey, uidl, author, recipients, subject, markDate, transportState, body.calculateBrief());
|
||||||
|
Mail mail = master.getCacheManager().newMail(header, body, attachments);
|
||||||
|
mail.getHeader().setDictionary(new Dictionary (mail));
|
||||||
|
|
||||||
|
if (!attachments.getList().isEmpty())
|
||||||
|
mail.getHeader().markState("ATTACHMENT");
|
||||||
|
|
||||||
|
return mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean alreadyProcessed(String path)
|
||||||
|
{
|
||||||
|
Indexer indexer = master.getIndexer();
|
||||||
|
return indexer.containsExternalKey(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processFailure(Direction direction, String path, Date date, Exception e)
|
||||||
|
{
|
||||||
|
log.error("processFailure ", direction, " ", path, " e:", e);
|
||||||
|
|
||||||
|
Indexer indexer = master.getIndexer();
|
||||||
|
indexer.addFailure(direction, path, date, e);
|
||||||
|
}
|
||||||
|
}
|
344
java/core/src/core/mail/client/CacheManager.java
Normal file
344
java/core/src/core/mail/client/CacheManager.java
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callbacks.CountDown;
|
||||||
|
import core.callbacks.Split;
|
||||||
|
import core.callbacks.SuccessFailure;
|
||||||
|
import core.connector.async.AsyncStoreConnector;
|
||||||
|
import core.constants.ConstantsClient;
|
||||||
|
import core.constants.ConstantsClient;
|
||||||
|
import core.constants.ConstantsSettings;
|
||||||
|
import core.crypt.CryptorSeed;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import mail.client.cache.Cache;
|
||||||
|
import mail.client.cache.ID;
|
||||||
|
import mail.client.cache.IndexedCache;
|
||||||
|
import mail.client.cache.IndexedCacheSerializer;
|
||||||
|
import mail.client.cache.ItemCacheFactory;
|
||||||
|
import mail.client.cache.ItemSerializer;
|
||||||
|
import mail.client.cache.JSON;
|
||||||
|
import mail.client.cache.StoreFactory;
|
||||||
|
import mail.client.cache.StoreLibrary;
|
||||||
|
import mail.client.cache.Type;
|
||||||
|
import mail.client.model.Attachments;
|
||||||
|
import mail.client.model.Body;
|
||||||
|
import mail.client.model.Conversation;
|
||||||
|
import mail.client.model.Folder;
|
||||||
|
import mail.client.model.FolderDefinition;
|
||||||
|
import mail.client.model.Header;
|
||||||
|
import mail.client.model.Model;
|
||||||
|
import mail.client.model.ModelFactory;
|
||||||
|
import mail.client.model.Mail;
|
||||||
|
import mail.client.model.ModelSerializer;
|
||||||
|
import mail.client.model.Settings;
|
||||||
|
|
||||||
|
public class CacheManager extends Servent<Master>
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(CacheManager.class);
|
||||||
|
|
||||||
|
Cache masterCache;
|
||||||
|
IndexedCache cacheMail;
|
||||||
|
IndexedCache cacheConversation;
|
||||||
|
IndexedCache cacheFolder;
|
||||||
|
Settings settings;
|
||||||
|
|
||||||
|
boolean isCaching = false;
|
||||||
|
|
||||||
|
ModelFactory itemFactory;
|
||||||
|
|
||||||
|
StoreLibrary library;
|
||||||
|
AsyncStoreConnector connector;
|
||||||
|
|
||||||
|
public CacheManager (CryptorSeed cryptorSeed, AsyncStoreConnector connector)
|
||||||
|
{
|
||||||
|
this.connector = connector;
|
||||||
|
this.itemFactory = new ModelFactory(this);
|
||||||
|
library = new StoreLibrary(cryptorSeed, new StoreFactory(85 * 1024), connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onModelDirty ()
|
||||||
|
{
|
||||||
|
log.debug("markDirty");
|
||||||
|
master.getEventPropagator().signal(Events.CacheDirty, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start ()
|
||||||
|
{
|
||||||
|
log.debug("start");
|
||||||
|
|
||||||
|
JSON json = getMaster().getJSON();
|
||||||
|
|
||||||
|
this.masterCache = new Cache(
|
||||||
|
null,
|
||||||
|
new MasterCacheSerializer(json),
|
||||||
|
library.instantiate("I", null, false) // false because I'm manually initiating below .start(...)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.settings = new Settings(this);
|
||||||
|
this.settings.setId(Constants.SETTINGS_ID);
|
||||||
|
this.masterCache.link(settings);
|
||||||
|
|
||||||
|
ItemSerializer itemSerializer = new ModelSerializer(json);
|
||||||
|
|
||||||
|
this.cacheMail = new IndexedCache(
|
||||||
|
new ItemCacheFactory ("M", library, itemFactory, itemSerializer)
|
||||||
|
);
|
||||||
|
this.cacheMail.setId(Constants.MAIL_ID);
|
||||||
|
masterCache.link(cacheMail);
|
||||||
|
|
||||||
|
this.cacheConversation = new IndexedCache(
|
||||||
|
new ItemCacheFactory ("C", library, itemFactory, itemSerializer)
|
||||||
|
);
|
||||||
|
this.cacheConversation.setId(Constants.CONVERSATION_ID);
|
||||||
|
masterCache.link(cacheConversation);
|
||||||
|
|
||||||
|
this.cacheFolder = new IndexedCache(
|
||||||
|
new ItemCacheFactory ("F", library, itemFactory, itemSerializer)
|
||||||
|
);
|
||||||
|
this.cacheFolder.setId(Constants.FOLDER_ID);
|
||||||
|
masterCache.link(cacheFolder);
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
Callback countDown =
|
||||||
|
new CountDown(
|
||||||
|
4,
|
||||||
|
getMaster().getEventPropagator().signal_(Events.Initialize_IndexedCacheLoadComplete)
|
||||||
|
);
|
||||||
|
|
||||||
|
// some how I need to watch when the indexCaches have been loaded
|
||||||
|
settings.apply(new Split(countDown));
|
||||||
|
cacheMail.apply(new Split(countDown));
|
||||||
|
cacheConversation.apply(new Split(countDown));
|
||||||
|
cacheFolder.apply(new Split(countDown));
|
||||||
|
|
||||||
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
|
library.start(
|
||||||
|
new SuccessFailure(
|
||||||
|
getMaster().getEventPropagator().signal_(Events.Initialize_IndexedCacheAcquired, (Object[])null),
|
||||||
|
getMaster().getEventPropagator().signal_(Events.Initialize_IndexedCacheLoadFailed, (Object[])null)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deserializeIndexCaches ()
|
||||||
|
{
|
||||||
|
log.debug("deserializeIndexCaches");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void firstRunInitialization ()
|
||||||
|
{
|
||||||
|
log.debug("firstRunInitialization");
|
||||||
|
|
||||||
|
// we create the cache the root folders are in
|
||||||
|
cacheFolder.newCache(Indexer.KnownFolderIds.RootCache);
|
||||||
|
|
||||||
|
cacheMail.markCreate();
|
||||||
|
cacheConversation.markCreate();
|
||||||
|
cacheFolder.markCreate();
|
||||||
|
masterCache.markCreate();
|
||||||
|
|
||||||
|
settings.markCreate();
|
||||||
|
settings.set(Settings.VERSION, Settings.CURRENT_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Settings getSettings ()
|
||||||
|
{
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createUIDL (ID id)
|
||||||
|
{
|
||||||
|
return "<" + id + ConstantsClient.ATHOST + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mail newMail(Header header, Body body, Attachments attachments) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("newMail");
|
||||||
|
|
||||||
|
|
||||||
|
Mail mail = (Mail)itemFactory.instantiate(Type.Mail);
|
||||||
|
cacheMail.put(mail);
|
||||||
|
|
||||||
|
mail.setHeader(header);
|
||||||
|
|
||||||
|
// if there is no UIDL we supply one, based off of the external key
|
||||||
|
if (header.getUIDL()==null)
|
||||||
|
header.setUIDL(createUIDL(mail.getId()));
|
||||||
|
|
||||||
|
mail.setBody(body);
|
||||||
|
mail.setAttachments(attachments);
|
||||||
|
|
||||||
|
master.getEventPropagator().signalOnce(Events.NewMail, mail);
|
||||||
|
return mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteMail(Mail mail)
|
||||||
|
{
|
||||||
|
log.debug("deleteMail");
|
||||||
|
master.getEventPropagator().signalOnce(Events.DeleteMail, mail);
|
||||||
|
mail.markDeleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Conversation newConversation (Mail mail) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("newConversation");
|
||||||
|
|
||||||
|
Conversation conversation = (Conversation)itemFactory.instantiate(Type.Conversation);
|
||||||
|
cacheConversation.put(conversation);
|
||||||
|
|
||||||
|
conversation.addItem(mail);
|
||||||
|
master.getEventPropagator().signalOnce(Events.NewConversation, conversation);
|
||||||
|
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteConversation(Conversation conversation)
|
||||||
|
{
|
||||||
|
log.debug("deleteConversation");
|
||||||
|
master.getEventPropagator().signalOnce(Events.DeleteConversation, conversation);
|
||||||
|
conversation.markDeleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Folder newFolder(Type type, FolderDefinition folderDefinition)
|
||||||
|
{
|
||||||
|
log.debug("newFolder", type);
|
||||||
|
Folder folder = (Folder)itemFactory.instantiate(type);
|
||||||
|
cacheFolder.put(folder);
|
||||||
|
|
||||||
|
folder.setFolderDefinition(folderDefinition);
|
||||||
|
master.getEventPropagator().signalOnce(Events.NewFolder, folder);
|
||||||
|
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Folder linkFolder(ID id, Type type, FolderDefinition folderDefinition)
|
||||||
|
{
|
||||||
|
log.debug("newFolder", type, id);
|
||||||
|
Folder folder = (Folder)itemFactory.instantiate(type);
|
||||||
|
cacheFolder.link(id, folder);
|
||||||
|
|
||||||
|
folder.setFolderDefinition(folderDefinition);
|
||||||
|
master.getEventPropagator().signalOnce(Events.NewFolder, folder);
|
||||||
|
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mail getMail(ID uid)
|
||||||
|
{
|
||||||
|
return (Mail) cacheMail.getAndAcquire(Type.Mail, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putMail(Mail m)
|
||||||
|
{
|
||||||
|
cacheMail.put(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Conversation getConversation(ID uid)
|
||||||
|
{
|
||||||
|
return (Conversation) cacheConversation.getAndAcquire(Type.Conversation, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putConversation(Conversation c)
|
||||||
|
{
|
||||||
|
cacheConversation.put(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Folder getFolder(Type type, ID id)
|
||||||
|
{
|
||||||
|
return (Folder)cacheFolder.getAndAcquire(type, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putFolder(Folder f)
|
||||||
|
{
|
||||||
|
cacheFolder.put(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullyCached ()
|
||||||
|
{
|
||||||
|
return (!library.hasDirtyChildren() && !masterCache.hasDirtyChildren());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback onCacheFinished_ ()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
isCaching = false;
|
||||||
|
if (isFullyCached())
|
||||||
|
master.getEventPropagator().signal(Events.CacheClean, (Object[])null);
|
||||||
|
|
||||||
|
master.getEventPropagator().signal(Events.CacheSuccess, (Object[])null);
|
||||||
|
master.getEventPropagator().signal(Events.CacheEnd, (Object[])null);
|
||||||
|
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFailure (Exception e)
|
||||||
|
{
|
||||||
|
isCaching = false;
|
||||||
|
master.getEventPropagator().signal(Events.CacheFailure, e);
|
||||||
|
master.getEventPropagator().signal(Events.CacheEnd, (Object[])null);
|
||||||
|
|
||||||
|
next(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush ()
|
||||||
|
{
|
||||||
|
if (isCaching)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (getMaster().getArrivalsMonitor().isChecking())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (isFullyCached())
|
||||||
|
return;
|
||||||
|
|
||||||
|
doFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doFlush ()
|
||||||
|
{
|
||||||
|
log.debug("doFlush");
|
||||||
|
|
||||||
|
isCaching = true;
|
||||||
|
master.getEventPropagator().signal(Events.CacheBegin, (Object[])null);
|
||||||
|
|
||||||
|
masterCache.debug_()
|
||||||
|
.addCallback(masterCache.flush_())
|
||||||
|
.addCallback(masterCache.checkClean_())
|
||||||
|
.addCallback(masterCache.debug_())
|
||||||
|
.addCallback(library.flush_())
|
||||||
|
.addCallback(masterCache.debug_())
|
||||||
|
.addCallback(onCacheFinished_())
|
||||||
|
.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback update_ ()
|
||||||
|
{
|
||||||
|
return library.update_(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update ()
|
||||||
|
{
|
||||||
|
update_().invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug ()
|
||||||
|
{
|
||||||
|
masterCache.debug_().invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSettingsChanged (Settings settings)
|
||||||
|
{
|
||||||
|
getMaster().getIdentity().setName(settings.get(ConstantsSettings.USERNAME));
|
||||||
|
}
|
||||||
|
}
|
137
java/core/src/core/mail/client/Client.java
Normal file
137
java/core/src/core/mail/client/Client.java
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import mail.client.cache.JSON;
|
||||||
|
import mail.client.model.AddressBook;
|
||||||
|
import mail.client.model.Identity;
|
||||||
|
import mail.client.model.UnregisteredIdentity;
|
||||||
|
import core.connector.async.AsyncStoreConnector;
|
||||||
|
import core.connector.async.AsyncStoreConnectorBase64;
|
||||||
|
import core.connector.async.AsyncStoreConnectorEncrypted;
|
||||||
|
import core.connector.dropbox.ClientInfoDropbox;
|
||||||
|
import core.connector.dropbox.async.ConnectorDropbox;
|
||||||
|
import core.connector.s3.ClientInfoS3;
|
||||||
|
import core.connector.s3.async.S3Connector;
|
||||||
|
import core.constants.ConstantsEnvironmentKeys;
|
||||||
|
import core.constants.ConstantsClient;
|
||||||
|
import core.constants.ConstantsStorage;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import core.crypt.CryptorNone;
|
||||||
|
import core.crypt.CryptorRSA;
|
||||||
|
import core.crypt.CryptorRSAAES;
|
||||||
|
import core.crypt.CryptorSeed;
|
||||||
|
import core.crypt.CryptorRSAFactoryEnvironment;
|
||||||
|
import core.util.Environment;
|
||||||
|
import core.util.HttpDelegate;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
|
||||||
|
public class Client
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull (Client.class);
|
||||||
|
public static String HOST = ConstantsClient.KEY_AUTH_HOST;
|
||||||
|
public static int KEYSERVER_PORT = ConstantsClient.KEY_AUTH_PORT;
|
||||||
|
public static int SENDMAIL_PORT = 8080;
|
||||||
|
|
||||||
|
HttpDelegate httpDelegate;
|
||||||
|
Master master;
|
||||||
|
|
||||||
|
public static Client start (Environment e, String identityString, HttpDelegate httpDelegate, EventDispatcher eventDispatcher) throws Exception
|
||||||
|
{
|
||||||
|
return new Client(e, identityString, httpDelegate, eventDispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client (Environment e, String identityString, HttpDelegate httpDelegate, EventDispatcher eventDispatcher) throws Exception
|
||||||
|
{
|
||||||
|
ArrivalsMonitor arrivalsMonitor = null;
|
||||||
|
this.httpDelegate = httpDelegate;
|
||||||
|
|
||||||
|
Environment mailBoxEnvironment = e.childEnvironment("client");
|
||||||
|
|
||||||
|
log.debug("environment");
|
||||||
|
for (String k : mailBoxEnvironment.keySet())
|
||||||
|
log.debug(k, mailBoxEnvironment.get(k));
|
||||||
|
|
||||||
|
AddressBook addressBook = new AddressBook();
|
||||||
|
|
||||||
|
Identity identity = addressBook.getIdentity(
|
||||||
|
new UnregisteredIdentity(identityString)
|
||||||
|
);
|
||||||
|
identity.setPrimary(true);
|
||||||
|
|
||||||
|
String handler = mailBoxEnvironment.get(ConstantsEnvironmentKeys.HANDLER);
|
||||||
|
CryptorRSA cryptorRSA = CryptorRSAFactoryEnvironment.create (mailBoxEnvironment);
|
||||||
|
CryptorRSAAES cryptorRSAAES = new CryptorRSAAES(cryptorRSA);
|
||||||
|
|
||||||
|
AsyncStoreConnector connector = null;
|
||||||
|
|
||||||
|
if (handler.equals(ConstantsStorage.HANDLER_DROPBOX))
|
||||||
|
{
|
||||||
|
Environment dbEnvironment = mailBoxEnvironment.childEnvironment(handler);
|
||||||
|
ClientInfoDropbox clientInfo = new ClientInfoDropbox (dbEnvironment);
|
||||||
|
connector = new ConnectorDropbox(clientInfo, httpDelegate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (handler.equals(ConstantsStorage.HANDLER_S3))
|
||||||
|
{
|
||||||
|
Environment s3Environment = mailBoxEnvironment.childEnvironment(handler);
|
||||||
|
ClientInfoS3 clientInfo = new ClientInfoS3 (s3Environment);
|
||||||
|
connector = new S3Connector(clientInfo, httpDelegate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception ("Unknown handler");
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackingConnector trackingConnector = new TrackingConnector(connector);
|
||||||
|
arrivalsMonitor = new ArrivalsMonitorDefault(cryptorRSAAES, trackingConnector);
|
||||||
|
|
||||||
|
CryptorSeed cryptorSeed = new CryptorSeed(cryptorRSA.getPrivateKey());
|
||||||
|
|
||||||
|
CacheManager manager = new CacheManager(
|
||||||
|
cryptorSeed,
|
||||||
|
new AsyncStoreConnectorBase64 (
|
||||||
|
trackingConnector
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
master =
|
||||||
|
new Master(
|
||||||
|
new Store(cryptorRSAAES, trackingConnector),
|
||||||
|
identity,
|
||||||
|
mailBoxEnvironment,
|
||||||
|
new Indexer (),
|
||||||
|
addressBook,
|
||||||
|
new ArrivalsProcessor (),
|
||||||
|
arrivalsMonitor,
|
||||||
|
eventDispatcher,
|
||||||
|
new Actions(),
|
||||||
|
new Mailer(httpDelegate),
|
||||||
|
cryptorRSAAES,
|
||||||
|
manager,
|
||||||
|
new JSON()
|
||||||
|
);
|
||||||
|
|
||||||
|
trackingConnector.setMaster(master);
|
||||||
|
|
||||||
|
master.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpDelegate getHttpDelegate ()
|
||||||
|
{
|
||||||
|
return httpDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Master getMaster ()
|
||||||
|
{
|
||||||
|
return master;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop ()
|
||||||
|
{
|
||||||
|
master = null;
|
||||||
|
}
|
||||||
|
}
|
30
java/core/src/core/mail/client/Constants.java
Normal file
30
java/core/src/core/mail/client/Constants.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import mail.client.cache.ID;
|
||||||
|
|
||||||
|
public class Constants
|
||||||
|
{
|
||||||
|
static final public String DEFAULT = "Default";
|
||||||
|
|
||||||
|
public static final String
|
||||||
|
PART = "_PART#";
|
||||||
|
|
||||||
|
public static final String
|
||||||
|
REPOSITORY = "Repository",
|
||||||
|
ALL = "All",
|
||||||
|
INBOX = "Inbox",
|
||||||
|
SENT = "Sent",
|
||||||
|
SPAM = "Spam",
|
||||||
|
DRAFTS = "Drafts",
|
||||||
|
TRASH = "Trash";
|
||||||
|
|
||||||
|
public static final ID
|
||||||
|
SETTINGS_ID = ID.fromLong(1),
|
||||||
|
MAIL_ID = ID.fromLong(2),
|
||||||
|
CONVERSATION_ID = ID.fromLong(3),
|
||||||
|
FOLDER_ID = ID.fromLong(4);
|
||||||
|
}
|
119
java/core/src/core/mail/client/CyclicalFileCheck.java
Normal file
119
java/core/src/core/mail/client/CyclicalFileCheck.java
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import mail.client.model.Direction;
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callback.CallbackWithVariables;
|
||||||
|
import core.connector.FileInfo;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
|
||||||
|
public class CyclicalFileCheck extends CallbackDefault
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(CyclicalFileCheck.class);
|
||||||
|
public int numChecked = 0;
|
||||||
|
|
||||||
|
ArrivalsMonitorDefault monitor;
|
||||||
|
|
||||||
|
public CyclicalFileCheck(ArrivalsMonitorDefault monitor)
|
||||||
|
{
|
||||||
|
this.monitor = monitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSuccess (Object...arguments) throws Exception {
|
||||||
|
|
||||||
|
log.debug("check_next_file");
|
||||||
|
ArrivalsProcessor arrivalsProcessor = monitor.master.getArrivalsProcessor();
|
||||||
|
|
||||||
|
FileInfo file = null;
|
||||||
|
if (!monitor.files.isEmpty())
|
||||||
|
{
|
||||||
|
file = monitor.files.get(0);
|
||||||
|
monitor.files.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("found file",file);
|
||||||
|
|
||||||
|
numChecked++;
|
||||||
|
|
||||||
|
arrivalsProcessor.getMaster().getEventPropagator().signal(
|
||||||
|
Events.CheckStep, "" + numChecked + ":" + monitor.numFilesWillCheck + ":" + monitor.totalFilesFound
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (file == null || (++numChecked % monitor.NUM_FILES_BEFORE_CACHE == 0))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
monitor.master.getCacheManager().flush();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.debug("ignoring during flush, because don't want to interrupt arrivals", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
log.debug("going to process file",file.path);
|
||||||
|
|
||||||
|
monitor.checkMailLock.relock_().invoke();
|
||||||
|
|
||||||
|
monitor.store.get_()
|
||||||
|
.addCallback(new CheckFileResult((Direction)file.user, file.path, file.date, this))
|
||||||
|
.invoke(file.path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.debug("no file, to proceeding in callback chain");
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckFileResult extends CallbackDefault
|
||||||
|
{
|
||||||
|
CheckFileResult (Direction direction, String path, Date date, Callback callback)
|
||||||
|
{
|
||||||
|
super(direction, path, date);
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSuccess(Object...arguments) throws Exception {
|
||||||
|
Direction direction = V(0);
|
||||||
|
String path = V(1);
|
||||||
|
Date date = V(2);
|
||||||
|
|
||||||
|
log.debug("check_file_result", path);
|
||||||
|
ArrivalsProcessor arrivalsProcessor = monitor.master.getArrivalsProcessor();
|
||||||
|
|
||||||
|
byte[] data = (byte[])arguments[0];
|
||||||
|
|
||||||
|
log.debug ("handling ", path);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
arrivalsProcessor.processSuccess(
|
||||||
|
direction,
|
||||||
|
path,
|
||||||
|
date,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
arrivalsProcessor.processFailure(direction, path, date, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
62
java/core/src/core/mail/client/EventDispatcher.java
Normal file
62
java/core/src/core/mail/client/EventDispatcher.java
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.Pair;
|
||||||
|
|
||||||
|
|
||||||
|
public class EventDispatcher extends EventPropagator
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(EventDispatcher.class);
|
||||||
|
List<Pair<String,Object[]>> eventQueue = new LinkedList<Pair<String,Object[]>>();
|
||||||
|
// Collections.synchronizedList(new LinkedList<Pair<String,Object[]>>()
|
||||||
|
// );
|
||||||
|
|
||||||
|
Set<String> onced = new HashSet<String>();
|
||||||
|
|
||||||
|
public synchronized void prepareForDispatch ()
|
||||||
|
{
|
||||||
|
eventQueue.add(null);
|
||||||
|
onced.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispatchEvents ()
|
||||||
|
{
|
||||||
|
prepareForDispatch();
|
||||||
|
|
||||||
|
Pair<String, Object[]> next = null;
|
||||||
|
while ((next = eventQueue.get(0))!=null)
|
||||||
|
{
|
||||||
|
eventQueue.remove(0);
|
||||||
|
doSignal(next.first, next.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventQueue.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signalOnce (String event, Object...parameters)
|
||||||
|
{
|
||||||
|
log.debug("signalOnce", event);
|
||||||
|
if (onced.contains(event))
|
||||||
|
return;
|
||||||
|
|
||||||
|
onced.add(event);
|
||||||
|
|
||||||
|
signal(event, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signal (String event, Object... parameters)
|
||||||
|
{
|
||||||
|
log.debug("signal",event,parameters);
|
||||||
|
eventQueue.add(new Pair<String,Object[]>(event, parameters));
|
||||||
|
}
|
||||||
|
}
|
104
java/core/src/core/mail/client/EventPropagator.java
Normal file
104
java/core/src/core/mail/client/EventPropagator.java
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Pair;
|
||||||
|
|
||||||
|
public class EventPropagator
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(EventPropagator.class);
|
||||||
|
|
||||||
|
public static final String INVOKE = "__INVOKE__";
|
||||||
|
|
||||||
|
Map<String, List< Pair<Object,Callback> > > listeners = new HashMap<String, List<Pair<Object, Callback>> >();
|
||||||
|
|
||||||
|
public void add (String event, Object tag, Callback callback)
|
||||||
|
{
|
||||||
|
if (!listeners.containsKey(event))
|
||||||
|
listeners.put(event, new ArrayList<Pair<Object,Callback>>());
|
||||||
|
|
||||||
|
listeners.get(event).add(new Pair<Object,Callback>(tag, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove (String event, Object tag)
|
||||||
|
{
|
||||||
|
List<Pair<Object,Callback>> callbacks = listeners.get(event);
|
||||||
|
if (callbacks != null)
|
||||||
|
{
|
||||||
|
ArrayList<Pair<Object,Callback>> remove = new ArrayList<Pair<Object,Callback>>();
|
||||||
|
|
||||||
|
for (Pair<Object,Callback> callback : callbacks)
|
||||||
|
{
|
||||||
|
if (callback.first.equals(tag))
|
||||||
|
remove.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Pair<Object,Callback> callback : remove)
|
||||||
|
{
|
||||||
|
callbacks.remove(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void signalOnce (String event, Object...parameters)
|
||||||
|
{
|
||||||
|
signal(event, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void signal (String event, Object...parameters)
|
||||||
|
{
|
||||||
|
log.debug("signal",event, parameters);
|
||||||
|
|
||||||
|
doSignal(event, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback signal_ (String event, Object...parameters)
|
||||||
|
{
|
||||||
|
log.debug("signal_",event, parameters);
|
||||||
|
|
||||||
|
return new CallbackDefault(event, parameters) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
String event = V(0);
|
||||||
|
Object[] parameters = V(1);
|
||||||
|
signal(event, parameters);
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doSignal (String event, Object...parameters)
|
||||||
|
{
|
||||||
|
log.debug("doSignal",event, parameters);
|
||||||
|
|
||||||
|
if (event.equals(INVOKE))
|
||||||
|
{
|
||||||
|
Callback c = (Callback)parameters[0];
|
||||||
|
Object[] params = new Object[parameters.length-1];
|
||||||
|
System.arraycopy(parameters, 1, params, 0, parameters.length-1);
|
||||||
|
c.invoke(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Pair<Object,Callback>> callbacks = listeners.get(event);
|
||||||
|
if (callbacks != null)
|
||||||
|
{
|
||||||
|
for (Pair<Object,Callback> callback : callbacks)
|
||||||
|
{
|
||||||
|
callback.second.invoke(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
java/core/src/core/mail/client/EventType.java
Normal file
13
java/core/src/core/mail/client/EventType.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
public enum EventType {
|
||||||
|
INITIAL_CACHE_RETRIEVAL_FINISHED,
|
||||||
|
REFRESH,
|
||||||
|
REPLY,
|
||||||
|
ARRIVALS_COMPLETED,
|
||||||
|
MAIL_RETRIEVED
|
||||||
|
};
|
52
java/core/src/core/mail/client/Events.java
Normal file
52
java/core/src/core/mail/client/Events.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
public class Events
|
||||||
|
{
|
||||||
|
public static final String
|
||||||
|
Login = "onLogin",
|
||||||
|
Initialize_Start = "Initialize_Start",
|
||||||
|
Initialize_IndexedCacheAcquired = "Initialize_IndexedCacheAcquired",
|
||||||
|
Initialize_IndexedCacheLoadFailed = "Initialize_IndexedCacheLoadFailed",
|
||||||
|
Initialize_IndexedCacheLoadComplete = "Initialize_IndexedCacheLoadComplete",
|
||||||
|
Initialize_FolderLoadComplete = "Initialize_FolderLoadComplete",
|
||||||
|
FirstRunInitialization = "onFirstRunInitialization",
|
||||||
|
Initialized = "onInitialized",
|
||||||
|
NewMail = "onNewMail",
|
||||||
|
DeleteMail = "onDeleteMail",
|
||||||
|
LoadMail = "onLoadMail",
|
||||||
|
LogNull = "onLogNull",
|
||||||
|
NewFolder = "onNewFolder",
|
||||||
|
DeleteFolder = "onDeleteFolder",
|
||||||
|
LoadFolder = "onLoadFolder",
|
||||||
|
LoadFolderPart = "onLoadFolderPart",
|
||||||
|
NewConversation = "onNewConversation",
|
||||||
|
ChangedConversation = "onChangedConversation",
|
||||||
|
DeleteConversation = "onDeleteConversation",
|
||||||
|
LoadConversation = "onLoadConversation",
|
||||||
|
LoadConversationBlock = "onLoadConversationBlock",
|
||||||
|
SendSucceeded = "onSendSucceeded",
|
||||||
|
SendFailed = "onSendFailed",
|
||||||
|
CheckRequest = "onCheckRequest",
|
||||||
|
CheckBegin = "onCheckBegin",
|
||||||
|
CheckSuccess = "onCheckSuccess",
|
||||||
|
CheckFailure = "onCheckFailure",
|
||||||
|
CheckEnd = "onCheckEnd",
|
||||||
|
CheckStep = "onCheckStep",
|
||||||
|
UploadBegin = "onUploadBegin",
|
||||||
|
UploadEnd = "onUploadEnd",
|
||||||
|
DownloadBegin = "onDownloadBegin",
|
||||||
|
DownloadEnd = "onDownloadEnd",
|
||||||
|
OriginalLoaded = "onOriginalLoaded",
|
||||||
|
CacheDirty = "onCacheDirty",
|
||||||
|
CacheClean = "onCacheClean",
|
||||||
|
CacheFailure = "onCacheFailure",
|
||||||
|
CacheBegin = "onCacheBegin",
|
||||||
|
CacheEnd = "onCacheEnd",
|
||||||
|
CacheSuccess = "onCacheSuccess",
|
||||||
|
LoadAttachments = "onAttachmentsLoaded",
|
||||||
|
LoadAttachmentsFailed = "onAttachmentsLoadedFailed";
|
||||||
|
}
|
435
java/core/src/core/mail/client/Indexer.java
Normal file
435
java/core/src/core/mail/client/Indexer.java
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callbacks.CountDown;
|
||||||
|
import core.callbacks.Single;
|
||||||
|
import core.callbacks.Split;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import mail.client.cache.ID;
|
||||||
|
import mail.client.cache.Type;
|
||||||
|
import mail.client.model.Body;
|
||||||
|
import mail.client.model.Conversation;
|
||||||
|
import mail.client.model.Dictionary;
|
||||||
|
import mail.client.model.Direction;
|
||||||
|
import mail.client.model.Folder;
|
||||||
|
import mail.client.model.FolderDefinition;
|
||||||
|
import mail.client.model.FolderFilter;
|
||||||
|
import mail.client.model.FolderMaster;
|
||||||
|
import mail.client.model.FolderRepository;
|
||||||
|
import mail.client.model.FolderSet;
|
||||||
|
import mail.client.model.Header;
|
||||||
|
import mail.client.model.Mail;
|
||||||
|
import mail.client.model.TransportState;
|
||||||
|
|
||||||
|
public class Indexer extends Servent<Master>
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Indexer.class);
|
||||||
|
|
||||||
|
public static class KnownFolderIds {
|
||||||
|
static final
|
||||||
|
ID RootCache = ID.fromLong(1),
|
||||||
|
SystemFoldersId = ID.combine(RootCache, ID.fromLong(1)),
|
||||||
|
RepositoryId = ID.combine(RootCache, ID.fromLong(2)),
|
||||||
|
AllId = ID.combine(RootCache, ID.fromLong(3)),
|
||||||
|
InboxId = ID.combine(RootCache, ID.fromLong(4)),
|
||||||
|
SentId = ID.combine(RootCache, ID.fromLong(5)),
|
||||||
|
DraftsId = ID.combine(RootCache, ID.fromLong(6)),
|
||||||
|
SpamId = ID.combine(RootCache, ID.fromLong(7)),
|
||||||
|
TrashId = ID.combine(RootCache, ID.fromLong(8)),
|
||||||
|
|
||||||
|
UserFoldersId = ID.combine(RootCache, ID.fromLong(100));
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
FolderMaster systemFolders;
|
||||||
|
FolderSet userFolders;
|
||||||
|
FolderRepository repository;
|
||||||
|
FolderFilter spam;
|
||||||
|
|
||||||
|
public Indexer ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void firstRunInitialization ()
|
||||||
|
{
|
||||||
|
log.debug("firstRunInitialization");
|
||||||
|
|
||||||
|
CacheManager cache = master.getCacheManager();
|
||||||
|
|
||||||
|
systemFolders = (FolderMaster)
|
||||||
|
cache.linkFolder(
|
||||||
|
KnownFolderIds.SystemFoldersId,
|
||||||
|
Type.FolderMaster,
|
||||||
|
new FolderDefinition("system")
|
||||||
|
);
|
||||||
|
systemFolders.markCreate();
|
||||||
|
|
||||||
|
userFolders = (FolderSet)
|
||||||
|
cache.linkFolder(
|
||||||
|
KnownFolderIds.UserFoldersId,
|
||||||
|
Type.FolderFilterSet,
|
||||||
|
new FolderDefinition("user")
|
||||||
|
);
|
||||||
|
userFolders.markCreate();
|
||||||
|
|
||||||
|
repository = (FolderRepository)cache.linkFolder(
|
||||||
|
KnownFolderIds.RepositoryId,
|
||||||
|
Type.FolderRepository,
|
||||||
|
new FolderDefinition(Constants.REPOSITORY)
|
||||||
|
);
|
||||||
|
repository.markCreate();
|
||||||
|
systemFolders.addFolder(repository);
|
||||||
|
|
||||||
|
Folder fs;
|
||||||
|
fs = cache.linkFolder(
|
||||||
|
KnownFolderIds.AllId,
|
||||||
|
Type.FolderFilter,
|
||||||
|
new FolderDefinition(Constants.ALL)
|
||||||
|
.setState(
|
||||||
|
null,
|
||||||
|
TransportState.fromList(TransportState.TRASH,TransportState.SPAM)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
fs.markCreate();
|
||||||
|
systemFolders.addFolder(fs);
|
||||||
|
|
||||||
|
fs = cache.linkFolder(
|
||||||
|
KnownFolderIds.InboxId,
|
||||||
|
Type.FolderFilter,
|
||||||
|
new FolderDefinition(Constants.INBOX)
|
||||||
|
.setState(
|
||||||
|
TransportState.fromList(TransportState.RECEIVED),
|
||||||
|
TransportState.fromList(TransportState.TRASH,TransportState.SPAM)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
fs.markCreate();
|
||||||
|
systemFolders.addFolder(fs);
|
||||||
|
|
||||||
|
fs = cache.linkFolder(
|
||||||
|
KnownFolderIds.SentId,
|
||||||
|
Type.FolderFilter,
|
||||||
|
new FolderDefinition(Constants.SENT)
|
||||||
|
.setState(
|
||||||
|
TransportState.fromList(TransportState.SENT),
|
||||||
|
TransportState.fromList(TransportState.TRASH,TransportState.SPAM)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
fs.markCreate();
|
||||||
|
systemFolders.addFolder(fs);
|
||||||
|
|
||||||
|
fs = cache.linkFolder(
|
||||||
|
KnownFolderIds.DraftsId,
|
||||||
|
Type.FolderFilter,
|
||||||
|
new FolderDefinition(Constants.DRAFTS)
|
||||||
|
.setState(
|
||||||
|
TransportState.fromList(TransportState.DRAFT, TransportState.SENDING),
|
||||||
|
TransportState.fromList(TransportState.TRASH, TransportState.SPAM)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
fs.markCreate();
|
||||||
|
systemFolders.addFolder(fs);
|
||||||
|
|
||||||
|
spam = (FolderFilter)
|
||||||
|
cache.linkFolder(
|
||||||
|
KnownFolderIds.SpamId,
|
||||||
|
Type.FolderFilter,
|
||||||
|
new FolderDefinition(Constants.SPAM)
|
||||||
|
.setState(
|
||||||
|
TransportState.fromList(TransportState.SPAM),
|
||||||
|
TransportState.fromList(TransportState.TRASH)
|
||||||
|
)
|
||||||
|
.setBayesianDictionary(new Dictionary())
|
||||||
|
.setAutoBayesian(false)
|
||||||
|
);
|
||||||
|
|
||||||
|
spam.markCreate();
|
||||||
|
systemFolders.addFolder(spam);
|
||||||
|
|
||||||
|
fs = cache.linkFolder(
|
||||||
|
KnownFolderIds.TrashId,
|
||||||
|
Type.FolderFilter,
|
||||||
|
new FolderDefinition(Constants.TRASH)
|
||||||
|
.setState(
|
||||||
|
TransportState.fromList(TransportState.TRASH),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
fs.markCreate();
|
||||||
|
systemFolders.addFolder(fs);
|
||||||
|
|
||||||
|
master.getEventPropagator().signal(Events.Initialize_FolderLoadComplete, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start ()
|
||||||
|
{
|
||||||
|
log.debug("indexer starting..");
|
||||||
|
|
||||||
|
CacheManager cache = master.getCacheManager();
|
||||||
|
|
||||||
|
systemFolders = (FolderMaster)cache.getFolder(
|
||||||
|
Type.FolderMaster,
|
||||||
|
KnownFolderIds.SystemFoldersId
|
||||||
|
);
|
||||||
|
|
||||||
|
systemFolders.apply(
|
||||||
|
new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
onMainFolderSucceeded((Folder)arguments[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
onMainFolderFailed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMainFolderSucceeded (Folder f)
|
||||||
|
{
|
||||||
|
log.debug("onMainFolderSucceeded");
|
||||||
|
systemFolders = (FolderMaster)f;
|
||||||
|
CacheManager cache = master.getCacheManager();
|
||||||
|
repository = (FolderRepository)cache.getFolder(Type.FolderRepository, KnownFolderIds.RepositoryId);
|
||||||
|
spam = (FolderFilter)cache.getFolder(Type.FolderFilter, KnownFolderIds.SpamId);
|
||||||
|
userFolders = (FolderSet)cache.getFolder(Type.FolderFilterSet, KnownFolderIds.UserFoldersId);
|
||||||
|
|
||||||
|
// cause subfolders to instantiate right away
|
||||||
|
List<Folder> folders = systemFolders.getFolders();
|
||||||
|
Callback countDown =
|
||||||
|
new CountDown(
|
||||||
|
folders.size(),
|
||||||
|
getMaster().getEventPropagator().signal_(Events.Initialize_FolderLoadComplete)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (Folder folder : folders)
|
||||||
|
folder.apply(new Split(countDown));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMainFolderFailed (Exception e)
|
||||||
|
{
|
||||||
|
log.debug("onMainFolderFailed");
|
||||||
|
log.exception(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized Conversation addMail (Mail mail) throws Exception
|
||||||
|
{
|
||||||
|
Header header = mail.getHeader();
|
||||||
|
boolean isNewConversation;
|
||||||
|
Conversation conversation = repository.getMatchingConversation(header);
|
||||||
|
if (conversation != null)
|
||||||
|
{
|
||||||
|
log.debug("founding matching conversation");
|
||||||
|
conversation.addItem(mail);
|
||||||
|
isNewConversation = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.debug("new conversation");
|
||||||
|
conversation = master.getCacheManager().newConversation(mail);
|
||||||
|
isNewConversation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// before we do anything we do the spam detection
|
||||||
|
conversation.getHeader().getTransportState().mark(
|
||||||
|
TransportState.SPAM, spam.getFolderDefinition().bayesianMatches(conversation)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isNewConversation)
|
||||||
|
addConversation(conversation);
|
||||||
|
else
|
||||||
|
conversationChanged (conversation);
|
||||||
|
|
||||||
|
|
||||||
|
// if the mail is new, no external key yet
|
||||||
|
if (mail.getHeader().getExternalKey()!=null)
|
||||||
|
{
|
||||||
|
log.debug("adding externalKey", mail.getHeader().getExternalKey());
|
||||||
|
systemFolders.addExternalKey(mail.getHeader().getExternalKey(), mail.getHeader().getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mail.getHeader().getUIDL() != null)
|
||||||
|
{
|
||||||
|
log.debug("adding UIDL", mail.getHeader().getUIDL());
|
||||||
|
systemFolders.addUIDL(mail.getHeader().getUIDL(), mail.getHeader().getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addFailure (Mail mail) throws Exception
|
||||||
|
{
|
||||||
|
addMail (mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addFailure (String externalKey, Date date)
|
||||||
|
{
|
||||||
|
systemFolders.addExternalKey(externalKey, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFailure (Direction direction, String path, Date date, Exception e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
Header header = new Header();
|
||||||
|
header.setSubject("Mail failed parsing. Look at original for file.");
|
||||||
|
header.setExternalKey(path);
|
||||||
|
header.setDate(date);
|
||||||
|
if (direction == Direction.IN)
|
||||||
|
header.setTransportState(TransportState.fromList(TransportState.RECEIVED));
|
||||||
|
else
|
||||||
|
header.setTransportState(TransportState.fromList(TransportState.SENT));
|
||||||
|
|
||||||
|
Body body = new Body();
|
||||||
|
body.setText("Failed to load: " + e);
|
||||||
|
|
||||||
|
Mail mail = master.getCacheManager().newMail(header, body, null);
|
||||||
|
addFailure(mail);
|
||||||
|
}
|
||||||
|
catch (Exception em)
|
||||||
|
{
|
||||||
|
em.printStackTrace();
|
||||||
|
addFailure(path, date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDuplicate (String externalKey, Date date)
|
||||||
|
{
|
||||||
|
systemFolders.addExternalKey(externalKey, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsExternalKey (String externalKey)
|
||||||
|
{
|
||||||
|
return systemFolders.containsExternalKey(externalKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsUIDL(String uidl)
|
||||||
|
{
|
||||||
|
return systemFolders.containsUIDL(uidl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addConversation (Conversation conversation)
|
||||||
|
{
|
||||||
|
for (Folder e : systemFolders.getFolders())
|
||||||
|
e.conversationAdded(conversation);
|
||||||
|
|
||||||
|
for (Folder e : userFolders.getFolders())
|
||||||
|
e.conversationAdded(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeConversation (Conversation conversation)
|
||||||
|
{
|
||||||
|
for (Folder e : systemFolders.getFolders())
|
||||||
|
e.conversationDeleted(conversation);
|
||||||
|
|
||||||
|
for (Folder e : userFolders.getFolders())
|
||||||
|
e.conversationDeleted(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void conversationChanged (Conversation conversation)
|
||||||
|
{
|
||||||
|
if (conversation != null)
|
||||||
|
{
|
||||||
|
for (Folder f : systemFolders.getFolders())
|
||||||
|
f.conversationChanged(conversation);
|
||||||
|
|
||||||
|
for (Folder e : userFolders.getFolders())
|
||||||
|
e.conversationChanged(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
master.getEventPropagator().signalOnce(Events.ChangedConversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replyMail (Conversation conversation, Mail mail)
|
||||||
|
{
|
||||||
|
systemFolders.addUIDL(mail.getHeader().getUIDL(), mail.getHeader().getDate());
|
||||||
|
|
||||||
|
conversationChanged(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Conversation newMail (Mail mail) throws Exception
|
||||||
|
{
|
||||||
|
return addMail(mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Folder getSystemFolder (String folderName)
|
||||||
|
{
|
||||||
|
for (Folder e : systemFolders.getFolders())
|
||||||
|
if (e.isLoaded())
|
||||||
|
if (e.getFolderDefinition().getName().equals(folderName))
|
||||||
|
return e;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderFilter getUserFolder (String folderName)
|
||||||
|
{
|
||||||
|
for (Folder e: userFolders.getFolders())
|
||||||
|
if (e.isLoaded())
|
||||||
|
if (e.getFolderDefinition().getName().equals(folderName))
|
||||||
|
return (FolderFilter)e;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Folder> getSystemFolders ()
|
||||||
|
{
|
||||||
|
return systemFolders.getFolders();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Folder> getUserFolders ()
|
||||||
|
{
|
||||||
|
return userFolders.getFolders();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Folder getRepository ()
|
||||||
|
{
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void newUserFolder(String name)
|
||||||
|
{
|
||||||
|
userFolders.addFolder(
|
||||||
|
getMaster().getCacheManager().newFolder(
|
||||||
|
Type.FolderFilter,
|
||||||
|
new FolderDefinition(name)
|
||||||
|
.setBayesianDictionary(new Dictionary())
|
||||||
|
.setAutoBayesian(false)
|
||||||
|
.setState(null, TransportState.fromList(TransportState.SPAM, TransportState.TRASH))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteUserFolder(Folder userFolder)
|
||||||
|
{
|
||||||
|
userFolders.removeFolder(userFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addToUserFolder(Folder userFolder, Conversation conversation)
|
||||||
|
{
|
||||||
|
FolderFilter folder = (FolderFilter)userFolder;
|
||||||
|
folder.manuallyAdd (conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeFromUserFolder(Folder userFolder, Conversation conversation)
|
||||||
|
{
|
||||||
|
FolderFilter folder = (FolderFilter)userFolder;
|
||||||
|
folder.manuallyRemove (conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderSet getInbox()
|
||||||
|
{
|
||||||
|
return (FolderSet)getMaster().getCacheManager().getFolder(Type.FolderFilter, KnownFolderIds.InboxId);
|
||||||
|
}
|
||||||
|
}
|
118
java/core/src/core/mail/client/Initializer.java
Normal file
118
java/core/src/core/mail/client/Initializer.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackEmpty;
|
||||||
|
import core.constants.ConstantsStorage;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
|
||||||
|
public class Initializer extends Servent<Master>
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Initializer.class);
|
||||||
|
int numRemainingIndexCaches = 3;
|
||||||
|
|
||||||
|
public Initializer ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start ()
|
||||||
|
{
|
||||||
|
EventDispatcher e = master.getEventPropagator();
|
||||||
|
|
||||||
|
e.add(Events.Initialize_Start, this, new Callback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(Object... arguments)
|
||||||
|
{
|
||||||
|
onInitializeStart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
e.add(Events.Initialize_IndexedCacheAcquired, this, new Callback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(Object... arguments)
|
||||||
|
{
|
||||||
|
onIndexedCacheAcquired();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
e.add(Events.Initialize_IndexedCacheLoadFailed, this, new Callback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(Object... arguments)
|
||||||
|
{
|
||||||
|
onIndexedCacheLoadFailed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
e.add(Events.Initialize_IndexedCacheLoadComplete, this, new Callback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(Object... arguments)
|
||||||
|
{
|
||||||
|
onIndexedCacheLoadComplete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
e.add(Events.Initialize_FolderLoadComplete, this, new Callback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(Object... arguments)
|
||||||
|
{
|
||||||
|
onFolderLoadComplete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
e.signal(Events.Initialize_Start, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onInitializeStart()
|
||||||
|
{
|
||||||
|
log.debug("onInitializeStart");
|
||||||
|
|
||||||
|
master.getStore().getConnector().ensureDirectories_(
|
||||||
|
new String[] {
|
||||||
|
ConstantsStorage.CACHE,
|
||||||
|
ConstantsStorage.NEW_IN_JSON,
|
||||||
|
ConstantsStorage.NEW_OUT_JSON
|
||||||
|
}
|
||||||
|
).invoke();
|
||||||
|
|
||||||
|
master.getCacheManager().start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onIndexedCacheLoadFailed ()
|
||||||
|
{
|
||||||
|
log.debug("onIndexedCacheLoadFailed");
|
||||||
|
|
||||||
|
master.getCacheManager().firstRunInitialization();
|
||||||
|
master.getIndexer().firstRunInitialization();
|
||||||
|
|
||||||
|
master.getEventPropagator().signal(Events.FirstRunInitialization, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onIndexedCacheAcquired ()
|
||||||
|
{
|
||||||
|
log.debug("onIndexedCacheLoadAcquire");
|
||||||
|
master.getCacheManager().deserializeIndexCaches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onIndexedCacheLoadComplete ()
|
||||||
|
{
|
||||||
|
log.debug("onIndexedCacheLoadComplete");
|
||||||
|
master.getIndexer().start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFolderLoadComplete()
|
||||||
|
{
|
||||||
|
log.debug("onFolderLoadComplete");
|
||||||
|
|
||||||
|
master.getEventPropagator().signal(Events.Initialized, (Object[])null);
|
||||||
|
}
|
||||||
|
}
|
154
java/core/src/core/mail/client/Mailer.java
Normal file
154
java/core/src/core/mail/client/Mailer.java
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import core.util.SecureRandom;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import mail.client.model.Conversation;
|
||||||
|
import mail.client.model.Identity;
|
||||||
|
import mail.client.model.Mail;
|
||||||
|
import mail.client.model.Settings;
|
||||||
|
import mail.client.model.TransportState;
|
||||||
|
import mail.client.model.UnregisteredIdentity;
|
||||||
|
import core.constants.ConstantsClient;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import core.crypt.CryptorRSA;
|
||||||
|
import core.crypt.CryptorRSAAES;
|
||||||
|
import core.crypt.CryptorRSAFactory;
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callbacks.JSONSerialize;
|
||||||
|
import core.util.Base64;
|
||||||
|
import core.util.FastRandom;
|
||||||
|
import core.util.HttpDelegate;
|
||||||
|
import core.util.InternalResource;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Pair;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
public class Mailer extends Servent<Master>
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Mailer.class);
|
||||||
|
HttpDelegate httpDelegate;
|
||||||
|
FastRandom fastRandom = new FastRandom();
|
||||||
|
|
||||||
|
public Mailer (HttpDelegate httpDelegate)
|
||||||
|
{
|
||||||
|
this.httpDelegate = httpDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMail (String password, Conversation conversation, Mail mail)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
log.debug("Mailer.sendMail");
|
||||||
|
mail.getHeader().unmarkState(TransportState.DRAFT);
|
||||||
|
mail.getHeader().markState(TransportState.SENDING);
|
||||||
|
mail.getHeader().getRecipients().registerRecipients(master.getAddressBook());
|
||||||
|
|
||||||
|
conversation.itemChanged(mail);
|
||||||
|
master.getIndexer().conversationChanged(conversation);
|
||||||
|
|
||||||
|
doSend(password, conversation, mail);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
onSendFailed(conversation, mail, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doSend(String password, Conversation conversation, Mail mail) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("Mailer.doSend");
|
||||||
|
Map<String,String> sendMap = new HashMap<String,String>();
|
||||||
|
|
||||||
|
Identity identity = getMaster().getIdentity();
|
||||||
|
sendMap.put("user", identity.getEmail());
|
||||||
|
sendMap.put("password", password);
|
||||||
|
|
||||||
|
sendMap.put("from", identity.getFull());
|
||||||
|
sendMap.put("to", Strings.concat(mail.getHeader().getRecipients().getTo(), ","));
|
||||||
|
sendMap.put("cc", Strings.concat(mail.getHeader().getRecipients().getCc(), ","));
|
||||||
|
sendMap.put("bcc", Strings.concat(mail.getHeader().getRecipients().getBcc(), ","));
|
||||||
|
sendMap.put("replyTo", Strings.concat(mail.getHeader().getRecipients().getReplyTo(), ","));
|
||||||
|
sendMap.put("publicKey", Base64.encode(((CryptorRSA)getMaster().getCryptor()).getPublicKey()));
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (mail.isPresendEncryptable())
|
||||||
|
{
|
||||||
|
Pair<String,String> presendEncrypted = mail.presendEncrypt();
|
||||||
|
|
||||||
|
sendMap.put("cryptors", presendEncrypted.first);
|
||||||
|
sendMap.put("block", presendEncrypted.second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*/
|
||||||
|
sendMap.put("subject", mail.getHeader().getSubject());
|
||||||
|
sendMap.put("text", mail.getBody().getText());
|
||||||
|
sendMap.put("html", mail.getBody().getHTML());
|
||||||
|
/*
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
sendMap.put("messageId", mail.getHeader().getUIDL());
|
||||||
|
log.debug("sendMap ", sendMap);
|
||||||
|
|
||||||
|
Cryptor cryptor = new CryptorRSAAES(CryptorRSAFactory.fromResources(null, InternalResource.getResourceAsStream(getClass(), "send-truststore.jks")));
|
||||||
|
|
||||||
|
new JSONSerialize()
|
||||||
|
.addCallback(cryptor.encrypt_())
|
||||||
|
.addCallback(Base64.encodeBytes_())
|
||||||
|
.addCallback(httpDelegate.execute_(
|
||||||
|
HttpDelegate.PUT, ConstantsClient.WEB_SERVER_TOMCAT + "Send?random="+ fastRandom.nextLong(), null, false, false
|
||||||
|
))
|
||||||
|
.addCallback(onFinish_(conversation, mail))
|
||||||
|
.invoke(sendMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback onFinish_ (Conversation conversation, Mail mail)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new CallbackDefault(conversation, mail) {
|
||||||
|
public void onSuccess(Object...arguments)
|
||||||
|
{
|
||||||
|
onSendSucceeded((Conversation)V(0), (Mail)V(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFailure(Exception e)
|
||||||
|
{
|
||||||
|
onSendFailed((Conversation)V(0), (Mail)V(1), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onSendSucceeded (Conversation conversation, Mail mail)
|
||||||
|
{
|
||||||
|
log.debug("Mailer.onSendSucceeded");
|
||||||
|
|
||||||
|
mail.getHeader().unmarkState(TransportState.SENDING);
|
||||||
|
mail.getHeader().markState(TransportState.SENT);
|
||||||
|
|
||||||
|
conversation.itemChanged(mail);
|
||||||
|
master.getIndexer().conversationChanged(conversation);
|
||||||
|
master.getEventPropagator().signal(Events.SendSucceeded, mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onSendFailed (Conversation conversation, Mail mail, Exception e)
|
||||||
|
{
|
||||||
|
log.debug("Mailer.onSendFailed");
|
||||||
|
log.exception(e);
|
||||||
|
|
||||||
|
mail.getHeader().unmarkState(TransportState.SENDING);
|
||||||
|
mail.getHeader().markState(TransportState.DRAFT);
|
||||||
|
|
||||||
|
conversation.itemChanged(mail);
|
||||||
|
master.getIndexer().conversationChanged(conversation);
|
||||||
|
master.getEventPropagator().signal(Events.SendFailed, mail);
|
||||||
|
}
|
||||||
|
}
|
151
java/core/src/core/mail/client/Master.java
Normal file
151
java/core/src/core/mail/client/Master.java
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import mail.client.cache.JSON;
|
||||||
|
import mail.client.model.AddressBook;
|
||||||
|
import mail.client.model.Identity;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import core.util.Environment;
|
||||||
|
|
||||||
|
public class Master
|
||||||
|
{
|
||||||
|
protected Initializer initializer;
|
||||||
|
protected Store store;
|
||||||
|
protected Identity identity;
|
||||||
|
protected Environment environment;
|
||||||
|
protected AddressBook addressBook;
|
||||||
|
protected Indexer indexer;
|
||||||
|
protected ArrivalsProcessor arrivalsProcessor;
|
||||||
|
protected ArrivalsMonitor arrivalsMonitor;
|
||||||
|
protected EventDispatcher eventDispatcher;
|
||||||
|
protected Actions actions;
|
||||||
|
protected Mailer mailer;
|
||||||
|
protected Cryptor cryptor;
|
||||||
|
protected CacheManager cacheManager;
|
||||||
|
protected JSON json;
|
||||||
|
|
||||||
|
public Master (
|
||||||
|
Store store,
|
||||||
|
Identity identity,
|
||||||
|
Environment environment,
|
||||||
|
Indexer indexer,
|
||||||
|
AddressBook addressBook,
|
||||||
|
ArrivalsProcessor arrivalsProcessor,
|
||||||
|
ArrivalsMonitor arrivalsMonitor,
|
||||||
|
EventDispatcher eventDispatcher,
|
||||||
|
Actions actions,
|
||||||
|
Mailer mailer,
|
||||||
|
Cryptor cryptor,
|
||||||
|
CacheManager cacheManager,
|
||||||
|
JSON json
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.identity = identity;
|
||||||
|
this.environment = environment;
|
||||||
|
this.addressBook = addressBook;
|
||||||
|
|
||||||
|
this.store = store;
|
||||||
|
store.setMaster(this);
|
||||||
|
|
||||||
|
this.initializer = new Initializer();
|
||||||
|
initializer.setMaster(this);
|
||||||
|
|
||||||
|
this.indexer = indexer;
|
||||||
|
indexer.setMaster(this);
|
||||||
|
|
||||||
|
this.arrivalsProcessor = arrivalsProcessor;
|
||||||
|
arrivalsProcessor.setMaster(this);
|
||||||
|
|
||||||
|
this.arrivalsMonitor = arrivalsMonitor;
|
||||||
|
arrivalsMonitor.setMaster(this);
|
||||||
|
|
||||||
|
this.eventDispatcher = eventDispatcher;
|
||||||
|
|
||||||
|
this.actions = actions;
|
||||||
|
actions.setMaster(this);
|
||||||
|
|
||||||
|
this.mailer = mailer;
|
||||||
|
mailer.setMaster(this);
|
||||||
|
|
||||||
|
this.cryptor = cryptor;
|
||||||
|
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
cacheManager.setMaster(this);
|
||||||
|
|
||||||
|
this.json = json;
|
||||||
|
json.setMaster(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws Exception
|
||||||
|
{
|
||||||
|
initializer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Store getStore ()
|
||||||
|
{
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Identity getIdentity ()
|
||||||
|
{
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment getEnvironment ()
|
||||||
|
{
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressBook getAddressBook ()
|
||||||
|
{
|
||||||
|
return addressBook;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Indexer getIndexer()
|
||||||
|
{
|
||||||
|
return indexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CacheManager getCacheManager()
|
||||||
|
{
|
||||||
|
return cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrivalsProcessor getArrivalsProcessor()
|
||||||
|
{
|
||||||
|
return arrivalsProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrivalsMonitor getArrivalsMonitor()
|
||||||
|
{
|
||||||
|
return arrivalsMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventDispatcher getEventPropagator ()
|
||||||
|
{
|
||||||
|
return eventDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Actions getActions ()
|
||||||
|
{
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mailer getMailer ()
|
||||||
|
{
|
||||||
|
return mailer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cryptor getCryptor ()
|
||||||
|
{
|
||||||
|
return cryptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSON getJSON ()
|
||||||
|
{
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
44
java/core/src/core/mail/client/MasterCacheSerializer.java
Normal file
44
java/core/src/core/mail/client/MasterCacheSerializer.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import mail.client.cache.IndexedCacheSerializer;
|
||||||
|
import mail.client.cache.Item;
|
||||||
|
import mail.client.cache.ItemSerializer;
|
||||||
|
import mail.client.cache.JSON;
|
||||||
|
import mail.client.model.ModelSerializer;
|
||||||
|
import mail.client.model.Settings;
|
||||||
|
|
||||||
|
public class MasterCacheSerializer implements ItemSerializer
|
||||||
|
{
|
||||||
|
IndexedCacheSerializer indexedCacheSerializer;
|
||||||
|
ModelSerializer settingsSerializer;
|
||||||
|
|
||||||
|
public MasterCacheSerializer (JSON json)
|
||||||
|
{
|
||||||
|
indexedCacheSerializer = new IndexedCacheSerializer();
|
||||||
|
settingsSerializer = new ModelSerializer(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback serialize_(Item item)
|
||||||
|
{
|
||||||
|
if (item instanceof Settings)
|
||||||
|
return settingsSerializer.serialize_(item);
|
||||||
|
|
||||||
|
return indexedCacheSerializer.serialize_(item);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Callback deserialize_(Item item)
|
||||||
|
{
|
||||||
|
if (item instanceof Settings)
|
||||||
|
return settingsSerializer.deserialize_(item);
|
||||||
|
|
||||||
|
return indexedCacheSerializer.deserialize_(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
30
java/core/src/core/mail/client/Servent.java
Normal file
30
java/core/src/core/mail/client/Servent.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
public class Servent<T>
|
||||||
|
{
|
||||||
|
protected T master;
|
||||||
|
|
||||||
|
public Servent (T master)
|
||||||
|
{
|
||||||
|
this.master = master;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Servent ()
|
||||||
|
{
|
||||||
|
this.master = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaster (T master)
|
||||||
|
{
|
||||||
|
this.master = master;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getMaster ()
|
||||||
|
{
|
||||||
|
return master;
|
||||||
|
}
|
||||||
|
}
|
86
java/core/src/core/mail/client/Store.java
Normal file
86
java/core/src/core/mail/client/Store.java
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callback.CallbackWithVariables;
|
||||||
|
import core.connector.async.AsyncStoreConnector;
|
||||||
|
import core.connector.async.AsyncStoreConnectorEncrypted;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import mail.client.model.Mail;
|
||||||
|
import mail.client.model.Original;
|
||||||
|
|
||||||
|
public class Store extends Servent<Master>
|
||||||
|
{
|
||||||
|
AsyncStoreConnector connector;
|
||||||
|
|
||||||
|
public Store(Cryptor cryptor, AsyncStoreConnector connector)
|
||||||
|
{
|
||||||
|
this.connector = new AsyncStoreConnectorEncrypted(cryptor, connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Original getOriginal(String path)
|
||||||
|
{
|
||||||
|
Original original = new Original(path);
|
||||||
|
connector.get_().addCallback(
|
||||||
|
new CallbackWithVariables(original){
|
||||||
|
@Override
|
||||||
|
public void invoke(Object... arguments)
|
||||||
|
{
|
||||||
|
Original original = V(0);
|
||||||
|
if (arguments[0] instanceof Exception)
|
||||||
|
original.setException((Exception)arguments[0]);
|
||||||
|
else
|
||||||
|
original.setData((byte[])arguments[0]);
|
||||||
|
|
||||||
|
master.getEventPropagator().signal(Events.OriginalLoaded, original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).invoke(path);
|
||||||
|
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncStoreConnector getConnector()
|
||||||
|
{
|
||||||
|
return connector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadAttachments(Mail mail)
|
||||||
|
{
|
||||||
|
connector.get_().addCallback(
|
||||||
|
new CallbackDefault(mail)
|
||||||
|
{
|
||||||
|
public void onSuccess(Object... arguments) throws Exception
|
||||||
|
{
|
||||||
|
Mail mail = V(0);
|
||||||
|
mail.getAttachments().loadFrom((byte[])arguments[0]);
|
||||||
|
master.getEventPropagator().signal(Events.LoadAttachments, mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFailure(Exception e)
|
||||||
|
{
|
||||||
|
Mail mail = V(0);
|
||||||
|
master.getEventPropagator().signal(Events.LoadAttachmentsFailed, mail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).invoke(mail.getHeader().getExternalKey());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteMail (Mail mail)
|
||||||
|
{
|
||||||
|
CallbackChain chain = new CallbackChain();
|
||||||
|
|
||||||
|
if (mail.getHeader().getExternalKey() != null)
|
||||||
|
chain.addCallback(connector.delete_(mail.getHeader().getExternalKey()));
|
||||||
|
|
||||||
|
if (mail.getHeader().getOriginalKey() != null)
|
||||||
|
chain.addCallback(connector.delete_(mail.getHeader().getOriginalKey()));
|
||||||
|
|
||||||
|
chain.invoke();
|
||||||
|
}
|
||||||
|
}
|
158
java/core/src/core/mail/client/TrackingConnector.java
Normal file
158
java/core/src/core/mail/client/TrackingConnector.java
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callback.CallbackWithVariables;
|
||||||
|
import core.connector.async.AsyncStoreConnector;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
|
||||||
|
public class TrackingConnector extends Servent<Master> implements AsyncStoreConnector
|
||||||
|
{
|
||||||
|
AsyncStoreConnector connector;
|
||||||
|
LogNull log = new LogNull(TrackingConnector.class);
|
||||||
|
|
||||||
|
public volatile int uploading = 0;
|
||||||
|
public volatile int downloading = 0;
|
||||||
|
|
||||||
|
public TrackingConnector (AsyncStoreConnector connector)
|
||||||
|
{
|
||||||
|
this.connector = connector;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized void onUploadBegin()
|
||||||
|
{
|
||||||
|
uploading++;
|
||||||
|
log.debug("onUploadBegin", uploading);
|
||||||
|
|
||||||
|
if (uploading == 1)
|
||||||
|
getMaster().getEventPropagator().signal(Events.UploadBegin, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Callback onUploadBegin_()
|
||||||
|
{
|
||||||
|
return new Callback() {
|
||||||
|
public void invoke (Object... arguments) {
|
||||||
|
onUploadBegin();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized void onUploadEnd()
|
||||||
|
{
|
||||||
|
log.debug("onUploadEnd", uploading);
|
||||||
|
uploading--;
|
||||||
|
|
||||||
|
if (uploading == 0)
|
||||||
|
getMaster().getEventPropagator().signal(Events.UploadEnd, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Callback onUploadEnd_()
|
||||||
|
{
|
||||||
|
return new Callback() {
|
||||||
|
public void invoke (Object... arguments) {
|
||||||
|
onUploadEnd();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized void onDownloadBegin()
|
||||||
|
{
|
||||||
|
downloading++;
|
||||||
|
log.debug("onDownloadBegin", downloading);
|
||||||
|
|
||||||
|
if (downloading == 1)
|
||||||
|
getMaster().getEventPropagator().signal(Events.DownloadBegin, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Callback onDownloadBegin_()
|
||||||
|
{
|
||||||
|
return new Callback() {
|
||||||
|
public void invoke (Object... arguments) {
|
||||||
|
onDownloadBegin();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized void onDownloadEnd ()
|
||||||
|
{
|
||||||
|
log.debug("onDownloadEnd", downloading);
|
||||||
|
downloading--;
|
||||||
|
|
||||||
|
if (downloading == 0)
|
||||||
|
getMaster().getEventPropagator().signal(Events.DownloadEnd, (Object[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Callback onDownloadEnd_()
|
||||||
|
{
|
||||||
|
return new Callback() {
|
||||||
|
public void invoke (Object... arguments) {
|
||||||
|
onDownloadEnd();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback list_(String path)
|
||||||
|
{
|
||||||
|
return onDownloadBegin_().addCallback(connector.list_(path)).addCallback(onDownloadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback createDirectory_(String path)
|
||||||
|
{
|
||||||
|
return onUploadBegin_().addCallback(connector.createDirectory_(path)).addCallback(onUploadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback ensureDirectories_(String[] directories)
|
||||||
|
{
|
||||||
|
return onUploadBegin_().addCallback(connector.ensureDirectories_(directories)).addCallback(onUploadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback get_()
|
||||||
|
{
|
||||||
|
return onDownloadBegin_().addCallback(connector.get_()).addCallback(onDownloadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback get_(String path)
|
||||||
|
{
|
||||||
|
return onDownloadBegin_().addCallback(connector.get_(path)).addCallback(onDownloadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback put_(String path, byte[] bytes)
|
||||||
|
{
|
||||||
|
return onUploadBegin_().addCallback(connector.put_(path, bytes)).addCallback(onUploadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback put_(String path)
|
||||||
|
{
|
||||||
|
return onUploadBegin_().addCallback(connector.put_(path)).addCallback(onUploadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback move_(String from, String to)
|
||||||
|
{
|
||||||
|
return onUploadBegin_().addCallback(connector.move_(from, to)).addCallback(onUploadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback delete_(String path)
|
||||||
|
{
|
||||||
|
return onUploadBegin_().addCallback(connector.delete_(path)).addCallback(onUploadEnd_()).setSlowFail();
|
||||||
|
}
|
||||||
|
}
|
288
java/core/src/core/mail/client/cache/Cache.java
vendored
Normal file
288
java/core/src/core/mail/client/cache/Cache.java
vendored
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
|
||||||
|
public class Cache extends ItemCollection
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Cache.class);
|
||||||
|
static LogNull logState = new LogNull("");
|
||||||
|
|
||||||
|
Store store;
|
||||||
|
Version storeVersion;
|
||||||
|
|
||||||
|
ItemFactory factory;
|
||||||
|
ItemSerializer serializer;
|
||||||
|
|
||||||
|
Map<ID, Item> items = new HashMap<ID, Item>();
|
||||||
|
|
||||||
|
public Cache(ItemFactory factory, ItemSerializer serializer, Store store)
|
||||||
|
{
|
||||||
|
this.factory = factory;
|
||||||
|
this.serializer = serializer;
|
||||||
|
this.store = store;
|
||||||
|
|
||||||
|
if (store != null)
|
||||||
|
{
|
||||||
|
storeVersion = store.getLocalVersion();
|
||||||
|
store.getLoadCallbacks().addCallback(update_());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCreate ()
|
||||||
|
{
|
||||||
|
store.markCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update ()
|
||||||
|
{
|
||||||
|
log.debug(this, "update");
|
||||||
|
if (store!=null && !store.getLocalVersion().equals(storeVersion))
|
||||||
|
for (Item item : items.values())
|
||||||
|
populate(item);
|
||||||
|
|
||||||
|
markLoad(store.getLocalVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback update_()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
update();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean has (ID id)
|
||||||
|
{
|
||||||
|
log.trace(this, "has", id);
|
||||||
|
return items.containsKey(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item getItem (Type type, ID id)
|
||||||
|
{
|
||||||
|
log.trace(this, "getItem", type, id);
|
||||||
|
return getAndAcquire(type, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item getAndAcquire(Type type, ID id)
|
||||||
|
{
|
||||||
|
log.trace(this, "getAndAcquire", type, id);
|
||||||
|
Item item = get(type, id);
|
||||||
|
acquire(item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item get (Type type, ID id)
|
||||||
|
{
|
||||||
|
log.trace(this, "get", type, id);
|
||||||
|
Item item = items.get(id);
|
||||||
|
if (item != null)
|
||||||
|
return item;
|
||||||
|
|
||||||
|
item = factory.instantiate(type);
|
||||||
|
item.setId(id);
|
||||||
|
item.onExisting();
|
||||||
|
|
||||||
|
link(item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void acquire (Item item)
|
||||||
|
{
|
||||||
|
log.trace(this, "acquire", item);
|
||||||
|
if (!item.isLoaded())
|
||||||
|
populate(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populate (Item item)
|
||||||
|
{
|
||||||
|
log.debug(this, "possibly populate", item);
|
||||||
|
if (store != null)
|
||||||
|
{
|
||||||
|
log.debug(this, "store is not null");
|
||||||
|
if (store.has(item.getId()))
|
||||||
|
{
|
||||||
|
log.debug(this, "store has", item);
|
||||||
|
Version version = store.version(item.getId());
|
||||||
|
if (!version.equals(item.getCacheVersion()))
|
||||||
|
{
|
||||||
|
log.debug(this, "version is different", item);
|
||||||
|
if (!item.isDirty())
|
||||||
|
{
|
||||||
|
log.debug(this, "item is not dirty", item);
|
||||||
|
if (serializer != null)
|
||||||
|
{
|
||||||
|
log.debug(this, "serializer is not null", item);
|
||||||
|
if (!storeVersion.equals(Version.DELETED))
|
||||||
|
{
|
||||||
|
log.debug(this, "version is not deleted", item);
|
||||||
|
log.debug(this, "populating", item);
|
||||||
|
|
||||||
|
store.get_(item.getId())
|
||||||
|
.addCallback(serializer.deserialize_(item))
|
||||||
|
.addCallback(item.markLoad_(version))
|
||||||
|
.invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.debug(this, "item is dirty, marking no load", item);
|
||||||
|
item.markNoLoad(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put (Item item)
|
||||||
|
{
|
||||||
|
log.debug(this, "put", item);
|
||||||
|
|
||||||
|
link (item);
|
||||||
|
item.markCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlink (Item item)
|
||||||
|
{
|
||||||
|
log.debug(this, "unlink", item);
|
||||||
|
|
||||||
|
items.remove(item.getId());
|
||||||
|
itemRemoved(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback unlink_ (Item item)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(item) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
Item item = V(0);
|
||||||
|
unlink(item);
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void link (Item item)
|
||||||
|
{
|
||||||
|
log.debug(this, "link", item);
|
||||||
|
|
||||||
|
items.put(item.getId(), item);
|
||||||
|
itemAdded(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback flush_ ()
|
||||||
|
{
|
||||||
|
log.debug(this, "flush_");
|
||||||
|
CallbackChain chain = new CallbackChain();
|
||||||
|
|
||||||
|
for (Item item : items.values())
|
||||||
|
{
|
||||||
|
if (item.isDirty())
|
||||||
|
{
|
||||||
|
log.debug(this, "flush_ isDirty",item);
|
||||||
|
if (store.isWritable())
|
||||||
|
{
|
||||||
|
log.debug(this, "flush store isWritable", item);
|
||||||
|
if (item.isDeleted())
|
||||||
|
{
|
||||||
|
CallbackChain itemChain = new CallbackChain()
|
||||||
|
.addCallback(log.debug_(this, "actually removing", item))
|
||||||
|
.addCallback(store.remove_(item.getId()))
|
||||||
|
.addCallback(unlink_(item));
|
||||||
|
|
||||||
|
chain.addCallback(itemChain);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CallbackChain itemChain = new CallbackChain()
|
||||||
|
.addCallback(log.debug_(this, "actually flushing", item))
|
||||||
|
.addCallback(item.flush_())
|
||||||
|
.addCallback(serializer.serialize_(item))
|
||||||
|
.addCallback(store.put_(item.getId(), item.getLocalVersion()))
|
||||||
|
.addCallback(item.markStore_(item.getLocalVersion()));
|
||||||
|
|
||||||
|
chain.addCallback(itemChain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (item.hasDirtyChildren())
|
||||||
|
{
|
||||||
|
log.debug(this, "flush_ hasDirtyChildren",item);
|
||||||
|
|
||||||
|
CallbackChain itemChain = new CallbackChain()
|
||||||
|
.addCallback(log.debug_(this, "actually flushing", item))
|
||||||
|
.addCallback(item.flush_());
|
||||||
|
|
||||||
|
chain.addCallback(itemChain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chain.addCallback(onFlush_());
|
||||||
|
chain.addCallback(this.markStore_(this.getLocalVersion()));
|
||||||
|
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback checkClean_() {
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
if (isDirty() || hasDirtyChildren())
|
||||||
|
throw new Exception("Still dirty after flush");
|
||||||
|
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Map<?, ? extends Item> getItemMap ()
|
||||||
|
{
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFull ()
|
||||||
|
{
|
||||||
|
log.debug(this, "isFull");
|
||||||
|
return store.isFull();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug (LogOut log, String prefix)
|
||||||
|
{
|
||||||
|
final String PREFIX = " |--";
|
||||||
|
log.debug (prefix,this);
|
||||||
|
store.debug(log, prefix+PREFIX);
|
||||||
|
for (Entry<ID, Item> entry : items.entrySet())
|
||||||
|
entry.getValue().debug(log, prefix+PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug (LogNull log, String prefix)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public Callback debug_ ()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
debug(logState, "");
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} ;
|
30
java/core/src/core/mail/client/cache/CacheFlush.java
vendored
Normal file
30
java/core/src/core/mail/client/cache/CacheFlush.java
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackWithVariables;
|
||||||
|
import core.connector.async.AsyncStoreConnector;
|
||||||
|
import core.util.LogNull;
|
||||||
|
|
||||||
|
public class CacheFlush extends CallbackChain
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(CacheFlush.class);
|
||||||
|
|
||||||
|
Cache cache;
|
||||||
|
String file;
|
||||||
|
AsyncStoreConnector connector;
|
||||||
|
|
||||||
|
CacheFlush (Cache cache, AsyncStoreConnector connector, String file, CallbackChain writeByteArray)
|
||||||
|
{
|
||||||
|
this.cache = cache;
|
||||||
|
this.file = file;
|
||||||
|
this.connector = connector;
|
||||||
|
|
||||||
|
addCallback(writeByteArray);
|
||||||
|
addCallback(connector.put_(file));
|
||||||
|
}
|
||||||
|
}
|
12
java/core/src/core/mail/client/cache/CacheState.java
vendored
Normal file
12
java/core/src/core/mail/client/cache/CacheState.java
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
public enum CacheState
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
NOTCACHED,
|
||||||
|
CACHED
|
||||||
|
}
|
126
java/core/src/core/mail/client/cache/ID.java
vendored
Normal file
126
java/core/src/core/mail/client/cache/ID.java
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import core.util.Arrays;
|
||||||
|
import core.util.Base16;
|
||||||
|
import core.util.SecureRandom;
|
||||||
|
|
||||||
|
public class ID
|
||||||
|
{
|
||||||
|
static public final int PartSize=6;
|
||||||
|
static public ID None = ID.fromLong(-1);
|
||||||
|
static SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
public static final byte VERSION = 1;
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
private ID(byte[] value)
|
||||||
|
{
|
||||||
|
this.value = Base16.encode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ID(String value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ID combine(ID left, ID right)
|
||||||
|
{
|
||||||
|
return new ID(left.value + "00" + right.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int indexOfDoubleNull(String s)
|
||||||
|
{
|
||||||
|
for (int i=0; i<s.length()-1; i+=2)
|
||||||
|
{
|
||||||
|
if (s.charAt(i)=='0' && s.charAt(i+1)=='0')
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ID left ()
|
||||||
|
{
|
||||||
|
return new ID(value.substring(0, indexOfDoubleNull(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ID right ()
|
||||||
|
{
|
||||||
|
int indexOf = indexOfDoubleNull(value);
|
||||||
|
if (indexOf == -1)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
return new ID(value.substring(indexOf+2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ID deserialize(byte[] bytes)
|
||||||
|
{
|
||||||
|
byte version = bytes[0];
|
||||||
|
return new ID(Arrays.copyOf(bytes,1, bytes.length-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] serialize()
|
||||||
|
{
|
||||||
|
return Base16.decode( ("0"+VERSION) + value );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ID fromLong(long l)
|
||||||
|
{
|
||||||
|
return new ID(new BigInteger("" + l).toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ID random ()
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[PartSize];
|
||||||
|
|
||||||
|
for (int i=0; i<bytes.length; ++i)
|
||||||
|
{
|
||||||
|
bytes[i]=0;
|
||||||
|
|
||||||
|
while (bytes[i]==0)
|
||||||
|
bytes[i] = (byte)random.nextInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ID(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ID fromString (String s)
|
||||||
|
{
|
||||||
|
return new ID(Base16.decode(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals (Object rhs)
|
||||||
|
{
|
||||||
|
if (this == rhs)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (rhs != null && ((ID)rhs).value.equals(value))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return toString().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toFileSystemSafe ()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
149
java/core/src/core/mail/client/cache/IndexedCache.java
vendored
Normal file
149
java/core/src/core/mail/client/cache/IndexedCache.java
vendored
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Pair;
|
||||||
|
|
||||||
|
public class IndexedCache extends ItemCollection
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(IndexedCache.class);
|
||||||
|
|
||||||
|
static abstract class Factory
|
||||||
|
{
|
||||||
|
public abstract Cache getCache(ID id, boolean isNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
ID workCacheID = ID.None;
|
||||||
|
Factory factory;
|
||||||
|
|
||||||
|
Map<ID, Cache> caches = new HashMap<ID, Cache>();
|
||||||
|
|
||||||
|
public IndexedCache (Factory factory)
|
||||||
|
{
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item getAndAcquire (Type type, ID iid)
|
||||||
|
{
|
||||||
|
log.trace(this, "getAndAcquire", type, iid);
|
||||||
|
Item t = get(type, iid);
|
||||||
|
acquire(t);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback flush_ ()
|
||||||
|
{
|
||||||
|
log.debug(this, "flush_");
|
||||||
|
|
||||||
|
CallbackChain chain = new CallbackChain();
|
||||||
|
for (Cache cache : caches.values())
|
||||||
|
{
|
||||||
|
if (cache.isDirty() || cache.hasDirtyChildren())
|
||||||
|
chain.addCallback(cache.flush_());
|
||||||
|
}
|
||||||
|
|
||||||
|
chain.addCallback(onFlush_());
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void acquire(Item t)
|
||||||
|
{
|
||||||
|
log.trace(this, "acquire", t.getId());
|
||||||
|
getKnownCache(t.getId().left()).acquire(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache getKnownCache(ID cid)
|
||||||
|
{
|
||||||
|
log.trace(this, "getKnownCache",cid);
|
||||||
|
if (!caches.containsKey(cid))
|
||||||
|
{
|
||||||
|
log.debug("getKnownCache creating");
|
||||||
|
Cache cache = factory.getCache(cid, false);
|
||||||
|
cache.setId(cid);
|
||||||
|
caches.put(cid, cache);
|
||||||
|
itemAdded(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
return caches.get(cid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void newCache(ID cid)
|
||||||
|
{
|
||||||
|
log.debug(this, "newCache", cid);
|
||||||
|
|
||||||
|
Cache cache = factory.getCache(cid, true);
|
||||||
|
caches.put(cid, cache);
|
||||||
|
itemAdded(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
ID getWorkCacheID ()
|
||||||
|
{
|
||||||
|
return workCacheID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWorkCacheID (ID id)
|
||||||
|
{
|
||||||
|
log.debug(this, "setWorkCacheID");
|
||||||
|
workCacheID = id;
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item get(Type type, ID iid)
|
||||||
|
{
|
||||||
|
log.trace(this, "get", type, iid);
|
||||||
|
return getKnownCache(iid.left()).get(type, iid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void link(ID id, Item t)
|
||||||
|
{
|
||||||
|
log.debug(this, "put", id, t);
|
||||||
|
|
||||||
|
t.setId(id);
|
||||||
|
getKnownCache(t.getId().left()).link(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(Item t)
|
||||||
|
{
|
||||||
|
log.debug(this, "put", t);
|
||||||
|
|
||||||
|
t.setId(instantiateID(ID.random()));
|
||||||
|
getKnownCache(t.getId().left()).put(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ID instantiateID(ID id)
|
||||||
|
{
|
||||||
|
ID workCache = getWorkCacheID();
|
||||||
|
if (workCache.equals(ID.None) || getKnownCache(workCache).isFull())
|
||||||
|
{
|
||||||
|
workCache = ID.random();
|
||||||
|
setWorkCacheID(workCache);
|
||||||
|
newCache(workCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ID.combine(workCache, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<?, ? extends Item> getItemMap ()
|
||||||
|
{
|
||||||
|
return caches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug (LogOut log, String prefix)
|
||||||
|
{
|
||||||
|
log.debug (prefix,this, "W", workCacheID);
|
||||||
|
for (Entry<ID, Cache> entry : caches.entrySet())
|
||||||
|
entry.getValue().debug(log, prefix+" |--");
|
||||||
|
}
|
||||||
|
}
|
41
java/core/src/core/mail/client/cache/IndexedCacheSerializer.java
vendored
Normal file
41
java/core/src/core/mail/client/cache/IndexedCacheSerializer.java
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Streams;
|
||||||
|
|
||||||
|
public class IndexedCacheSerializer extends ItemSerializerSync
|
||||||
|
{
|
||||||
|
protected final static byte VERSION = 1;
|
||||||
|
|
||||||
|
static LogNull log = new LogNull(IndexedCacheSerializer.class);
|
||||||
|
public byte[] serialize(Item item) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("serialize", item);
|
||||||
|
IndexedCache indexedCache = (IndexedCache)item;
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
bos.write(VERSION);
|
||||||
|
Streams.writeBoundedArray(bos, indexedCache.workCacheID.serialize());
|
||||||
|
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deserialize(Item item, byte[] bytes) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("deserialize", item);
|
||||||
|
IndexedCache indexedCache = (IndexedCache)item;
|
||||||
|
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||||
|
int version = bis.read();
|
||||||
|
indexedCache.workCacheID.deserialize(Streams.readBoundedArray(bis));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
287
java/core/src/core/mail/client/cache/Info.java
vendored
Normal file
287
java/core/src/core/mail/client/cache/Info.java
vendored
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.util.LogNull;
|
||||||
|
|
||||||
|
public class Info
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull (Info.class);
|
||||||
|
|
||||||
|
protected ItemOwner owner;
|
||||||
|
private Version cacheVersion;
|
||||||
|
private Version localVersion;
|
||||||
|
private ID id;
|
||||||
|
|
||||||
|
protected Info ()
|
||||||
|
{
|
||||||
|
cacheVersion = Version.NONE;
|
||||||
|
localVersion = Version.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ID getId ()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId (ID id)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onExisting ()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwner (ItemOwner owner)
|
||||||
|
{
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalVersion (Version localVersion)
|
||||||
|
{
|
||||||
|
this.localVersion = localVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version getLocalVersion ()
|
||||||
|
{
|
||||||
|
return localVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version getCacheVersion ()
|
||||||
|
{
|
||||||
|
return cacheVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCacheVersion (Version cacheVersion)
|
||||||
|
{
|
||||||
|
this.cacheVersion = cacheVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void markNoLoad (Version cacheVersion)
|
||||||
|
{
|
||||||
|
this.cacheVersion = cacheVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void markLoad (Version cacheVersion)
|
||||||
|
{
|
||||||
|
this.cacheVersion = this.localVersion = cacheVersion;
|
||||||
|
onLoaded();
|
||||||
|
onModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markDeleted ()
|
||||||
|
{
|
||||||
|
onDeleting();
|
||||||
|
this.localVersion = Version.DELETED;
|
||||||
|
|
||||||
|
onDirty();
|
||||||
|
onModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleted()
|
||||||
|
{
|
||||||
|
return localVersion.equals(Version.DELETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void nextVersion ()
|
||||||
|
{
|
||||||
|
localVersion = Version.random();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final void markDirtyNoCheck ()
|
||||||
|
{
|
||||||
|
nextVersion();
|
||||||
|
onDirty();
|
||||||
|
onModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void markDirty ()
|
||||||
|
{
|
||||||
|
log.debug("info.markDirty", this);
|
||||||
|
if (!isWritable())
|
||||||
|
throw new RuntimeException("markDirty can't be called on an unwritable item");
|
||||||
|
|
||||||
|
markDirtyNoCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isDirty ()
|
||||||
|
{
|
||||||
|
return !localVersion.equals(cacheVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasDirtyChildren ()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoaded ()
|
||||||
|
{
|
||||||
|
return !localVersion.equals(Version.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWritable ()
|
||||||
|
{
|
||||||
|
return isLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
final public void markStore (Version version)
|
||||||
|
{
|
||||||
|
log.debug(this, "markStore", version);
|
||||||
|
cacheVersion = version;
|
||||||
|
|
||||||
|
onStored();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markCreate ()
|
||||||
|
{
|
||||||
|
log.debug(this, "onCreate");
|
||||||
|
markDirtyNoCheck();
|
||||||
|
|
||||||
|
onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markPreLoad ()
|
||||||
|
{
|
||||||
|
onPreLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markPreStore ()
|
||||||
|
{
|
||||||
|
onPreStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onCreate ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreLoad ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreStore ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onLoaded()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onStored()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onModified ()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onDirty ()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onDeleting ()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String classNameLastPart ()
|
||||||
|
{
|
||||||
|
String s = getClass().getName();
|
||||||
|
int lastPeriod = s.lastIndexOf('.');
|
||||||
|
return s.substring(lastPeriod == -1 ? 0 : lastPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringRepOfLoadState ()
|
||||||
|
{
|
||||||
|
String str = "";
|
||||||
|
if (!isLoaded())
|
||||||
|
str += "NotLoaded:";
|
||||||
|
if (isDirty())
|
||||||
|
str += "DIRTY!:" + getLocalVersion() + ":" + getCacheVersion();
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
// return classNameLastPart() + "(" + getId() + ":" + getLocalVersion() + ":" + getCacheVersion() + ")";
|
||||||
|
return classNameLastPart() + "(" + getId() + ":" + getStringRepOfLoadState() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback markPreLoad_ ()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
onPreLoad();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback markLoad_ (Version version)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new CallbackDefault(version) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
Version version = (Version)V(0);
|
||||||
|
markLoad(version);
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public Callback markPartialLoad_ (Version version)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new CallbackDefault(version) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
Version version = (Version)V(0);
|
||||||
|
setCacheVersion(version);
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
public Callback markStore_ (Version localVersion)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new CallbackDefault(localVersion) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
Version version = (Version)V(0);
|
||||||
|
markStore(version);
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback markPreStore_()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
onPreStore();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback markDeleted_()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
markDeleted();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
89
java/core/src/core/mail/client/cache/Item.java
vendored
Normal file
89
java/core/src/core/mail/client/cache/Item.java
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callback.CallbackEmpty;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
|
||||||
|
public abstract class Item extends Info
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Item.class);
|
||||||
|
|
||||||
|
CallbackChain onLoadCallbacks;
|
||||||
|
CallbackChain onLoadOnceCallbacks;
|
||||||
|
|
||||||
|
public Item ()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onDirty()
|
||||||
|
{
|
||||||
|
super.onDirty();
|
||||||
|
|
||||||
|
if (owner != null)
|
||||||
|
owner.onDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onLoaded()
|
||||||
|
{
|
||||||
|
log.debug("onLoaded", this);
|
||||||
|
|
||||||
|
super.onLoaded();
|
||||||
|
|
||||||
|
if (onLoadCallbacks != null)
|
||||||
|
onLoadCallbacks.invoke(this);
|
||||||
|
|
||||||
|
if (onLoadOnceCallbacks != null)
|
||||||
|
{
|
||||||
|
CallbackChain chain = onLoadOnceCallbacks;
|
||||||
|
onLoadOnceCallbacks = null;
|
||||||
|
chain.invoke(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public CallbackChain getLoadCallbacks()
|
||||||
|
{
|
||||||
|
if (onLoadCallbacks == null)
|
||||||
|
{
|
||||||
|
onLoadCallbacks = new CallbackChain();
|
||||||
|
onLoadCallbacks.setPropagateOriginalArguments().setSlowFail();
|
||||||
|
}
|
||||||
|
return onLoadCallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallbackChain getLoadOnceCallbacks()
|
||||||
|
{
|
||||||
|
if (onLoadOnceCallbacks == null)
|
||||||
|
{
|
||||||
|
onLoadOnceCallbacks = new CallbackChain();
|
||||||
|
onLoadOnceCallbacks.setPropagateOriginalArguments().setSlowFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
return onLoadOnceCallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply (Callback callback)
|
||||||
|
{
|
||||||
|
if (isLoaded())
|
||||||
|
callback.invoke(this);
|
||||||
|
else
|
||||||
|
getLoadOnceCallbacks().addCallback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback flush_ ()
|
||||||
|
{
|
||||||
|
return new CallbackEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug(LogOut log, String prefix)
|
||||||
|
{
|
||||||
|
log.debug(prefix,this);
|
||||||
|
}
|
||||||
|
}
|
32
java/core/src/core/mail/client/cache/ItemCacheFactory.java
vendored
Normal file
32
java/core/src/core/mail/client/cache/ItemCacheFactory.java
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
public class ItemCacheFactory extends IndexedCache.Factory
|
||||||
|
{
|
||||||
|
String prefix;
|
||||||
|
StoreLibrary library;
|
||||||
|
ItemFactory factory;
|
||||||
|
ItemSerializer serializer;
|
||||||
|
|
||||||
|
public ItemCacheFactory(String prefix, StoreLibrary library, ItemFactory factory, ItemSerializer serializer)
|
||||||
|
{
|
||||||
|
this.library = library;
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.factory = factory;
|
||||||
|
this.serializer = serializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cache getCache(ID id, boolean isNew)
|
||||||
|
{
|
||||||
|
return new Cache(
|
||||||
|
factory,
|
||||||
|
serializer,
|
||||||
|
library.instantiate(prefix, id, isNew)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
java/core/src/core/mail/client/cache/ItemCollection.java
vendored
Normal file
72
java/core/src/core/mail/client/cache/ItemCollection.java
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
|
||||||
|
public abstract class ItemCollection extends Item implements ItemOwner
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean hasDirtyChildren ()
|
||||||
|
{
|
||||||
|
return areAnyChildrenDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDirty (Item item)
|
||||||
|
{
|
||||||
|
onDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Map<?, ? extends Item> getItemMap ();
|
||||||
|
|
||||||
|
public final boolean areAnyChildrenDirty ()
|
||||||
|
{
|
||||||
|
for (Entry<?, ? extends Item> i : getItemMap().entrySet())
|
||||||
|
{
|
||||||
|
Item item = i.getValue();
|
||||||
|
if (item.isDirty() || item.hasDirtyChildren())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFlush ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback onFlush_ ()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
onFlush();
|
||||||
|
next(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void itemAdded (Item item)
|
||||||
|
{
|
||||||
|
item.setOwner(this);
|
||||||
|
|
||||||
|
if (item.isDirty() || item.hasDirtyChildren())
|
||||||
|
onDirty(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void itemRemoved (Item item)
|
||||||
|
{
|
||||||
|
item.setOwner(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return super.toString() + (areAnyChildrenDirty() ? " ChildrenDirty" : "");
|
||||||
|
}
|
||||||
|
}
|
10
java/core/src/core/mail/client/cache/ItemFactory.java
vendored
Normal file
10
java/core/src/core/mail/client/cache/ItemFactory.java
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
public interface ItemFactory
|
||||||
|
{
|
||||||
|
public Item instantiate (Type type);
|
||||||
|
}
|
10
java/core/src/core/mail/client/cache/ItemOwner.java
vendored
Normal file
10
java/core/src/core/mail/client/cache/ItemOwner.java
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
public interface ItemOwner
|
||||||
|
{
|
||||||
|
public void onDirty (Item item);
|
||||||
|
}
|
13
java/core/src/core/mail/client/cache/ItemSerializer.java
vendored
Normal file
13
java/core/src/core/mail/client/cache/ItemSerializer.java
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
|
||||||
|
public interface ItemSerializer
|
||||||
|
{
|
||||||
|
Callback serialize_ (Item item);
|
||||||
|
Callback deserialize_ (Item item);
|
||||||
|
}
|
44
java/core/src/core/mail/client/cache/ItemSerializerSync.java
vendored
Normal file
44
java/core/src/core/mail/client/cache/ItemSerializerSync.java
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
|
||||||
|
public abstract class ItemSerializerSync implements ItemSerializer
|
||||||
|
{
|
||||||
|
public abstract byte[] serialize(Item item) throws Exception;
|
||||||
|
public abstract void deserialize(Item item, byte[] bytes) throws Exception;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback serialize_(Item item) {
|
||||||
|
return new CallbackDefault(item) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
Item item = V(0);
|
||||||
|
item.onPreStore();
|
||||||
|
next(serialize(item));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callback deserialize_(Item item) {
|
||||||
|
return new CallbackDefault(item) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
Item item = V(0);
|
||||||
|
item.onPreLoad();
|
||||||
|
deserialize(item, (byte[])arguments[0]);
|
||||||
|
next((Item)V(0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
576
java/core/src/core/mail/client/cache/JSON.java
vendored
Normal file
576
java/core/src/core/mail/client/cache/JSON.java
vendored
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import mail.client.Master;
|
||||||
|
import mail.client.Servent;
|
||||||
|
import mail.client.model.Attachment;
|
||||||
|
import mail.client.model.Attachments;
|
||||||
|
import mail.client.model.Body;
|
||||||
|
import mail.client.model.Conversation;
|
||||||
|
import mail.client.model.Dictionary;
|
||||||
|
import mail.client.model.Folder;
|
||||||
|
import mail.client.model.FolderDefinition;
|
||||||
|
import mail.client.model.FolderMaster;
|
||||||
|
import mail.client.model.FolderSet;
|
||||||
|
import mail.client.model.Header;
|
||||||
|
import mail.client.model.Identity;
|
||||||
|
import mail.client.model.Mail;
|
||||||
|
import mail.client.model.Recipients;
|
||||||
|
import mail.client.model.Settings;
|
||||||
|
import mail.client.model.TransportState;
|
||||||
|
import mail.client.model.UnregisteredIdentity;
|
||||||
|
|
||||||
|
import core.util.Base64;
|
||||||
|
|
||||||
|
import core.util.JSON_;
|
||||||
|
import core.util.JSON_.JSONException;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Pair;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
public class JSON extends Servent<Master>
|
||||||
|
{
|
||||||
|
LogNull log = new LogNull(JSON.class);
|
||||||
|
|
||||||
|
public Object toJSON (ID id)
|
||||||
|
{
|
||||||
|
return toJSON(id.serialize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ID toID (String json)
|
||||||
|
{
|
||||||
|
return ID.deserialize(toBytes(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Identity toIdentity(String s)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
master.getAddressBook().getIdentity(
|
||||||
|
new UnregisteredIdentity(s)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (String s)
|
||||||
|
{
|
||||||
|
return JSON_.newString(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (boolean b)
|
||||||
|
{
|
||||||
|
return JSON_.newBoolean(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (int v)
|
||||||
|
{
|
||||||
|
return JSON_.newNumber(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (Identity i)
|
||||||
|
{
|
||||||
|
return toJSON(i.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Identity> toIdentityList (Object a) throws JSONException
|
||||||
|
{
|
||||||
|
List<Identity> l = new ArrayList<Identity>();
|
||||||
|
|
||||||
|
for (int i=0; i<JSON_.size(a); ++i)
|
||||||
|
{
|
||||||
|
l.add(toIdentity(JSON_.getString(a,i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (Collection<Identity> l) throws JSONException
|
||||||
|
{
|
||||||
|
Object a = JSON_.newArray();
|
||||||
|
for (Identity i: l)
|
||||||
|
JSON_.add(a, toJSON(i));
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Recipients toRecipients(Object o) throws JSONException
|
||||||
|
{
|
||||||
|
Recipients r = new Recipients();
|
||||||
|
r.setTo(toIdentityList(JSON_.getArray(o, "to")));
|
||||||
|
r.setCc(toIdentityList(JSON_.getArray(o, "cc")));
|
||||||
|
r.setBcc(toIdentityList(JSON_.getArray(o, "bcc")));
|
||||||
|
r.setReplyTo(toIdentityList(JSON_.getArray(o, "replyTo")));
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Object toJSON (Recipients r) throws JSONException
|
||||||
|
{
|
||||||
|
Object o = JSON_.newObject();
|
||||||
|
JSON_.put(o, "to", toJSON(r.getTo()));
|
||||||
|
JSON_.put(o, "cc", toJSON(r.getCc()));
|
||||||
|
JSON_.put(o, "bcc", toJSON(r.getBcc()));
|
||||||
|
JSON_.put(o, "replyTo", toJSON(r.getReplyTo()));
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary toDictionary(String s)
|
||||||
|
{
|
||||||
|
Dictionary d = new Dictionary();
|
||||||
|
d.fromSerializableString(s);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (Dictionary d)
|
||||||
|
{
|
||||||
|
return toJSON(d.toSerializableString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date toDate (long d)
|
||||||
|
{
|
||||||
|
return new Date(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (Date d)
|
||||||
|
{
|
||||||
|
return JSON_.newNumber(d.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Header toHeader (Object h) throws JSONException
|
||||||
|
{
|
||||||
|
Header header = new Header();
|
||||||
|
|
||||||
|
if (JSON_.has(h, "externalKey"))
|
||||||
|
header.setExternalKey(JSON_.getString(h, "externalKey"));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "originalKey"))
|
||||||
|
header.setOriginalKey(JSON_.getString(h, "originalKey"));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "uidl"))
|
||||||
|
header.setUIDL(JSON_.getString(h, "uidl"));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "author"))
|
||||||
|
header.setAuthor(toIdentity (JSON_.getString(h, "author")));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "authors"))
|
||||||
|
header.setAuthors(toIdentityList(JSON_.getArray(h, "authors")));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "recipients"))
|
||||||
|
header.setRecipients(toRecipients(JSON_.getObject(h, "recipients")));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "subject"))
|
||||||
|
header.setSubject(JSON_.getString(h, "subject"));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "date"))
|
||||||
|
header.setDate(toDate(JSON_.getLong(h, "date")));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "transportState"))
|
||||||
|
header.setTransportState(TransportState.fromString(JSON_.getString(h, "transportState")));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "brief"))
|
||||||
|
header.setBrief(JSON_.getString(h, "brief"));
|
||||||
|
|
||||||
|
if (JSON_.has(h, "dictionary"))
|
||||||
|
header.setDictionary(toDictionary(JSON_.getString(h, "dictionary")));
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (Header h) throws JSONException
|
||||||
|
{
|
||||||
|
Object o = JSON_.newObject();
|
||||||
|
|
||||||
|
if (h.getExternalKey()!=null)
|
||||||
|
JSON_.put(o, "externalKey", toJSON(h.getExternalKey()));
|
||||||
|
|
||||||
|
if (h.getOriginalKey()!=null)
|
||||||
|
JSON_.put(o, "originalKey", toJSON(h.getOriginalKey()));
|
||||||
|
|
||||||
|
if (h.getUIDL()!=null)
|
||||||
|
JSON_.put(o, "uidl", toJSON(h.getUIDL()));
|
||||||
|
|
||||||
|
if (h.getAuthor() != null)
|
||||||
|
JSON_.put(o, "author", toJSON(h.getAuthor()));
|
||||||
|
|
||||||
|
if (h.getAuthors() != null)
|
||||||
|
JSON_.put(o, "authors", toJSON(h.getAuthors()));
|
||||||
|
|
||||||
|
if (h.getRecipients() != null)
|
||||||
|
JSON_.put(o, "recipients", toJSON(h.getRecipients()));
|
||||||
|
|
||||||
|
if (h.getSubject() != null)
|
||||||
|
JSON_.put(o, "subject", toJSON(h.getSubject()));
|
||||||
|
|
||||||
|
if (h.getDate() != null)
|
||||||
|
JSON_.put(o, "date", toJSON(h.getDate()));
|
||||||
|
|
||||||
|
if (h.getTransportState() != null)
|
||||||
|
JSON_.put(o, "transportState", toJSON(h.getTransportState().toString()));
|
||||||
|
|
||||||
|
if (h.getBrief() != null)
|
||||||
|
JSON_.put(o, "brief", toJSON(h.getBrief()));
|
||||||
|
|
||||||
|
if (h.getDictionary() != null)
|
||||||
|
JSON_.put(o, "dictionary", toJSON(h.getDictionary()));
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Body toBody (Object o) throws JSONException
|
||||||
|
{
|
||||||
|
Body body = new Body();
|
||||||
|
|
||||||
|
if (JSON_.has(o, "text"))
|
||||||
|
body.setText(JSON_.getString(o, "text"));
|
||||||
|
|
||||||
|
if (JSON_.has(o, "html"))
|
||||||
|
body.setHTML(JSON_.getString(o, "html"));
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (Body b) throws JSONException
|
||||||
|
{
|
||||||
|
Object o = JSON_.newObject();
|
||||||
|
if (b.hasText())
|
||||||
|
JSON_.put(o, "text", toJSON(b.getText()));
|
||||||
|
|
||||||
|
if (b.hasHTML())
|
||||||
|
JSON_.put(o, "html", toJSON(b.getHTML()));
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderDefinition toFolderDefinition(Object o) throws JSONException
|
||||||
|
{
|
||||||
|
FolderDefinition f = new FolderDefinition(JSON_.getString(o, "name"));
|
||||||
|
|
||||||
|
if (JSON_.has(o, "subject"))
|
||||||
|
f.setSubject(JSON_.getString(o, "subject"));
|
||||||
|
|
||||||
|
if (JSON_.has(o, "author"))
|
||||||
|
f.setAuthor(toIdentity(JSON_.getString(o, "author")));
|
||||||
|
|
||||||
|
if (JSON_.has(o, "recipient"))
|
||||||
|
f.setRecipient(toIdentity(JSON_.getString(o, "recipient")));
|
||||||
|
|
||||||
|
if (JSON_.has(o, "stateDiffers") || JSON_.has(o, "stateEquals"))
|
||||||
|
{
|
||||||
|
TransportState d=null,e=null;
|
||||||
|
if (JSON_.has(o, "stateDiffers"))
|
||||||
|
d = TransportState.fromString(JSON_.getString(o, "stateDiffers"));
|
||||||
|
|
||||||
|
if (JSON_.has(o, "stateEquals"))
|
||||||
|
e = TransportState.fromString(JSON_.getString(o, "stateEquals"));
|
||||||
|
|
||||||
|
f.setState(e, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_.has(o, "bayesianDictionary"))
|
||||||
|
f.setBayesianDictionary(toDictionary(JSON_.getString(o, "bayesianDictionary")));
|
||||||
|
|
||||||
|
if (JSON_.has(o, "autoBayesian"))
|
||||||
|
f.setAutoBayesian(JSON_.getBoolean(o, "autoBayesian"));
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON(FolderDefinition d) throws JSONException
|
||||||
|
{
|
||||||
|
Object o = JSON_.newObject();
|
||||||
|
JSON_.put(o, "name", toJSON(d.getName()));
|
||||||
|
|
||||||
|
if (d.getAuthor()!=null)
|
||||||
|
JSON_.put(o, "author", toJSON(d.getAuthor()));
|
||||||
|
|
||||||
|
if (d.getSubject()!=null)
|
||||||
|
JSON_.put(o, "subject", toJSON(d.getSubject()));
|
||||||
|
|
||||||
|
if (d.getRecipient()!=null)
|
||||||
|
JSON_.put(o, "recipient", toJSON(d.getRecipient()));
|
||||||
|
|
||||||
|
if (d.getStateDiffers()!=null)
|
||||||
|
JSON_.put(o, "stateDiffers", toJSON(d.getStateDiffers().toString()));
|
||||||
|
|
||||||
|
if (d.getStateEquals()!=null)
|
||||||
|
JSON_.put(o, "stateEquals", toJSON(d.getStateEquals().toString()));
|
||||||
|
|
||||||
|
if (d.getBayesianDictionary()!=null)
|
||||||
|
JSON_.put(o, "bayesianDictionary", toJSON(d.getBayesianDictionary()));
|
||||||
|
|
||||||
|
if (d.getAutoBayesian())
|
||||||
|
JSON_.put(o, "autoBayesian", toJSON(d.getAutoBayesian()));
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON(byte[] id)
|
||||||
|
{
|
||||||
|
return toJSON(Base64.encode(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toBytes(String string)
|
||||||
|
{
|
||||||
|
return Base64.decode(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attachments toAttachments(Object json) throws JSONException
|
||||||
|
{
|
||||||
|
Attachments attachments = new Attachments ();
|
||||||
|
for (int i=0; i<JSON_.size(json); ++i)
|
||||||
|
{
|
||||||
|
Object v = JSON_.get(json, i);
|
||||||
|
|
||||||
|
attachments.addAttachment(
|
||||||
|
new Attachment(
|
||||||
|
JSON_.has(v, "id") ? JSON_.getString(v, "id") : null,
|
||||||
|
JSON_.has(v, "disposition") ? JSON_.getString(v, "disposition") : null,
|
||||||
|
JSON_.has(v, "mime-type") ? JSON_.getString(v, "mime-type") : null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON (Attachments attachments) throws JSONException
|
||||||
|
{
|
||||||
|
Object a = JSON_.newArray();
|
||||||
|
for (Attachment attachment : attachments.getList())
|
||||||
|
{
|
||||||
|
Object v = JSON_.newObject();
|
||||||
|
JSON_.put(v, "id", toJSON(attachment.getId()));
|
||||||
|
JSON_.put(v, "disposition", toJSON(attachment.getDisposition()));
|
||||||
|
JSON_.put(v, "mime-type", toJSON(attachment.getMimeType()));
|
||||||
|
|
||||||
|
JSON_.add(a, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fromJSON(Mail m, Object v) throws JSONException
|
||||||
|
{
|
||||||
|
log.debug("fromJSON mail",m.getId() ," ", m);
|
||||||
|
|
||||||
|
String version = JSON_.getString(v, "version");
|
||||||
|
|
||||||
|
m.setHeader (
|
||||||
|
toHeader(JSON_.getObject(v, "header"))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (JSON_.has(v, "body"))
|
||||||
|
m.setBody (
|
||||||
|
toBody (JSON_.getObject(v, "body"))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (JSON_.has(v, "attachments"))
|
||||||
|
m.setAttachments (
|
||||||
|
toAttachments (JSON_.getObject(v, "attachments"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON(Mail m) throws Exception
|
||||||
|
{
|
||||||
|
Object v = JSON_.newObject();
|
||||||
|
JSON_.put(v, "version", toJSON("1.0"));
|
||||||
|
|
||||||
|
JSON_.put(v, "header", toJSON(m.getHeader()));
|
||||||
|
|
||||||
|
if (m.getBody() != null)
|
||||||
|
JSON_.put(v, "body", toJSON(m.getBody()));
|
||||||
|
|
||||||
|
if (m.getAttachments() != null)
|
||||||
|
JSON_.put(v, "attachments", toJSON(m.getAttachments()));
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fromJSON(Conversation c, Object v) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("fromJSON conversation", c.getId());
|
||||||
|
|
||||||
|
String version = JSON_.getString(v, "version");
|
||||||
|
c.setHeader(toHeader(JSON_.getObject(v, "header")));
|
||||||
|
|
||||||
|
Object m = JSON_.getArray(v, "mail");
|
||||||
|
for (int i=0; i<JSON_.size(m); ++i)
|
||||||
|
{
|
||||||
|
Object iNd = JSON_.getArray(m, i);
|
||||||
|
c.addItemId(toID(JSON_.getString(iNd,0)), toDate(JSON_.getLong(iNd,1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON(Conversation c) throws Exception
|
||||||
|
{
|
||||||
|
Object v = JSON_.newObject();
|
||||||
|
JSON_.put(v, "version", toJSON("1.0"));
|
||||||
|
|
||||||
|
JSON_.put(v, "header", toJSON(c.getHeader()));
|
||||||
|
|
||||||
|
Object m = JSON_.newArray();
|
||||||
|
for (Pair<ID,Date> p : c.getItemIds())
|
||||||
|
{
|
||||||
|
Object iNd = JSON_.newArray();
|
||||||
|
JSON_.add(iNd, toJSON(p.first));
|
||||||
|
JSON_.add(iNd, toJSON(p.second));
|
||||||
|
JSON_.add(m, iNd);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_.put(v, "mail", m);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fromJSON(Folder f, Object v) throws Exception
|
||||||
|
{
|
||||||
|
String version = JSON_.getString(v, "version");
|
||||||
|
|
||||||
|
if (JSON_.has(v, "definition"))
|
||||||
|
f.setFolderDefinition(toFolderDefinition(JSON_.getObject(v, "definition")));
|
||||||
|
|
||||||
|
if (f instanceof FolderSet)
|
||||||
|
{
|
||||||
|
if (f instanceof FolderMaster)
|
||||||
|
{
|
||||||
|
FolderMaster fm = (FolderMaster)f;
|
||||||
|
|
||||||
|
{
|
||||||
|
Object a = JSON_.getArray(v, "uidl");
|
||||||
|
for (int i=0; i<JSON_.size(a); ++i)
|
||||||
|
{
|
||||||
|
Object iNd = JSON_.getArray(a, i);
|
||||||
|
fm.addUIDLHash(JSON_.getString(iNd,0), toDate(JSON_.getLong(iNd, 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Object a = JSON_.getArray(v, "externalKey");
|
||||||
|
for (int i=0; i<JSON_.size(a); ++i)
|
||||||
|
{
|
||||||
|
Object iNd = JSON_.getArray(a, i);
|
||||||
|
fm.addExternalKeyHash(JSON_.getString(iNd,0), toDate(JSON_.getLong(iNd,1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FolderSet fs = (FolderSet)f;
|
||||||
|
|
||||||
|
Object a = JSON_.getArray(v, "parts");
|
||||||
|
for (int i=JSON_.size(a)-1; i>=0; --i) // reverse it
|
||||||
|
{
|
||||||
|
fs.addFolderId(toID(JSON_.getString(a, i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.setNumConversations(JSON_.getInt(v, "numConversations"));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object a = JSON_.getArray(v, "conversations");
|
||||||
|
for (int i=JSON_.size(a)-1; i>=0; --i) // reverse it
|
||||||
|
{
|
||||||
|
Object iNd = JSON_.getArray(a, i);
|
||||||
|
f.addConversationId(toID(JSON_.getString(iNd,0)), toDate(JSON_.getLong(iNd,1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON(Folder f) throws Exception
|
||||||
|
{
|
||||||
|
Object v = JSON_.newObject();
|
||||||
|
JSON_.put(v, "version", toJSON("1.0"));
|
||||||
|
|
||||||
|
if (f.getFolderDefinition() != null)
|
||||||
|
JSON_.put(v, "definition", toJSON(f.getFolderDefinition()));
|
||||||
|
|
||||||
|
if (f instanceof FolderSet)
|
||||||
|
{
|
||||||
|
if (f instanceof FolderMaster)
|
||||||
|
{
|
||||||
|
FolderMaster fm = (FolderMaster)f;
|
||||||
|
|
||||||
|
{
|
||||||
|
Object a = JSON_.newArray();
|
||||||
|
for (Map.Entry<String, Date> id : fm.getUIDLHashes().entrySet())
|
||||||
|
{
|
||||||
|
Object iNd = JSON_.newArray();
|
||||||
|
JSON_.add(iNd, toJSON(id.getKey()));
|
||||||
|
JSON_.add(iNd, toJSON(id.getValue()));
|
||||||
|
JSON_.add(a, iNd);
|
||||||
|
}
|
||||||
|
JSON_.put(v, "uidl", a);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Object a = JSON_.newArray();
|
||||||
|
for (Map.Entry<String, Date> id : fm.getExternalKeyHashes().entrySet())
|
||||||
|
{
|
||||||
|
Object iNd = JSON_.newArray();
|
||||||
|
JSON_.add(iNd, toJSON(id.getKey()));
|
||||||
|
JSON_.add(iNd, toJSON(id.getValue()));
|
||||||
|
JSON_.add(a, iNd);
|
||||||
|
}
|
||||||
|
JSON_.put(v, "externalKey", a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FolderSet fs = (FolderSet)f;
|
||||||
|
Object a = JSON_.newArray();
|
||||||
|
for (ID id : fs.getFolderIds())
|
||||||
|
JSON_.add(a, toJSON(id));
|
||||||
|
|
||||||
|
JSON_.put(v, "parts", a);
|
||||||
|
JSON_.put(v, "numConversations", toJSON(fs.getNumConversations()));
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object a = JSON_.newArray();
|
||||||
|
for (Pair<ID,Date> p : f.getConversationIds())
|
||||||
|
{
|
||||||
|
Object iNd = JSON_.newArray();
|
||||||
|
JSON_.add(iNd, toJSON(p.first));
|
||||||
|
JSON_.add(iNd, toJSON(p.second));
|
||||||
|
JSON_.add(a, iNd);
|
||||||
|
}
|
||||||
|
JSON_.put(v, "conversations", a);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fromJSON(Settings item, Object o) throws JSONException
|
||||||
|
{
|
||||||
|
String[] keys = JSON_.keys(o);
|
||||||
|
|
||||||
|
Map<String,String> kv = new HashMap<String,String>();
|
||||||
|
|
||||||
|
for (String key : keys)
|
||||||
|
kv.put(key, JSON_.getString(o, key));
|
||||||
|
|
||||||
|
item.setKV(kv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object toJSON(Settings item) throws JSONException
|
||||||
|
{
|
||||||
|
Object o = JSON_.newObject();
|
||||||
|
|
||||||
|
for (Entry<String, String> key : item.getKV().entrySet())
|
||||||
|
{
|
||||||
|
JSON_.put(o, key.getKey(), toJSON(key.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
13
java/core/src/core/mail/client/cache/LoadState.java
vendored
Normal file
13
java/core/src/core/mail/client/cache/LoadState.java
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
public enum LoadState
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
LOADING,
|
||||||
|
LOADED,
|
||||||
|
FAILED
|
||||||
|
}
|
25
java/core/src/core/mail/client/cache/Operation.java
vendored
Normal file
25
java/core/src/core/mail/client/cache/Operation.java
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
public class Operation {
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
GET,
|
||||||
|
PUT,
|
||||||
|
REMOVE
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type type;
|
||||||
|
public ID id;
|
||||||
|
public Item t;
|
||||||
|
|
||||||
|
public Operation(Type type, ID id, Item t)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
this.id = id;
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
}
|
239
java/core/src/core/mail/client/cache/Store.java
vendored
Normal file
239
java/core/src/core/mail/client/cache/Store.java
vendored
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Pair;
|
||||||
|
import core.util.Triple;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store sequence:
|
||||||
|
* Store exists
|
||||||
|
* #0 Store.markDirty (new local version A)
|
||||||
|
* Item is added -> takes on storeVersionWhenModified=Store.getLocalVersion() A
|
||||||
|
* #1 Store.flush_
|
||||||
|
* #1 Store.serialize_
|
||||||
|
* #1 Store.markDirty (new local version B)
|
||||||
|
* Item is added -> takes on NEW storeVersionWhenModified B
|
||||||
|
* #1 Store.markStore (local version A)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author tprepscius
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Store extends ItemCollection
|
||||||
|
{
|
||||||
|
public static class Data extends Item
|
||||||
|
{
|
||||||
|
byte[] bytes;
|
||||||
|
|
||||||
|
public Data (ID id)
|
||||||
|
{
|
||||||
|
setId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markLoad (byte[] bytes, Version version)
|
||||||
|
{
|
||||||
|
this.bytes = bytes;
|
||||||
|
markLoad(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markDirty (byte[] bytes, Version version)
|
||||||
|
{
|
||||||
|
this.bytes = bytes;
|
||||||
|
setLocalVersion(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markStore ()
|
||||||
|
{
|
||||||
|
markStore(getLocalVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return super.toString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LogNull log = new LogNull(Store.class);
|
||||||
|
protected Map<ID, Data> map = new HashMap<ID, Data>();
|
||||||
|
int requestedMaxSize;
|
||||||
|
boolean locked = false;
|
||||||
|
|
||||||
|
public Store (int requestedMaxSize)
|
||||||
|
{
|
||||||
|
this.requestedMaxSize = requestedMaxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] get(ID id)
|
||||||
|
{
|
||||||
|
return map.get(id).bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallbackDefault get_(ID id)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(id) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
next(get((ID)V(0)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallbackDefault put_(ID id, Version version)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(id, version) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
put((ID)V(0), (Version)V(1), (byte[])arguments[0]);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public CallbackDefault remove_(ID id)
|
||||||
|
{
|
||||||
|
return new CallbackDefault(id) {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
remove((ID)V(0));
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Version version(ID id)
|
||||||
|
{
|
||||||
|
return map.get(id).getLocalVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean has(ID id)
|
||||||
|
{
|
||||||
|
if (map.containsKey(id))
|
||||||
|
{
|
||||||
|
log.debug(this, "has", id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.debug(this, "does not have", id);
|
||||||
|
log.debug(map.keySet().toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return map.containsKey(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(ID id)
|
||||||
|
{
|
||||||
|
log.debug(this, "remove", id);
|
||||||
|
put(id, Version.DELETED, new byte[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(ID id, Version version, byte[] bytes)
|
||||||
|
{
|
||||||
|
log.debug(this, "update", id, version);
|
||||||
|
|
||||||
|
Data item = map.get(id);
|
||||||
|
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
item = new Data(id);
|
||||||
|
map.put(id, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.isDirty())
|
||||||
|
{
|
||||||
|
log.debug("store updating data", id);
|
||||||
|
item.markLoad(bytes,version);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.debug("store not updating data because of conflict", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(ID id, Version version, byte[] bytes)
|
||||||
|
{
|
||||||
|
assert(isWritable());
|
||||||
|
|
||||||
|
log.debug(this, "put", id, version);
|
||||||
|
|
||||||
|
Data item = map.get(id);
|
||||||
|
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
item = new Data(id);
|
||||||
|
map.put(id, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.markDirty(bytes, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSize ()
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
for (Data item : map.values())
|
||||||
|
size += item.bytes.length;
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWritable ()
|
||||||
|
{
|
||||||
|
return super.isWritable() && !locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isFull ()
|
||||||
|
{
|
||||||
|
if (requestedMaxSize < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return getSize() > requestedMaxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return super.toString() + " " + (getSize()/1000) + "k " + (isFull() ? "Full" :"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lock ()
|
||||||
|
{
|
||||||
|
locked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStored ()
|
||||||
|
{
|
||||||
|
super.onStored();
|
||||||
|
|
||||||
|
for (Data item : map.values())
|
||||||
|
{
|
||||||
|
if (item.isDirty())
|
||||||
|
{
|
||||||
|
item.markStore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<?, ? extends Item> getItemMap()
|
||||||
|
{
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug (LogOut log, String prefix)
|
||||||
|
{
|
||||||
|
final String PREFIX = " |--";
|
||||||
|
log.debug (prefix+" store:",this);
|
||||||
|
for (Entry<ID, Data> entry : map.entrySet())
|
||||||
|
entry.getValue().debug(log, prefix+PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
19
java/core/src/core/mail/client/cache/StoreFactory.java
vendored
Normal file
19
java/core/src/core/mail/client/cache/StoreFactory.java
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
public class StoreFactory implements ItemFactory
|
||||||
|
{
|
||||||
|
int requestedMaxSize;
|
||||||
|
|
||||||
|
public StoreFactory (int requestedMaxSize)
|
||||||
|
{
|
||||||
|
this.requestedMaxSize = requestedMaxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item instantiate(Type type) {
|
||||||
|
return new Store(requestedMaxSize);
|
||||||
|
}
|
||||||
|
}
|
354
java/core/src/core/mail/client/cache/StoreLibrary.java
vendored
Normal file
354
java/core/src/core/mail/client/cache/StoreLibrary.java
vendored
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.callback.CallbackChain;
|
||||||
|
import core.callback.CallbackDefault;
|
||||||
|
import core.callback.CallbackEmpty;
|
||||||
|
import core.callbacks.SaveArguments;
|
||||||
|
import core.connector.FileInfo;
|
||||||
|
import core.connector.async.AsyncStoreConnector;
|
||||||
|
import core.connector.async.Lock;
|
||||||
|
import core.constants.ConstantsStorage;
|
||||||
|
import core.crypt.Cryptor;
|
||||||
|
import core.crypt.CryptorAES;
|
||||||
|
import core.crypt.CryptorSeed;
|
||||||
|
import core.crypt.HashSha256;
|
||||||
|
import core.util.Arrays;
|
||||||
|
import core.util.Comparators;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Maps;
|
||||||
|
import core.util.Pair;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
public class StoreLibrary
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(StoreLibrary.class);
|
||||||
|
|
||||||
|
protected Map<String, String> versions = new HashMap<String, String>();
|
||||||
|
protected Map<String, Store> stores = new HashMap<String, Store>();
|
||||||
|
protected Map<String, Cryptor> cryptors = new HashMap<String, Cryptor>();
|
||||||
|
|
||||||
|
ItemSerializer storeSerializer;
|
||||||
|
ItemFactory storeFactory;
|
||||||
|
|
||||||
|
HashSha256 derivedKeyGenerator = new HashSha256();
|
||||||
|
CryptorSeed cryptorSeed;
|
||||||
|
|
||||||
|
Lock flushLock;
|
||||||
|
AsyncStoreConnector storeConnector;
|
||||||
|
|
||||||
|
public StoreLibrary (CryptorSeed cryptorSeed, StoreFactory storeFactory, AsyncStoreConnector storeConnector)
|
||||||
|
{
|
||||||
|
this.cryptorSeed = cryptorSeed;
|
||||||
|
this.storeSerializer = new StoreSerializer();
|
||||||
|
this.storeFactory = storeFactory;
|
||||||
|
this.storeConnector = storeConnector;
|
||||||
|
flushLock =
|
||||||
|
new Lock(
|
||||||
|
storeConnector,
|
||||||
|
ConstantsStorage.CACHE_PREFIX + "flush.lock",
|
||||||
|
ConstantsStorage.FLUSH_LOCK_TIME_SECONDS,
|
||||||
|
ConstantsStorage.FLUSH_LOCK_TIME_ALLOWED_BEFORE_RELOCK_SECONDS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullPathFor (String relative)
|
||||||
|
{
|
||||||
|
return ConstantsStorage.CACHE_PREFIX + relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Store instantiate (String prefix, ID id, boolean isNew)
|
||||||
|
{
|
||||||
|
log.debug("instantiating", prefix, id, isNew);
|
||||||
|
|
||||||
|
Store store = (Store)storeFactory.instantiate(null);
|
||||||
|
String relative = id != null ? (prefix + "_" + id.toFileSystemSafe()) : prefix;
|
||||||
|
stores.put(relative, store);
|
||||||
|
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
store.markCreate();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loadSingleStore_(relative).invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] createCryptorKeyFor (String key)
|
||||||
|
{
|
||||||
|
byte[] data = Arrays.concat(Strings.toBytes(key), cryptorSeed.seed);
|
||||||
|
return derivedKeyGenerator.hash(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cryptor getOrCreateCryptorForKey (String key)
|
||||||
|
{
|
||||||
|
Cryptor cryptor = cryptors.get(key);
|
||||||
|
if (cryptor == null)
|
||||||
|
{
|
||||||
|
cryptor = new CryptorAES(createCryptorKeyFor(key));
|
||||||
|
cryptors.put(key, cryptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cryptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback load_ (String key)
|
||||||
|
{
|
||||||
|
log.debug("load_", key);
|
||||||
|
|
||||||
|
Store store = stores.get(key);
|
||||||
|
SaveArguments saveArgs = new SaveArguments();
|
||||||
|
Cryptor cryptor = getOrCreateCryptorForKey(key);
|
||||||
|
|
||||||
|
return
|
||||||
|
log.debug_("loading", key)
|
||||||
|
.addCallback(saveArgs)
|
||||||
|
.addCallback(cryptor.decrypt_())
|
||||||
|
.addCallback(storeSerializer.deserialize_(store))
|
||||||
|
.addCallback(store.markLoad_(Version.random()))
|
||||||
|
.addCallback(saveArgs.restore_(101))
|
||||||
|
.addCallback(Maps.put_(versions, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback store_ (String key, Version version)
|
||||||
|
{
|
||||||
|
log.debug("store_", key);
|
||||||
|
|
||||||
|
Store store = stores.get(key);
|
||||||
|
Cryptor cryptor = getOrCreateCryptorForKey(key);
|
||||||
|
|
||||||
|
return
|
||||||
|
log.debug_("storing", key)
|
||||||
|
.addCallback(flushLock.relock_())
|
||||||
|
.addCallback(storeSerializer.serialize_(store))
|
||||||
|
.addCallback(cryptor.encrypt_())
|
||||||
|
.addCallback(storeConnector.put_(getFullPathFor(key)))
|
||||||
|
.addCallback(Maps.put_(versions, key))
|
||||||
|
.addCallback(store.markStore_(version));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SortByCacheType implements Comparator<String>
|
||||||
|
{
|
||||||
|
static String priority = "MCFI";
|
||||||
|
|
||||||
|
public int getPriority(String s)
|
||||||
|
{
|
||||||
|
String file = s.substring(s.lastIndexOf("/")+1);
|
||||||
|
return priority.indexOf(file.charAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(String lhs, String rhs) {
|
||||||
|
return getPriority((String)rhs) - getPriority((String)lhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback checkIncludesMainIndex_()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object...arguments) throws Exception
|
||||||
|
{
|
||||||
|
log.debug("found files");
|
||||||
|
List<FileInfo> fileInfos = (List<FileInfo>)arguments[0];
|
||||||
|
|
||||||
|
boolean hasMainIndex = false;
|
||||||
|
for (FileInfo info : fileInfos)
|
||||||
|
{
|
||||||
|
log.trace(info.path);
|
||||||
|
|
||||||
|
if (info.path.endsWith("/I"))
|
||||||
|
hasMainIndex = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMainIndex)
|
||||||
|
throw new Exception("Main index not found!");
|
||||||
|
|
||||||
|
next(fileInfos);
|
||||||
|
}
|
||||||
|
} ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback getFilesToLoad_()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object...arguments)
|
||||||
|
{
|
||||||
|
log.debug("getFilesToLoad_");
|
||||||
|
|
||||||
|
List<FileInfo> fileInfos = (List<FileInfo>)arguments[0];
|
||||||
|
List<Pair<String, String>> filesToLoad = new ArrayList<Pair<String, String>>();
|
||||||
|
|
||||||
|
for (FileInfo info : fileInfos)
|
||||||
|
{
|
||||||
|
boolean storeForFileIsInstantiated =
|
||||||
|
stores.containsKey(info.relativePath);
|
||||||
|
|
||||||
|
if (storeForFileIsInstantiated)
|
||||||
|
{
|
||||||
|
log.trace("storeForFileIsInstantiated", info.relativePath);
|
||||||
|
String remoteVersion = info.version;
|
||||||
|
String localVersion = versions.get(info.relativePath);
|
||||||
|
|
||||||
|
if (!remoteVersion.equals(localVersion))
|
||||||
|
{
|
||||||
|
log.debug("will load");
|
||||||
|
filesToLoad.add(new Pair<String, String>(info.relativePath, remoteVersion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(filesToLoad, new Comparators.SortByFirst<String>(new SortByCacheType()));
|
||||||
|
next(filesToLoad);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback handleFileInfos_ () {
|
||||||
|
return
|
||||||
|
new CallbackDefault() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
log.debug("handleFileInfos_");
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Pair<String, String>> filesToLoad = (List<Pair<String, String>>)arguments[0];
|
||||||
|
|
||||||
|
CallbackChain chain = new CallbackChain();
|
||||||
|
for (Pair<String, String> i : filesToLoad)
|
||||||
|
{
|
||||||
|
log.trace("building load sequence", i.first);
|
||||||
|
|
||||||
|
Callback callback =
|
||||||
|
storeConnector.get_(getFullPathFor(i.first))
|
||||||
|
.addCallback(load_(i.first));
|
||||||
|
|
||||||
|
chain.addCallback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("starting load sequence");
|
||||||
|
chain.setReturn(callback);
|
||||||
|
chain.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback loadSingleStore_(String fileName)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
storeConnector.get_(getFullPathFor(fileName))
|
||||||
|
.addCallback(load_(fileName));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback update_ (boolean shouldTestLock)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
storeConnector.list_(ConstantsStorage.CACHE_PREFIX)
|
||||||
|
.addCallback(shouldTestLock ? flushLock.testLock_() : new CallbackEmpty())
|
||||||
|
.addCallback(shouldTestLock ? new CallbackEmpty() : checkIncludesMainIndex_())
|
||||||
|
.addCallback(getFilesToLoad_())
|
||||||
|
.addCallback(handleFileInfos_());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback flushDirty_()
|
||||||
|
{
|
||||||
|
log.debug("flushDirty_");
|
||||||
|
List<Pair<String, Version>> filesToStore = new ArrayList<Pair<String,Version>>();
|
||||||
|
|
||||||
|
for (Map.Entry<String,Store> p : stores.entrySet())
|
||||||
|
{
|
||||||
|
log.trace("flushDirty_ iterating over", p.getKey(), p.getValue());
|
||||||
|
Store store = p.getValue();
|
||||||
|
if (store.hasDirtyChildren())
|
||||||
|
{
|
||||||
|
log.debug("will store",p.getKey(),p.getValue());
|
||||||
|
store.lock();
|
||||||
|
filesToStore.add(new Pair<String,Version>(p.getKey(),p.getValue().getLocalVersion()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(
|
||||||
|
filesToStore,
|
||||||
|
new Comparators.SortByFirstReverse<String>(new SortByCacheType())
|
||||||
|
);
|
||||||
|
|
||||||
|
CallbackChain chain = new CallbackChain();
|
||||||
|
for (Pair<String, Version> file : filesToStore)
|
||||||
|
{
|
||||||
|
log.debug("will store (ordered):", file.first);
|
||||||
|
chain.addCallback(store_(file.first, file.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback flushDirty__ ()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
flushDirty_().setReturn(callback).invoke(arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasDirtyChildren ()
|
||||||
|
{
|
||||||
|
for (Map.Entry<String,Store> p : stores.entrySet())
|
||||||
|
{
|
||||||
|
Store store = p.getValue();
|
||||||
|
if (store.hasDirtyChildren())
|
||||||
|
{
|
||||||
|
log.debug("found dirty store");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Callback flush_ ()
|
||||||
|
{
|
||||||
|
return new CallbackDefault() {
|
||||||
|
public void onSuccess(Object... arguments) throws Exception {
|
||||||
|
if (hasDirtyChildren())
|
||||||
|
{
|
||||||
|
call (
|
||||||
|
flushLock.lock_()
|
||||||
|
.addCallback (update_(true))
|
||||||
|
.addCallback (flushDirty__())
|
||||||
|
.addCallback(flushLock.unlock_())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(Callback callback)
|
||||||
|
{
|
||||||
|
log.debug("start!");
|
||||||
|
|
||||||
|
update_(false).addCallback(callback).invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
java/core/src/core/mail/client/cache/StoreSerializer.java
vendored
Normal file
72
java/core/src/core/mail/client/cache/StoreSerializer.java
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import mail.client.cache.Store.Data;
|
||||||
|
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Pair;
|
||||||
|
import core.util.Streams;
|
||||||
|
|
||||||
|
public class StoreSerializer extends ItemSerializerSync
|
||||||
|
{
|
||||||
|
protected final static byte VERSION = 1;
|
||||||
|
|
||||||
|
static LogNull log = new LogNull(StoreSerializer.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] serialize(Item item) throws IOException
|
||||||
|
{
|
||||||
|
log.debug(this, "serialize", item);
|
||||||
|
|
||||||
|
Store store = (Store)item;
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
bos.write(VERSION);
|
||||||
|
|
||||||
|
Streams.writeInt(bos, store.map.size());
|
||||||
|
for (Entry<ID, Data> value : store.map.entrySet())
|
||||||
|
{
|
||||||
|
Streams.writeBoundedArray(bos, value.getKey().serialize());
|
||||||
|
Streams.writeBoundedArray(bos, value.getValue().bytes);
|
||||||
|
Streams.writeBoundedArray(bos, value.getValue().getLocalVersion().toBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deserialize(Item item, byte[] bytes) throws IOException
|
||||||
|
{
|
||||||
|
log.debug(this, "deserialize", item);
|
||||||
|
|
||||||
|
Store store = (Store)item;
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||||
|
int version = bis.read();
|
||||||
|
|
||||||
|
// NEVER clear, because, we may overwrite
|
||||||
|
// store.map.clear();
|
||||||
|
// deletes happen through writing a zero byte array, not by removing
|
||||||
|
|
||||||
|
int size = Streams.readInt(bis);
|
||||||
|
for (int i=0; i<size; ++i)
|
||||||
|
{
|
||||||
|
byte[] id = Streams.readBoundedArray(bis);
|
||||||
|
byte[] value = Streams.readBoundedArray(bis);
|
||||||
|
byte[] itemVersion = Streams.readBoundedArray(bis);
|
||||||
|
|
||||||
|
store.update(ID.deserialize(id), new Version(itemVersion), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
java/core/src/core/mail/client/cache/Type.java
vendored
Normal file
17
java/core/src/core/mail/client/cache/Type.java
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
Mail,
|
||||||
|
Conversation,
|
||||||
|
FolderRepository,
|
||||||
|
FolderPart,
|
||||||
|
FolderFilter,
|
||||||
|
FolderFilterSet,
|
||||||
|
FolderMaster,
|
||||||
|
Index,
|
||||||
|
Settings
|
||||||
|
}
|
60
java/core/src/core/mail/client/cache/Version.java
vendored
Normal file
60
java/core/src/core/mail/client/cache/Version.java
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.cache;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import core.util.Base64;
|
||||||
|
import core.util.FastRandom;
|
||||||
|
|
||||||
|
public class Version
|
||||||
|
{
|
||||||
|
public static final Version
|
||||||
|
DELETED = Version.fromLong(-1),
|
||||||
|
NONE = Version.fromLong(0);
|
||||||
|
|
||||||
|
static FastRandom fastRandom = new FastRandom();
|
||||||
|
|
||||||
|
protected String value;
|
||||||
|
|
||||||
|
public Version(byte[] value)
|
||||||
|
{
|
||||||
|
this.value = Base64.encode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Version fromLong (long value)
|
||||||
|
{
|
||||||
|
return new Version(new BigInteger("" + value).toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toBytes()
|
||||||
|
{
|
||||||
|
return Base64.decode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Version random ()
|
||||||
|
{
|
||||||
|
long i=0;
|
||||||
|
while (i == 0)
|
||||||
|
i = Math.abs(fastRandom.nextLong());
|
||||||
|
|
||||||
|
return Version.fromLong(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object rhs) {
|
||||||
|
if (this == rhs)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (rhs != null && ((Version)rhs).value.equals(this.value))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
122
java/core/src/core/mail/client/model/AddressBook.java
Normal file
122
java/core/src/core/mail/client/model/AddressBook.java
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.Exportable;
|
||||||
|
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
|
||||||
|
@Export
|
||||||
|
public class AddressBook implements Exportable
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(AddressBook.class);
|
||||||
|
|
||||||
|
List<Identity> people;
|
||||||
|
Map<String, Identity> indexedByEmail;
|
||||||
|
|
||||||
|
public AddressBook ()
|
||||||
|
{
|
||||||
|
people = new ArrayList<Identity>();
|
||||||
|
indexedByEmail = new HashMap<String,Identity>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasIdentity (UnregisteredIdentity identity)
|
||||||
|
{
|
||||||
|
return indexedByEmail.containsKey(identity.getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Identity getIdentity (UnregisteredIdentity identity)
|
||||||
|
{
|
||||||
|
Identity stored = indexedByEmail.get(identity.getEmail());
|
||||||
|
|
||||||
|
if (stored != null)
|
||||||
|
{
|
||||||
|
log.debug("getIdentity",identity,"exists");
|
||||||
|
clearIndices (stored);
|
||||||
|
stored.copyFrom (identity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.debug("getIdentity",identity,"new");
|
||||||
|
stored = new Identity();
|
||||||
|
stored.copyFrom(identity);
|
||||||
|
people.add(stored);
|
||||||
|
}
|
||||||
|
|
||||||
|
index(stored);
|
||||||
|
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Identity> parseAddressString (String s)
|
||||||
|
{
|
||||||
|
List<Identity> list = new ArrayList<Identity>();
|
||||||
|
String[] split = s.split(",");
|
||||||
|
for (String address : split)
|
||||||
|
{
|
||||||
|
address = address.trim();
|
||||||
|
if (!address.isEmpty())
|
||||||
|
list.add(getIdentity (new UnregisteredIdentity(address)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Identity> parseUnfinishedAddressString (String s)
|
||||||
|
{
|
||||||
|
List<Identity> list = new ArrayList<Identity>();
|
||||||
|
String[] split = s.split(",");
|
||||||
|
for (String address : split)
|
||||||
|
{
|
||||||
|
address = address.trim();
|
||||||
|
if (!address.isEmpty())
|
||||||
|
{
|
||||||
|
UnregisteredIdentity uri = new UnregisteredIdentity (address);
|
||||||
|
if (hasIdentity(uri))
|
||||||
|
list.add(getIdentity(uri));
|
||||||
|
else
|
||||||
|
list.add(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeIdentity (Identity identity)
|
||||||
|
{
|
||||||
|
Identity stored = indexedByEmail.get(identity.getEmail());
|
||||||
|
|
||||||
|
if (stored != null)
|
||||||
|
{
|
||||||
|
clearIndices (stored);
|
||||||
|
people.remove(stored);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void clearIndices (Identity identity)
|
||||||
|
{
|
||||||
|
indexedByEmail.values().remove(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void index (Identity identity)
|
||||||
|
{
|
||||||
|
if (identity.email != null)
|
||||||
|
{
|
||||||
|
indexedByEmail.put(identity.email, identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Identity> getAddressList ()
|
||||||
|
{
|
||||||
|
return people;
|
||||||
|
}
|
||||||
|
}
|
119
java/core/src/core/mail/client/model/Attachment.java
Normal file
119
java/core/src/core/mail/client/model/Attachment.java
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.Exportable;
|
||||||
|
|
||||||
|
import core.util.Base64;
|
||||||
|
|
||||||
|
import core.crypt.HashSha256;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
@Export()
|
||||||
|
public class Attachment implements Exportable
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Attachment.class);
|
||||||
|
|
||||||
|
String id;
|
||||||
|
String disposition;
|
||||||
|
String mimeType;
|
||||||
|
byte[] data = null;
|
||||||
|
|
||||||
|
boolean loaded = false;
|
||||||
|
|
||||||
|
public Attachment (String id, String disposition, String mimeType)
|
||||||
|
{
|
||||||
|
log.debug("Attachment", id,disposition,mimeType);
|
||||||
|
|
||||||
|
this.disposition = disposition;
|
||||||
|
this.id = id;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public String getAttachmentId (String disposition, String id) throws Exception
|
||||||
|
{
|
||||||
|
boolean hasDisposition = disposition != null;
|
||||||
|
boolean hasId = id!=null;
|
||||||
|
boolean hasFileName = false;
|
||||||
|
if (hasDisposition)
|
||||||
|
{
|
||||||
|
String oneLineDisposition = Strings.concat(Strings.splitLines(disposition)," ");
|
||||||
|
hasFileName = oneLineDisposition.toLowerCase().matches(".*filename=.*");
|
||||||
|
log.debug(oneLineDisposition, "hasFileName", hasFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasDisposition || hasId)
|
||||||
|
{
|
||||||
|
if (hasId)
|
||||||
|
return id;
|
||||||
|
|
||||||
|
if (hasFileName)
|
||||||
|
return calculateId(disposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static protected String calculateId (String disposition) throws Exception
|
||||||
|
{
|
||||||
|
HashSha256 hash = new HashSha256();
|
||||||
|
return Base64.encode(hash.hash(Strings.toBytes(disposition)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDataBase64 ()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
log.debug("getDataBase64 a ", data.length, " ", new Date());
|
||||||
|
|
||||||
|
return Base64.encode(data);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
log.debug("getDataBase64 b", new Date());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId ()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData ()
|
||||||
|
{
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData (byte[] data)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearData ()
|
||||||
|
{
|
||||||
|
this.data = null;
|
||||||
|
loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisposition ()
|
||||||
|
{
|
||||||
|
return disposition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType ()
|
||||||
|
{
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoaded ()
|
||||||
|
{
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
}
|
126
java/core/src/core/mail/client/model/Attachments.java
Normal file
126
java/core/src/core/mail/client/model/Attachments.java
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import mail.client.ArrivalsProcessor;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.Exportable;
|
||||||
|
|
||||||
|
import core.util.Base64;
|
||||||
|
import core.constants.ConstantsMailJson;
|
||||||
|
import core.util.JSON_;
|
||||||
|
import core.util.JSON_.JSONException;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
@Export
|
||||||
|
public class Attachments implements Exportable
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Attachments.class);
|
||||||
|
|
||||||
|
List<Attachment> attachments;
|
||||||
|
boolean loaded = false;
|
||||||
|
|
||||||
|
public Attachments ()
|
||||||
|
{
|
||||||
|
attachments = new ArrayList<Attachment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAttachment (Attachment attachment)
|
||||||
|
{
|
||||||
|
attachments.add(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAttachmentId (Attachment attachment)
|
||||||
|
{
|
||||||
|
attachments.remove(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Attachment> getList ()
|
||||||
|
{
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attachment getAttachment (String id)
|
||||||
|
{
|
||||||
|
log.debug("getAttachment", id);
|
||||||
|
|
||||||
|
if (id == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (Attachment a : attachments)
|
||||||
|
{
|
||||||
|
log.debug("comparing",a.getId(), id);
|
||||||
|
if (a.getId().equals(id))
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoaded (boolean loaded)
|
||||||
|
{
|
||||||
|
this.loaded = loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoaded ()
|
||||||
|
{
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadFrom(byte[] bs) throws Exception
|
||||||
|
{
|
||||||
|
String text = Strings.toString(bs);
|
||||||
|
log.debug("loading json", text);
|
||||||
|
Object json = JSON_.parse(text);
|
||||||
|
Object content = JSON_.getObject(json, ConstantsMailJson.Content);
|
||||||
|
|
||||||
|
List<Object> contents = new ArrayList<Object>();
|
||||||
|
contents.add(content);
|
||||||
|
while (contents.size() > 0)
|
||||||
|
{
|
||||||
|
Object c = contents.get(0);
|
||||||
|
contents.remove(0);
|
||||||
|
|
||||||
|
String clazz = JSON_.getString(c, ConstantsMailJson.Class);
|
||||||
|
Object value = JSON_.has(c, ConstantsMailJson.Value) ?
|
||||||
|
JSON_.get(c,ConstantsMailJson.Value) : null;
|
||||||
|
|
||||||
|
if (clazz.equals(ConstantsMailJson.MultiPart))
|
||||||
|
{
|
||||||
|
for (int i=0; i<JSON_.size(value); ++i)
|
||||||
|
{
|
||||||
|
Object valueContent = JSON_.getObject(value, i);
|
||||||
|
contents.add(valueContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (clazz.equals(ConstantsMailJson.Bytes))
|
||||||
|
{
|
||||||
|
String contentDisposition = ArrivalsProcessor.getFirstHeader(c, "Content-Disposition", "None");
|
||||||
|
String contentId = ArrivalsProcessor.getFirstHeader(c, "Content-Id", "None");
|
||||||
|
|
||||||
|
String attachmentId = Attachment.getAttachmentId(contentDisposition, contentId);
|
||||||
|
if (attachmentId != null)
|
||||||
|
{
|
||||||
|
for (Attachment i : attachments)
|
||||||
|
{
|
||||||
|
if (attachmentId.equals(i.getId()))
|
||||||
|
i.setData(Base64.decode(JSON_.asString(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoaded(true);
|
||||||
|
}
|
||||||
|
}
|
386
java/core/src/core/mail/client/model/Body.java
Normal file
386
java/core/src/core/mail/client/model/Body.java
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.Exportable;
|
||||||
|
|
||||||
|
import core.util.Characters;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.Pair;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
@Export()
|
||||||
|
public class Body implements Exportable
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Body.class);
|
||||||
|
|
||||||
|
String text, html;
|
||||||
|
|
||||||
|
public Body ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Body(Body body)
|
||||||
|
{
|
||||||
|
this.text = body.text;
|
||||||
|
this.html = body.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasText()
|
||||||
|
{
|
||||||
|
return text != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText()
|
||||||
|
{
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text)
|
||||||
|
{
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasHTML()
|
||||||
|
{
|
||||||
|
return html != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHTML()
|
||||||
|
{
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static class Patterns {
|
||||||
|
static Pattern
|
||||||
|
removeStartHtml = Pattern.compile(".*<\\s*+html.*?>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
|
||||||
|
removeHeadBlock = Pattern.compile(".*<\\s*+\\/head.*?>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
|
||||||
|
removeScripting = Pattern.compile("<\\s*+script[\\s>]++.*?<\\s*+\\/script.*?>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
|
||||||
|
removeBeforeBody1 = Pattern.compile(".*\\<body\\>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
|
||||||
|
removeBeforeBody2 = Pattern.compile(".*\\<body\\s", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
|
||||||
|
removeAfterBody = Pattern.compile("<\\s*+\\/body[\\s>]++.*", Pattern.CASE_INSENSITIVE | Pattern.DOTALL),
|
||||||
|
removeEndHtml = Pattern.compile("<\\s*+\\/html[\\s>]++.*", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
public String getStrippedHTML ()
|
||||||
|
{
|
||||||
|
return stripHTML(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStrippedText ()
|
||||||
|
{
|
||||||
|
return stripHTML(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* too slow
|
||||||
|
public String stripHTML (String html)
|
||||||
|
{
|
||||||
|
if (html == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
log.debug("stripHTML");
|
||||||
|
|
||||||
|
// remove the <html> tag
|
||||||
|
html = Patterns.removeStartHtml.matcher(html).replaceFirst("");
|
||||||
|
log.debug("after removeStartHtml");
|
||||||
|
|
||||||
|
// try to remove the head block
|
||||||
|
html = Patterns.removeHeadBlock.matcher(html).replaceFirst("");
|
||||||
|
log.debug("after removeHeadBlock");
|
||||||
|
|
||||||
|
// remove scripting if possible
|
||||||
|
html = Patterns.removeScripting.matcher(html).replaceAll("");
|
||||||
|
log.debug("after removeScripting");
|
||||||
|
|
||||||
|
// remove before the body
|
||||||
|
html = Patterns.removeBeforeBody1.matcher(html).replaceFirst("<div>");
|
||||||
|
log.debug("after removeBeforeBody1");
|
||||||
|
|
||||||
|
html = Patterns.removeBeforeBody2.matcher(html).replaceFirst("<div ");
|
||||||
|
log.debug("after removeBeforeBody2");
|
||||||
|
|
||||||
|
// remove after the body
|
||||||
|
html = Patterns.removeAfterBody.matcher(html).replaceFirst("</div>");
|
||||||
|
log.debug("after removeAfterBody");
|
||||||
|
|
||||||
|
// remove the html tag ender and everyhting aftewards
|
||||||
|
html = Patterns.removeEndHtml.matcher(html).replaceAll("");
|
||||||
|
log.debug("after removeEndHtml");
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* maybe for another day
|
||||||
|
public String stripHTML (String html) throws Exception
|
||||||
|
{
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
dbf.setValidating(false);
|
||||||
|
dbf.setNamespaceAware(true);
|
||||||
|
dbf.setIgnoringComments(false);
|
||||||
|
dbf.setIgnoringElementContentWhitespace(false);
|
||||||
|
dbf.setExpandEntityReferences(false);
|
||||||
|
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||||
|
Document doc = db.parse(new StringInputStream(html));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected static Pair<Integer, Integer> findTag(boolean last, String html, String tag, Pair<Integer, Integer> search)
|
||||||
|
{
|
||||||
|
// adkjfkasdflk asdlkasd < html > ojwefijoweiofjoiwjfwe
|
||||||
|
// find: "html"
|
||||||
|
|
||||||
|
Pair<Integer,Integer> range = Pair.create(search.first, search.second);
|
||||||
|
int pos = -1;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (last)
|
||||||
|
{
|
||||||
|
if (pos != -1)
|
||||||
|
range.second = pos-1;
|
||||||
|
|
||||||
|
pos = html.lastIndexOf(tag, range.second);
|
||||||
|
if (pos == -1 || pos < range.first)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (pos != -1)
|
||||||
|
range.first = pos + tag.length();
|
||||||
|
|
||||||
|
if (html.length() <= range.first + tag.length())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
pos = html.indexOf(tag, range.first);
|
||||||
|
if (pos == -1 || pos > range.second)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// search backwards for <
|
||||||
|
boolean restart = false;
|
||||||
|
int i;
|
||||||
|
for (i=pos-1; i>=0; i--)
|
||||||
|
{
|
||||||
|
char c = html.charAt(i);
|
||||||
|
if (c=='<')
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
// wan't our tag signal for restart search
|
||||||
|
if (!Characters.isWhitespace(c))
|
||||||
|
{
|
||||||
|
restart = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// restart the search if signaled
|
||||||
|
if (restart)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
int j;
|
||||||
|
for (j=pos+tag.length(); j<html.length(); ++j)
|
||||||
|
{
|
||||||
|
char c = html.charAt(j);
|
||||||
|
if (c=='>')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// restart the search
|
||||||
|
if (j == html.length())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
log.debug("found",tag,i,j);
|
||||||
|
|
||||||
|
return Pair.create(i,j+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void andLeftRange (Pair<Integer, Integer> range, Pair<Integer,Integer> tag)
|
||||||
|
{
|
||||||
|
if (tag == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tag.second > range.first)
|
||||||
|
range.first = tag.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void andRightRange (Pair<Integer, Integer> range, Pair<Integer,Integer> tag)
|
||||||
|
{
|
||||||
|
if (tag == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tag.first < range.second)
|
||||||
|
range.second = tag.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String stripHTML (String html)
|
||||||
|
{
|
||||||
|
if (html == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String lower = html.toLowerCase();
|
||||||
|
Pair<Integer,Integer> range = Pair.create(0, html.length());
|
||||||
|
Pair<Integer,Integer> tag;
|
||||||
|
|
||||||
|
//-----------------
|
||||||
|
|
||||||
|
tag = findTag(true, lower, "body", range);
|
||||||
|
andLeftRange(range,tag);
|
||||||
|
|
||||||
|
tag = findTag(true, lower, "html", range);
|
||||||
|
andLeftRange(range,tag);
|
||||||
|
|
||||||
|
tag = findTag(true, lower, "/head", range);
|
||||||
|
andLeftRange(range,tag);
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
tag = findTag(false, lower, "/body", range);
|
||||||
|
andRightRange(range,tag);
|
||||||
|
|
||||||
|
tag = findTag(false, lower, "/html", range);
|
||||||
|
andRightRange(range,tag);
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
String result = html.substring(range.first, range.second);
|
||||||
|
log.debug("stripHTML", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public static void main (String[] args)
|
||||||
|
{
|
||||||
|
String html = "kjhasdfkjakjd <nothtml> < html > <nothtml> < body > woeifjweijf <nothtml> </body> </nothtml> </html> <nothtml>";
|
||||||
|
System.out.println(stripHTML(html));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void setHTML(String html)
|
||||||
|
{
|
||||||
|
this.html = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String calculateBrief ()
|
||||||
|
{
|
||||||
|
if (text == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String content = calculateTextWithoutReply();
|
||||||
|
int length = Math.min(content.length(), 256);
|
||||||
|
String brief = content.substring(0, length).replace("\n", " ");
|
||||||
|
|
||||||
|
return brief;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String calculateReply ()
|
||||||
|
{
|
||||||
|
if (text == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ArrayList<String> lines = new ArrayList<String>();
|
||||||
|
BufferedReader r = new BufferedReader(new StringReader(text));
|
||||||
|
String line;
|
||||||
|
while ((line = r.readLine()) != null)
|
||||||
|
{
|
||||||
|
lines.add("> " + line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Strings.concat(lines.iterator(), "\n").trim();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return "Failed to calculate reply";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProbablyReplyHeader (String s)
|
||||||
|
{
|
||||||
|
return (s.endsWith(":") && s.contains("On"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPossiblyQuoteBeginning (String s)
|
||||||
|
{
|
||||||
|
return s.startsWith("--") || s.startsWith("==");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String calculateTextWithoutReply ()
|
||||||
|
{
|
||||||
|
if (text == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ArrayList<String> lines = new ArrayList<String>();
|
||||||
|
BufferedReader r = new BufferedReader(new StringReader(text));
|
||||||
|
String line;
|
||||||
|
|
||||||
|
boolean alreadyFoundReply = false;
|
||||||
|
int possiblyFoundQuote = -1;
|
||||||
|
while ((line = r.readLine()) != null)
|
||||||
|
{
|
||||||
|
if (line.startsWith(">"))
|
||||||
|
{
|
||||||
|
if (!alreadyFoundReply)
|
||||||
|
{
|
||||||
|
alreadyFoundReply = true;
|
||||||
|
|
||||||
|
for (int i=0; i<5; ++i)
|
||||||
|
{
|
||||||
|
int index = (lines.size()-i)-1;
|
||||||
|
if (index < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (isProbablyReplyHeader(lines.get(index)))
|
||||||
|
{
|
||||||
|
assert(index >=0 );
|
||||||
|
while (lines.size() > index)
|
||||||
|
lines.remove(lines.size()-1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lines.add(line);
|
||||||
|
|
||||||
|
if (isPossiblyQuoteBeginning(line))
|
||||||
|
possiblyFoundQuote = lines.size()-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!lines.isEmpty() && lines.get(lines.size()-1).trim().isEmpty())
|
||||||
|
lines.remove(lines.size()-1);
|
||||||
|
|
||||||
|
if (possiblyFoundQuote >= 0 && possiblyFoundQuote > (lines.size()-6))
|
||||||
|
{
|
||||||
|
while (lines.size() > possiblyFoundQuote)
|
||||||
|
lines.remove(lines.size()-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Strings.concat(lines.iterator(), "\n").trim();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
java/core/src/core/mail/client/model/ConstantsMisc.java
Normal file
12
java/core/src/core/mail/client/model/ConstantsMisc.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
public class ConstantsMisc {
|
||||||
|
|
||||||
|
public static String ALL = "All";
|
||||||
|
public static final String REPLY_PREFIX = "Re:";
|
||||||
|
|
||||||
|
}
|
190
java/core/src/core/mail/client/model/Conversation.java
Normal file
190
java/core/src/core/mail/client/model/Conversation.java
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.Exportable;
|
||||||
|
import org.timepedia.exporter.client.NoExport;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import core.util.Collectionz;
|
||||||
|
import core.util.Comparators;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.Pair;
|
||||||
|
import mail.client.CacheManager;
|
||||||
|
import mail.client.Events;
|
||||||
|
import mail.client.cache.ID;
|
||||||
|
|
||||||
|
@Export
|
||||||
|
public class Conversation extends Model implements Exportable
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(Conversation.class);
|
||||||
|
|
||||||
|
static class SortByDateLatestFirst implements Comparator<Conversation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Conversation l, Conversation r)
|
||||||
|
{
|
||||||
|
Date ld = l.getHeader().getDate(), rd = r.getHeader().getDate();
|
||||||
|
return rd.compareTo(ld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Header header;
|
||||||
|
protected List<Pair<ID,Date>> items;
|
||||||
|
protected Set<ID> itemIds = new HashSet<ID>();
|
||||||
|
|
||||||
|
public Conversation (CacheManager manager)
|
||||||
|
{
|
||||||
|
super(manager);
|
||||||
|
reset();
|
||||||
|
|
||||||
|
getLoadCallbacks()
|
||||||
|
.addCallback(manager.getMaster().getEventPropagator().signal_(Events.LoadConversation, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset ()
|
||||||
|
{
|
||||||
|
items = new ArrayList<Pair<ID, Date>>();
|
||||||
|
recomputeHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void recomputeHeader ()
|
||||||
|
{
|
||||||
|
header = new Header();
|
||||||
|
header.setDictionary(new Dictionary());
|
||||||
|
header.setAuthors(new ArrayList<Identity>());
|
||||||
|
header.setRecipients(new Recipients());
|
||||||
|
header.setTransportState(TransportState.NONE());
|
||||||
|
|
||||||
|
for (Pair<ID,Date> p : items)
|
||||||
|
{
|
||||||
|
Mail m = getManager().getMail(p.first);
|
||||||
|
if (m.isLoaded())
|
||||||
|
accumulate(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void accumulate(Mail m)
|
||||||
|
{
|
||||||
|
Header h = m.getHeader();
|
||||||
|
|
||||||
|
if (h.getAuthor() != null)
|
||||||
|
header.getAuthors().add(h.getAuthor());
|
||||||
|
|
||||||
|
if (header.getDate() == null || h.getDate().after(header.getDate()))
|
||||||
|
{
|
||||||
|
header.setDate(h.getDate());
|
||||||
|
header.setBrief(h.getBrief());
|
||||||
|
header.setSubject(h.getSubjectExcludingReplyPrefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (h.getRecipients() != null)
|
||||||
|
header.getRecipients().add(h.getRecipients());
|
||||||
|
|
||||||
|
header.getDictionary().add(m);
|
||||||
|
header.getTransportState().mark(h.getTransportState());
|
||||||
|
header.unmarkState(TransportState.READ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Mail> getItems ()
|
||||||
|
{
|
||||||
|
List<Mail> result = new ArrayList<Mail>(items.size());
|
||||||
|
for (Pair<ID,Date> p : items)
|
||||||
|
{
|
||||||
|
Mail m = getManager().getMail(p.first);
|
||||||
|
result.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Pair<ID,Date>> getItemIds ()
|
||||||
|
{
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItemId (ID id, Date date)
|
||||||
|
{
|
||||||
|
items.add(new Pair<ID,Date>(id,date));
|
||||||
|
Collections.sort(items, new Comparators.SortBySecondNatural<Date>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeItemId (ID id)
|
||||||
|
{
|
||||||
|
Collectionz.removeByFirst(items, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItem (Mail mail)
|
||||||
|
{
|
||||||
|
addItemId (mail.getId(), mail.getHeader().getDate());
|
||||||
|
accumulate (mail);
|
||||||
|
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeItem (Mail mail)
|
||||||
|
{
|
||||||
|
removeItemId (mail.getId());
|
||||||
|
recomputeHeader();
|
||||||
|
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void itemChanged (Mail mail)
|
||||||
|
{
|
||||||
|
for (Pair<ID,Date> p : items)
|
||||||
|
{
|
||||||
|
if (p.first == mail.getId())
|
||||||
|
p.second = mail.getHeader().getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(items, new Comparators.SortBySecondNatural<Date>());
|
||||||
|
recomputeHeader();
|
||||||
|
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Header getHeader ()
|
||||||
|
{
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeader (Header header)
|
||||||
|
{
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumItems ()
|
||||||
|
{
|
||||||
|
return items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markState (String state)
|
||||||
|
{
|
||||||
|
if (!getHeader().hasState(state))
|
||||||
|
{
|
||||||
|
getHeader().markState(state);
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unmarkState (String state)
|
||||||
|
{
|
||||||
|
if (getHeader().hasState(state))
|
||||||
|
{
|
||||||
|
getHeader().unmarkState(state);
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
282
java/core/src/core/mail/client/model/Dictionary.java
Normal file
282
java/core/src/core/mail/client/model/Dictionary.java
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import core.util.Comparators;
|
||||||
|
import core.util.FastRandom;
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogOut;
|
||||||
|
import core.util.Pair;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
public class Dictionary implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
static LogNull log = new LogNull(Dictionary.class);
|
||||||
|
static FastRandom random = new FastRandom();
|
||||||
|
|
||||||
|
public Map<String, Integer> vocabulary;
|
||||||
|
int bayesianSize=0;
|
||||||
|
|
||||||
|
public Dictionary ()
|
||||||
|
{
|
||||||
|
vocabulary = new HashMap<String, Integer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary (String filter)
|
||||||
|
{
|
||||||
|
this();
|
||||||
|
|
||||||
|
add(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary (Mail mail)
|
||||||
|
{
|
||||||
|
this();
|
||||||
|
|
||||||
|
add (mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Integer> getVocabulary ()
|
||||||
|
{
|
||||||
|
return vocabulary;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String TOKENS = " \t\r\n!@#$%^&*()_+-=`~{}[]\\|;:'\",./<>?";
|
||||||
|
|
||||||
|
public Dictionary add(String text)
|
||||||
|
{
|
||||||
|
if (text != null)
|
||||||
|
{
|
||||||
|
StringTokenizer st = new StringTokenizer(text,TOKENS);
|
||||||
|
while (st.hasMoreTokens())
|
||||||
|
{
|
||||||
|
String token = st.nextToken().toLowerCase();
|
||||||
|
|
||||||
|
int occurences = 0;
|
||||||
|
|
||||||
|
if (vocabulary.containsKey(token))
|
||||||
|
occurences = vocabulary.get(token);
|
||||||
|
|
||||||
|
bayesianSize ++;
|
||||||
|
vocabulary.put(token, new Integer(occurences+1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary add (Mail mail)
|
||||||
|
{
|
||||||
|
if (mail.getHeader().getAuthor()!=null)
|
||||||
|
add (mail.getHeader().getAuthor().toString());
|
||||||
|
|
||||||
|
if (mail.getHeader().getRecipients()!=null)
|
||||||
|
for (Identity i : mail.getHeader().getRecipients().getAll())
|
||||||
|
add(i.toString());
|
||||||
|
|
||||||
|
add (mail.getBody().getText());
|
||||||
|
add (mail.getHeader().getSubject());
|
||||||
|
|
||||||
|
log.debug(this, "after add", toSerializableString());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches (Dictionary filter)
|
||||||
|
{
|
||||||
|
final String Q = "\"";
|
||||||
|
|
||||||
|
for (Entry<String, Integer> i : filter.vocabulary.entrySet())
|
||||||
|
{
|
||||||
|
String match = i.getKey();
|
||||||
|
|
||||||
|
boolean exact = (match.startsWith(Q) && match.endsWith(Q));
|
||||||
|
|
||||||
|
if (exact)
|
||||||
|
{
|
||||||
|
match = match.substring(1, match.length()-1);
|
||||||
|
log.debug("match is surrounded by quotes, using exact match:",match);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vocabulary.containsKey(match))
|
||||||
|
{
|
||||||
|
if (exact)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
for (Entry<String, Integer> j : vocabulary.entrySet())
|
||||||
|
{
|
||||||
|
if (j.getKey().startsWith(match))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toSerializableString()
|
||||||
|
{
|
||||||
|
String[] strings = new String[vocabulary.size()];
|
||||||
|
|
||||||
|
int j=0;
|
||||||
|
for (Entry<String, Integer> i : vocabulary.entrySet())
|
||||||
|
{
|
||||||
|
strings[j++] = i.getKey() + ":" + i.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Strings.concat(strings, ",");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fromSerializableString(String string)
|
||||||
|
{
|
||||||
|
String[] strings = string.split(",");
|
||||||
|
|
||||||
|
for (String i : strings)
|
||||||
|
{
|
||||||
|
if (i.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String[] split = i.split(":");
|
||||||
|
int occurences = Integer.parseInt(split[1]);
|
||||||
|
|
||||||
|
bayesianSize += occurences;
|
||||||
|
vocabulary.put(split[0], occurences);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.exception(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add (Dictionary dictionary)
|
||||||
|
{
|
||||||
|
for (Entry<String, Integer> i : dictionary.vocabulary.entrySet())
|
||||||
|
{
|
||||||
|
int occurences = 0;
|
||||||
|
String token = i.getKey();
|
||||||
|
|
||||||
|
if (vocabulary.containsKey(i))
|
||||||
|
occurences = vocabulary.get(token);
|
||||||
|
|
||||||
|
bayesianSize += i.getValue();
|
||||||
|
vocabulary.put(token, new Integer(occurences+i.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bayesianPrune();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subtract(Dictionary dictionary)
|
||||||
|
{
|
||||||
|
for (Entry<String, Integer> i : dictionary.vocabulary.entrySet())
|
||||||
|
{
|
||||||
|
int occurences = 0;
|
||||||
|
String token = i.getKey();
|
||||||
|
|
||||||
|
if (vocabulary.containsKey(i))
|
||||||
|
occurences = vocabulary.get(token);
|
||||||
|
|
||||||
|
bayesianSize -= i.getValue();
|
||||||
|
vocabulary.put(token, new Integer(occurences-i.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bayesianPrune();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float bayesianProbabilityOfTerm (String term)
|
||||||
|
{
|
||||||
|
Integer v = vocabulary.get(term);
|
||||||
|
if (v == null)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
log.trace(this, "bayesianProbabilityOfTerm", term, v, "+1 /", bayesianSize);
|
||||||
|
|
||||||
|
return (float)(v + 1)/(float)bayesianSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float bayesianProbability (Dictionary match)
|
||||||
|
{
|
||||||
|
float probability = 0.0f;
|
||||||
|
for (Entry<String, Integer> i : match.vocabulary.entrySet())
|
||||||
|
{
|
||||||
|
// probability += (float)i.getValue() * bayesianProbabilityOfTerm(i.getKey());
|
||||||
|
probability += bayesianProbabilityOfTerm(i.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
return probability;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bayesianPrune ()
|
||||||
|
{
|
||||||
|
List<Pair<String, Integer>> remove = new ArrayList<Pair<String,Integer>>();
|
||||||
|
|
||||||
|
// remove all negative and zero values
|
||||||
|
for (Entry<String, Integer> i : vocabulary.entrySet())
|
||||||
|
{
|
||||||
|
if (i.getValue() <= 0)
|
||||||
|
remove.add(new Pair<String, Integer>(i.getKey(), i.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Pair<String, Integer> i : remove)
|
||||||
|
{
|
||||||
|
bayesianSize -= i.second;
|
||||||
|
vocabulary.remove(i.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove.clear();
|
||||||
|
|
||||||
|
// remove some parts of the remaining
|
||||||
|
for (Entry<String, Integer> i : vocabulary.entrySet())
|
||||||
|
remove.add(new Pair<String, Integer>(i.getKey(), i.getValue()));
|
||||||
|
|
||||||
|
Collections.sort(remove, new Comparators.SortBySecondNatural<Integer>());
|
||||||
|
|
||||||
|
float numToPossiblyRemove = remove.size() - 100;
|
||||||
|
float numOkAfterThreshold = 100;
|
||||||
|
if (numToPossiblyRemove < numOkAfterThreshold)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float probabilityOfRemoval = numToPossiblyRemove/(numToPossiblyRemove + numOkAfterThreshold);
|
||||||
|
log.trace(this, "bayesianPrune", probabilityOfRemoval, numToPossiblyRemove, numOkAfterThreshold);
|
||||||
|
|
||||||
|
for (int i=0; i<numToPossiblyRemove; ++i)
|
||||||
|
{
|
||||||
|
if (random.nextFloat() < probabilityOfRemoval)
|
||||||
|
{
|
||||||
|
Pair<String,Integer> term = remove.get(i);
|
||||||
|
bayesianSize -= term.second;
|
||||||
|
vocabulary.remove(term.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean bayesianMatches(Dictionary dictionary)
|
||||||
|
{
|
||||||
|
float result = bayesianProbability(dictionary);
|
||||||
|
log.debug(this, "bayesianProbability", result, this.toSerializableString(), dictionary.toSerializableString());
|
||||||
|
|
||||||
|
return result > 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
java/core/src/core/mail/client/model/Direction.java
Normal file
11
java/core/src/core/mail/client/model/Direction.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
public enum Direction
|
||||||
|
{
|
||||||
|
IN,
|
||||||
|
OUT
|
||||||
|
};
|
66
java/core/src/core/mail/client/model/Folder.java
Normal file
66
java/core/src/core/mail/client/model/Folder.java
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import core.callback.Callback;
|
||||||
|
import core.util.Pair;
|
||||||
|
import mail.client.CacheManager;
|
||||||
|
import mail.client.cache.ID;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.Exportable;
|
||||||
|
import org.timepedia.exporter.client.NoExport;
|
||||||
|
|
||||||
|
@Export()
|
||||||
|
public abstract class Folder extends Model implements Exportable
|
||||||
|
{
|
||||||
|
FolderDefinition folderDefinition;
|
||||||
|
|
||||||
|
@NoExport()
|
||||||
|
public Folder (CacheManager manager)
|
||||||
|
{
|
||||||
|
super(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderDefinition getFolderDefinition()
|
||||||
|
{
|
||||||
|
return folderDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFolderDefinition(FolderDefinition folderDefinition)
|
||||||
|
{
|
||||||
|
this.folderDefinition = folderDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return folderDefinition.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
folderDefinition.setName(name);
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract List<Pair<ID,Date>> getConversationIds ();
|
||||||
|
public abstract void addConversationId (ID id, Date date);
|
||||||
|
public abstract boolean isFull ();
|
||||||
|
|
||||||
|
public abstract List<Conversation> getConversations (int from, int length, String filter);
|
||||||
|
public abstract boolean hasConversation (Conversation conversation);
|
||||||
|
public abstract void conversationAdded (Conversation conversation);
|
||||||
|
public abstract void conversationDeleted (Conversation conversation);
|
||||||
|
public abstract Conversation getMatchingConversation (Header header);
|
||||||
|
|
||||||
|
public final void conversationChanged (Conversation conversation)
|
||||||
|
{
|
||||||
|
conversationDeleted(conversation);
|
||||||
|
conversationAdded(conversation);
|
||||||
|
}
|
||||||
|
}
|
167
java/core/src/core/mail/client/model/FolderDefinition.java
Normal file
167
java/core/src/core/mail/client/model/FolderDefinition.java
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.Exportable;
|
||||||
|
|
||||||
|
import core.util.LogNull;
|
||||||
|
import core.util.LogNull;
|
||||||
|
|
||||||
|
@Export
|
||||||
|
public class FolderDefinition implements Exportable
|
||||||
|
{
|
||||||
|
static LogNull log = new LogNull(FolderDefinition.class);
|
||||||
|
|
||||||
|
String name;
|
||||||
|
Identity author;
|
||||||
|
Identity recipient;
|
||||||
|
String subject;
|
||||||
|
TransportState stateEquals, stateDiffers;
|
||||||
|
|
||||||
|
boolean autoBayesian = false;
|
||||||
|
Dictionary bayesianDictionary;
|
||||||
|
|
||||||
|
public FolderDefinition (String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName ()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderDefinition setAuthor(Identity author)
|
||||||
|
{
|
||||||
|
this.author = author;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Identity getAuthor ()
|
||||||
|
{
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderDefinition setRecipient(Identity recipient)
|
||||||
|
{
|
||||||
|
this.recipient = recipient;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Identity getRecipient ()
|
||||||
|
{
|
||||||
|
return recipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderDefinition setSubject(String subject)
|
||||||
|
{
|
||||||
|
this.subject = subject;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubject ()
|
||||||
|
{
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderDefinition setState(TransportState stateEquals, TransportState stateDiffers)
|
||||||
|
{
|
||||||
|
this.stateEquals = stateEquals;
|
||||||
|
this.stateDiffers = stateDiffers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransportState getStateEquals ()
|
||||||
|
{
|
||||||
|
return stateEquals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransportState getStateDiffers ()
|
||||||
|
{
|
||||||
|
return stateDiffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matchesFilter (Conversation conversation)
|
||||||
|
{
|
||||||
|
Header h = conversation.getHeader();
|
||||||
|
|
||||||
|
boolean matches = true;
|
||||||
|
if (matches && author != null)
|
||||||
|
{
|
||||||
|
matches = h.getAuthors().contains(author);
|
||||||
|
}
|
||||||
|
if (matches && recipient != null)
|
||||||
|
{
|
||||||
|
matches = h.getRecipients().contains(recipient);
|
||||||
|
}
|
||||||
|
if (matches && subject != null)
|
||||||
|
{
|
||||||
|
matches = h.getSubject().equals(subject);
|
||||||
|
}
|
||||||
|
if (matches && (stateEquals != null || stateDiffers != null))
|
||||||
|
{
|
||||||
|
boolean equalMatch = stateEquals != null ? (h.getTransportState().hasOne(stateEquals)) : true;
|
||||||
|
boolean differMatch = stateDiffers != null ? (h.getTransportState().hasNone(stateDiffers)) : true;
|
||||||
|
|
||||||
|
matches = equalMatch && differMatch;
|
||||||
|
log.debug("matches filter ", stateEquals, ":!", stateDiffers, " : ", h.getTransportState(), " = ("+ equalMatch, "&", differMatch, ") = ", matches);
|
||||||
|
}
|
||||||
|
if (matches && bayesianDictionary != null && autoBayesian)
|
||||||
|
{
|
||||||
|
matches = bayesianMatches(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary getBayesianDictionary ()
|
||||||
|
{
|
||||||
|
return bayesianDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderDefinition setBayesianDictionary(Dictionary bayesianDictionary)
|
||||||
|
{
|
||||||
|
this.bayesianDictionary = bayesianDictionary;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderDefinition setAutoBayesian (boolean autoBayesian)
|
||||||
|
{
|
||||||
|
this.autoBayesian = autoBayesian;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean bayesianMatches (Conversation conversation)
|
||||||
|
{
|
||||||
|
return bayesianDictionary.bayesianMatches(conversation.getHeader().getDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAutoBayesian ()
|
||||||
|
{
|
||||||
|
return autoBayesian;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void conversationAdded(Conversation conversation)
|
||||||
|
{
|
||||||
|
if (bayesianDictionary != null)
|
||||||
|
{
|
||||||
|
bayesianDictionary.add(conversation.getHeader().getDictionary());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void conversationDeleted(Conversation conversation)
|
||||||
|
{
|
||||||
|
if (bayesianDictionary != null)
|
||||||
|
{
|
||||||
|
bayesianDictionary.subtract(conversation.getHeader().getDictionary());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
java/core/src/core/mail/client/model/FolderFilter.java
Normal file
67
java/core/src/core/mail/client/model/FolderFilter.java
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.NoExport;
|
||||||
|
|
||||||
|
import mail.client.CacheManager;
|
||||||
|
import mail.client.cache.Type;
|
||||||
|
|
||||||
|
public class FolderFilter extends FolderSet
|
||||||
|
{
|
||||||
|
@NoExport
|
||||||
|
public FolderFilter (CacheManager manager)
|
||||||
|
{
|
||||||
|
super(manager, Type.FolderPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLoaded()
|
||||||
|
{
|
||||||
|
super.onLoaded();
|
||||||
|
preCacheMostRecentFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matchesFilter (Conversation conversation)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void conversationAdded (Conversation conversation)
|
||||||
|
{
|
||||||
|
if (matchesFilter(conversation))
|
||||||
|
{
|
||||||
|
super.conversationAdded(conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void conversationDeleted (Conversation conversation)
|
||||||
|
{
|
||||||
|
if (hasConversation(conversation))
|
||||||
|
{
|
||||||
|
super.conversationDeleted(conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void manuallyAdd (Conversation conversation)
|
||||||
|
{
|
||||||
|
if (!super.hasConversation(conversation))
|
||||||
|
{
|
||||||
|
folderDefinition.conversationAdded(conversation);
|
||||||
|
super.conversationAdded(conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void manuallyRemove (Conversation conversation)
|
||||||
|
{
|
||||||
|
if (super.hasConversation(conversation))
|
||||||
|
{
|
||||||
|
folderDefinition.conversationDeleted(conversation);
|
||||||
|
super.conversationDeleted(conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
java/core/src/core/mail/client/model/FolderFilterSet.java
Normal file
18
java/core/src/core/mail/client/model/FolderFilterSet.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import mail.client.CacheManager;
|
||||||
|
import mail.client.cache.Type;
|
||||||
|
|
||||||
|
public class FolderFilterSet extends FolderSet
|
||||||
|
{
|
||||||
|
|
||||||
|
public FolderFilterSet(CacheManager manager)
|
||||||
|
{
|
||||||
|
super(manager, Type.FolderFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
java/core/src/core/mail/client/model/FolderFilterSimple.java
Normal file
27
java/core/src/core/mail/client/model/FolderFilterSimple.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.Exportable;
|
||||||
|
import org.timepedia.exporter.client.NoExport;
|
||||||
|
|
||||||
|
import mail.client.CacheManager;
|
||||||
|
|
||||||
|
@Export()
|
||||||
|
public class FolderFilterSimple extends FolderFilter
|
||||||
|
{
|
||||||
|
@NoExport
|
||||||
|
public FolderFilterSimple(CacheManager manager)
|
||||||
|
{
|
||||||
|
super(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesFilter (Conversation conversation)
|
||||||
|
{
|
||||||
|
return folderDefinition.matchesFilter(conversation);
|
||||||
|
}
|
||||||
|
}
|
88
java/core/src/core/mail/client/model/FolderMaster.java
Normal file
88
java/core/src/core/mail/client/model/FolderMaster.java
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Author: Timothy Prepscius
|
||||||
|
* License: GPLv3 Affero + keep my name in the code!
|
||||||
|
*/
|
||||||
|
package mail.client.model;
|
||||||
|
|
||||||
|
import org.timepedia.exporter.client.Export;
|
||||||
|
import org.timepedia.exporter.client.NoExport;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import core.util.Base64;
|
||||||
|
import core.util.Strings;
|
||||||
|
|
||||||
|
import core.crypt.HashSha256;
|
||||||
|
|
||||||
|
import mail.client.CacheManager;
|
||||||
|
import mail.client.cache.Type;
|
||||||
|
|
||||||
|
@Export()
|
||||||
|
public class FolderMaster extends FolderFilterSet
|
||||||
|
{
|
||||||
|
HashSha256 hasher = new HashSha256();
|
||||||
|
Map<String, Date> externalKeys = new HashMap<String, Date>();
|
||||||
|
Map<String, Date> uidls = new HashMap<String, Date>();
|
||||||
|
|
||||||
|
@NoExport
|
||||||
|
public FolderMaster(CacheManager manager)
|
||||||
|
{
|
||||||
|
super(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String hash (String key)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Base64.encode(hasher.hash(Strings.toBytes(key)));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addExternalKey (String id, Date date)
|
||||||
|
{
|
||||||
|
addExternalKeyHash(hash(id), date);
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addExternalKeyHash (String hash, Date date)
|
||||||
|
{
|
||||||
|
externalKeys.put(hash, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsExternalKey (String id)
|
||||||
|
{
|
||||||
|
return externalKeys.containsKey(hash(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUIDL (String uidl, Date date)
|
||||||
|
{
|
||||||
|
addUIDLHash(hash(uidl), date);
|
||||||
|
markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUIDLHash (String hash, Date date)
|
||||||
|
{
|
||||||
|
uidls.put(hash, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsUIDL (String uidl)
|
||||||
|
{
|
||||||
|
return uidls.containsKey(hash(uidl));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Date> getUIDLHashes()
|
||||||
|
{
|
||||||
|
return uidls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Date> getExternalKeyHashes ()
|
||||||
|
{
|
||||||
|
return externalKeys;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user