From cf67b1543db19da1bbad57e691cf55bf57dd81d8 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Mon, 18 Jun 2018 19:54:31 +0300 Subject: [PATCH 01/22] Initial integration of sockets tool --- lib/definitions/livesync.d.ts | 3 +- lib/device-path-provider.ts | 2 +- .../android-device-livesync-service.ts | 9 ++- ...android-device-livesync-sockets-service.ts | 59 +++++++++++++++++++ .../livesync/android-livesync-service.ts | 11 +++- .../livesync/device-livesync-service-base.ts | 16 ++++- .../livesync/ios-device-livesync-service.ts | 10 ++-- .../platform-livesync-service-base.ts | 21 ++++--- 8 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 lib/services/livesync/android-device-livesync-sockets-service.ts diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 82d70d7958..291d6acc26 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -364,7 +364,8 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. * @return {Promise} */ - removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise; + removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath?: string): Promise; + transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise; } interface IAndroidNativeScriptDeviceLiveSyncService { diff --git a/lib/device-path-provider.ts b/lib/device-path-provider.ts index 092d77eac2..c18d9a73ef 100644 --- a/lib/device-path-provider.ts +++ b/lib/device-path-provider.ts @@ -25,7 +25,7 @@ export class DevicePathProvider implements IDevicePathProvider { } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { projectRoot = `/data/local/tmp/${options.appIdentifier}`; if (!options.getDirname) { - const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device }); + const deviceLiveSyncService = this.$injector.resolve(AndroidDeviceLiveSyncService, { device }); const hashService = deviceLiveSyncService.getDeviceHashService(options.appIdentifier); const hashFile = options.syncAllFiles ? null : await hashService.doesShasumFileExistsOnDevice(); const syncFolderName = options.watch || hashFile ? LiveSyncPaths.SYNC_DIR_NAME : LiveSyncPaths.FULLSYNC_DIR_NAME; diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index c99f514c6a..62fdd07492 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -8,17 +8,16 @@ import * as path from "path"; import * as net from "net"; export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService { - private device: Mobile.IAndroidDevice; private port: number; - constructor(_device: Mobile.IDevice, + constructor( private $mobileHelper: Mobile.IMobileHelper, private $devicePathProvider: IDevicePathProvider, private $injector: IInjector, private $androidProcessService: Mobile.IAndroidProcessService, - protected $platformsData: IPlatformsData) { - super($platformsData); - this.device = (_device); + protected $platformsData: IPlatformsData, + protected device: Mobile.IAndroidDevice) { + super($platformsData, device); } public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts new file mode 100644 index 0000000000..7e42ec3fb0 --- /dev/null +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -0,0 +1,59 @@ +import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-android-debug-bridge"; +import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; +import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; +import { cache } from "../../common/decorators"; +const LivesyncTool = require("nativescript-android-livesync-lib"); + +export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService { + private port: number; + private livesyncTool: any; + + constructor( + private data: IProjectData, + private $injector: IInjector, + protected $platformsData: IPlatformsData, + protected $staticConfig: Config.IStaticConfig, + protected device: Mobile.IAndroidDevice) { + super($platformsData, device); + this.livesyncTool = new LivesyncTool(); + } + + public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { + await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName }); + } + + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { + //await this.connectLivesyncTool("", projectData.projectId); + await this.livesyncTool.sendDoSyncOperation() + } + + public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { + await this.connectLivesyncTool(projectFilesPath, deviceAppData.appIdentifier); + await this.livesyncTool.removeFilesArray(_.map(localToDevicePaths, (element: any) => { return element.filePath })); + } + + public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { + await this.connectLivesyncTool(projectFilesPath, deviceAppData.appIdentifier); + await this.livesyncTool.sendFilesArray(localToDevicePaths.map(localToDevicePathData => localToDevicePathData.getLocalPath())); + + return localToDevicePaths; + } + + private async connectLivesyncTool(projectFilesPath: string, appIdentifier: string) { + const adbPath = await this.$staticConfig.getAdbFilePath(); + await this.livesyncTool.connect({ + fullApplicationName: appIdentifier, + port: this.port, + deviceIdentifier: this.device.deviceInfo.identifier, + baseDir: projectFilesPath, + adbPath: adbPath + }); + } + + + @cache() + public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService { + const adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier }); + return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier }); + } +} diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index ec33162b2c..93d63c449d 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -1,5 +1,7 @@ import { AndroidDeviceLiveSyncService } from "./android-device-livesync-service"; +import { AndroidDeviceSocketsLiveSyncService } from "./android-device-livesync-sockets-service"; import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base"; +import * as semver from "semver"; export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { constructor(protected $platformsData: IPlatformsData, @@ -12,9 +14,12 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider, $projectFilesProvider); } - protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir): INativeScriptDeviceLiveSyncService { - const service = this.$injector.resolve(AndroidDeviceLiveSyncService, { _device: device, data }); - return service; + protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir, frameworkVersion: string): INativeScriptDeviceLiveSyncService { + if(semver.gte(frameworkVersion, "4.2.0")){ + return this.$injector.resolve(AndroidDeviceSocketsLiveSyncService, { device, data }); + } + + return this.$injector.resolve(AndroidDeviceLiveSyncService, { device, data }); } public async prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir): Promise { /* */ } diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts index bb1f44961f..0598cd22d9 100644 --- a/lib/services/livesync/device-livesync-service-base.ts +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -4,7 +4,10 @@ import * as path from "path"; export abstract class DeviceLiveSyncServiceBase { private static FAST_SYNC_FILE_EXTENSIONS = [".css", ".xml", ".html"]; - constructor(protected $platformsData: IPlatformsData) { } + constructor( + protected $platformsData: IPlatformsData, + protected device: Mobile.IDevice + ) { } public canExecuteFastSync(filePath: string, projectData: IProjectData, platform: string): boolean { const fastSyncFileExtensions = this.getFastLiveSyncFileExtensions(platform, projectData); @@ -18,4 +21,15 @@ export abstract class DeviceLiveSyncServiceBase { return fastSyncFileExtensions; } + public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { + let transferredFiles: Mobile.ILocalToDevicePathData[] = []; + + if (isFullSync) { + transferredFiles = await this.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); + } else { + transferredFiles = await this.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); + } + + return transferredFiles; + } } diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index a797d11d09..df2291d319 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -7,10 +7,8 @@ let currentPageReloadId = 0; export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements INativeScriptDeviceLiveSyncService { private socket: net.Socket; - private device: Mobile.IiOSDevice; - constructor(_device: Mobile.IiOSDevice, - data: IProjectDir, + constructor( private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $iOSNotification: IiOSNotification, private $iOSEmulatorServices: Mobile.IiOSSimulatorService, @@ -18,9 +16,9 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen private $logger: ILogger, private $fs: IFileSystem, private $processService: IProcessService, - protected $platformsData: IPlatformsData) { - super($platformsData); - this.device = _device; + protected $platformsData: IPlatformsData, + protected device: Mobile.IiOSDevice) { + super($platformsData, device); } private async setupSocketIfNeeded(projectData: IProjectData): Promise { diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 8c25c182fd..d846cf742c 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -14,14 +14,15 @@ export abstract class PlatformLiveSyncServiceBase { public getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService { const key = device.deviceInfo.identifier + projectData.projectId; + const frameworkVersion = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData).platformProjectService.getFrameworkVersion(projectData); if (!this._deviceLiveSyncServicesCache[key]) { - this._deviceLiveSyncServicesCache[key] = this._getDeviceLiveSyncService(device, projectData); + this._deviceLiveSyncServicesCache[key] = this._getDeviceLiveSyncService(device, projectData, frameworkVersion); } return this._deviceLiveSyncServicesCache[key]; } - protected abstract _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir): INativeScriptDeviceLiveSyncService; + protected abstract _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectData, frameworkVersion: string): INativeScriptDeviceLiveSyncService; public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { if (liveSyncInfo.isFullSync || liveSyncInfo.modifiedFilesData.length) { @@ -44,7 +45,7 @@ export abstract class PlatformLiveSyncServiceBase { const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, null, []); - const modifiedFilesData = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true); + const modifiedFilesData = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, true, projectData); return { modifiedFilesData, @@ -77,7 +78,7 @@ export abstract class PlatformLiveSyncServiceBase { const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, existingFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - modifiedLocalToDevicePaths = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false); + modifiedLocalToDevicePaths = await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, false, projectData); } } @@ -94,7 +95,7 @@ export abstract class PlatformLiveSyncServiceBase { modifiedLocalToDevicePaths.push(...localToDevicePaths); const deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); - await deviceLiveSyncService.removeFiles(deviceAppData, localToDevicePaths); + await deviceLiveSyncService.removeFiles(deviceAppData, localToDevicePaths, projectFilesPath); } return { @@ -104,13 +105,11 @@ export abstract class PlatformLiveSyncServiceBase { }; } - protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { + protected async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean, projectData: IProjectData): Promise { let transferredFiles: Mobile.ILocalToDevicePathData[] = []; - if (isFullSync) { - transferredFiles = await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); - } else { - transferredFiles = await deviceAppData.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths); - } + const deviceLiveSyncService = this.getDeviceLiveSyncService(deviceAppData.device, projectData); + + transferredFiles = await deviceLiveSyncService.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, isFullSync); this.logFilesSyncInformation(transferredFiles, "Successfully transferred %s.", this.$logger.info); From 27babb849487260c48702abac21895bdf182269b Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Fri, 22 Jun 2018 13:59:52 +0300 Subject: [PATCH 02/22] Generate hashes and init connection on beforeLiveSyncAction --- lib/common | 2 +- ...android-device-livesync-sockets-service.ts | 43 ++++++++++++++++--- .../platform-livesync-service-base.ts | 6 ++- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/common b/lib/common index c3aaa6cf49..ec5e03102e 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit c3aaa6cf49e108da379a4445dd5e1d001817110e +Subproject commit ec5e03102e5772458c0e768c16ad2ecf939b3933 diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 7e42ec3fb0..75198483a4 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -1,7 +1,9 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-android-debug-bridge"; import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; -import { cache } from "../../common/decorators"; +import { APP_FOLDER_NAME } from "../../constants"; +import * as path from "path"; + const LivesyncTool = require("nativescript-android-livesync-lib"); export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService { @@ -13,32 +15,61 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa private $injector: IInjector, protected $platformsData: IPlatformsData, protected $staticConfig: Config.IStaticConfig, - protected device: Mobile.IAndroidDevice) { + protected device: Mobile.IAndroidDevice, + private $options: ICommonOptions) { super($platformsData, device); this.livesyncTool = new LivesyncTool(); } public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { + const platformData = this.$platformsData.getPlatformData(deviceAppData.platform, this.data); + const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); + await this.connectLivesyncTool(projectFilesPath, this.data.projectId); await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName }); } public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { - //await this.connectLivesyncTool("", projectData.projectId); await this.livesyncTool.sendDoSyncOperation() } public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { - await this.connectLivesyncTool(projectFilesPath, deviceAppData.appIdentifier); await this.livesyncTool.removeFilesArray(_.map(localToDevicePaths, (element: any) => { return element.filePath })); } public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { - await this.connectLivesyncTool(projectFilesPath, deviceAppData.appIdentifier); + let transferredFiles; + + if (isFullSync) { + transferredFiles = await this._transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); + } else { + transferredFiles = this._transferFiles(deviceAppData, localToDevicePaths, projectFilesPath); + } + + return transferredFiles; + } + + private async _transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { await this.livesyncTool.sendFilesArray(localToDevicePaths.map(localToDevicePathData => localToDevicePathData.getLocalPath())); return localToDevicePaths; } + private async _transferDirectory(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { + const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier); + const currentShasums: IStringDictionary = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths); + const oldShasums = await deviceHashService.getShasumsFromDevice(); + + if(this.$options.force || !oldShasums) { + this.livesyncTool.sendDirectory(projectFilesPath); + + return localToDevicePaths; + } else { + const changedShasums = deviceHashService.getChnagedShasums(oldShasums, currentShasums); + await this.livesyncTool.sendFilesArray(_.map(changedShasums, (hash: string, pathToFile: string) => pathToFile)); + await deviceHashService.uploadHashFileToDevice(currentShasums); + } + } + private async connectLivesyncTool(projectFilesPath: string, appIdentifier: string) { const adbPath = await this.$staticConfig.getAdbFilePath(); await this.livesyncTool.connect({ @@ -50,8 +81,6 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa }); } - - @cache() public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService { const adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier }); return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier }); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index d846cf742c..616ca4899b 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -1,6 +1,7 @@ import * as path from "path"; import * as util from "util"; import { APP_FOLDER_NAME } from "../../constants"; +import { getHash } from "../common/helpers"; export abstract class PlatformLiveSyncServiceBase { private _deviceLiveSyncServicesCache: IDictionary = {}; @@ -13,8 +14,9 @@ export abstract class PlatformLiveSyncServiceBase { private $projectFilesProvider: IProjectFilesProvider) { } public getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService { - const key = device.deviceInfo.identifier + projectData.projectId; - const frameworkVersion = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData).platformProjectService.getFrameworkVersion(projectData); + const platformData = this.$platformsData.getPlatformData(device.deviceInfo.platform, projectData); + const frameworkVersion = platformData.platformProjectService.getFrameworkVersion(projectData); + const key = getHash(`${device.deviceInfo.identifier}${projectData.projectId}${projectData.projectDir}${frameworkVersion}`); if (!this._deviceLiveSyncServicesCache[key]) { this._deviceLiveSyncServicesCache[key] = this._getDeviceLiveSyncService(device, projectData, frameworkVersion); } From d9f8cb870d43a7844f41f1c643c4c7a02ee87ed3 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Wed, 27 Jun 2018 19:27:27 +0300 Subject: [PATCH 03/22] Await sync before restart. Update checksums. --- ...android-device-livesync-sockets-service.ts | 39 +++++++++++++++---- .../platform-livesync-service-base.ts | 7 +++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 75198483a4..893a0a8b7d 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -24,12 +24,22 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { const platformData = this.$platformsData.getPlatformData(deviceAppData.platform, this.data); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); - await this.connectLivesyncTool(projectFilesPath, this.data.projectId); await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName }); + await this.connectLivesyncTool(projectFilesPath, this.data.projectId); } public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { - await this.livesyncTool.sendDoSyncOperation() + const canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(liveSyncInfo.modifiedFilesData, + (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); + + if(!canExecuteFastSync && liveSyncInfo.modifiedFilesData.length) { + await this.livesyncTool.sendDoSyncOperation(); + await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); + } else if(liveSyncInfo.modifiedFilesData.length) { + await this.livesyncTool.sendDoSyncOperation(); + } + + this.livesyncTool.end(); } public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { @@ -42,7 +52,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa if (isFullSync) { transferredFiles = await this._transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); } else { - transferredFiles = this._transferFiles(deviceAppData, localToDevicePaths, projectFilesPath); + transferredFiles = await this._transferFiles(deviceAppData, localToDevicePaths, projectFilesPath); } return transferredFiles; @@ -55,19 +65,32 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa } private async _transferDirectory(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { + let transferedFiles: Mobile.ILocalToDevicePathData[]; const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier); const currentShasums: IStringDictionary = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths); const oldShasums = await deviceHashService.getShasumsFromDevice(); if(this.$options.force || !oldShasums) { - this.livesyncTool.sendDirectory(projectFilesPath); - - return localToDevicePaths; + await this.livesyncTool.sendDirectory(projectFilesPath); + await deviceHashService.uploadHashFileToDevice(currentShasums); + transferedFiles = localToDevicePaths; } else { const changedShasums = deviceHashService.getChnagedShasums(oldShasums, currentShasums); - await this.livesyncTool.sendFilesArray(_.map(changedShasums, (hash: string, pathToFile: string) => pathToFile)); - await deviceHashService.uploadHashFileToDevice(currentShasums); + const changedFiles = _.map(changedShasums, (hash: string, pathToFile: string) => pathToFile); + if(changedFiles.length){ + await this.livesyncTool.sendFilesArray(changedFiles); + await deviceHashService.uploadHashFileToDevice(currentShasums); + transferedFiles = localToDevicePaths.filter(localToDevicePathData => { + if(changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0){ + return true; + } + }); + } else { + transferedFiles = []; + } } + + return transferedFiles; } private async connectLivesyncTool(projectFilesPath: string, appIdentifier: string) { diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 616ca4899b..dad57c0e53 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -1,7 +1,7 @@ import * as path from "path"; import * as util from "util"; import { APP_FOLDER_NAME } from "../../constants"; -import { getHash } from "../common/helpers"; +import { getHash } from "../../common/helpers"; export abstract class PlatformLiveSyncServiceBase { private _deviceLiveSyncServicesCache: IDictionary = {}; @@ -58,9 +58,14 @@ export abstract class PlatformLiveSyncServiceBase { public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { const projectData = liveSyncInfo.projectData; + const deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); const deviceAppData = await this.getAppData(syncInfo); + if (deviceLiveSyncService.beforeLiveSyncAction) { + await deviceLiveSyncService.beforeLiveSyncAction(deviceAppData); + } + let modifiedLocalToDevicePaths: Mobile.ILocalToDevicePathData[] = []; if (liveSyncInfo.filesToSync.length) { const filesToSync = liveSyncInfo.filesToSync; From 688e43127493943843821a38d3d7f3e2a971bf16 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Tue, 3 Jul 2018 14:43:09 +0300 Subject: [PATCH 04/22] chore: improve code --- .../livesync/android-device-livesync-sockets-service.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 893a0a8b7d..ee7a1f366e 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -32,11 +32,12 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa const canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(liveSyncInfo.modifiedFilesData, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); - if(!canExecuteFastSync && liveSyncInfo.modifiedFilesData.length) { - await this.livesyncTool.sendDoSyncOperation(); - await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); - } else if(liveSyncInfo.modifiedFilesData.length) { + if(liveSyncInfo.modifiedFilesData.length) { await this.livesyncTool.sendDoSyncOperation(); + + if(!canExecuteFastSync){ + await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); + } } this.livesyncTool.end(); From 4e3477eb50d880fa42a2dabfbb51334d2ec41feb Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Fri, 6 Jul 2018 14:34:47 +0300 Subject: [PATCH 05/22] Add interval for syc wait feedback --- .../android-device-livesync-sockets-service.ts | 17 ++++++++++++++++- package.json | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index ee7a1f366e..d88ca7ba95 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -15,6 +15,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa private $injector: IInjector, protected $platformsData: IPlatformsData, protected $staticConfig: Config.IStaticConfig, + private $logger: ILogger, protected device: Mobile.IAndroidDevice, private $options: ICommonOptions) { super($platformsData, device); @@ -33,7 +34,21 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); if(liveSyncInfo.modifiedFilesData.length) { - await this.livesyncTool.sendDoSyncOperation(); + const operationUid = this.livesyncTool.generateOperationUid(); + const doSyncPromise = this.livesyncTool.sendDoSyncOperation(operationUid); + + //TODO clear interval on exit sygnals/stopLivesync + const syncInterval : NodeJS.Timer = setInterval(() => { + if(this.livesyncTool.isOperationInProgress(operationUid)){ + this.$logger.info("Sync operation in progress..."); + } + }, 10000); + + const clearSyncInterval = () => { + clearInterval(syncInterval); + } + doSyncPromise.then(clearSyncInterval, clearSyncInterval); + await doSyncPromise; if(!canExecuteFastSync){ await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); diff --git a/package.json b/package.json index b97ef7398c..7ca8bc8d25 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,8 @@ "xmldom": "0.1.21", "xmlhttprequest": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", "yargs": "6.0.0", - "zipstream": "https://github.com/Icenium/node-zipstream/tarball/master" + "zipstream": "https://github.com/Icenium/node-zipstream/tarball/master", + "nativescript-android-livesync-lib": "file:../nativescript-android-livesync-lib" }, "analyze": true, "devDependencies": { From fb8af6980b8f88ed86be28ae0dda54aed397238d Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 10 Jul 2018 14:01:33 +0300 Subject: [PATCH 06/22] Move https://github.com/NativeScript/nativescript-android-livesync-lib to {N} CLI and fix lint errors --- lib/definitions/livesync-global.d.ts | 8 + lib/definitions/livesync.d.ts | 64 +++ ...android-device-livesync-sockets-service.ts | 39 +- .../livesync/android-livesync-service.ts | 4 +- lib/services/livesync/livesync-library.ts | 397 ++++++++++++++++++ .../platform-livesync-service-base.ts | 4 +- npm-shrinkwrap.json | 40 +- 7 files changed, 529 insertions(+), 27 deletions(-) create mode 100644 lib/definitions/livesync-global.d.ts create mode 100644 lib/services/livesync/livesync-library.ts diff --git a/lib/definitions/livesync-global.d.ts b/lib/definitions/livesync-global.d.ts new file mode 100644 index 0000000000..ac93fae85a --- /dev/null +++ b/lib/definitions/livesync-global.d.ts @@ -0,0 +1,8 @@ +import * as stream from "stream"; + +declare global { + interface IDuplexSocket extends stream.Duplex { + uid?: string; + } +} + diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 291d6acc26..49d8b0bba6 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -377,6 +377,70 @@ interface IAndroidNativeScriptDeviceLiveSyncService { getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService; } +interface ILivesyncTool { + /** + * Creates new socket connection. + * @param configuration - The configuration to the socket connection. + * @returns {Promise} + */ + connect(configuration: ILivesyncToolConfiguration): Promise; + /** + * Sends a file through the socket. + * @param filePath - The full path to the file. + * @returns {Promise} + */ + sendFile(filePath: string): Promise; + /** + * Sends files through the socket. + * @param filePaths - Array of files that will be send by the socket. + * @returns {Promise} + */ + sendFiles(filePaths: string[]): Promise; + /** + * Sends all files from directory by the socket. + * @param directoryPath - The path to the directory which files will be send by the socket. + * @returns {Promise} + */ + sendDirectory(directoryPath: string): Promise; + /** + * Removes file + * @param filePath - The full path to the file. + * @returns {Promise} + */ + removeFile(filePath: string): Promise; + /** + * Removes files + * @param filePaths - Array of files that will be removed. + * @returns {Promise} + */ + removeFiles(filePaths: string[]): Promise; + /** + * Sends doSyncOperation that will be handeled by the runtime. + * @param operationId - The identifier of the operation + * @param timeout - The timeout in miliseconds + * @returns {Promise} + */ + sendDoSyncOperation(operationId: string, timeout?: number): Promise; + /** + * Generates new operation identifier. + */ + generateOperationIdentifier(): string; + /** + * Checks if the current operation is in progress. + * @param operationId - The identifier of the operation. + */ + isOperationInProgress(operationId: string): boolean; + end(): void; +} + +interface ILivesyncToolConfiguration { + appIdentifier: string; + deviceIdentifier: string; + appPlatformsPath: string; // path to platforms/android/app/src/main/assets/app/ + localHostAddress?: string; + errorHandler?: any; +} + interface IDeviceProjectRootOptions { appIdentifier: string; getDirname?: boolean; diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index d88ca7ba95..ef1dbc8667 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -2,13 +2,11 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-and import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; import { APP_FOLDER_NAME } from "../../constants"; +import { LivesyncTool } from "./livesync-library"; import * as path from "path"; -const LivesyncTool = require("nativescript-android-livesync-lib"); - export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService { - private port: number; - private livesyncTool: any; + private livesyncTool: ILivesyncTool; constructor( private data: IProjectData, @@ -19,7 +17,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa protected device: Mobile.IAndroidDevice, private $options: ICommonOptions) { super($platformsData, device); - this.livesyncTool = new LivesyncTool(); + this.livesyncTool = this.$injector.resolve(LivesyncTool); } public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { @@ -33,24 +31,24 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa const canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(liveSyncInfo.modifiedFilesData, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); - if(liveSyncInfo.modifiedFilesData.length) { - const operationUid = this.livesyncTool.generateOperationUid(); - const doSyncPromise = this.livesyncTool.sendDoSyncOperation(operationUid); + if (liveSyncInfo.modifiedFilesData.length) { + const operationIdentifier = this.livesyncTool.generateOperationIdentifier(); + const doSyncPromise = this.livesyncTool.sendDoSyncOperation(operationIdentifier); //TODO clear interval on exit sygnals/stopLivesync const syncInterval : NodeJS.Timer = setInterval(() => { - if(this.livesyncTool.isOperationInProgress(operationUid)){ + if (this.livesyncTool.isOperationInProgress(operationIdentifier)) { this.$logger.info("Sync operation in progress..."); } }, 10000); const clearSyncInterval = () => { clearInterval(syncInterval); - } + }; doSyncPromise.then(clearSyncInterval, clearSyncInterval); await doSyncPromise; - if(!canExecuteFastSync){ + if (!canExecuteFastSync) { await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); } } @@ -59,7 +57,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa } public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { - await this.livesyncTool.removeFilesArray(_.map(localToDevicePaths, (element: any) => { return element.filePath })); + await this.livesyncTool.removeFiles(_.map(localToDevicePaths, (element: any) => { return element.filePath; })); } public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { @@ -75,7 +73,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa } private async _transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { - await this.livesyncTool.sendFilesArray(localToDevicePaths.map(localToDevicePathData => localToDevicePathData.getLocalPath())); + await this.livesyncTool.sendFiles(localToDevicePaths.map(localToDevicePathData => localToDevicePathData.getLocalPath())); return localToDevicePaths; } @@ -86,18 +84,18 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa const currentShasums: IStringDictionary = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths); const oldShasums = await deviceHashService.getShasumsFromDevice(); - if(this.$options.force || !oldShasums) { + if (this.$options.force || !oldShasums) { await this.livesyncTool.sendDirectory(projectFilesPath); await deviceHashService.uploadHashFileToDevice(currentShasums); transferedFiles = localToDevicePaths; } else { const changedShasums = deviceHashService.getChnagedShasums(oldShasums, currentShasums); const changedFiles = _.map(changedShasums, (hash: string, pathToFile: string) => pathToFile); - if(changedFiles.length){ - await this.livesyncTool.sendFilesArray(changedFiles); + if (changedFiles.length) { + await this.livesyncTool.sendFiles(changedFiles); await deviceHashService.uploadHashFileToDevice(currentShasums); transferedFiles = localToDevicePaths.filter(localToDevicePathData => { - if(changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0){ + if (changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0) { return true; } }); @@ -110,13 +108,10 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa } private async connectLivesyncTool(projectFilesPath: string, appIdentifier: string) { - const adbPath = await this.$staticConfig.getAdbFilePath(); await this.livesyncTool.connect({ - fullApplicationName: appIdentifier, - port: this.port, + appIdentifier, deviceIdentifier: this.device.deviceInfo.identifier, - baseDir: projectFilesPath, - adbPath: adbPath + appPlatformsPath: projectFilesPath }); } diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index 93d63c449d..4af4857cd8 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -15,10 +15,10 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen } protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir, frameworkVersion: string): INativeScriptDeviceLiveSyncService { - if(semver.gte(frameworkVersion, "4.2.0")){ + if (semver.gte(frameworkVersion, "4.2.0")) { return this.$injector.resolve(AndroidDeviceSocketsLiveSyncService, { device, data }); } - + return this.$injector.resolve(AndroidDeviceLiveSyncService, { device, data }); } diff --git a/lib/services/livesync/livesync-library.ts b/lib/services/livesync/livesync-library.ts new file mode 100644 index 0000000000..34c78fe72e --- /dev/null +++ b/lib/services/livesync/livesync-library.ts @@ -0,0 +1,397 @@ +import * as net from "net"; +import * as path from "path"; +import * as crypto from "crypto"; + +const PROTOCOL_VERSION_LENGTH_SIZE = 1; +const PROTOCOL_OPERATION_LENGTH_SIZE = 1; +const SIZE_BYTE_LENGTH = 1; +const DELETE_FILE_OPERATION = 7; +const CREATE_FILE_OPERATION = 8; +const DO_SYNC_OPERATION = 9; +const ERROR_REPORT = 1; +const OPERATION_END_REPORT = 2; +const REPORT_LENGTH = 1; +const DEFAULT_LOCAL_HOST_ADDRESS = "127.0.0.1"; + +export class LivesyncTool implements ILivesyncTool { + private operationPromises: IDictionary; + private socketError: string | Error; + private socketConnection: IDuplexSocket; + private configuration: ILivesyncToolConfiguration; + private appPlatformsPath: string; + + constructor(private $androidProcessService: Mobile.IAndroidProcessService, + private $errors: IErrors, + private $fs: IFileSystem, + private $logger: ILogger) { + this.operationPromises = Object.create(null); + this.socketError = null; + this.socketConnection = null; + } + + public async connect(configuration: ILivesyncToolConfiguration): Promise { + if (!configuration.appIdentifier) { + this.$errors.fail(`You need to provide "appIdentifier" as a configuration property!`); + } + + if (!configuration.appPlatformsPath) { + this.$errors.fail(`You need to provide "baseDir" as a configuration property!`); + } + + if (!configuration.localHostAddress) { + configuration.localHostAddress = DEFAULT_LOCAL_HOST_ADDRESS; + } + + this.configuration = configuration; + this.appPlatformsPath = this.configuration.appPlatformsPath; + this.socketError = null; + + const port = await this.$androidProcessService.forwardFreeTcpToAbstractPort({ + deviceIdentifier: configuration.deviceIdentifier, + appIdentifier: configuration.appIdentifier, + abstractPort: `localabstract:${configuration.appIdentifier}-livesync` + }); + + const connectionResult = await this.connectEventuallyUntilTimeout(this.createSocket.bind(this, port), 30000); + this.handleConnection(connectionResult); + } + + public async sendFile(filePath: string): Promise { + await this.sendFileHeader(filePath); + await this.sendFileContent(filePath); + } + + public async sendFiles(filePaths: string[]) { + for (const filePath of filePaths) { + if (this.$fs.getLsStats(filePath).isFile()) { + if (!this.$fs.exists(filePath)) { + this.$errors.fail(`${filePath} doesn't exist.`); + } + + await this.sendFile(filePath); + } + } + } + + public sendDirectory(directoryPath: string) { + const list = this.$fs.enumerateFilesInDirectorySync(directoryPath); + return this.sendFiles(list); + } + + public removeFile(filePath: string): Promise { + return new Promise((resolve: Function, reject: Function) => { + this.verifyActiveConnection(reject); + const filePathData = this.getFilePathData(filePath); + const headerBuffer = Buffer.alloc(PROTOCOL_OPERATION_LENGTH_SIZE + + SIZE_BYTE_LENGTH + + filePathData.filePathLengthSize + + filePathData.filePathLengthBytes); + + let offset = 0; + offset += headerBuffer.write(DELETE_FILE_OPERATION.toString(), offset, PROTOCOL_OPERATION_LENGTH_SIZE); + offset = headerBuffer.writeInt8(filePathData.filePathLengthSize, offset); + offset += headerBuffer.write(filePathData.filePathLengthString, offset, filePathData.filePathLengthSize); + headerBuffer.write(filePathData.relativeFilePath, offset, filePathData.filePathLengthBytes); + const hash = crypto.createHash("md5").update(headerBuffer).digest(); + + this.socketConnection.write(headerBuffer); + this.socketConnection.write(hash, () => { + resolve(true); + }); + }); + } + + public removeFiles(files: string[]) { + return Promise.all(files.map(file => this.removeFile(file))); + } + + public generateOperationIdentifier(): string { + return crypto.randomBytes(16).toString("hex"); + } + + public isOperationInProgress(operationId: string): boolean { + return !!this.operationPromises[operationId]; + } + + public sendDoSyncOperation(operationId: string, timeout: number): Promise { + const id = operationId || this.generateOperationIdentifier(); + const operationPromise = new Promise((resolve: Function, reject: Function) => { + this.verifyActiveConnection(reject); + + const message = `${DO_SYNC_OPERATION}${id}`; + const socketId = this.socketConnection.uid; + const hash = crypto.createHash("md5").update(message).digest(); + + this.operationPromises[id] = { + resolve, + reject, + socketId + }; + + this.socketConnection.write(message); + this.socketConnection.write(hash); + + setTimeout(() => { + if (this.isOperationInProgress(id)) { + this.handleSocketError(socketId, "Sync operation is taking too long"); + } + }, 60000); + }); + + return operationPromise; + } + + public end() { + if (this.socketConnection) { + this.socketConnection.end(); + } + } + + private sendFileHeader(filePath: string): Promise { + return new Promise((resolve, reject) => { + let error; + this.verifyActiveConnection(reject); + const filePathData = this.getFilePathData(filePath); + const stats = this.$fs.getFsStats(filePathData.filePath); + const fileContentLengthBytes = stats.size; + const fileContentLengthString = fileContentLengthBytes.toString(); + const fileContentLengthSize = Buffer.byteLength(fileContentLengthString); + const headerBuffer = Buffer.alloc(PROTOCOL_OPERATION_LENGTH_SIZE + + SIZE_BYTE_LENGTH + + filePathData.filePathLengthSize + + filePathData.filePathLengthBytes + + SIZE_BYTE_LENGTH + + fileContentLengthSize); + + if (filePathData.filePathLengthSize > 255) { + error = this.getErrorWithMessage("File name size is longer that 255 digits."); + } else if (fileContentLengthSize > 255) { + error = this.getErrorWithMessage("File name size is longer that 255 digits."); + } + + if (error) { + reject(error); + } + + let offset = 0; + offset += headerBuffer.write(CREATE_FILE_OPERATION.toString(), offset, PROTOCOL_OPERATION_LENGTH_SIZE); + offset = headerBuffer.writeUInt8(filePathData.filePathLengthSize, offset); + offset += headerBuffer.write(filePathData.filePathLengthString, offset, filePathData.filePathLengthSize); + offset += headerBuffer.write(filePathData.relativeFilePath, offset, filePathData.filePathLengthBytes); + offset = headerBuffer.writeUInt8(fileContentLengthSize, offset); + headerBuffer.write(fileContentLengthString, offset, fileContentLengthSize); + const hash = crypto.createHash("md5").update(headerBuffer).digest(); + + this.socketConnection.write(headerBuffer); + this.socketConnection.write(hash); + resolve(); + }); + } + + private sendFileContent(filePath: string): Promise { + return new Promise((resolve, reject) => { + this.verifyActiveConnection(reject); + const fileStream = this.$fs.createReadStream(filePath); + const fileHash = crypto.createHash("md5"); + + fileStream + .on("data", (chunk: string | Buffer) => { + fileHash.update(chunk); + if (this.socketConnection) { + this.socketConnection.write(chunk); + } else { + const error = this.checkConnectionStatus(); + //TODO check if properly destroys stream + //fileStream.destroy(); + reject(error); + } + }) + .on("end", () => { + if (this.socketConnection) { + this.socketConnection.write(fileHash.digest(), () => { + resolve(true); + }); + } else { + const error = this.checkConnectionStatus(); + reject(error); + } + }) + .on("error", (error: Error) => { + reject(error); + }); + }); + } + + private createSocket(port: number): IDuplexSocket { + const socket = new net.Socket(); + socket.connect(port, this.configuration.localHostAddress); + return socket; + } + + private checkConnectionStatus() { + if (this.socketConnection === null) { + const defaultError = this.getErrorWithMessage("No socket connection available."); + const error = this.socketError || defaultError; + + return error; + } + } + + private verifyActiveConnection(rejectHandler?: any) { + const error = this.checkConnectionStatus(); + if (error && rejectHandler) { + rejectHandler(error); + } + + if (error && !rejectHandler) { + this.$errors.failWithoutHelp(error.toString()); + } + } + + private handleConnection({ socket, data }: { socket: IDuplexSocket, data: NodeBuffer | string }) { + this.socketConnection = socket; + this.socketConnection.uid = this.generateOperationIdentifier(); + + const versionLength = (data).readUInt8(0); + const versionBuffer = data.slice(PROTOCOL_VERSION_LENGTH_SIZE, versionLength + PROTOCOL_VERSION_LENGTH_SIZE); + const appIdentifierBuffer = data.slice(versionLength + PROTOCOL_VERSION_LENGTH_SIZE, data.length); + + const protocolVersion = versionBuffer.toString(); + const appIdentifier = appIdentifierBuffer.toString(); + this.$logger.trace(`Handle socket connection for app identifier: ${appIdentifier} with protocol version: ${protocolVersion}.`); + + this.socketConnection.on("data", (connectionData: NodeBuffer) => this.handleData(socket.uid, connectionData)); + this.socketConnection.on("close", (hasError: boolean) => this.handleSocketClose(socket.uid, hasError)); + this.socketConnection.on("error", (err: Error) => { + const error = new Error(`Socket Error:\n${err}`); + if (this.configuration.errorHandler) { + this.configuration.errorHandler(error); + } + }); + } + + private connectEventuallyUntilTimeout(factory: () => IDuplexSocket, timeout: number): Promise<{socket: IDuplexSocket, data: NodeBuffer | string}> { + return new Promise((resolve, reject) => { + let lastKnownError: Error | string, + isResolved = false; + + setTimeout(() => { + if (!isResolved) { + isResolved = true; + reject(lastKnownError); + } + }, timeout); + + const tryConnect = () => { + const tryConnectAfterTimeout = (error: Error) => { + if (isResolved) { + return; + } + + if (typeof (error) === "boolean" && error) { + error = new Error("Socket closed due to error"); + } + + lastKnownError = error; + setTimeout(tryConnect, 1000); + }; + + const socket = factory(); + + socket.once("data", data => { + socket.removeListener("close", tryConnectAfterTimeout); + socket.removeListener("error", tryConnectAfterTimeout); + isResolved = true; + resolve({ socket, data }); + }); + socket.on("close", tryConnectAfterTimeout); + socket.on("error", tryConnectAfterTimeout); + }; + + tryConnect(); + }); + } + + private handleData(socketId: string, data: any) { + const reportType = data.readUInt8(); + const infoBuffer = data.slice(REPORT_LENGTH, data.length); + + if (reportType === ERROR_REPORT) { + const errorMessage = infoBuffer.toString(); + this.handleSocketError(socketId, errorMessage); + } else if (reportType === OPERATION_END_REPORT) { + this.handleSyncEnd(infoBuffer); + } + } + + private handleSyncEnd(data: any) { + const operationId = data.toString(); + const promiseHandler = this.operationPromises[operationId]; + + if (promiseHandler) { + promiseHandler.resolve(operationId); + delete this.operationPromises[operationId]; + } + } + + private handleSocketClose(socketId: string, hasError: boolean) { + const errorMessage = "Socket closed from server before operation end."; + this.handleSocketError(socketId, errorMessage); + } + + private handleSocketError(socketId: string, errorMessage: string) { + const error = this.getErrorWithMessage(errorMessage); + if (this.socketConnection && this.socketConnection.uid === socketId) { + this.end(); + this.socketConnection = null; + this.socketError = error; + } + + _.keys(this.operationPromises) + .forEach(operationId => { + const operationPromise = this.operationPromises[operationId]; + if (operationPromise.socketId === socketId) { + operationPromise.reject(error); + delete this.operationPromises[operationId]; + } + }); + } + + private getErrorWithMessage(errorMessage: string) { + const error = new Error(errorMessage); + error.message = errorMessage; + + return error; + } + + private getFilePathData(filePath: string): { relativeFilePath: string, filePathLengthBytes: number, filePathLengthString: string, filePathLengthSize: number, filePath: string } { + const relativeFilePath = this.resolveRelativePath(filePath); + const filePathLengthBytes = Buffer.byteLength(relativeFilePath); + const filePathLengthString = filePathLengthBytes.toString(); + const filePathLengthSize = Buffer.byteLength(filePathLengthString); + + return { + relativeFilePath, + filePathLengthBytes, + filePathLengthString, + filePathLengthSize, + filePath + }; + } + + private resolveRelativePath(filePath: string): string { + let relativeFilePath; + + if (this.configuration.appPlatformsPath) { + relativeFilePath = path.relative(this.configuration.appPlatformsPath, filePath); + } else if (this.appPlatformsPath) { + relativeFilePath = path.relative(this.appPlatformsPath, filePath); + } else { + this.$errors.failWithoutHelp("You need to pass either \"baseDir\" " + + "when you initialize the tool or \"basePath\" as a second argument to this method!"); + } + + return relativeFilePath.split(path.sep).join(path.posix.sep); + } +} +$injector.register("livesyncTool", LivesyncTool); diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index dad57c0e53..599261a00a 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -58,7 +58,7 @@ export abstract class PlatformLiveSyncServiceBase { public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { const projectData = liveSyncInfo.projectData; - const deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); + let deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); const deviceAppData = await this.getAppData(syncInfo); @@ -101,7 +101,7 @@ export abstract class PlatformLiveSyncServiceBase { const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - const deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); + deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); await deviceLiveSyncService.removeFiles(deviceAppData, localToDevicePaths, projectFilesPath); } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 1e30d9550b..7521ca1868 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3890,7 +3890,7 @@ "requires": { "cardinal": "1.0.0", "chalk": "1.1.3", - "cli-table": "https://github.com/telerik/cli-table/tarball/v0.3.1.2", + "cli-table": "^0.3.1", "lodash.assign": "4.2.0", "node-emoji": "1.8.1" }, @@ -4200,6 +4200,44 @@ "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", "integrity": "sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==" }, + "nativescript-android-livesync-lib": { + "version": "file:../nativescript-android-livesync-lib", + "requires": { + "recursive-readdir": "2.2.2" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "recursive-readdir": { + "version": "2.2.2", + "bundled": true, + "requires": { + "minimatch": "3.0.4" + } + } + } + }, "nativescript-doctor": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/nativescript-doctor/-/nativescript-doctor-1.2.0.tgz", From 0a62b82e2640386982af7397d8f5c285d23ac5ab Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Tue, 10 Jul 2018 17:09:52 +0300 Subject: [PATCH 07/22] Rename android livesync-library and extract as public class --- lib/bootstrap.ts | 1 + lib/definitions/livesync.d.ts | 2 +- .../livesync/android-device-livesync-sockets-service.ts | 6 +++--- .../{livesync-library.ts => android-livesync-library.ts} | 4 ++-- package.json | 3 +-- 5 files changed, 8 insertions(+), 8 deletions(-) rename lib/services/livesync/{livesync-library.ts => android-livesync-library.ts} (98%) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 6c016f1dd0..c382cc6e02 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -122,6 +122,7 @@ $injector.require("deployCommandHelper", "./helpers/deploy-command-helper"); $injector.requirePublicClass("localBuildService", "./services/local-build-service"); $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); +$injector.requirePublicClass("androidLivesyncLibrary", "./services/livesync/android-livesync-library"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 49d8b0bba6..579164a0ab 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -377,7 +377,7 @@ interface IAndroidNativeScriptDeviceLiveSyncService { getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService; } -interface ILivesyncTool { +interface IAndroidLivesyncTool { /** * Creates new socket connection. * @param configuration - The configuration to the socket connection. diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index ef1dbc8667..c2d32c4c25 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -2,11 +2,11 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-and import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; import { APP_FOLDER_NAME } from "../../constants"; -import { LivesyncTool } from "./livesync-library"; +import { AndroidLivesyncTool } from "./android-livesync-library"; import * as path from "path"; export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService { - private livesyncTool: ILivesyncTool; + private livesyncTool: IAndroidLivesyncTool; constructor( private data: IProjectData, @@ -17,7 +17,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa protected device: Mobile.IAndroidDevice, private $options: ICommonOptions) { super($platformsData, device); - this.livesyncTool = this.$injector.resolve(LivesyncTool); + this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); } public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { diff --git a/lib/services/livesync/livesync-library.ts b/lib/services/livesync/android-livesync-library.ts similarity index 98% rename from lib/services/livesync/livesync-library.ts rename to lib/services/livesync/android-livesync-library.ts index 34c78fe72e..5bd558a68c 100644 --- a/lib/services/livesync/livesync-library.ts +++ b/lib/services/livesync/android-livesync-library.ts @@ -13,7 +13,7 @@ const OPERATION_END_REPORT = 2; const REPORT_LENGTH = 1; const DEFAULT_LOCAL_HOST_ADDRESS = "127.0.0.1"; -export class LivesyncTool implements ILivesyncTool { +export class AndroidLivesyncTool implements IAndroidLivesyncTool { private operationPromises: IDictionary; private socketError: string | Error; private socketConnection: IDuplexSocket; @@ -394,4 +394,4 @@ export class LivesyncTool implements ILivesyncTool { return relativeFilePath.split(path.sep).join(path.posix.sep); } } -$injector.register("livesyncTool", LivesyncTool); +$injector.register("androidLivesyncLibrary", AndroidLivesyncTool); diff --git a/package.json b/package.json index 7ca8bc8d25..b97ef7398c 100644 --- a/package.json +++ b/package.json @@ -80,8 +80,7 @@ "xmldom": "0.1.21", "xmlhttprequest": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", "yargs": "6.0.0", - "zipstream": "https://github.com/Icenium/node-zipstream/tarball/master", - "nativescript-android-livesync-lib": "file:../nativescript-android-livesync-lib" + "zipstream": "https://github.com/Icenium/node-zipstream/tarball/master" }, "analyze": true, "devDependencies": { From 23af849266562b25bfcdd8b5bcfb2f7398aaa747 Mon Sep 17 00:00:00 2001 From: fatme Date: Thu, 12 Jul 2018 14:25:15 +0300 Subject: [PATCH 08/22] Regenerate npm-shrinkwrap file --- npm-shrinkwrap.json | 40 +--------------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7521ca1868..1e30d9550b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3890,7 +3890,7 @@ "requires": { "cardinal": "1.0.0", "chalk": "1.1.3", - "cli-table": "^0.3.1", + "cli-table": "https://github.com/telerik/cli-table/tarball/v0.3.1.2", "lodash.assign": "4.2.0", "node-emoji": "1.8.1" }, @@ -4200,44 +4200,6 @@ "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", "integrity": "sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==" }, - "nativescript-android-livesync-lib": { - "version": "file:../nativescript-android-livesync-lib", - "requires": { - "recursive-readdir": "2.2.2" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "recursive-readdir": { - "version": "2.2.2", - "bundled": true, - "requires": { - "minimatch": "3.0.4" - } - } - } - }, "nativescript-doctor": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/nativescript-doctor/-/nativescript-doctor-1.2.0.tgz", From 4e7e81588e6ab181287899d534135d6757c77e65 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Thu, 12 Jul 2018 18:59:03 +0300 Subject: [PATCH 09/22] Hook socket livesync with exit signals. --- .../livesync/android-device-livesync-sockets-service.ts | 8 ++++++-- lib/services/livesync/android-livesync-library.ts | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index c2d32c4c25..fe9547d04a 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -15,9 +15,11 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa protected $staticConfig: Config.IStaticConfig, private $logger: ILogger, protected device: Mobile.IAndroidDevice, - private $options: ICommonOptions) { + private $options: ICommonOptions, + private $processService: IProcessService) { super($platformsData, device); this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); + this.$processService.attachToProcessExitSignals(this.livesyncTool, this.livesyncTool.end); } public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { @@ -35,7 +37,6 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa const operationIdentifier = this.livesyncTool.generateOperationIdentifier(); const doSyncPromise = this.livesyncTool.sendDoSyncOperation(operationIdentifier); - //TODO clear interval on exit sygnals/stopLivesync const syncInterval : NodeJS.Timer = setInterval(() => { if (this.livesyncTool.isOperationInProgress(operationIdentifier)) { this.$logger.info("Sync operation in progress..."); @@ -45,7 +46,10 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa const clearSyncInterval = () => { clearInterval(syncInterval); }; + + this.$processService.attachToProcessExitSignals(this, clearSyncInterval); doSyncPromise.then(clearSyncInterval, clearSyncInterval); + await doSyncPromise; if (!canExecuteFastSync) { diff --git a/lib/services/livesync/android-livesync-library.ts b/lib/services/livesync/android-livesync-library.ts index 5bd558a68c..7e4608ca20 100644 --- a/lib/services/livesync/android-livesync-library.ts +++ b/lib/services/livesync/android-livesync-library.ts @@ -201,8 +201,9 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { this.socketConnection.write(chunk); } else { const error = this.checkConnectionStatus(); - //TODO check if properly destroys stream - //fileStream.destroy(); + //TODO Destroy method added in node 8.0.0. + //when we depricate node 6.x uncoment the line belolw + //fileStream.destroy(error); reject(error); } }) From 91d3a31022259c2470e38110d06b366149582d61 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Thu, 12 Jul 2018 19:00:11 +0300 Subject: [PATCH 10/22] Cleanul livesync with sockets code. --- .../android-device-livesync-service.ts | 4 ++-- .../livesync/android-livesync-library.ts | 22 +++++++++---------- .../livesync/android-livesync-service.ts | 3 ++- .../platform-livesync-service-base.ts | 3 +-- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 62fdd07492..c3703be1c8 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -52,9 +52,9 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl getDirname: true }); - await this.device.adb.executeShellCommand(["rm", "-rf", await this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.FULLSYNC_DIR_NAME), + await this.device.adb.executeShellCommand(["rm", "-rf", this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.FULLSYNC_DIR_NAME), this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.SYNC_DIR_NAME), - await this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.REMOVEDSYNC_DIR_NAME)]); + this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.REMOVEDSYNC_DIR_NAME)]); } private async restartApplication(deviceAppData: Mobile.IDeviceAppData, projectName: string): Promise { diff --git a/lib/services/livesync/android-livesync-library.ts b/lib/services/livesync/android-livesync-library.ts index 7e4608ca20..1e2f5549c1 100644 --- a/lib/services/livesync/android-livesync-library.ts +++ b/lib/services/livesync/android-livesync-library.ts @@ -23,7 +23,8 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { constructor(private $androidProcessService: Mobile.IAndroidProcessService, private $errors: IErrors, private $fs: IFileSystem, - private $logger: ILogger) { + private $logger: ILogger, + private $mobileHelper: Mobile.IMobileHelper) { this.operationPromises = Object.create(null); this.socketError = null; this.socketConnection = null; @@ -38,6 +39,10 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { this.$errors.fail(`You need to provide "baseDir" as a configuration property!`); } + if (this.socketConnection) { + this.$errors.fail("Socket connection already exists."); + } + if (!configuration.localHostAddress) { configuration.localHostAddress = DEFAULT_LOCAL_HOST_ADDRESS; } @@ -267,6 +272,8 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { const error = new Error(`Socket Error:\n${err}`); if (this.configuration.errorHandler) { this.configuration.errorHandler(error); + } else { + this.handleSocketError(socket.uid, error.message); } }); } @@ -381,18 +388,9 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { } private resolveRelativePath(filePath: string): string { - let relativeFilePath; - - if (this.configuration.appPlatformsPath) { - relativeFilePath = path.relative(this.configuration.appPlatformsPath, filePath); - } else if (this.appPlatformsPath) { - relativeFilePath = path.relative(this.appPlatformsPath, filePath); - } else { - this.$errors.failWithoutHelp("You need to pass either \"baseDir\" " + - "when you initialize the tool or \"basePath\" as a second argument to this method!"); - } + const relativeFilePath = path.relative(this.appPlatformsPath, filePath); - return relativeFilePath.split(path.sep).join(path.posix.sep); + return this.$mobileHelper.buildDevicePath(relativeFilePath); } } $injector.register("androidLivesyncLibrary", AndroidLivesyncTool); diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index 4af4857cd8..cb4269c905 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -4,6 +4,7 @@ import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base"; import * as semver from "semver"; export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { + private static MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION = "4.2.0"; constructor(protected $platformsData: IPlatformsData, protected $projectFilesManager: IProjectFilesManager, private $injector: IInjector, @@ -15,7 +16,7 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen } protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir, frameworkVersion: string): INativeScriptDeviceLiveSyncService { - if (semver.gte(frameworkVersion, "4.2.0")) { + if (semver.gte(frameworkVersion, AndroidLiveSyncService.MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION)) { return this.$injector.resolve(AndroidDeviceSocketsLiveSyncService, { device, data }); } diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 599261a00a..30bb8db5e9 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -58,7 +58,7 @@ export abstract class PlatformLiveSyncServiceBase { public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { const projectData = liveSyncInfo.projectData; - let deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); + const deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); const syncInfo = _.merge({ device, watch: true }, liveSyncInfo); const deviceAppData = await this.getAppData(syncInfo); @@ -101,7 +101,6 @@ export abstract class PlatformLiveSyncServiceBase { const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, mappedFiles, []); modifiedLocalToDevicePaths.push(...localToDevicePaths); - deviceLiveSyncService = this.getDeviceLiveSyncService(device, projectData); await deviceLiveSyncService.removeFiles(deviceAppData, localToDevicePaths, projectFilesPath); } From dc600e11d5eba2566503e328d2285c91dee51f4f Mon Sep 17 00:00:00 2001 From: "Kristian D. Dimitrov" Date: Fri, 13 Jul 2018 11:42:47 +0300 Subject: [PATCH 11/22] fix: resovle ios livesync device service --- lib/services/livesync/ios-livesync-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/livesync/ios-livesync-service.ts b/lib/services/livesync/ios-livesync-service.ts index 19264718d8..ae9c0a1bc5 100644 --- a/lib/services/livesync/ios-livesync-service.ts +++ b/lib/services/livesync/ios-livesync-service.ts @@ -72,7 +72,7 @@ export class IOSLiveSyncService extends PlatformLiveSyncServiceBase implements I } protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir): INativeScriptDeviceLiveSyncService { - const service = this.$injector.resolve(IOSDeviceLiveSyncService, { _device: device, data }); + const service = this.$injector.resolve(IOSDeviceLiveSyncService, { device, data }); return service; } } From 645b5279aa5ba6d6009c42cb7dddc3a544cfc3c0 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Fri, 13 Jul 2018 17:50:06 +0300 Subject: [PATCH 12/22] fix: requirement for runtime version supporting sockets LS --- lib/services/livesync/android-livesync-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index cb4269c905..e4e517ff79 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -4,7 +4,7 @@ import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base"; import * as semver from "semver"; export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { - private static MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION = "4.2.0"; + private static MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION = "4.2.0-2018-07-13-01"; constructor(protected $platformsData: IPlatformsData, protected $projectFilesManager: IProjectFilesManager, private $injector: IInjector, From c0a1ec3783d2816a9ecfedf9e3870569e024e9c1 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Fri, 13 Jul 2018 19:37:36 +0300 Subject: [PATCH 13/22] docs: add documentation to the android-livesync-library --- lib/definitions/livesync.d.ts | 8 +- .../livesync/android-livesync-library.md | 209 ++++++++++++++++++ 2 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 lib/services/livesync/android-livesync-library.md diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 579164a0ab..f3a567c7d7 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -415,9 +415,9 @@ interface IAndroidLivesyncTool { */ removeFiles(filePaths: string[]): Promise; /** - * Sends doSyncOperation that will be handeled by the runtime. + * Sends doSyncOperation that will be handled by the runtime. * @param operationId - The identifier of the operation - * @param timeout - The timeout in miliseconds + * @param timeout - The timeout in milliseconds * @returns {Promise} */ sendDoSyncOperation(operationId: string, timeout?: number): Promise; @@ -430,6 +430,10 @@ interface IAndroidLivesyncTool { * @param operationId - The identifier of the operation. */ isOperationInProgress(operationId: string): boolean; + + /** + * Closes the current socket connection. + */ end(): void; } diff --git a/lib/services/livesync/android-livesync-library.md b/lib/services/livesync/android-livesync-library.md new file mode 100644 index 0000000000..29399cc61e --- /dev/null +++ b/lib/services/livesync/android-livesync-library.md @@ -0,0 +1,209 @@ +# android-livesync-library +Library for livesyncing changes to a NativeScript application on Android. + +## Usage +The library has a few public methods that allow file manipulation to the files of a NativeScript application and provide control for refreshing the application. Restarting the application if necessary should be done by the user of this library. + +### Getting an instance + +* Example: +```JavaScript +const globalModulesPath = require("global-modules-path"); +const cliPath = globalModulesPath.getPath("nativescript", "tns"); +cli = require(cliPath); + +const liveSyncTool = cli.androidLivesyncLibrary; +``` + + +### Calling connect +Connect method will establish a fresh socket connection with the application. The method takes a configuration as a parameter. + +* Definition +```TypeScript +interface ILivesyncToolConfiguration { + appIdentifier: string; + deviceIdentifier: string; + appPlatformsPath: string; // path to /c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/ + localHostAddress?: string; + errorHandler?: any; +} + +/** + * Creates new socket connection. + * @param configuration - The configuration to the socket connection. + * @returns {Promise} + */ +connect(configuration: ILivesyncToolConfiguration): Promise; +``` + +* Example: +```JavaScript + +var configuration = { + appPlatformsPath: "/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/", + fullApplicationName: "com.tns.myapp", + deviceIdentifier: "aaaaaaaa" +} + +liveSyncTool.connect(configuration) +``` + +The method returns a promise which is resolved once the connection is established. There is a 30 seconds timeout for establishing the connection. In order the connection to be established successfully, the app must be started. + +### Calling sendFile +Send file will create/update the file with the file content it reads from the filePath that is provided. It will compute the relative path based on the fullApplicationName provided in configuration that was passed to the connect method. This method resolves its promise once the file is written to the output stream of the socket. To be sure the all files have been read and saved by the runtime see sendDoSyncOperation. + +* Definition +```TypeScript +/** + * Sends a file through the socket. + * @param filePath - The full path to the file. + * @returns {Promise} + */ +sendFile(filePath: string): Promise; +``` + +* Example: +```JavaScript +liveSyncTool.sendFile("/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/index.js"); +``` + +### Calling sendFiles +This method takes an array of file paths as an argument and sends their content to the application. + +* Definition +```TypeScript +/** + * Sends files through the socket. + * @param filePaths - Array of files that will be send by the socket. + * @returns {Promise} + */ +sendFiles(filePaths: string[]): Promise; +``` + +* Example: +```JavaScript +liveSyncTool.sendFile([ + "/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/index.js" + "/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/test.js" + "/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/page.js" +]); +``` + +### Calling sendDirectory +This method takes a path to a directory, enumerates the files recursively and sends the to the device. + +* Definition +```TypeScript +/** + * Sends all files from directory by the socket. + * @param directoryPath - The path to the directory which files will be send by the socket. + * @returns {Promise} + */ +sendDirectory(directoryPath: string): Promise; +``` + +* Example: +```JavaScript +liveSyncTool.sendDirectory("/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app"); +``` + +### Calling removeFile +When called, removeFile will compute the relative path based on the fullApplicationName provided in configuration that was passed to the connect method and delete the corresponding file/directory on the device. + +* Definition +```TypeScript +/** + * Removes file + * @param filePath - The full path to the file. + * @returns {Promise} + */ +removeFile(filePath: string): Promise; +``` + +* Example: +```JavaScript +liveSyncTool.removeFile("/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/index.js"); +``` + +### Calling removeFiles +When called, removeFiles will compute the relative paths based on the fullApplicationName provided in configuration that was passed to the connect method and delete the corresponding files/directories on the device. + +* Definition +```TypeScript +/** + * Removes files + * @param filePaths - Array of files that will be removed. + * @returns {Promise} + */ +removeFiles(filePaths: string[]): Promise; +``` + +* Example: +```JavaScript +liveSyncTool.removeFiles([ + "/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/index.js" + "/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/test.js" + "/c/myprojects/myapp/app/platforms/android/app/src/main/assets/app/page.js" +]); +``` + +### Calling sendDoSyncOperation +When called, sendDoSyncOperation will tell the runtime to execute a script that will refresh the application(this will render changes to the .html and .css files). This method accepts an optional parameter - operationId. It can be used for status check of the operation. The promise returned from this method will be resolved when the application has read the operation and started executing it. This can be used as a sync point - once this promise is resolved, the user can be sure that all other operations have been read and executed by the application. The operation accepts an operation id + +* Definition +```TypeScript +/** + * Sends doSyncOperation that will be handeled by the runtime. + * @param operationId - The identifier of the operation + * @param timeout - The timeout in miliseconds + * @returns {Promise} + */ +sendDoSyncOperation(operationId: string, timeout?: number): Promise; +``` + +* Example: +```JavaScript +const operationId = liveSyncTool.generateOperationIdentifier(); +await liveSyncTool.sendDoSyncOperation(operationId); +``` + +### Calling end +End will close the current liveSync socket. Any sync operations that are still in progress will be rejected. + +* Definition +```TypeScript +/** + * Closes the current socket connection. + */ +end(): void; +``` + +* Example: +```JavaScript +liveSyncTool.end(); +``` + +## Protocol: + +Application input + +|Operation Name(not send) | Operation | Operation id | File Name Length Size | File Name Length | File Name | File Content Length Size | File Content Length | Header Hash | File Content | File hash | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| runSync: | 9 | 32bit | | | | | | md5 hash | | | +| create: | 8 | | 1 | 7 | ./a.txt | 2 | 11 | md5 hash | fileContent | md5 hash | +| delete: | 7 | | 1 | 3 | ./a | | | md5 hash | | | + +Application output on connect + +| Protocol Version length | Protocol Version String | Application Identifier | +| --- | --- | --- | +| 1 byte | "0.1.0" | "org.nativescript.myapp" | + +Application output after connect + +| Report Name(not send) | Report Code | Payload | +| --- | --- | --- | +| Error | 1 | Error message string | +| Sync end | 2 | Sync operation uid | \ No newline at end of file From 39f7db4a1254831979f538eccb702cf076bc3f25 Mon Sep 17 00:00:00 2001 From: fatme Date: Mon, 16 Jul 2018 14:06:32 +0300 Subject: [PATCH 14/22] Fix PR comments --- lib/definitions/livesync.d.ts | 21 ++++++++++++++++--- ...android-device-livesync-sockets-service.ts | 12 ++++------- .../livesync/android-livesync-library.ts | 4 ++-- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index f3a567c7d7..6a91e66b21 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -383,7 +383,7 @@ interface IAndroidLivesyncTool { * @param configuration - The configuration to the socket connection. * @returns {Promise} */ - connect(configuration: ILivesyncToolConfiguration): Promise; + connect(configuration: IAndroidLivesyncToolConfiguration): Promise; /** * Sends a file through the socket. * @param filePath - The full path to the file. @@ -437,11 +437,26 @@ interface IAndroidLivesyncTool { end(): void; } -interface ILivesyncToolConfiguration { +interface IAndroidLivesyncToolConfiguration { + /** + * The application identifier. + */ appIdentifier: string; + /** + * The device identifier. + */ deviceIdentifier: string; - appPlatformsPath: string; // path to platforms/android/app/src/main/assets/app/ + /** + * The path to app folder inside platforms folder: platforms/android/app/src/main/assets/app/ + */ + appPlatformsPath: string; + /** + * If not provided, defaults to 127.0.0.1 + */ localHostAddress?: string; + /** + * If provider will call it when an error occurs. + */ errorHandler?: any; } diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index fe9547d04a..798f3a60fa 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -70,13 +70,13 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa if (isFullSync) { transferredFiles = await this._transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); } else { - transferredFiles = await this._transferFiles(deviceAppData, localToDevicePaths, projectFilesPath); + transferredFiles = await this._transferFiles(localToDevicePaths); } return transferredFiles; } - private async _transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { + private async _transferFiles(localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { await this.livesyncTool.sendFiles(localToDevicePaths.map(localToDevicePathData => localToDevicePathData.getLocalPath())); return localToDevicePaths; @@ -94,15 +94,11 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa transferedFiles = localToDevicePaths; } else { const changedShasums = deviceHashService.getChnagedShasums(oldShasums, currentShasums); - const changedFiles = _.map(changedShasums, (hash: string, pathToFile: string) => pathToFile); + const changedFiles = _.keys(changedShasums); if (changedFiles.length) { await this.livesyncTool.sendFiles(changedFiles); await deviceHashService.uploadHashFileToDevice(currentShasums); - transferedFiles = localToDevicePaths.filter(localToDevicePathData => { - if (changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0) { - return true; - } - }); + transferedFiles = localToDevicePaths.filter(localToDevicePathData => changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0); } else { transferedFiles = []; } diff --git a/lib/services/livesync/android-livesync-library.ts b/lib/services/livesync/android-livesync-library.ts index 1e2f5549c1..5a6355055f 100644 --- a/lib/services/livesync/android-livesync-library.ts +++ b/lib/services/livesync/android-livesync-library.ts @@ -17,7 +17,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { private operationPromises: IDictionary; private socketError: string | Error; private socketConnection: IDuplexSocket; - private configuration: ILivesyncToolConfiguration; + private configuration: IAndroidLivesyncToolConfiguration; private appPlatformsPath: string; constructor(private $androidProcessService: Mobile.IAndroidProcessService, @@ -30,7 +30,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { this.socketConnection = null; } - public async connect(configuration: ILivesyncToolConfiguration): Promise { + public async connect(configuration: IAndroidLivesyncToolConfiguration): Promise { if (!configuration.appIdentifier) { this.$errors.fail(`You need to provide "appIdentifier" as a configuration property!`); } From 911b61f1d5c910b080aea933d607c93a30ea3175 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Mon, 16 Jul 2018 19:20:46 +0300 Subject: [PATCH 15/22] fix: app not restarted if in error activity --- lib/definitions/livesync.d.ts | 7 ++++++- .../android-device-livesync-sockets-service.ts | 6 +++--- lib/services/livesync/android-livesync-library.ts | 13 ++++++++----- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 6a91e66b21..1f714ecd32 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -420,7 +420,7 @@ interface IAndroidLivesyncTool { * @param timeout - The timeout in milliseconds * @returns {Promise} */ - sendDoSyncOperation(operationId: string, timeout?: number): Promise; + sendDoSyncOperation(operationId: string, timeout?: number): Promise; /** * Generates new operation identifier. */ @@ -460,6 +460,11 @@ interface IAndroidLivesyncToolConfiguration { errorHandler?: any; } +interface IAndroidLivesyncSyncOperationResult { + operationId: string, + didRefresh: boolean +} + interface IDeviceProjectRootOptions { appIdentifier: string; getDirname?: boolean; diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 798f3a60fa..06dc5e04e5 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -50,9 +50,9 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa this.$processService.attachToProcessExitSignals(this, clearSyncInterval); doSyncPromise.then(clearSyncInterval, clearSyncInterval); - await doSyncPromise; + const refreshResult = await doSyncPromise; - if (!canExecuteFastSync) { + if (!canExecuteFastSync || !refreshResult.didRefresh) { await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); } } @@ -61,7 +61,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa } public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { - await this.livesyncTool.removeFiles(_.map(localToDevicePaths, (element: any) => { return element.filePath; })); + await this.livesyncTool.removeFiles(_.map(localToDevicePaths, (element: any) => element.filePath)); } public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { diff --git a/lib/services/livesync/android-livesync-library.ts b/lib/services/livesync/android-livesync-library.ts index 5a6355055f..cf1321a564 100644 --- a/lib/services/livesync/android-livesync-library.ts +++ b/lib/services/livesync/android-livesync-library.ts @@ -10,6 +10,7 @@ const CREATE_FILE_OPERATION = 8; const DO_SYNC_OPERATION = 9; const ERROR_REPORT = 1; const OPERATION_END_REPORT = 2; +const OPERATION_END_NO_REFRESH_REPORT_CODE = 3; const REPORT_LENGTH = 1; const DEFAULT_LOCAL_HOST_ADDRESS = "127.0.0.1"; @@ -118,9 +119,9 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { return !!this.operationPromises[operationId]; } - public sendDoSyncOperation(operationId: string, timeout: number): Promise { + public sendDoSyncOperation(operationId: string, timeout: number): Promise { const id = operationId || this.generateOperationIdentifier(); - const operationPromise = new Promise((resolve: Function, reject: Function) => { + const operationPromise: Promise = new Promise((resolve: Function, reject: Function) => { this.verifyActiveConnection(reject); const message = `${DO_SYNC_OPERATION}${id}`; @@ -328,16 +329,18 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { const errorMessage = infoBuffer.toString(); this.handleSocketError(socketId, errorMessage); } else if (reportType === OPERATION_END_REPORT) { - this.handleSyncEnd(infoBuffer); + this.handleSyncEnd({data:infoBuffer, didRefresh: true}); + } else if (reportType === OPERATION_END_NO_REFRESH_REPORT_CODE) { + this.handleSyncEnd({data:infoBuffer, didRefresh: false}); } } - private handleSyncEnd(data: any) { + private handleSyncEnd({data, didRefresh}: {data: any, didRefresh: boolean}) { const operationId = data.toString(); const promiseHandler = this.operationPromises[operationId]; if (promiseHandler) { - promiseHandler.resolve(operationId); + promiseHandler.resolve({operationId, didRefresh}); delete this.operationPromises[operationId]; } } From 2d8039b93f03c6f0d9a013eaddbb6e996751fd3c Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Tue, 17 Jul 2018 17:15:50 +0300 Subject: [PATCH 16/22] chore: fix comments of PR --- lib/bootstrap.ts | 2 +- lib/definitions/livesync.d.ts | 10 +++++++++ ...android-device-livesync-sockets-service.ts | 20 ++++++++--------- ...nc-library.md => android-livesync-tool.md} | 8 +++---- ...nc-library.ts => android-livesync-tool.ts} | 22 +++++++++---------- .../livesync/device-livesync-service-base.ts | 6 +++++ .../livesync/ios-device-livesync-service.ts | 2 +- 7 files changed, 43 insertions(+), 27 deletions(-) rename lib/services/livesync/{android-livesync-library.md => android-livesync-tool.md} (94%) rename lib/services/livesync/{android-livesync-library.ts => android-livesync-tool.ts} (96%) diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index c382cc6e02..ac06ce0eed 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -122,7 +122,7 @@ $injector.require("deployCommandHelper", "./helpers/deploy-command-helper"); $injector.requirePublicClass("localBuildService", "./services/local-build-service"); $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); -$injector.requirePublicClass("androidLivesyncLibrary", "./services/livesync/android-livesync-library"); +$injector.requirePublicClass("androidLivesyncTool", "./services/livesync/android-livesync-tool"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 1f714ecd32..ddadfc610e 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -362,9 +362,19 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase * Removes specified files from a connected device * @param {Mobile.IDeviceAppData} deviceAppData Data about device and app. * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. + * @param {string} projectFilesPath The Path to the app folder inside platforms folder * @return {Promise} */ removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath?: string): Promise; + + /** + * Transfers specified files to a connected device + * @param {Mobile.IDeviceAppData} deviceAppData Data about device and app. + * @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device. + * @param {string} projectFilesPath The Path to the app folder inside platforms folder + * @param {boolean} isFullSync Indicates if the operation is part of a fullSync + * @return {Promise} Returns the ILocalToDevicePathData of all transfered files + */ transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise; } diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 06dc5e04e5..794256d488 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -2,11 +2,12 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-and import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service"; import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; import { APP_FOLDER_NAME } from "../../constants"; -import { AndroidLivesyncTool } from "./android-livesync-library"; +import { AndroidLivesyncTool } from "./android-livesync-tool"; import * as path from "path"; export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService { private livesyncTool: IAndroidLivesyncTool; + private static STATUS_UPDATE_INTERVAL = 10000; constructor( private data: IProjectData, @@ -30,8 +31,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa } public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { - const canExecuteFastSync = !liveSyncInfo.isFullSync && !_.some(liveSyncInfo.modifiedFilesData, - (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); + const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); if (liveSyncInfo.modifiedFilesData.length) { const operationIdentifier = this.livesyncTool.generateOperationIdentifier(); @@ -41,7 +41,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa if (this.livesyncTool.isOperationInProgress(operationIdentifier)) { this.$logger.info("Sync operation in progress..."); } - }, 10000); + }, AndroidDeviceSocketsLiveSyncService.STATUS_UPDATE_INTERVAL); const clearSyncInterval = () => { clearInterval(syncInterval); @@ -83,7 +83,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa } private async _transferDirectory(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { - let transferedFiles: Mobile.ILocalToDevicePathData[]; + let transferredLocalToDevicePaths : Mobile.ILocalToDevicePathData[]; const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier); const currentShasums: IStringDictionary = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths); const oldShasums = await deviceHashService.getShasumsFromDevice(); @@ -91,20 +91,20 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa if (this.$options.force || !oldShasums) { await this.livesyncTool.sendDirectory(projectFilesPath); await deviceHashService.uploadHashFileToDevice(currentShasums); - transferedFiles = localToDevicePaths; + transferredLocalToDevicePaths = localToDevicePaths; } else { - const changedShasums = deviceHashService.getChnagedShasums(oldShasums, currentShasums); + const changedShasums = deviceHashService.getChangedShasums(oldShasums, currentShasums); const changedFiles = _.keys(changedShasums); if (changedFiles.length) { await this.livesyncTool.sendFiles(changedFiles); await deviceHashService.uploadHashFileToDevice(currentShasums); - transferedFiles = localToDevicePaths.filter(localToDevicePathData => changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0); + transferredLocalToDevicePaths = localToDevicePaths.filter(localToDevicePathData => changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0); } else { - transferedFiles = []; + transferredLocalToDevicePaths = []; } } - return transferedFiles; + return transferredLocalToDevicePaths ; } private async connectLivesyncTool(projectFilesPath: string, appIdentifier: string) { diff --git a/lib/services/livesync/android-livesync-library.md b/lib/services/livesync/android-livesync-tool.md similarity index 94% rename from lib/services/livesync/android-livesync-library.md rename to lib/services/livesync/android-livesync-tool.md index 29399cc61e..828b8d4f15 100644 --- a/lib/services/livesync/android-livesync-library.md +++ b/lib/services/livesync/android-livesync-tool.md @@ -1,8 +1,8 @@ -# android-livesync-library -Library for livesyncing changes to a NativeScript application on Android. +# android-livesync-tool +Tool for livesyncing changes to a NativeScript application on Android. ## Usage -The library has a few public methods that allow file manipulation to the files of a NativeScript application and provide control for refreshing the application. Restarting the application if necessary should be done by the user of this library. +The tool has a few public methods that allow file manipulation to the files of a NativeScript application and provide control for refreshing the application. Restarting the application if necessary should be done by the user of this tool. ### Getting an instance @@ -12,7 +12,7 @@ const globalModulesPath = require("global-modules-path"); const cliPath = globalModulesPath.getPath("nativescript", "tns"); cli = require(cliPath); -const liveSyncTool = cli.androidLivesyncLibrary; +const liveSyncTool = cli.androidLivesyncTool; ``` diff --git a/lib/services/livesync/android-livesync-library.ts b/lib/services/livesync/android-livesync-tool.ts similarity index 96% rename from lib/services/livesync/android-livesync-library.ts rename to lib/services/livesync/android-livesync-tool.ts index cf1321a564..ed382dfbb5 100644 --- a/lib/services/livesync/android-livesync-library.ts +++ b/lib/services/livesync/android-livesync-tool.ts @@ -12,6 +12,8 @@ const ERROR_REPORT = 1; const OPERATION_END_REPORT = 2; const OPERATION_END_NO_REFRESH_REPORT_CODE = 3; const REPORT_LENGTH = 1; +const SYNC_OPERATION_TIMEOUT = 60000; +const TRY_CONNECT_TIMEOUT = 30000; const DEFAULT_LOCAL_HOST_ADDRESS = "127.0.0.1"; export class AndroidLivesyncTool implements IAndroidLivesyncTool { @@ -19,7 +21,6 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { private socketError: string | Error; private socketConnection: IDuplexSocket; private configuration: IAndroidLivesyncToolConfiguration; - private appPlatformsPath: string; constructor(private $androidProcessService: Mobile.IAndroidProcessService, private $errors: IErrors, @@ -49,7 +50,6 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { } this.configuration = configuration; - this.appPlatformsPath = this.configuration.appPlatformsPath; this.socketError = null; const port = await this.$androidProcessService.forwardFreeTcpToAbstractPort({ @@ -58,7 +58,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { abstractPort: `localabstract:${configuration.appIdentifier}-livesync` }); - const connectionResult = await this.connectEventuallyUntilTimeout(this.createSocket.bind(this, port), 30000); + const connectionResult = await this.connectEventuallyUntilTimeout(this.createSocket.bind(this, port), TRY_CONNECT_TIMEOUT); this.handleConnection(connectionResult); } @@ -141,7 +141,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { if (this.isOperationInProgress(id)) { this.handleSocketError(socketId, "Sync operation is taking too long"); } - }, 60000); + }, SYNC_OPERATION_TIMEOUT); }); return operationPromise; @@ -282,18 +282,18 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { private connectEventuallyUntilTimeout(factory: () => IDuplexSocket, timeout: number): Promise<{socket: IDuplexSocket, data: NodeBuffer | string}> { return new Promise((resolve, reject) => { let lastKnownError: Error | string, - isResolved = false; + isConnected = false; setTimeout(() => { - if (!isResolved) { - isResolved = true; + if (!isConnected) { + isConnected = true; reject(lastKnownError); } }, timeout); const tryConnect = () => { const tryConnectAfterTimeout = (error: Error) => { - if (isResolved) { + if (isConnected) { return; } @@ -310,7 +310,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { socket.once("data", data => { socket.removeListener("close", tryConnectAfterTimeout); socket.removeListener("error", tryConnectAfterTimeout); - isResolved = true; + isConnected = true; resolve({ socket, data }); }); socket.on("close", tryConnectAfterTimeout); @@ -391,9 +391,9 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { } private resolveRelativePath(filePath: string): string { - const relativeFilePath = path.relative(this.appPlatformsPath, filePath); + const relativeFilePath = path.relative(this.configuration.appPlatformsPath, filePath); return this.$mobileHelper.buildDevicePath(relativeFilePath); } } -$injector.register("androidLivesyncLibrary", AndroidLivesyncTool); +$injector.register("androidLivesyncTool", AndroidLivesyncTool); diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts index 0598cd22d9..cacdb74467 100644 --- a/lib/services/livesync/device-livesync-service-base.ts +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -14,6 +14,12 @@ export abstract class DeviceLiveSyncServiceBase { return _.includes(fastSyncFileExtensions, path.extname(filePath)); } + protected canExecuteFastSyncForPaths(localToDevicePaths: Mobile.ILocalToDevicePathData[], projectData: IProjectData, platform: string) { + return !_.some(localToDevicePaths, + (localToDevicePath: Mobile.ILocalToDevicePathData) => + !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, this.device.deviceInfo.platform)); + } + @cache() private getFastLiveSyncFileExtensions(platform: string, projectData: IProjectData): string[] { const platformData = this.$platformsData.getPlatformData(platform, projectData); diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index df2291d319..c857e5a3ec 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -61,7 +61,7 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => scriptRelatedFiles = _.concat(scriptRelatedFiles, localToDevicePaths.filter(file => minimatch(file.getDevicePath(), pattern, { nocase: true })))); const otherFiles = _.difference(localToDevicePaths, _.concat(scriptFiles, scriptRelatedFiles)); - const shouldRestart = _.some(otherFiles, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.canExecuteFastSync(localToDevicePath.getLocalPath(), projectData, deviceAppData.platform)); + const shouldRestart = this.canExecuteFastSyncForPaths(otherFiles, projectData, deviceAppData.platform); if (shouldRestart || (!liveSyncInfo.useLiveEdit && scriptFiles.length)) { await this.restartApplication(deviceAppData, projectData.projectName); From e22a361a45642c6f8d91179fb40ada9f653dc32c Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Thu, 19 Jul 2018 17:33:50 +0300 Subject: [PATCH 17/22] fix: unnecessary refresh before restart --- lib/definitions/livesync.d.ts | 3 ++- .../android-device-livesync-sockets-service.ts | 3 ++- lib/services/livesync/android-livesync-tool.md | 9 +++++---- lib/services/livesync/android-livesync-tool.ts | 18 +++++++++++++----- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index ddadfc610e..1974890b3e 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -426,11 +426,12 @@ interface IAndroidLivesyncTool { removeFiles(filePaths: string[]): Promise; /** * Sends doSyncOperation that will be handled by the runtime. + * @param doRefresh - Indicates if the application should be restarted. Defaults to true. * @param operationId - The identifier of the operation * @param timeout - The timeout in milliseconds * @returns {Promise} */ - sendDoSyncOperation(operationId: string, timeout?: number): Promise; + sendDoSyncOperation(doRefresh: boolean, timeout?: number, operationId?: string): Promise; /** * Generates new operation identifier. */ diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 794256d488..429ca6b2d1 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -35,7 +35,8 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa if (liveSyncInfo.modifiedFilesData.length) { const operationIdentifier = this.livesyncTool.generateOperationIdentifier(); - const doSyncPromise = this.livesyncTool.sendDoSyncOperation(operationIdentifier); + + const doSyncPromise = this.livesyncTool.sendDoSyncOperation(canExecuteFastSync, null, operationIdentifier); const syncInterval : NodeJS.Timer = setInterval(() => { if (this.livesyncTool.isOperationInProgress(operationIdentifier)) { diff --git a/lib/services/livesync/android-livesync-tool.md b/lib/services/livesync/android-livesync-tool.md index 828b8d4f15..1ea6f60fe5 100644 --- a/lib/services/livesync/android-livesync-tool.md +++ b/lib/services/livesync/android-livesync-tool.md @@ -155,18 +155,19 @@ When called, sendDoSyncOperation will tell the runtime to execute a script that * Definition ```TypeScript /** - * Sends doSyncOperation that will be handeled by the runtime. + * Sends doSyncOperation that will be handled by the runtime. + * @param doRefresh - Indicates if the application should be restarted. Defaults to true. * @param operationId - The identifier of the operation - * @param timeout - The timeout in miliseconds + * @param timeout - The timeout in milliseconds * @returns {Promise} */ -sendDoSyncOperation(operationId: string, timeout?: number): Promise; +sendDoSyncOperation(doRefresh: boolean, timeout?: number, operationId?: string): Promise; ``` * Example: ```JavaScript const operationId = liveSyncTool.generateOperationIdentifier(); -await liveSyncTool.sendDoSyncOperation(operationId); +await liveSyncTool.sendDoSyncOperation(true, 10000, operationId); ``` ### Calling end diff --git a/lib/services/livesync/android-livesync-tool.ts b/lib/services/livesync/android-livesync-tool.ts index ed382dfbb5..403859923c 100644 --- a/lib/services/livesync/android-livesync-tool.ts +++ b/lib/services/livesync/android-livesync-tool.ts @@ -12,6 +12,9 @@ const ERROR_REPORT = 1; const OPERATION_END_REPORT = 2; const OPERATION_END_NO_REFRESH_REPORT_CODE = 3; const REPORT_LENGTH = 1; +const DO_REFRESH_LENGTH = 1; +const DO_REFRESH = 1; +const SKIP_REFRESH = 0; const SYNC_OPERATION_TIMEOUT = 60000; const TRY_CONNECT_TIMEOUT = 30000; const DEFAULT_LOCAL_HOST_ADDRESS = "127.0.0.1"; @@ -119,14 +122,18 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { return !!this.operationPromises[operationId]; } - public sendDoSyncOperation(operationId: string, timeout: number): Promise { + public sendDoSyncOperation(doRefresh = true , timeout?: number, operationId?: string): Promise { const id = operationId || this.generateOperationIdentifier(); const operationPromise: Promise = new Promise((resolve: Function, reject: Function) => { this.verifyActiveConnection(reject); - const message = `${DO_SYNC_OPERATION}${id}`; + const headerBuffer = Buffer.alloc(Buffer.byteLength(message) + DO_REFRESH_LENGTH); const socketId = this.socketConnection.uid; - const hash = crypto.createHash("md5").update(message).digest(); + const doRefreshCode = doRefresh ? DO_REFRESH : SKIP_REFRESH; + const offset = headerBuffer.write(message); + + headerBuffer.writeUInt8(doRefreshCode, offset); + const hash = crypto.createHash("md5").update(headerBuffer).digest(); this.operationPromises[id] = { resolve, @@ -134,14 +141,15 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { socketId }; - this.socketConnection.write(message); + this.socketConnection.write(headerBuffer); this.socketConnection.write(hash); + timeout = timeout || SYNC_OPERATION_TIMEOUT; setTimeout(() => { if (this.isOperationInProgress(id)) { this.handleSocketError(socketId, "Sync operation is taking too long"); } - }, SYNC_OPERATION_TIMEOUT); + }, timeout); }); return operationPromise; From 294db1b7aab364874a74d6d10ec7cc7b22fbaf4b Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Fri, 20 Jul 2018 16:39:20 +0300 Subject: [PATCH 18/22] fix: timout not cleared on exit signals --- ...android-device-livesync-sockets-service.ts | 1 - .../livesync/android-livesync-tool.ts | 33 ++++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 429ca6b2d1..1b622426d2 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -20,7 +20,6 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa private $processService: IProcessService) { super($platformsData, device); this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); - this.$processService.attachToProcessExitSignals(this.livesyncTool, this.livesyncTool.end); } public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { diff --git a/lib/services/livesync/android-livesync-tool.ts b/lib/services/livesync/android-livesync-tool.ts index 403859923c..238d94f3eb 100644 --- a/lib/services/livesync/android-livesync-tool.ts +++ b/lib/services/livesync/android-livesync-tool.ts @@ -29,10 +29,12 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger, - private $mobileHelper: Mobile.IMobileHelper) { + private $mobileHelper: Mobile.IMobileHelper, + private $processService: IProcessService) { this.operationPromises = Object.create(null); this.socketError = null; this.socketConnection = null; + this.$processService.attachToProcessExitSignals(this, this.dispose); } public async connect(configuration: IAndroidLivesyncToolConfiguration): Promise { @@ -135,21 +137,22 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { headerBuffer.writeUInt8(doRefreshCode, offset); const hash = crypto.createHash("md5").update(headerBuffer).digest(); - this.operationPromises[id] = { - resolve, - reject, - socketId - }; - this.socketConnection.write(headerBuffer); this.socketConnection.write(hash); timeout = timeout || SYNC_OPERATION_TIMEOUT; - setTimeout(() => { + const timeoutId = setTimeout(() => { if (this.isOperationInProgress(id)) { this.handleSocketError(socketId, "Sync operation is taking too long"); } }, timeout); + + this.operationPromises[id] = { + resolve, + reject, + socketId, + timeoutId + }; }); return operationPromise; @@ -216,7 +219,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { } else { const error = this.checkConnectionStatus(); //TODO Destroy method added in node 8.0.0. - //when we depricate node 6.x uncoment the line belolw + //when we deprecate node 6.x uncomment the line below //fileStream.destroy(error); reject(error); } @@ -348,6 +351,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { const promiseHandler = this.operationPromises[operationId]; if (promiseHandler) { + clearTimeout(promiseHandler.timeoutId); promiseHandler.resolve({operationId, didRefresh}); delete this.operationPromises[operationId]; } @@ -370,6 +374,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { .forEach(operationId => { const operationPromise = this.operationPromises[operationId]; if (operationPromise.socketId === socketId) { + clearTimeout(operationPromise.timeoutId); operationPromise.reject(error); delete this.operationPromises[operationId]; } @@ -403,5 +408,15 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { return this.$mobileHelper.buildDevicePath(relativeFilePath); } + + private dispose(): void { + this.end(); + + _.keys(this.operationPromises) + .forEach(operationId => { + const operationPromise = this.operationPromises[operationId]; + clearTimeout(operationPromise.timeoutId); + }); + } } $injector.register("androidLivesyncTool", AndroidLivesyncTool); From d42cd90c35c76053aa99b03213a5cc6ee1d96191 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Mon, 23 Jul 2018 15:38:46 +0300 Subject: [PATCH 19/22] fix: debug doesn't await file trasfer end with socket implementation --- lib/definitions/livesync.d.ts | 8 +++++ ...android-device-livesync-sockets-service.ts | 34 +++++++++++++------ .../livesync/device-livesync-service-base.ts | 4 +++ lib/services/livesync/livesync-service.ts | 3 ++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 1974890b3e..75291a5922 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -344,6 +344,7 @@ interface IPlatformLiveSyncService { liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise; refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise; prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir, liveSyncInfo: ILiveSyncInfo, debugOptions: IDebugOptions): Promise; + getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService; } interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase { @@ -376,6 +377,13 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase * @return {Promise} Returns the ILocalToDevicePathData of all transfered files */ transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise; + + /** + * Guarantees all remove/update operations have finished + * @param {ILiveSyncResultInfo} liveSyncInfo Describes the LiveSync operation - for which project directory is the operation and other settings. + * @return {Promise} + */ + finalizeSync(liveSyncInfo: ILiveSyncResultInfo): Promise; } interface IAndroidNativeScriptDeviceLiveSyncService { diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 1b622426d2..5e8c58f9fd 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -29,16 +29,22 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa await this.connectLivesyncTool(projectFilesPath, this.data.projectId); } - public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise { - const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); + public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo) { + await this.doSync(liveSyncInfo, false); + } + + private async doSync(liveSyncInfo: ILiveSyncResultInfo, doRefresh = false): Promise { + let result; + const operationId = this.livesyncTool.generateOperationIdentifier(); + + result = {operationId, didRefresh: true }; if (liveSyncInfo.modifiedFilesData.length) { - const operationIdentifier = this.livesyncTool.generateOperationIdentifier(); - const doSyncPromise = this.livesyncTool.sendDoSyncOperation(canExecuteFastSync, null, operationIdentifier); + const doSyncPromise = this.livesyncTool.sendDoSyncOperation(doRefresh, null, operationId); const syncInterval : NodeJS.Timer = setInterval(() => { - if (this.livesyncTool.isOperationInProgress(operationIdentifier)) { + if (this.livesyncTool.isOperationInProgress(operationId)) { this.$logger.info("Sync operation in progress..."); } }, AndroidDeviceSocketsLiveSyncService.STATUS_UPDATE_INTERVAL); @@ -50,14 +56,22 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa this.$processService.attachToProcessExitSignals(this, clearSyncInterval); doSyncPromise.then(clearSyncInterval, clearSyncInterval); - const refreshResult = await doSyncPromise; - - if (!canExecuteFastSync || !refreshResult.didRefresh) { - await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); - } + result = await doSyncPromise; } + return result; + } + + public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo) { + const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); + + const syncOperationResult = await this.doSync(liveSyncInfo, canExecuteFastSync); + this.livesyncTool.end(); + + if (!canExecuteFastSync || !syncOperationResult.didRefresh) { + await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName }); + } } public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise { diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts index cacdb74467..6bf76cbac1 100644 --- a/lib/services/livesync/device-livesync-service-base.ts +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -38,4 +38,8 @@ export abstract class DeviceLiveSyncServiceBase { return transferredFiles; } + + public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo): Promise { + //implement in case a sync point for all remove/create operation is needed + } } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 5a6de53cd0..ad17982098 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -164,6 +164,9 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi }; try { + const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); + const deviceLivesyncService = platformLiveSyncService.getDeviceLiveSyncService(deviceAppData.device, projectData); + await deviceLivesyncService.finalizeSync(liveSyncResultInfo); await deviceAppData.device.applicationManager.stopApplication({ appId: applicationId, projectName: projectData.projectName }); // Now that we've stopped the application we know it isn't started, so set debugOptions.start to false // so that it doesn't default to true in attachDebugger method From 5ffaba7a8905cc6ffc1b276258a25a997d8e6f16 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Mon, 23 Jul 2018 16:39:15 +0300 Subject: [PATCH 20/22] chore: fix comments --- .../livesync/android-device-livesync-sockets-service.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 5e8c58f9fd..586b5b0f74 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -30,14 +30,13 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa } public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo) { - await this.doSync(liveSyncInfo, false); + await this.doSync(liveSyncInfo); } - private async doSync(liveSyncInfo: ILiveSyncResultInfo, doRefresh = false): Promise { - let result; + private async doSync(liveSyncInfo: ILiveSyncResultInfo, {doRefresh = false}: {doRefresh?: boolean} = {}): Promise { const operationId = this.livesyncTool.generateOperationIdentifier(); - result = {operationId, didRefresh: true }; + let result = {operationId, didRefresh: true }; if (liveSyncInfo.modifiedFilesData.length) { @@ -65,7 +64,7 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo) { const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); - const syncOperationResult = await this.doSync(liveSyncInfo, canExecuteFastSync); + const syncOperationResult = await this.doSync(liveSyncInfo, {doRefresh: canExecuteFastSync}); this.livesyncTool.end(); From 460b4a4f3fb0f275cdb3b579d064ae5bf4cb7a63 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Tue, 24 Jul 2018 11:10:57 +0300 Subject: [PATCH 21/22] chore: update LS with sockets required runtime version --- lib/services/livesync/android-livesync-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index e4e517ff79..481b2af57e 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -4,7 +4,7 @@ import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base"; import * as semver from "semver"; export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService { - private static MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION = "4.2.0-2018-07-13-01"; + private static MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION = "4.2.0-2018-07-20-02"; constructor(protected $platformsData: IPlatformsData, protected $projectFilesManager: IProjectFilesManager, private $injector: IInjector, @@ -16,7 +16,7 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen } protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir, frameworkVersion: string): INativeScriptDeviceLiveSyncService { - if (semver.gte(frameworkVersion, AndroidLiveSyncService.MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION)) { + if (semver.gt(frameworkVersion, AndroidLiveSyncService.MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION)) { return this.$injector.resolve(AndroidDeviceSocketsLiveSyncService, { device, data }); } From 927336664a9d7781d1baa806f2f8627fcd59f382 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Tue, 24 Jul 2018 16:21:34 +0300 Subject: [PATCH 22/22] chore: update common lib --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index ec5e03102e..61cdaaaf75 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit ec5e03102e5772458c0e768c16ad2ecf939b3933 +Subproject commit 61cdaaaf7533394afbbe84dd4eee355072ade2de