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;
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 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;
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);
deleteRecursive(fullSyncDir);
return;
}
if (syncDir.exists()) {
executePartialSync(context, syncDir);
deleteRecursive(syncDir);
}
if (removedSyncDir.exists()) {
executeRemovedSync(context, removedSyncDir);
deleteRecursive(removedSyncDir);
}
}
private class LocalServerSocketThread implements Runnable {
private volatile boolean running;
private final String name;
private ListenerWorker commThread;
private LocalServerSocket serverSocket;
public LocalServerSocketThread(String name) {
this.name = name;
this.running = false;
}
public void stop() {
this.running = false;
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
running = true;
try {
serverSocket = new LocalServerSocket(this.name);
while (running) {
LocalSocket socket = serverSocket.accept();
commThread = new ListenerWorker(socket);
new Thread(commThread).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();
}
public void run() {
try {
int length = input.readInt();
input.readFully(new byte[length]); // ignore the payload
executePartialSync(context, syncDir);
// delete temporary /sync dir after syncing .xml/.css resources
deleteRecursive(syncDir);
executeRemovedSync(context, removedSyncDir);
// delete temporary /removedsync dir after removing files from the project
deleteRecursive(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();
}
}
}
public void startServer() {
localServerThread = new LocalServerSocketThread(context.getPackageName() + "-livesync");
localServerJavaThread = new Thread(localServerThread);
localServerJavaThread.start();
}
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
deleteRecursive(child);
}
}
boolean success = fileOrDirectory.delete();
if (!success && fileOrDirectory.isDirectory()) {
android.util.Log.d("Sync", "Failed to delete temp sync directory: " + fileOrDirectory.getAbsolutePath());
}
}
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;
}
return shouldExecuteSync;
}
final FileFilter deletingFilesFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
return true;
}
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);
}
}
boolean success = directory.delete();
if (!success && directory.exists()) {
logger.write("Syncing: directory not deleted: " + directory.getAbsolutePath().toString());
}
}
private void moveFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) {
File[] files = sourceDir.listFiles();
if (files != null) {
if (logger.isEnabled()) {
logger.write("Syncing total number of fiiles: " + files.length);
}
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 targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath);
File targetFileDir = new File(targetFilePath);
File targetParent = targetFileDir.getParentFile();
if (targetParent != null) {
targetParent.mkdirs();
}
boolean success = copyFile(file.getAbsolutePath(), targetFilePath);
if (!success) {
logger.write("Sync failed: " + file.getAbsolutePath().toString());
}
} else {
moveFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath);
}
}
} else {
if (logger.isEnabled()) {
logger.write("Can't move files. Source is empty.");
}
}
}
// 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);
if (appDir.exists()) {
deleteDir(appDir);
moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath());
}
}
private void executePartialSync(Context context, File sourceDir) {
String appPath = context.getFilesDir().getAbsolutePath() + "/app";
final File appDir = new File(appPath);
if (!appDir.exists()) {
Log.e("TNS", "Application dir does not exists. Partial Sync failed. appDir: " + appPath);
return;
}
if (logger.isEnabled()) {
logger.write("Syncing sourceDir " + sourceDir.getAbsolutePath() + " with " + appDir.getAbsolutePath());
}
moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath());
}
private void deleteRemovedFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) {
if (!sourceDir.exists()) {
if (logger.isEnabled()) {
logger.write("Directory does not exist: " + sourceDir.getAbsolutePath());
}
}
File[] files = sourceDir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
File file = files[i];
if (file.isFile()) {
if (logger.isEnabled()) {
logger.write("Syncing removed file: " + file.getAbsolutePath().toString());
}
String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath);
File targetFile = new File(targetFilePath);
targetFile.delete();
} else {
deleteRemovedFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath);
}
}
}
}
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);
byte[] buffer = new byte[4096];
int read = 0;
while ((read = fis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
} 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();
}
} catch (IOException e) {
}
}
return true;
}
}