added more files

This commit is contained in:
Timothy Prepscius 2013-07-16 22:27:16 -04:00
parent e332e79f6d
commit 20d47dbe50
161 changed files with 14418 additions and 0 deletions

46
.gitignore vendored Normal file
View 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
View 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
View 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>

View 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);
*/
}
}

View 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() } );
}
}

View 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>

View File

@ -0,0 +1 @@
WScript.CreateObject( "WScript.Shell" ).Run("#TARGET#")

View 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

View 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();
}
}

View 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);
}
}

View 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");
}
}

View 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();
}
}
}
}

View 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";
}

View 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";
}
}
}

View 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";
}

View 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";
}

View 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";
}

View 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";
}

View File

@ -0,0 +1,10 @@
package core.constants;
public class ConstantsS3 {
public static final String
AWSAccessKeyId = "AWSAccessKeyId",
AWSSecretKey = "AWSSecretKey",
AWSBucketName = "AWSBucketName",
AWSBucketRegion = "AWSBucketRegion";
}

View 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;
}

View File

@ -0,0 +1,9 @@
package core.constants;
public class ConstantsSettings
{
public static final String
USERNAME = "name",
SIGNATURE = "signature";
}

View 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;
}

View 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";
}

View 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();
}
}

View 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)));
}
}

View 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();
}
}

View File

@ -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();
}
});
}
}

View 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];
}
}

View 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);
}
}

View 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);
}
}

View 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");
}
}

View 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);
}
}

View 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;
}
}

View File

@ -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
}
}

View 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");
}
}

View 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);
}
}
}

View 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;
}
}

View 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();
}
}

View 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);
}
}

View 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;
}
}

View File

@ -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();
}
}

View File

@ -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];
}
}

View 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);
}
}

View 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;
}
}

View 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 ();
}

View 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();
}
};
}
}

View 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);
}
}

View 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));
}
}

View 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;
}
}

View 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);
}

View 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();
}
};
}

View 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));
}
}

View 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);
}
}
}
}

View 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
};

View 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";
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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();
}
}

View 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();
}
}

View 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);
}
};
}
} ;

View 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));
}
}

View 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
}

View 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;
}
}

View 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+" |--");
}
}

View 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));
}
};

View 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);
}
};
}
}

View 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);
}
}

View 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)
);
}
}

View 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" : "");
}
}

View 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);
}

View 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);
}

View 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);
}

View 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));
}
};
}
}

View 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;
}
}

View 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
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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();
}
}

View 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);
}
}
}

View 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
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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();
}
}
}

View 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:";
}

View 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();
}
}
}

View 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;
}
}

View 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
};

View 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);
}
}

View 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());
}
}
}

View 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);
}
}
}

View 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);
}
}

View 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);
}
}

View 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