From 32a86bb764370777b2c40cb5bd35fb9374dfc46b Mon Sep 17 00:00:00 2001 From: mmichalek Date: Mon, 27 Jun 2016 16:01:26 -0400 Subject: [PATCH] 0002650: Support File Sync on Android. --- .../android/AndroidFileSyncService.java | 43 ++++++++++++++++ .../symmetric/android/AndroidJobManager.java | 49 +++++++++++++++++++ .../android/AndroidSymmetricEngine.java | 17 ++++++- .../db/sqlite/SqliteJdbcSymmetricDialect.java | 3 +- .../symmetric/AbstractSymmetricEngine.java | 7 ++- .../db/sqlite/SqliteSymmetricDialect.java | 16 +++++- .../symmetric/file/FileSyncZipDataWriter.java | 22 +++++++++ .../service/impl/FileSyncService.java | 22 +++++++-- 8 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidFileSyncService.java diff --git a/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidFileSyncService.java b/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidFileSyncService.java new file mode 100644 index 0000000000..b39d6fc0ac --- /dev/null +++ b/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidFileSyncService.java @@ -0,0 +1,43 @@ +/** + * Licensed to JumpMind Inc under one or more contributor + * license agreements. See the NOTICE file distributed + * with this work for additional information regarding + * copyright ownership. JumpMind Inc licenses this file + * to you under the GNU General Public License, version 3.0 (GPLv3) + * (the "License"); you may not use this file except in compliance + * with the License. + * + * You should have received a copy of the GNU General Public License, + * version 3.0 (GPLv3) along with this library; if not, see + * . + * + * 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 org.jumpmind.symmetric.android; + +import java.io.File; + +import org.jumpmind.symmetric.ISymmetricEngine; +import org.jumpmind.symmetric.service.impl.FileSyncService; + +import android.os.Environment; +import bsh.EvalError; +import bsh.Interpreter; + +public class AndroidFileSyncService extends FileSyncService { + + public AndroidFileSyncService(ISymmetricEngine engine) { + super(engine); + } + + @Override + protected void setInterpreterVariables(ISymmetricEngine engine, String sourceNodeId, File batchDir, Interpreter interpreter) throws EvalError { + super.setInterpreterVariables(engine, sourceNodeId, batchDir, interpreter); + interpreter.set("androidBaseDir", Environment.getExternalStorageDirectory() + "/"); + } +} diff --git a/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidJobManager.java b/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidJobManager.java index 48c0a56725..83276f747b 100644 --- a/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidJobManager.java +++ b/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidJobManager.java @@ -52,6 +52,12 @@ public class AndroidJobManager implements IJobManager { protected long lastPurgeTime = System.currentTimeMillis(); protected long lastRouteTime = System.currentTimeMillis(); + + protected long lastFileSyncPullTime = System.currentTimeMillis(); + + protected long lastFileSyncTrackerTime = System.currentTimeMillis(); + + protected long lastFileSyncPushTime = System.currentTimeMillis(); public AndroidJobManager(ISymmetricEngine engine) { this.engine = engine; @@ -183,6 +189,49 @@ public boolean invoke(boolean force) { lastPurgeTime = System.currentTimeMillis(); } } + + if (parameterService.is(ParameterConstants.FILE_SYNC_ENABLE) + && parameterService.is("start.file.sync.tracker.job") + && parameterService.getLong("job.file.sync.tracker.period.time.ms", 5000) < (System + .currentTimeMillis() - lastFileSyncTrackerTime)) { + try { + didWork = true; + engine.getFileSyncService().trackChanges(false); + } catch (Throwable ex) { + log.error("", ex); + } finally { + lastFileSyncTrackerTime = System.currentTimeMillis(); + } + } + + if (parameterService.is(ParameterConstants.FILE_SYNC_ENABLE) + && parameterService.is("start.file.sync.pull.job") + && parameterService.getLong("job.file.sync.pull.period.time.ms", 60000) < (System + .currentTimeMillis() - lastFileSyncPullTime)) { + try { + didWork = true; + engine.getFileSyncService().pullFilesFromNodes(false); + } catch (Throwable ex) { + log.error("", ex); + } finally { + lastFileSyncPullTime = System.currentTimeMillis(); + } + } + + if (parameterService.is(ParameterConstants.FILE_SYNC_ENABLE) + && parameterService.is("job.file.sync.push") + && parameterService.getLong("job.file.sync.push.period.time.ms", 60000) < (System + .currentTimeMillis() - lastFileSyncPullTime)) { + try { + + didWork = true; + engine.getFileSyncService().pushFilesToNodes(false); + } catch (Throwable ex) { + log.error("", ex); + } finally { + lastFileSyncPushTime = System.currentTimeMillis(); + } + } } finally { if (didWork) { diff --git a/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidSymmetricEngine.java b/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidSymmetricEngine.java index 2f73240a3d..5d64806197 100644 --- a/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidSymmetricEngine.java +++ b/symmetric-android/src/main/java/org/jumpmind/symmetric/android/AndroidSymmetricEngine.java @@ -48,6 +48,7 @@ import org.jumpmind.symmetric.service.IClusterService; import org.jumpmind.symmetric.service.IConfigurationService; import org.jumpmind.symmetric.service.IExtensionService; +import org.jumpmind.symmetric.service.IFileSyncService; import org.jumpmind.symmetric.service.IMonitorService; import org.jumpmind.symmetric.service.INodeCommunicationService; import org.jumpmind.symmetric.service.INodeService; @@ -82,6 +83,15 @@ public AndroidSymmetricEngine(String registrationUrl, String externalId, String init(); } + protected void init() { + super.init(); + if (symmetricDialect instanceof SqliteSymmetricDialect) { + // + SqliteSymmetricDialect sqliteDialect = (SqliteSymmetricDialect)symmetricDialect; + sqliteDialect.setContextService(contextService); + } + } + @Override protected SecurityServiceType getSecurityServiceType() { return SecurityServiceType.CLIENT; @@ -126,7 +136,7 @@ public Collection getResourceReferences() { @Override protected ISymmetricDialect createSymmetricDialect() { - return new SqliteSymmetricDialect(parameterService, contextService, platform); + return new SqliteSymmetricDialect(parameterService, platform); } @Override @@ -143,6 +153,11 @@ protected IExtensionService createExtensionService() { protected IRouterService buildRouterService() { return new AndroidRouterService(this); } + + @Override + protected IFileSyncService buildFileSyncService() { + return new AndroidFileSyncService(this); + } class AndroidRouterService extends RouterService { diff --git a/symmetric-client/src/main/java/org/jumpmind/symmetric/db/sqlite/SqliteJdbcSymmetricDialect.java b/symmetric-client/src/main/java/org/jumpmind/symmetric/db/sqlite/SqliteJdbcSymmetricDialect.java index 14fe940b3d..021cd2f78e 100644 --- a/symmetric-client/src/main/java/org/jumpmind/symmetric/db/sqlite/SqliteJdbcSymmetricDialect.java +++ b/symmetric-client/src/main/java/org/jumpmind/symmetric/db/sqlite/SqliteJdbcSymmetricDialect.java @@ -13,7 +13,8 @@ public class SqliteJdbcSymmetricDialect extends SqliteSymmetricDialect { public SqliteJdbcSymmetricDialect(IParameterService parameterService, IContextService contextService, IDatabasePlatform platform) { - super(parameterService, contextService, platform); + super(parameterService, platform); + setContextService(contextService); } @Override diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/AbstractSymmetricEngine.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/AbstractSymmetricEngine.java index caeac7bd31..a23ac06532 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/AbstractSymmetricEngine.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/AbstractSymmetricEngine.java @@ -353,9 +353,10 @@ protected void init() { this.offlinePullService = new OfflinePullService(parameterService, symmetricDialect, nodeService, dataLoaderService, clusterService, nodeCommunicationService, configurationService, extensionService, offlineTransportManager); - this.fileSyncService = new FileSyncService(this); + this.fileSyncService = buildFileSyncService(); this.mailService = new MailService(parameterService, symmetricDialect); this.contextService = new ContextService(parameterService, symmetricDialect); + this.jobManager = createJobManager(); extensionService.addExtensionPoint(new DefaultOfflineServerListener( @@ -375,6 +376,10 @@ protected void init() { protected IRouterService buildRouterService() { return new RouterService(this); } + + protected IFileSyncService buildFileSyncService() { + return new FileSyncService(this); + } protected INodeCommunicationService buildNodeCommunicationService(IClusterService clusterService, INodeService nodeService, IParameterService parameterService, IConfigurationService configurationService, ISymmetricDialect symmetricDialect) { diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/db/sqlite/SqliteSymmetricDialect.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/db/sqlite/SqliteSymmetricDialect.java index 6e91144d5a..ba5d1ceb80 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/db/sqlite/SqliteSymmetricDialect.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/db/sqlite/SqliteSymmetricDialect.java @@ -27,6 +27,7 @@ import org.jumpmind.db.sql.ISqlTransaction; import org.jumpmind.db.sql.SqlException; import org.jumpmind.db.util.BinaryEncoding; +import org.jumpmind.symmetric.SymmetricException; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.common.TableConstants; import org.jumpmind.symmetric.db.AbstractSymmetricDialect; @@ -43,13 +44,16 @@ public class SqliteSymmetricDialect extends AbstractSymmetricDialect { String sqliteFunctionToOverride; - public SqliteSymmetricDialect(IParameterService parameterService, IContextService contextService, IDatabasePlatform platform) { + public SqliteSymmetricDialect(IParameterService parameterService, IDatabasePlatform platform) { super(parameterService, platform); - this.contextService = contextService; this.triggerTemplate = new SqliteTriggerTemplate(this); sqliteFunctionToOverride = parameterService.getString(ParameterConstants.SQLITE_TRIGGER_FUNCTION_TO_USE); } + public void setContextService(IContextService contextService) { + this.contextService = contextService; + } + @Override public void createRequiredDatabaseObjects() { } @@ -66,6 +70,10 @@ protected void setSqliteFunctionResult(ISqlTransaction transaction, final String public void disableSyncTriggers(ISqlTransaction transaction, String nodeId) { if (isBlank(sqliteFunctionToOverride)) { + if (contextService == null) { + throw new SymmetricException("contextService can't be null by this point. " + + "Please provide a non-null contextService before using this functionality."); + } contextService.insert(transaction, SYNC_TRIGGERS_DISABLED_USER_VARIABLE, "1"); if (nodeId != null) { contextService.insert(transaction, SYNC_TRIGGERS_DISABLED_NODE_VARIABLE, nodeId); @@ -78,6 +86,10 @@ public void disableSyncTriggers(ISqlTransaction transaction, String nodeId) { public void enableSyncTriggers(ISqlTransaction transaction) { if (isBlank(sqliteFunctionToOverride)) { + if (contextService == null) { + throw new SymmetricException("contextService can't be null by this point. " + + "Please provide a non-null contextService before using this functionality."); + } contextService.delete(transaction, SYNC_TRIGGERS_DISABLED_USER_VARIABLE); contextService.delete(transaction, SYNC_TRIGGERS_DISABLED_NODE_VARIABLE); } else { diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/file/FileSyncZipDataWriter.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/file/FileSyncZipDataWriter.java index b7cb51b9af..0abc0d5aed 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/file/FileSyncZipDataWriter.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/file/FileSyncZipDataWriter.java @@ -169,6 +169,12 @@ public void end(Batch batch, boolean inError) { targetBaseDir = StringEscapeUtils.escapeJava(targetBaseDir); command.append("targetBaseDir = \"").append(targetBaseDir).append("\";\n"); + + command.append("if (targetBaseDir.startsWith(\"${androidBaseDir}\")) { \n"); + command.append(" targetBaseDir = targetBaseDir.replace(\"${androidBaseDir}\", \"\"); \n"); + command.append(" targetBaseDir = androidBaseDir + targetBaseDir; \n"); + command.append("} \n"); + command.append("processFile = true;\n"); command.append("sourceFileName = \"").append(snapshot.getFileName()) .append("\";\n"); @@ -357,5 +363,21 @@ public void finish() { public boolean readyToSend() { return byteCount > maxBytesToSync; } + +// public static void main(String[] args) { +// String androidBaseDir = "/emulated/0/"; +// +// String targetBaseDir = "${androidBaseDir}/manuals"; +// if (targetBaseDir.startsWith("${androidBaseDir}")) { +// targetBaseDir = targetBaseDir.replace("${androidBaseDir}", ""); +// targetBaseDir = androidBaseDir + targetBaseDir; +// } +// System.out.println(targetBaseDir); +// +// +// +// //command.append("targetBaseDir = \"").append(targetBaseDir).append("\";\n"); +//// command.append("targetBaseDir.replaceAll(\"${androidBaseDir}\", \" + androidBaseDir + \"); ;\n"); +// } } diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/FileSyncService.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/FileSyncService.java index c8c5b3a17a..1b7cc29d0e 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/FileSyncService.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/service/impl/FileSyncService.java @@ -709,7 +709,15 @@ protected List processZip(InputStream is, String sourceNodeId, FileUtils.deleteDirectory(unzipDir); unzipDir.mkdirs(); - AppUtils.unzip(is, unzipDir); + try { + AppUtils.unzip(is, unzipDir); + } catch (IoException ex) { + if (ex.toString().contains("EOFException")) { // This happens on Android, when there is an empty zip. + //log.debug("Caught exception while unzipping.", ex); + } else { + throw ex; + } + } Set batchIds = new TreeSet(); String[] files = unzipDir.list(DirectoryFileFilter.INSTANCE); @@ -762,10 +770,7 @@ protected List processZip(InputStream is, String sourceNodeId, Interpreter interpreter = new Interpreter(); boolean isLocked = false; try { - interpreter.set("log", log); - interpreter.set("batchDir", batchDir.getAbsolutePath().replace('\\', '/')); - interpreter.set("engine", engine); - interpreter.set("sourceNodeId", sourceNodeId); + setInterpreterVariables(engine, sourceNodeId, batchDir, interpreter); long waitMillis = getParameterService().getLong( ParameterConstants.FILE_SYNC_LOCK_WAIT_MS); @@ -839,6 +844,13 @@ protected List processZip(InputStream is, String sourceNodeId, return batchesProcessed; } + + protected void setInterpreterVariables(ISymmetricEngine engine, String sourceNodeId, File batchDir, Interpreter interpreter) throws EvalError { + interpreter.set("log", log); + interpreter.set("batchDir", batchDir.getAbsolutePath().replace('\\', '/')); + interpreter.set("engine", engine); + interpreter.set("sourceNodeId", sourceNodeId); + } protected void updateFileIncoming(String nodeId, Map filesToEventType) { Set filePaths = filesToEventType.keySet();