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..7c1161880 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,43 +26,16 @@ 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 { + 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; @@ -88,7 +45,7 @@ public LocalServerSocketThread(String name) { public void stop() { this.running = false; try { - serverSocket.close(); + deviceSystemSocket.close(); } catch (IOException e) { e.printStackTrace(); } @@ -97,46 +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(); } } - } - private class ListenerWorker implements Runnable { - private final DataInputStream input; - private Closeable socket; - private OutputStream output; - - public ListenerWorker(LocalSocket socket) throws IOException { - this.socket = socket; - input = new DataInputStream(socket.getInputStream()); - output = socket.getOutputStream(); + @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; } - public void run() { - try { - int length = input.readInt(); - input.readFully(new byte[length]); // ignore the payload - executePartialSync(context, syncDir); - executeRemovedSync(context, removedSyncDir); - - runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); - try { - output.write(1); - } catch (IOException e) { - e.printStackTrace(); - } - socket.close(); - } catch (IOException e) { - e.printStackTrace(); - } + @Override + protected void finalize() throws Throwable { + deviceSystemSocket.close(); } } @@ -146,187 +91,253 @@ public void startServer() { 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; + 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; + 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"; + 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 livesyncSocket; + private OutputStream output; + + public LiveSyncWorker(LocalSocket systemSocket) throws IOException { + this.livesyncSocket = systemSocket; + input = systemSocket.getInputStream(); + output = systemSocket.getOutputStream(); } - return shouldExecuteSync; - } + public void run() { + try { - final FileFilter deletingFilesFilter = new FileFilter() { - @Override - public boolean accept(File pathname) { - if (pathname.isDirectory()) { - return true; - } + output.write(context.getPackageName().getBytes()); - 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 (IOException e) { + logger.write(String.format("Error while LiveSyncing: Client socket might be closed!", e.toString())); + e.printStackTrace(); } - } + try { + do { + int operation = getOperation(); - boolean success = directory.delete(); - if (!success && directory.exists()) { - logger.write("Syncing: directory not deleted: " + directory.getAbsolutePath().toString()); - } - } + if (operation == DELETE_FILE_OPERATION) { - private void moveFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) { - File[] files = sourceDir.listFiles(); + String fileName = getFileName(); + deleteRecursive(new File(DEVICE_APP_DIR, fileName)); - if (files != null) { - if (logger.isEnabled()) { - logger.write("Syncing total number of fiiles: " + files.length); - } + } else if (operation == CREATE_FILE_OPERATION) { - for (int i = 0; i < files.length; i++) { - File file = files[i]; - if (file.isFile()) { - if (logger.isEnabled()) { - logger.write("Syncing: " + file.getAbsolutePath().toString()); - } + String fileName = getFileName(); + byte[] content = getFileContent(fileName); + createOrOverrideFile(fileName, content); - String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); - File targetFileDir = new File(targetFilePath); + } else if (operation == DO_SYNC_OPERATION) { - File targetParent = targetFileDir.getParentFile(); - if (targetParent != null) { - targetParent.mkdirs(); - } + runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); - boolean success = copyFile(file.getAbsolutePath(), targetFilePath); - if (!success) { - logger.write("Sync failed: " + file.getAbsolutePath().toString()); + } 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)); } - } else { - moveFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); + + } while (true); + + } catch (Exception e) { + logger.write(String.format("Error while LiveSyncing: %s", e.toString())); + e.printStackTrace(); + 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(); } } - } else { - if (logger.isEnabled()) { - logger.write("Can't move files. Source is empty."); + + } + + private void flushInputStream() { + try { + this.input.skip(Long.MAX_VALUE); + } catch (IOException e) { } } - } - // 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); + /* + * Tries to read operation input stream + * If the stream is empty, method returns -1 + * */ + private int getOperation() { + Integer operation = DEFAULT_OPERATION; + try { + + byte[] operationBuff = readNextBytes(OPERATION_BYTE_SIZE); + if (operationBuff == null) { + return DEFAULT_OPERATION; + } + operation = Integer.parseInt(new String(operationBuff)); - if (appDir.exists()) { - deleteDir(appDir); - moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); + } 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; } - } - private void executePartialSync(Context context, File sourceDir) { - String appPath = context.getFilesDir().getAbsolutePath() + "/app"; - final File appDir = new File(appPath); + private String getFileName() { + byte[] fileNameBuffer; + int fileNameLenth = -1; + byte[] fileNameLengthBuffer; - if (!appDir.exists()) { - Log.e("TNS", "Application dir does not exists. Partial Sync failed. appDir: " + appPath); - return; - } + try { - if (logger.isEnabled()) { - logger.write("Syncing sourceDir " + sourceDir.getAbsolutePath() + " with " + appDir.getAbsolutePath()); - } + fileNameLengthBuffer = readNextBytes(FILE_NAME_LENGTH_BYTE_SIZE); - moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); - } + } 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())); + } - private void deleteRemovedFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) { - if (!sourceDir.exists()) { - if (logger.isEnabled()) { - logger.write("Directory does not exist: " + sourceDir.getAbsolutePath()); + if (fileNameLengthBuffer == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. %s", FILE_NAME_LENGTH, LIVESYNC_ERROR_SUGGESTION)); } - } - File[] files = sourceDir.listFiles(); + try { + fileNameLenth = Integer.valueOf(new String(fileNameLengthBuffer)); + fileNameBuffer = readNextBytes(fileNameLenth); - 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()); - } + } 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())); + } - targetFile.delete(); - } else { - deleteRemovedFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); + if (fileNameBuffer == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. %s", FILE_NAME, LIVESYNC_ERROR_SUGGESTION)); + } - // this is done so empty folders, if any, are deleted after we're don deleting files. - if (targetFile.listFiles().length == 0) { - targetFile.delete(); - } - } + 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)); } + + return fileName.trim(); } - } - private void executeRemovedSync(final Context context, final File sourceDir) { - String appPath = context.getFilesDir().getAbsolutePath() + "/app"; - deleteRemovedFiles(sourceDir, sourceDir.getAbsolutePath(), appPath); - } + 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. %s\nOriginal Exception: %s", fileName, FILE_CONTENT_LENGTH, LIVESYNC_ERROR_SUGGESTION, e.toString())); + } - private boolean copyFile(String sourceFile, String destinationFile) { - FileInputStream fis = null; - FileOutputStream fos = null; + if (contentLength == null) { + 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 { - fis = new FileInputStream(sourceFile); - fos = new FileOutputStream(destinationFile, false); + try { + contentL = Integer.parseInt(new String(contentLength)); + contentBuff = readNextBytes(contentL); - byte[] buffer = new byte[4096]; - int read = 0; + } 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())); + } 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())); + } - while ((read = fis.read(buffer)) != -1) { - fos.write(buffer, 0, read); + if (contentBuff == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes. Did you send %s %s? %s", FILE_CONTENT, fileName, FILE_CONTENT, LIVESYNC_ERROR_SUGGESTION)); } - } 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 { + + return contentBuff; + } + + private void createOrOverrideFile(String fileName, byte[] content) throws IOException { + File fileToCreate = prepareFile(fileName); try { - if (fis != null) { - fis.close(); - } - if (fos != null) { - fos.close(); + + fileToCreate.getParentFile().mkdirs(); + FileOutputStream fos = new FileOutputStream(fileToCreate.getCanonicalPath()); + fos.write(content); + fos.close(); + + } catch (Exception e) { + throw new IOException(String.format("\nLiveSync: failed to write file: %s\nOriginal Exception: %s", fileName, e.toString())); + } + } + + void deleteRecursive(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) + for (File child : fileOrDirectory.listFiles()) { + deleteRecursive(child); } - } catch (IOException e) { + + fileOrDirectory.delete(); + } + + @NonNull + private File prepareFile(String fileName) { + File fileToCreate = new File(DEVICE_APP_DIR, fileName); + if (fileToCreate.exists()) { + fileToCreate.delete(); } + 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; + } + size -= bytesRead; + bufferWriteOffset += bytesRead; + } while (size > 0); + + return buffer; } - return true; + @Override + protected void finalize() throws Throwable { + this.livesyncSocket.close(); + } } } 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) {