diff --git a/README.md b/README.md
index 349bf4f..c06f5ab 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,37 @@ These dedicated folders are designed to keep the intermediate data files that
are in fact the patches or bundles of Git or other VCS system.
This bridge is duplex - so that you can exchange data in both directions.
+Contents
+--------
+
+1. [The Short Explanation](#the-short-explanation)
+2. [How It Works In A Nutshell](#how-it-works-in-a-nutshell)
+ 1. [The Overall Diagram](#the-overall-diagram)
+3. [Build And Run](#build-and-run)
+ 1. [Prerequisites](#prerequisites)
+ 2. [Build](#build)
+ 3. [Run](#run)
+ 4. [Notices](#notices)
+4. [Usage](#usage)
+ 1. [Configuration file](#configuration-file)
+ 1. [EWS Settings](#ews-settings)
+ 2. [Test bridge on one node](#test-bridge-on-one-node)
+ 3. [Both sides use the same EWS server](#both-sides-use-the-same-ews-server)
+ 4. [Each side uses its own EWS server](#each-side-uses-its-own-ews-server)
+ 2. [Git Bundle Mode](#git-bundle-mode)
+ 1. [Case 1. Existing repo on Side 1 and new empty repo on Side 2
+ ](#case-1-existing-repo-on-side-1-and-new-empty-repo-on-side-2)
+ 2. [Case 2. Changes are committed on Side 1, need to be pushed to Side 2
+ ](#case-2-changes-are-committed-on-side-1-need-to-be-pushed-to-side-2)
+ 3. [Git Email Mode](#git-email-mode)
+ 1. [Get full patch set from the beginning](#get-full-patch-set-from-the-beginning)
+ 2. [Get partial patch set from tagged commit](#get-partial-patch-set-from-tagged-commit)
+ 3. [Restore the patch set](#restore-the-patch-set)
+ 4. [Post-Receive Script](#post-receive-script)
+ 5. [Tune Logging](#tune-logging)
+5. [References](#references)
+6. [TODO](#todo)
+
The Short Explanation
---------------------
@@ -24,7 +55,7 @@ initial conditions:
non-detectable (encrypted) attachments.
* Only files (not sub-folders) are synchronized between computers. That's
by design limitation, b/c application's `inbox` and `outbox` purpose is to
- temporarily keep VCS (Git) patches or bundles that in fact is plain list, so
+ temporarily keep VCS ([Git]) patches or bundles that in fact is plain list, so
that any kind of file hierarchy support is business of VCS.
How It Works In A Nutshell
@@ -54,7 +85,7 @@ Both sides have symmetric settings and work in absolutely the same manner.
So the main flow may look like that:
1. User of "Side 1" puts file or files into `outbox` folder (these files are
- intended to be the result of Git command - see the **"Usage"** section below
+ intended to be the result of [Git] command - see the **"Usage"** section below
for details).
2. App on "Side 1" detects that new files are appeared in `outbox` and creates
new Email message(s) where attaches these files (packed and/or encrypted if
@@ -64,7 +95,7 @@ So the main flow may look like that:
4. App on "Side 2" detects that new email is available, so receives it and
processes to obtain files from attachments and puts these files into `inbox`
folder in its original representation (decrypted and/or unpacked).
-5. User of "Side 2" runs appropriate commands (e.g. Git) to apply new changes
+5. User of "Side 2" runs appropriate commands (e.g. [Git]) to apply new changes
represented by these files, and cleans up the `inbox` folder.
The last point may be automated as well - see **"Post-Receive Script"** section
@@ -75,12 +106,18 @@ Build And Run
### Prerequisites ###
-To build and run this application you need:
+To build this application you need:
-* Java 7+;
-* Maven 3+;
+* JDK 7+;
+* [Maven](https://maven.apache.org/) 3+;
* Internet connection.
+To run:
+
+* JRE/JDK 7+;
+* [Git];
+* If OS is Windows - [Cygwin] - optional but recommended.
+
### Build ###
This project is Maven-driven, so all what you need to do to build it is to run
@@ -89,19 +126,17 @@ the following command:
$ mvn clean package
### Run ###
-
+
If build is successful, you can run the resulting JAR file as standalone Java
app like that:
- $ java -jar target/email-bridge-X.X.X-standalone.jar
-
-Where `X.X.X` is current version number.
+ $ java -jar target/email-bridge-0.1.2-standalone.jar
The invocation w/o arguments will show an error that proper configuration file
is required. Just in case - you can get the short help about supported command
line arguments by specifying `-h` option:
- $ java -jar target/email-bridge-X.X.X-standalone.jar -h
+ $ java -jar target/email-bridge-0.1.2-standalone.jar -h
### Notices ###
@@ -457,12 +492,14 @@ References
3. [Prepare patches for e-mail submission][git-format-patch]
4. [Apply a series of patches from a mailbox][git-am]
5. [Cygwin Project][Cygwin]
+6. [Git VCS][Git]
[ews-java-api]: https://github.com/OfficeDev/ews-java-api
[git-bundle]: http://git-scm.com/docs/git-bundle
[git-format-patch]: http://git-scm.com/docs/git-format-patch
[git-am]: http://git-scm.com/docs/git-am
[Cygwin]: http://cygwin.org/
+[Git]: https://git-scm.com/downloads
TODO
----
diff --git a/data/config-template.properties b/data/config-template.properties
index 6092f8b..05c96d9 100644
--- a/data/config-template.properties
+++ b/data/config-template.properties
@@ -162,3 +162,10 @@ email.recipients.to =
# If file size is bigger, it will be split to several parts.
# Default value is 5.
#email.attach.max.size =
+
+# Optional path to PID file.
+# If PID file is specified but cannot be created/rewritten then application stops with error.
+#pid.file =
+
+# Whether to keep or not the PID file on application finish. Default value is "false".
+#pid.file.keep =
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index b76c0d7..bdce6a1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.exchange.git
email-bridge
- 0.1.1
+ 0.1.2
jar
email-bridge
diff --git a/src/main/java/org/mail/bridge/AbstractMonitor.java b/src/main/java/org/mail/bridge/AbstractMonitor.java
index 501f3b6..840f4a2 100644
--- a/src/main/java/org/mail/bridge/AbstractMonitor.java
+++ b/src/main/java/org/mail/bridge/AbstractMonitor.java
@@ -33,11 +33,12 @@
/**
* @author Maksym Dominichenko
*/
+@SuppressWarnings("UnusedReturnValue")
public abstract class AbstractMonitor {
private final Map>, List>> callbacks = new HashMap<>();
- protected AbstractMonitor addCallback(Class extends Message> messageClass, MonitorCallback callback) {
+ AbstractMonitor addCallback(Class extends Message> messageClass, MonitorCallback callback) {
if (callback != null) {
List> callbackList = callbacks.get(messageClass);
if (callbackList == null) {
@@ -50,7 +51,7 @@ protected AbstractMonitor addCallback(Class extends Message> messageCla
}
@SuppressWarnings({"unchecked", "SuspiciousMethodCalls"})
- protected void postMessage(Message message) {
+ void postMessage(Message message) {
List callbackList = Utils.ensureEmpty(callbacks.get(message.getClass()));
for (MonitorCallback callback : (List>) callbackList)
callback.onMessage(message);
diff --git a/src/main/java/org/mail/bridge/Config.java b/src/main/java/org/mail/bridge/Config.java
index 4a088f0..f96ddb0 100644
--- a/src/main/java/org/mail/bridge/Config.java
+++ b/src/main/java/org/mail/bridge/Config.java
@@ -82,6 +82,9 @@ public class Config {
private final String emailAttachExtEnc;
private final int emailAttachMaxSize;
+ private final String pidFile;
+ private final boolean pidFileKeep;
+
public Config(String propertiesFileName) throws IOException {
Properties config = new Properties();
config.load(new FileInputStream(propertiesFileName));
@@ -165,133 +168,145 @@ public Config(String propertiesFileName) throws IOException {
emailAttachExtEnc = checkExt(s.isEmpty() ? ".enc" : s);
s = config.getProperty("email.attach.max.size", "");
emailAttachMaxSize = s.isEmpty() ? 5 : Integer.parseInt(s);
+
+ pidFile = config.getProperty("pid.file", "");
+ s = config.getProperty("pid.file.keep", "");
+ pidFileKeep = !s.isEmpty() && Boolean.parseBoolean(s);
}
- public String getEwsEmail() {
+ String getEwsEmail() {
return ewsEmail;
}
- public String getEwsDomain() {
+ String getEwsDomain() {
return ewsDomain;
}
- public String getEwsUsername() {
+ String getEwsUsername() {
return ewsUsername;
}
- public String getEwsPassword() {
+ String getEwsPassword() {
return ewsPassword;
}
- public String getEwsServer() {
+ String getEwsServer() {
return ewsServer;
}
- public int getEwsViewSize() {
+ int getEwsViewSize() {
return ewsViewSize;
}
- public int getEwsSubscriptionLifetime() {
+ int getEwsSubscriptionLifetime() {
return ewsSubscriptionLifetime;
}
- public String getProxyHost() {
+ String getProxyHost() {
return proxyHost;
}
- public int getProxyPort() {
+ int getProxyPort() {
return proxyPort;
}
- public String getProxyUsername() {
+ String getProxyUsername() {
return proxyUsername;
}
- public String getProxyPassword() {
+ String getProxyPassword() {
return proxyPassword;
}
- public String getProxyDomain() {
+ String getProxyDomain() {
return proxyDomain;
}
- public String getOutboxFolder() {
+ String getOutboxFolder() {
return outboxFolder;
}
- public boolean isOutboxCleanup() {
+ boolean isOutboxCleanup() {
return outboxCleanup;
}
- public String getOutboxFileRegexp() {
+ String getOutboxFileRegexp() {
return outboxFileRegexp;
}
- public String getInboxFolder() {
+ String getInboxFolder() {
return inboxFolder;
}
- public String getInboxScript() {
+ String getInboxScript() {
return inboxScript;
}
- public int getInboxScriptStopCode() {
+ int getInboxScriptStopCode() {
return inboxScriptStopCode;
}
- public boolean isEmailInboxCleanup() {
+ boolean isEmailInboxCleanup() {
return emailInboxCleanup;
}
- public String getEmailTagIncoming() {
+ String getEmailTagIncoming() {
return emailTagIncoming;
}
- public String getEmailTagOutgoing() {
+ String getEmailTagOutgoing() {
return emailTagOutgoing;
}
- public MessageFormat getEmailSubjectFormat() {
+ MessageFormat getEmailSubjectFormat() {
return emailSubjectFormat;
}
- public MessageFormat getEmailBodyFormat() {
+ MessageFormat getEmailBodyFormat() {
return emailBodyFormat;
}
- public String[] getEmailRecipientsTo() {
+ String[] getEmailRecipientsTo() {
return emailRecipientsTo;
}
- public String[] getEmailRecipientsCc() {
+ String[] getEmailRecipientsCc() {
return emailRecipientsCc;
}
- public String[] getEmailRecipientsBcc() {
+ String[] getEmailRecipientsBcc() {
return emailRecipientsBcc;
}
- public String getEmailAttachPassword() {
+ String getEmailAttachPassword() {
return emailAttachPassword;
}
- public boolean isEmailAttachGzip() {
+ boolean isEmailAttachGzip() {
return emailAttachGzip;
}
- public String getEmailAttachExtGzip() {
+ String getEmailAttachExtGzip() {
return emailAttachExtGzip;
}
- public String getEmailAttachExtEnc() {
+ String getEmailAttachExtEnc() {
return emailAttachExtEnc;
}
- public int getEmailAttachMaxSize() {
+ int getEmailAttachMaxSize() {
return emailAttachMaxSize;
}
- public Map asEnvironmentMap() {
+ String getPidFile() {
+ return pidFile;
+ }
+
+ boolean isPidFileKeep() {
+ return pidFileKeep;
+ }
+
+ Map asEnvironmentMap() {
Map result = new HashMap<>();
result.put("EWS_EMAIL", ewsEmail);
result.put("EWS_DOMAIN", ewsDomain);
@@ -324,6 +339,8 @@ public Map asEnvironmentMap() {
result.put("EMAIL_ATTACH_EXT_GZIP", emailAttachExtGzip);
result.put("EMAIL_ATTACH_EXT_ENC", emailAttachExtEnc);
result.put("EMAIL_ATTACH_MAX_SIZE", "" + emailAttachMaxSize);
+ result.put("PID_FILE", pidFile);
+ result.put("PID_FILE_KEEP", "" + pidFileKeep);
return result;
}
@@ -367,6 +384,8 @@ public String toString() {
",\n\temailAttachExtGzip='" + emailAttachExtGzip + '\'' +
",\n\temailAttachExtEnc='" + emailAttachExtEnc + '\'' +
",\n\temailAttachMaxSize=" + emailAttachMaxSize +
+ ",\n\tpidFile='" + pidFile + '\'' +
+ ",\n\tpidFileKeep=" + pidFileKeep +
'}';
}
}
diff --git a/src/main/java/org/mail/bridge/ExchangeMonitor.java b/src/main/java/org/mail/bridge/ExchangeMonitor.java
index b6b9f71..4f838ae 100644
--- a/src/main/java/org/mail/bridge/ExchangeMonitor.java
+++ b/src/main/java/org/mail/bridge/ExchangeMonitor.java
@@ -77,20 +77,20 @@ public class ExchangeMonitor extends AbstractMonitor implements
private static final Pattern RE_ZIP_VOL = compile("^(" + RE_UUID + ")_(\\d+)\\.z\\d{2}$", CASE_INSENSITIVE);
private static final String ZIP_EXT = ".z00";
- public static class NewMailMessage extends Message> {
- public NewMailMessage(List emails) {
+ static class NewMailMessage extends Message> {
+ NewMailMessage(List emails) {
super(emails);
}
}
- public static class ReopenMonitorMessage extends Message {
- public ReopenMonitorMessage() {
+ static class ReopenMonitorMessage extends Message {
+ ReopenMonitorMessage() {
super(null);
}
}
- public static class NewIncomingFilesMessage extends Message> {
- public NewIncomingFilesMessage(List files) {
+ static class NewIncomingFilesMessage extends Message> {
+ NewIncomingFilesMessage(List files) {
super(files);
}
}
@@ -98,7 +98,7 @@ public NewIncomingFilesMessage(List files) {
private final Config config;
private ExchangeService service;
- public ExchangeMonitor(Config config) {
+ ExchangeMonitor(Config config) {
this.config = config;
LOG.debug("Instantiated");
}
@@ -130,19 +130,19 @@ public boolean autodiscoverRedirectionUrlValidationCallback(String redirectionUr
}
}
- public ExchangeMonitor addStopCallback(MonitorCallback callback) {
+ ExchangeMonitor addStopCallback(MonitorCallback callback) {
return (ExchangeMonitor) addCallback(Main.StopMessage.class, callback);
}
- public ExchangeMonitor addNewMailCallback(MonitorCallback> callback) {
+ ExchangeMonitor addNewMailCallback(MonitorCallback> callback) {
return (ExchangeMonitor) addCallback(NewMailMessage.class, callback);
}
- public ExchangeMonitor addReopenMonitorCallback(MonitorCallback callback) {
+ ExchangeMonitor addReopenMonitorCallback(MonitorCallback callback) {
return (ExchangeMonitor) addCallback(ReopenMonitorMessage.class, callback);
}
- public ExchangeMonitor addIncomingFilesReadyCallback(MonitorCallback> callback) {
+ ExchangeMonitor addIncomingFilesReadyCallback(MonitorCallback> callback) {
return (ExchangeMonitor) addCallback(NewIncomingFilesMessage.class, callback);
}
@@ -375,7 +375,7 @@ public void subscriptionErrorDelegate(Object sender, SubscriptionErrorEventArgs
postMessage(new ReopenMonitorMessage());
}
- public synchronized void sendFiles(List files) {
+ synchronized void sendFiles(List files) {
if (Utils.isEmpty(files)) return;
LOG.info("Sending files '{}'", files);
@@ -558,7 +558,7 @@ private void removeTempDir(File dir) {
else LOG.error("Cannot remove temporary folder '{}'", dir.getAbsolutePath());
}
- public synchronized void processNewMail(List newMailsIds) {
+ synchronized void processNewMail(List newMailsIds) {
LOG.info("Start new mail processing - {} message(s)", newMailsIds.size());
try {
ServiceResponseCollection responses =
diff --git a/src/main/java/org/mail/bridge/FolderMonitor.java b/src/main/java/org/mail/bridge/FolderMonitor.java
index 6733b52..a9c49e0 100644
--- a/src/main/java/org/mail/bridge/FolderMonitor.java
+++ b/src/main/java/org/mail/bridge/FolderMonitor.java
@@ -48,8 +48,8 @@ public class FolderMonitor extends AbstractMonitor implements Runnable {
private static final long NEW_FILES_PROCESS_DELAY = 1000;
private static final int SCRIPT_TIMEOUT = 60000;
- public static class SendFileMessage extends Message> {
- public SendFileMessage(List data) {
+ static class SendFileMessage extends Message> {
+ SendFileMessage(List data) {
super(data);
}
}
@@ -84,7 +84,7 @@ public boolean accept(File file) {
}
};
- public FolderMonitor(Config config) throws IOException {
+ FolderMonitor(Config config) throws IOException {
this.config = config;
outboxFolder = new File(config.getOutboxFolder());
if (outboxFolder.exists()) {
@@ -97,11 +97,11 @@ else if (!outboxFolder.canRead() || !outboxFolder.canWrite())
LOG.debug("Instantiated");
}
- public FolderMonitor addStopCallback(MonitorCallback callback) {
+ FolderMonitor addStopCallback(MonitorCallback callback) {
return (FolderMonitor) addCallback(Main.StopMessage.class, callback);
}
- public FolderMonitor addSendFileCallback(MonitorCallback> callback) {
+ FolderMonitor addSendFileCallback(MonitorCallback> callback) {
return (FolderMonitor) addCallback(SendFileMessage.class, callback);
}
@@ -111,7 +111,7 @@ private void processFiles(List files) throws IOException {
postMessage(new SendFileMessage(files));
}
- public synchronized void runScriptAgainstReceivedFiles(List inboxFiles) {
+ synchronized void runScriptAgainstReceivedFiles(List inboxFiles) {
if (config.getInboxScript().isEmpty() || Utils.isEmpty(inboxFiles)) return;
LOG.debug("Run script '{}' against files {}", config.getInboxScript(), inboxFiles);
ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git a/src/main/java/org/mail/bridge/Main.java b/src/main/java/org/mail/bridge/Main.java
index d31acbb..1015b81 100644
--- a/src/main/java/org/mail/bridge/Main.java
+++ b/src/main/java/org/mail/bridge/Main.java
@@ -31,7 +31,9 @@
import org.slf4j.LoggerFactory;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
@@ -42,8 +44,8 @@ public class Main implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(Main.class);
- public static class StopMessage extends Message {
- public StopMessage(String stopMessage) {
+ static class StopMessage extends Message {
+ StopMessage(String stopMessage) {
super(stopMessage);
}
}
@@ -51,6 +53,7 @@ public StopMessage(String stopMessage) {
private final FolderMonitor folderMonitor;
private final ExchangeMonitor exchangeMonitor;
private final ConcurrentLinkedDeque> messages = new ConcurrentLinkedDeque<>();
+ private final File pidFile;
public static void main(String[] args) throws Exception {
ArgumentParser parser = ArgumentParsers.newArgumentParser(Main.class.getSimpleName());
@@ -63,7 +66,7 @@ public static void main(String[] args) throws Exception {
new Main(config);
}
- Main(Config config) throws IOException {
+ private Main(Config config) throws IOException {
folderMonitor = new FolderMonitor(config)
.addStopCallback(new MonitorCallback() {
@Override
@@ -102,6 +105,11 @@ public void onMessage(Message message) {
postMessage(message);
}
});
+ pidFile = config.getPidFile().isEmpty() ? null : new File(config.getPidFile());
+ if (pidFile != null) {
+ if (pidFile.exists() && pidFile.delete()) LOG.debug("Old PID file was removed");
+ if (!config.isPidFileKeep()) pidFile.deleteOnExit();
+ }
new Thread(this, Main.class.getSimpleName()).start();
}
@@ -111,6 +119,23 @@ private synchronized void postMessage(Message> message) {
@Override
public void run() {
+ if (pidFile != null) {
+ long pid;
+ try {
+ pid = Long.parseLong(ManagementFactory.getRuntimeMXBean().getName().split("@", 2)[0]);
+ } catch (Exception e) {
+ LOG.error("Cannot retrieve the PID of current process", e);
+ return;
+ }
+ try (FileOutputStream os = new FileOutputStream(pidFile)) {
+ os.write(String.valueOf(pid).getBytes());
+ os.flush();
+ } catch (Exception e) {
+ LOG.error("Cannot cannot write PID file", e);
+ return;
+ }
+ }
+
exchangeMonitor.scan().monitor();
folderMonitor.scan().monitor();
while (true) {
@@ -141,5 +166,7 @@ else if (message instanceof StopMessage) {
break;
}
}
+
+
}
}
diff --git a/src/main/java/org/mail/bridge/Message.java b/src/main/java/org/mail/bridge/Message.java
index e82e3f3..589b5cc 100644
--- a/src/main/java/org/mail/bridge/Message.java
+++ b/src/main/java/org/mail/bridge/Message.java
@@ -30,11 +30,11 @@ public class Message {
private final T data;
- public Message(T data) {
+ Message(T data) {
this.data = data;
}
- public T getData() {
+ T getData() {
return data;
}
diff --git a/src/main/java/org/mail/bridge/util/EncryptUtil.java b/src/main/java/org/mail/bridge/util/EncryptUtil.java
index c03a507..64797ce 100644
--- a/src/main/java/org/mail/bridge/util/EncryptUtil.java
+++ b/src/main/java/org/mail/bridge/util/EncryptUtil.java
@@ -100,9 +100,10 @@ public static void decrypt(String password, InputStream cipherData, OutputStream
}
}
- public static void gzipEncrypt(String password,
- final InputStream clearData,
- OutputStream cipherData) throws IOException {
+ public static void gzipEncrypt(
+ String password,
+ final InputStream clearData,
+ OutputStream cipherData) throws IOException {
final PipedOutputStream output = new PipedOutputStream();
final PipedInputStream input = new PipedInputStream(output);
final Thread thread = new Thread(new Runnable() {
@@ -120,9 +121,10 @@ public void run() {
encrypt(password, input, cipherData);
}
- public static void decryptGunzip(final String password,
- final InputStream cipherData,
- OutputStream clearData) throws IOException {
+ public static void decryptGunzip(
+ final String password,
+ final InputStream cipherData,
+ OutputStream clearData) throws IOException {
final PipedOutputStream output = new PipedOutputStream();
final PipedInputStream input = new PipedInputStream(output);
final Thread thread = new Thread(new Runnable() {
diff --git a/src/main/java/org/mail/bridge/util/Utils.java b/src/main/java/org/mail/bridge/util/Utils.java
index fe7cdbb..ee5a98f 100644
--- a/src/main/java/org/mail/bridge/util/Utils.java
+++ b/src/main/java/org/mail/bridge/util/Utils.java
@@ -29,7 +29,7 @@
/**
* @author Maksym Dominichenko
*/
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "WeakerAccess", "SameParameterValue"})
public class Utils {
public static final Comparator LAST_MODIFIED_COMPARATOR = new Comparator() {