From 7500ce6ea4e929bdea529a40120745af05128f0a Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Tue, 23 Jan 2018 14:26:35 +0200 Subject: [PATCH 1/7] feature(livesync): work with file streams through named socket --- .../java/com/tns/NativeScriptSyncService.java | 393 ++++++++---------- .../src/main/java/com/tns/LogcatLogger.java | 8 +- .../src/main/java/com/tns/RuntimeHelper.java | 2 - 3 files changed, 188 insertions(+), 215 deletions(-) diff --git a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java index a443b358e..1f53cf425 100644 --- a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java +++ b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java @@ -1,40 +1,24 @@ package com.tns; import java.io.Closeable; -import java.io.DataInputStream; import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; import android.net.LocalServerSocket; import android.net.LocalSocket; -import android.util.Log; +import android.support.annotation.NonNull; public class NativeScriptSyncService { - private static String SYNC_ROOT_SOURCE_DIR = "/data/local/tmp/"; - private static final String SYNC_SOURCE_DIR = "/sync/"; - private static final String FULL_SYNC_SOURCE_DIR = "/fullsync/"; - private static final String REMOVED_SYNC_SOURCE_DIR = "/removedsync/"; + private static String DEVICE_APP_DIR; private final Runtime runtime; private static Logger logger; private final Context context; - private final String syncPath; - private final String fullSyncPath; - private final String removedSyncPath; - private final File fullSyncDir; - private final File syncDir; - private final File removedSyncDir; - private LocalServerSocketThread localServerThread; private Thread localServerJavaThread; @@ -42,35 +26,7 @@ public NativeScriptSyncService(Runtime runtime, Logger logger, Context context) this.runtime = runtime; this.logger = logger; this.context = context; - - syncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + SYNC_SOURCE_DIR; - fullSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + FULL_SYNC_SOURCE_DIR; - removedSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + REMOVED_SYNC_SOURCE_DIR; - fullSyncDir = new File(fullSyncPath); - syncDir = new File(syncPath); - removedSyncDir = new File(removedSyncPath); - } - - public void sync() { - if (logger != null && logger.isEnabled()) { - logger.write("Sync is enabled:"); - logger.write("Sync path : " + syncPath); - logger.write("Full sync path : " + fullSyncPath); - logger.write("Removed files sync path: " + removedSyncPath); - } - - if (fullSyncDir.exists()) { - executeFullSync(context, fullSyncDir); - return; - } - - if (syncDir.exists()) { - executePartialSync(context, syncDir); - } - - if (removedSyncDir.exists()) { - executeRemovedSync(context, removedSyncDir); - } + DEVICE_APP_DIR = this.context.getFilesDir().getAbsolutePath() + "/app"; } private class LocalServerSocketThread implements Runnable { @@ -109,224 +65,239 @@ public void run() { } } + public void startServer() { + localServerThread = new LocalServerSocketThread(context.getPackageName() + "-livesync"); + localServerJavaThread = new Thread(localServerThread); + localServerJavaThread.start(); + } + private class ListenerWorker implements Runnable { - private final DataInputStream input; + public static final int OPERATION_BYTE_SIZE = 1; + public static final int FILE_NAME_LENGTH_BYTE_SIZE = 5; + public static final int CONTENT_LENGTH_BYTE_SIZE = 10; + public static final int DELETE_FILE_OPERATION = 7; + public static final int CREATE_FILE_OPERATION = 8; + public static final String FILE_NAME = "fileName"; + public static final String FILE_NAME_LENGTH = FILE_NAME + "Length"; + public static final String OPERATION = "operation"; + public static final String FILE_CONTENT = "fileContent"; + public static final String FILE_CONTENT_LENGTH = FILE_CONTENT + "Length"; + public static final int DEFAULT_OPERATION = -1; + public final String LIVESYNC_ERROR_SUGGESTION = String.format("\nMake sure you are following this protocol when transferring files." + + "\nTransfer protocol: \n\tdelete: (%s)(%s)(%s)" + + "\n\tcreate: (%s)(%s)(%s)(%s)(%s)" + + "\n\t%s: exactly %s btye (%s - delete, %s - create)" + + "\n\t%s: exactly %s bytes" + + "\n\t%s: relative to app folder" + + "\n\t%s: exactly %s bytes" + + "\n\t%s: byte buffer" + + "\n\tExample delete: 700003./a" + + "\n\tExample create: 800007./a.txt0000000011fileContent", + OPERATION, FILE_NAME_LENGTH, FILE_NAME, + OPERATION, FILE_NAME_LENGTH, FILE_NAME, FILE_CONTENT_LENGTH, FILE_CONTENT, + OPERATION, OPERATION_BYTE_SIZE, DELETE_FILE_OPERATION, CREATE_FILE_OPERATION, + FILE_NAME_LENGTH, FILE_NAME_LENGTH_BYTE_SIZE, + FILE_NAME, + FILE_CONTENT_LENGTH, CONTENT_LENGTH_BYTE_SIZE, + FILE_CONTENT); + private final InputStream input; private Closeable socket; private OutputStream output; public ListenerWorker(LocalSocket socket) throws IOException { this.socket = socket; - input = new DataInputStream(socket.getInputStream()); + input = socket.getInputStream(); output = socket.getOutputStream(); } public void run() { + boolean exceptionWhileLivesyncing = false; try { - int length = input.readInt(); - input.readFully(new byte[length]); // ignore the payload - executePartialSync(context, syncDir); - executeRemovedSync(context, removedSyncDir); + do { + int operation = getOperation(); + if (operation == DELETE_FILE_OPERATION) { - runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); + String fileName = getFileName(); + deleteRecursive(new File(DEVICE_APP_DIR, fileName)); + + } else if (operation == CREATE_FILE_OPERATION) { + + String fileName = getFileName(); + byte[] content = getFileContent(); + createOrOverrideFile(fileName, content); + + } else if (operation == DEFAULT_OPERATION) { + logger.write("LiveSync: input stream is empty!"); + break; + } else { + throw new IllegalArgumentException(String.format("\nLiveSync: Operation not recognised. %s", LIVESYNC_ERROR_SUGGESTION)); + } + + } while (this.input.available() > 0); + + } catch (Exception e) { + logger.write(String.format("Error while LiveSyncing: %s", e.toString())); + e.printStackTrace(); + exceptionWhileLivesyncing = true; + } finally { try { - output.write(1); + socket.close(); } catch (IOException e) { e.printStackTrace(); } - socket.close(); - } catch (IOException e) { - e.printStackTrace(); } - } - } - - public void startServer() { - localServerThread = new LocalServerSocketThread(context.getPackageName() + "-livesync"); - localServerJavaThread = new Thread(localServerThread); - localServerJavaThread.start(); - } - public static boolean isSyncEnabled(Context context) { - int flags; - boolean shouldExecuteSync = false; - try { - flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags; - shouldExecuteSync = ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); - } catch (NameNotFoundException e) { - e.printStackTrace(); - return false; + if (!exceptionWhileLivesyncing) { + runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); + } } - return shouldExecuteSync; - } + /* + * Tries to read operation input stream + * If the stream is empty, method returns -1 + * */ + private int getOperation() { + Integer operation = DEFAULT_OPERATION; + try { - final FileFilter deletingFilesFilter = new FileFilter() { - @Override - public boolean accept(File pathname) { - if (pathname.isDirectory()) { - return true; - } + byte[] operationBuff = readNextBytes(OPERATION_BYTE_SIZE); + if (operationBuff == null) { + return DEFAULT_OPERATION; + } + operation = Integer.parseInt(new String(operationBuff)); - boolean success = pathname.delete(); - if (!success) { - logger.write("Syncing: file not deleted: " + pathname.getAbsolutePath().toString()); - } - return false; - } - }; - - private void deleteDir(File directory) { - File[] subDirectories = directory.listFiles(deletingFilesFilter); - if (subDirectories != null) { - for (int i = 0; i < subDirectories.length; i++) { - File subDir = subDirectories[i]; - deleteDir(subDir); + } catch (NumberFormatException e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", OPERATION, LIVESYNC_ERROR_SUGGESTION, e.toString())); + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", OPERATION, LIVESYNC_ERROR_SUGGESTION, e.toString())); } + return operation; } - boolean success = directory.delete(); - if (!success && directory.exists()) { - logger.write("Syncing: directory not deleted: " + directory.getAbsolutePath().toString()); - } - } + private String getFileName() { + byte[] fileNameBuffer; + int fileNameLenth = -1; + byte[] fileNameLengthBuffer; - private void moveFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) { - File[] files = sourceDir.listFiles(); + try { - if (files != null) { - if (logger.isEnabled()) { - logger.write("Syncing total number of fiiles: " + files.length); - } + fileNameLengthBuffer = readNextBytes(FILE_NAME_LENGTH_BYTE_SIZE); - for (int i = 0; i < files.length; i++) { - File file = files[i]; - if (file.isFile()) { - if (logger.isEnabled()) { - logger.write("Syncing: " + file.getAbsolutePath().toString()); - } + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_NAME_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + } - String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); - File targetFileDir = new File(targetFilePath); + if (fileNameLengthBuffer == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. %s", FILE_NAME_LENGTH, LIVESYNC_ERROR_SUGGESTION)); + } - File targetParent = targetFileDir.getParentFile(); - if (targetParent != null) { - targetParent.mkdirs(); - } + try { + fileNameLenth = Integer.valueOf(new String(fileNameLengthBuffer)); + fileNameBuffer = readNextBytes(fileNameLenth); - boolean success = copyFile(file.getAbsolutePath(), targetFilePath); - if (!success) { - logger.write("Sync failed: " + file.getAbsolutePath().toString()); - } - } else { - moveFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); - } + } catch (NumberFormatException e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_NAME, LIVESYNC_ERROR_SUGGESTION, e.toString())); + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_NAME, LIVESYNC_ERROR_SUGGESTION, e.toString())); } - } else { - if (logger.isEnabled()) { - logger.write("Can't move files. Source is empty."); + + if (fileNameBuffer == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. %s", FILE_NAME, LIVESYNC_ERROR_SUGGESTION)); } - } - } - // this removes only the app directory from the device to preserve - // any existing files in /files directory on the device - private void executeFullSync(Context context, final File sourceDir) { - String appPath = context.getFilesDir().getAbsolutePath() + "/app"; - final File appDir = new File(appPath); + String fileName = new String(fileNameBuffer); + if (fileName.trim().length() < fileNameLenth) { + logger.write(String.format("WARNING: %s parsed length is less than %s. We read less information than you specified!", FILE_NAME, FILE_NAME_LENGTH)); + } - if (appDir.exists()) { - deleteDir(appDir); - moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); + return fileName.trim(); } - } - private void executePartialSync(Context context, File sourceDir) { - String appPath = context.getFilesDir().getAbsolutePath() + "/app"; - final File appDir = new File(appPath); + private byte[] getFileContent() throws IOException { + byte[] contentBuff; + int contentL = -1; + byte[] contentLength; + try { + contentLength = readNextBytes(CONTENT_LENGTH_BYTE_SIZE); + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + } - if (!appDir.exists()) { - Log.e("TNS", "Application dir does not exists. Partial Sync failed. appDir: " + appPath); - return; - } + if (contentLength == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. %s", FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION)); + } - if (logger.isEnabled()) { - logger.write("Syncing sourceDir " + sourceDir.getAbsolutePath() + " with " + appDir.getAbsolutePath()); - } + try { + contentL = Integer.parseInt(new String(contentLength)); + contentBuff = readNextBytes(contentL); - moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); - } + } catch (NumberFormatException e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION, e.toString())); + } - private void deleteRemovedFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) { - if (!sourceDir.exists()) { - if (logger.isEnabled()) { - logger.write("Directory does not exist: " + sourceDir.getAbsolutePath()); + if (contentBuff == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. %s", FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION)); } - } - File[] files = sourceDir.listFiles(); + return contentBuff; + } - if (files != null) { - for (int i = 0; i < files.length; i++) { - File file = files[i]; - String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); - File targetFile = new File(targetFilePath); - if (file.isFile()) { - if (logger.isEnabled()) { - logger.write("Syncing removed file: " + file.getAbsolutePath().toString()); - } + private void createOrOverrideFile(String fileName, byte[] content) throws IOException { + File fileToCreate = prepareFile(fileName); + try { - targetFile.delete(); - } else { - deleteRemovedFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); + fileToCreate.getParentFile().mkdirs(); + FileOutputStream fos = new FileOutputStream(fileToCreate.getCanonicalPath()); + fos.write(content); + fos.close(); - // this is done so empty folders, if any, are deleted after we're don deleting files. - if (targetFile.listFiles().length == 0) { - targetFile.delete(); - } - } + } catch (Exception e) { + throw new IOException(String.format("\nLiveSync: failed to write file: %s\noriginal exception: %s", fileName, e.toString())); } } - } - - private void executeRemovedSync(final Context context, final File sourceDir) { - String appPath = context.getFilesDir().getAbsolutePath() + "/app"; - deleteRemovedFiles(sourceDir, sourceDir.getAbsolutePath(), appPath); - } - - private boolean copyFile(String sourceFile, String destinationFile) { - FileInputStream fis = null; - FileOutputStream fos = null; - try { - fis = new FileInputStream(sourceFile); - fos = new FileOutputStream(destinationFile, false); + void deleteRecursive(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) + for (File child : fileOrDirectory.listFiles()) { + deleteRecursive(child); + } - byte[] buffer = new byte[4096]; - int read = 0; + fileOrDirectory.delete(); + } - while ((read = fis.read(buffer)) != -1) { - fos.write(buffer, 0, read); + @NonNull + private File prepareFile(String fileName) { + File fileToCreate = new File(DEVICE_APP_DIR, fileName); + if (fileToCreate.exists()) { + fileToCreate.delete(); } - } catch (FileNotFoundException e) { - logger.write("Error copying file " + sourceFile); - e.printStackTrace(); - return false; - } catch (IOException e) { - logger.write("Error copying file " + sourceFile); - e.printStackTrace(); - return false; - } finally { - try { - if (fis != null) { - fis.close(); - } - if (fos != null) { - fos.close(); + return fileToCreate; + } + + /* + * Reads next bites from input stream. Bytes read depend on passed parameter. + * */ + private byte[] readNextBytes(int size) throws IOException { + byte[] buffer = new byte[size]; + int bytesRead = 0; + int bufferWriteOffset = bytesRead; + do { + + bytesRead = this.input.read(buffer, bufferWriteOffset, size); + if (bytesRead == -1) { + if (bufferWriteOffset == 0) { + return null; + } + break; } - } catch (IOException e) { - } + size -= bytesRead; + bufferWriteOffset += bytesRead; + } while (size > 0); + + return buffer; } - return true; } } diff --git a/test-app/app/src/main/java/com/tns/LogcatLogger.java b/test-app/app/src/main/java/com/tns/LogcatLogger.java index 4be958e9c..7373fea6b 100644 --- a/test-app/app/src/main/java/com/tns/LogcatLogger.java +++ b/test-app/app/src/main/java/com/tns/LogcatLogger.java @@ -21,11 +21,15 @@ public final void setEnabled(boolean isEnabled) { } public final void write(String msg) { - Log.d(DEFAULT_LOG_TAG, msg); + if (this.enabled) { + Log.d(DEFAULT_LOG_TAG, msg); + } } public final void write(String tag, String msg) { - Log.d(tag, msg); + if (this.enabled) { + Log.d(tag, msg); + } } private void initLogging(Context context) { diff --git a/test-app/app/src/main/java/com/tns/RuntimeHelper.java b/test-app/app/src/main/java/com/tns/RuntimeHelper.java index 2d1bf26fd..22543a2cd 100644 --- a/test-app/app/src/main/java/com/tns/RuntimeHelper.java +++ b/test-app/app/src/main/java/com/tns/RuntimeHelper.java @@ -190,8 +190,6 @@ public static Runtime initRuntime(Application app) { Constructor cons = NativeScriptSyncService.getConstructor(new Class[] {Runtime.class, Logger.class, Context.class}); Object syncService = cons.newInstance(runtime, logger, app); - Method syncMethod = NativeScriptSyncService.getMethod("sync"); - syncMethod.invoke(syncService); Method startServerMethod = NativeScriptSyncService.getMethod("startServer"); startServerMethod.invoke(syncService); } catch (ClassNotFoundException e) { From 54fd9f0d793aa2b8b03b5e8f78c9f099554f0a2a Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Wed, 24 Jan 2018 14:52:57 +0200 Subject: [PATCH 2/7] feature(livesync): send packge to client ASAP to establish state --- .../src/debug/java/com/tns/NativeScriptSyncService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java index 1f53cf425..ae9392e39 100644 --- a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java +++ b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java @@ -112,6 +112,15 @@ public ListenerWorker(LocalSocket socket) throws IOException { public void run() { boolean exceptionWhileLivesyncing = false; + try { + + output.write(context.getPackageName().getBytes()); + + } catch (IOException e) { + logger.write(String.format("Error while LiveSyncing: Client socket might be closed!", e.toString())); + exceptionWhileLivesyncing = true; + e.printStackTrace(); + } try { do { int operation = getOperation(); From 460507f9c0793e8517889370db73f311331fdede Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Thu, 25 Jan 2018 11:16:49 +0200 Subject: [PATCH 3/7] refactor(livesync): close socket when application process is killed --- .../java/com/tns/NativeScriptSyncService.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java index ae9392e39..0ec6f961f 100644 --- a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java +++ b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java @@ -30,6 +30,7 @@ public NativeScriptSyncService(Runtime runtime, Logger logger, Context context) } private class LocalServerSocketThread implements Runnable { + private volatile boolean running; private final String name; @@ -63,6 +64,11 @@ public void run() { e.printStackTrace(); } } + + @Override + protected void finalize() throws Throwable { + this.serverSocket.close(); + } } public void startServer() { @@ -148,12 +154,6 @@ public void run() { logger.write(String.format("Error while LiveSyncing: %s", e.toString())); e.printStackTrace(); exceptionWhileLivesyncing = true; - } finally { - try { - socket.close(); - } catch (IOException e) { - e.printStackTrace(); - } } if (!exceptionWhileLivesyncing) { @@ -308,5 +308,10 @@ private byte[] readNextBytes(int size) throws IOException { return buffer; } + @Override + protected void finalize() throws Throwable { + this.socket.close(); + } + } } From e1b464bdb23bf059a01f424deee2075cc867ce03 Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Thu, 25 Jan 2018 12:01:57 +0200 Subject: [PATCH 4/7] refactor(livesync): error handling shows file name that operation failed for --- .../java/com/tns/NativeScriptSyncService.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java index 0ec6f961f..8d26b8319 100644 --- a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java +++ b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java @@ -138,9 +138,10 @@ public void run() { } else if (operation == CREATE_FILE_OPERATION) { String fileName = getFileName(); - byte[] content = getFileContent(); + byte[] content = getFileContent(fileName); createOrOverrideFile(fileName, content); + } else if (operation == DEFAULT_OPERATION) { logger.write("LiveSync: input stream is empty!"); break; @@ -222,18 +223,18 @@ private String getFileName() { return fileName.trim(); } - private byte[] getFileContent() throws IOException { + private byte[] getFileContent(String fileName) throws IllegalStateException { byte[] contentBuff; int contentL = -1; byte[] contentLength; try { contentLength = readNextBytes(CONTENT_LENGTH_BYTE_SIZE); } catch (Exception e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\noriginal exception: %s", fileName, FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); } if (contentLength == null) { - throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. %s", FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION)); + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. Did you send %s %s? %s", FILE_CONTENT_LENGTH, fileName, FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION)); } try { @@ -241,13 +242,13 @@ private byte[] getFileContent() throws IOException { contentBuff = readNextBytes(contentL); } catch (NumberFormatException e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\noriginal exception: %s", fileName, FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); } catch (Exception e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\noriginal exception: %s", fileName, FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION, e.toString())); } if (contentBuff == null) { - throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. %s", FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION)); + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. Did you send %s %s? %s", FILE_CONTENT, fileName, FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION)); } return contentBuff; From ddd0b9f7f76b7edcf8ecdcf3c1952aa368104beb Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Thu, 25 Jan 2018 14:54:40 +0200 Subject: [PATCH 5/7] refactor(livesync): handle cases when livesync thread dies or is interrupted --- .../java/com/tns/NativeScriptSyncService.java | 78 +++++++++++++------ 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java index 8d26b8319..1b4d025fe 100644 --- a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java +++ b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java @@ -34,8 +34,8 @@ private class LocalServerSocketThread implements Runnable { private volatile boolean running; private final String name; - private ListenerWorker commThread; - private LocalServerSocket serverSocket; + private LiveSyncWorker livesyncWorker; + private LocalServerSocket deviceSystemSocket; public LocalServerSocketThread(String name) { this.name = name; @@ -45,7 +45,7 @@ public LocalServerSocketThread(String name) { public void stop() { this.running = false; try { - serverSocket.close(); + deviceSystemSocket.close(); } catch (IOException e) { e.printStackTrace(); } @@ -54,20 +54,34 @@ public void stop() { public void run() { running = true; try { - serverSocket = new LocalServerSocket(this.name); + deviceSystemSocket = new LocalServerSocket(this.name); while (running) { - LocalSocket socket = serverSocket.accept(); - commThread = new ListenerWorker(socket); - new Thread(commThread).start(); + LocalSocket systemSocket = deviceSystemSocket.accept(); + livesyncWorker = new LiveSyncWorker(systemSocket); + Thread livesyncThread = setUpLivesyncThread(); + livesyncThread.start(); } } catch (IOException e) { e.printStackTrace(); } } + @NonNull + private Thread setUpLivesyncThread() { + Thread livesyncThread = new Thread(livesyncWorker); + livesyncThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.write(String.format("%s(%s): %s", t.getName(), t.getId(), e.toString())); + } + }); + livesyncThread.setName("Livesync Thread"); + return livesyncThread; + } + @Override protected void finalize() throws Throwable { - this.serverSocket.close(); + deviceSystemSocket.close(); } } @@ -77,7 +91,7 @@ public void startServer() { localServerJavaThread.start(); } - private class ListenerWorker implements Runnable { + private class LiveSyncWorker implements Runnable { public static final int OPERATION_BYTE_SIZE = 1; public static final int FILE_NAME_LENGTH_BYTE_SIZE = 5; public static final int CONTENT_LENGTH_BYTE_SIZE = 10; @@ -107,13 +121,13 @@ private class ListenerWorker implements Runnable { FILE_CONTENT_LENGTH, CONTENT_LENGTH_BYTE_SIZE, FILE_CONTENT); private final InputStream input; - private Closeable socket; + private Closeable livesyncSocket; private OutputStream output; - public ListenerWorker(LocalSocket socket) throws IOException { - this.socket = socket; - input = socket.getInputStream(); - output = socket.getOutputStream(); + public LiveSyncWorker(LocalSocket systemSocket) throws IOException { + this.livesyncSocket = systemSocket; + input = systemSocket.getInputStream(); + output = systemSocket.getOutputStream(); } public void run() { @@ -155,6 +169,14 @@ public void run() { logger.write(String.format("Error while LiveSyncing: %s", e.toString())); e.printStackTrace(); exceptionWhileLivesyncing = true; + flushInputStream(); + } catch (Throwable e) { + logger.write(String.format("%s(%s): Error while LiveSyncing.\nOriginal Exception: %s", Thread.currentThread().getName(), Thread.currentThread().getId(), e.toString())); + try { + this.livesyncSocket.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } } if (!exceptionWhileLivesyncing) { @@ -162,6 +184,13 @@ public void run() { } } + private void flushInputStream() { + try { + this.input.skip(1000000); + } catch (IOException e) { + } + } + /* * Tries to read operation input stream * If the stream is empty, method returns -1 @@ -177,9 +206,9 @@ private int getOperation() { operation = Integer.parseInt(new String(operationBuff)); } catch (NumberFormatException e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", OPERATION, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\nOriginal Exception: %s", OPERATION, LIVESYNC_ERROR_SUGGESTION, e.toString())); } catch (Exception e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", OPERATION, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\nOriginal Exception: %s", OPERATION, LIVESYNC_ERROR_SUGGESTION, e.toString())); } return operation; } @@ -194,7 +223,7 @@ private String getFileName() { fileNameLengthBuffer = readNextBytes(FILE_NAME_LENGTH_BYTE_SIZE); } catch (Exception e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_NAME_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\nOriginal Exception: %s", FILE_NAME_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); } if (fileNameLengthBuffer == null) { @@ -206,9 +235,9 @@ private String getFileName() { fileNameBuffer = readNextBytes(fileNameLenth); } catch (NumberFormatException e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_NAME, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\nOriginal Exception: %s", FILE_NAME, LIVESYNC_ERROR_SUGGESTION, e.toString())); } catch (Exception e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\noriginal exception: %s", FILE_NAME, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\nOriginal Exception: %s", FILE_NAME, LIVESYNC_ERROR_SUGGESTION, e.toString())); } if (fileNameBuffer == null) { @@ -230,7 +259,7 @@ private byte[] getFileContent(String fileName) throws IllegalStateException { try { contentLength = readNextBytes(CONTENT_LENGTH_BYTE_SIZE); } catch (Exception e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\noriginal exception: %s", fileName, FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\nOriginal Exception: %s", fileName, FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); } if (contentLength == null) { @@ -242,9 +271,9 @@ private byte[] getFileContent(String fileName) throws IllegalStateException { contentBuff = readNextBytes(contentL); } catch (NumberFormatException e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\noriginal exception: %s", fileName, FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\nOriginal Exception: %s", fileName, FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); } catch (Exception e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\noriginal exception: %s", fileName, FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION, e.toString())); + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s %s. %s\nOriginal Exception: %s", fileName, FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION, e.toString())); } if (contentBuff == null) { @@ -264,7 +293,7 @@ private void createOrOverrideFile(String fileName, byte[] content) throws IOExce fos.close(); } catch (Exception e) { - throw new IOException(String.format("\nLiveSync: failed to write file: %s\noriginal exception: %s", fileName, e.toString())); + throw new IOException(String.format("\nLiveSync: failed to write file: %s\nOriginal Exception: %s", fileName, e.toString())); } } @@ -311,8 +340,7 @@ private byte[] readNextBytes(int size) throws IOException { @Override protected void finalize() throws Throwable { - this.socket.close(); + this.livesyncSocket.close(); } - } } From bb0510955e528b5e995cd0160158f44cc5a582b6 Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Fri, 26 Jan 2018 14:37:40 +0200 Subject: [PATCH 6/7] feature: keep socket always open --- .../java/com/tns/NativeScriptSyncService.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java index 1b4d025fe..0d725ea0e 100644 --- a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java +++ b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java @@ -131,19 +131,19 @@ public LiveSyncWorker(LocalSocket systemSocket) throws IOException { } public void run() { - boolean exceptionWhileLivesyncing = false; try { output.write(context.getPackageName().getBytes()); } catch (IOException e) { logger.write(String.format("Error while LiveSyncing: Client socket might be closed!", e.toString())); - exceptionWhileLivesyncing = true; e.printStackTrace(); } try { do { int operation = getOperation(); + + System.out.println("Operation: " + input.available()); if (operation == DELETE_FILE_OPERATION) { String fileName = getFileName(); @@ -155,7 +155,6 @@ public void run() { byte[] content = getFileContent(fileName); createOrOverrideFile(fileName, content); - } else if (operation == DEFAULT_OPERATION) { logger.write("LiveSync: input stream is empty!"); break; @@ -163,12 +162,13 @@ public void run() { throw new IllegalArgumentException(String.format("\nLiveSync: Operation not recognised. %s", LIVESYNC_ERROR_SUGGESTION)); } - } while (this.input.available() > 0); + //TODO: do debouncing + runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); + } while (true); } catch (Exception e) { logger.write(String.format("Error while LiveSyncing: %s", e.toString())); e.printStackTrace(); - exceptionWhileLivesyncing = true; flushInputStream(); } catch (Throwable e) { logger.write(String.format("%s(%s): Error while LiveSyncing.\nOriginal Exception: %s", Thread.currentThread().getName(), Thread.currentThread().getId(), e.toString())); @@ -179,14 +179,11 @@ public void run() { } } - if (!exceptionWhileLivesyncing) { - runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); - } } private void flushInputStream() { try { - this.input.skip(1000000); + this.input.skip(Long.MAX_VALUE); } catch (IOException e) { } } @@ -205,8 +202,6 @@ private int getOperation() { } operation = Integer.parseInt(new String(operationBuff)); - } catch (NumberFormatException e) { - throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\nOriginal Exception: %s", OPERATION, LIVESYNC_ERROR_SUGGESTION, e.toString())); } catch (Exception e) { throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. %s\nOriginal Exception: %s", OPERATION, LIVESYNC_ERROR_SUGGESTION, e.toString())); } From 155aa2be4d59e3881ab0875960d013acff4e5fa3 Mon Sep 17 00:00:00 2001 From: plamen5kov Date: Fri, 26 Jan 2018 16:38:44 +0200 Subject: [PATCH 7/7] feature(livesnc): add dosync operation to avoid debouncing --- .../src/debug/java/com/tns/NativeScriptSyncService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java index 0d725ea0e..7c1161880 100644 --- a/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java +++ b/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java @@ -97,6 +97,7 @@ private class LiveSyncWorker implements Runnable { public static final int CONTENT_LENGTH_BYTE_SIZE = 10; public static final int DELETE_FILE_OPERATION = 7; public static final int CREATE_FILE_OPERATION = 8; + public static final int DO_SYNC_OPERATION = 9; public static final String FILE_NAME = "fileName"; public static final String FILE_NAME_LENGTH = FILE_NAME + "Length"; public static final String OPERATION = "operation"; @@ -143,7 +144,6 @@ public void run() { do { int operation = getOperation(); - System.out.println("Operation: " + input.available()); if (operation == DELETE_FILE_OPERATION) { String fileName = getFileName(); @@ -155,6 +155,10 @@ public void run() { byte[] content = getFileContent(fileName); createOrOverrideFile(fileName, content); + } else if (operation == DO_SYNC_OPERATION) { + + runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); + } else if (operation == DEFAULT_OPERATION) { logger.write("LiveSync: input stream is empty!"); break; @@ -162,8 +166,6 @@ public void run() { throw new IllegalArgumentException(String.format("\nLiveSync: Operation not recognised. %s", LIVESYNC_ERROR_SUGGESTION)); } - //TODO: do debouncing - runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); } while (true); } catch (Exception e) {