diff --git a/lib/definitions/livesync-global.d.ts b/lib/definitions/livesync-global.d.ts index ac93fae85a..ecbc48f0d9 100644 --- a/lib/definitions/livesync-global.d.ts +++ b/lib/definitions/livesync-global.d.ts @@ -1,7 +1,7 @@ -import * as stream from "stream"; +import { Socket } from "net"; declare global { - interface IDuplexSocket extends stream.Duplex { + interface INetSocket extends Socket { uid?: string; } } diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index d10d86fd43..4989f16056 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -332,6 +332,8 @@ interface ILiveSyncResultInfo { useLiveEdit?: boolean; } +interface IAndroidLiveSyncResultInfo extends ILiveSyncResultInfo, IAndroidLivesyncSyncOperationResult { } + interface IFullSyncInfo extends IProjectDataComposition { device: Mobile.IDevice; watch: boolean; @@ -377,22 +379,22 @@ 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 { +interface IAndroidNativeScriptDeviceLiveSyncService extends INativeScriptDeviceLiveSyncService { /** * Retrieves the android device's hash service. * @param {string} appIdentifier Application identifier. * @return {Promise} The hash service */ getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService; + + /** + * 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, projectData: IProjectData): Promise; } interface IAndroidLivesyncTool { diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index 5bb4048c2f..e2d172fda0 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -39,18 +39,23 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa return `${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appIdentifier}-livesync-in-progress`; } - public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo) { - await this.doSync(liveSyncInfo); + public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise { + try { + const result = await this.doSync(liveSyncInfo, projectData); + return result; + } finally { + this.livesyncTool.end(); + } } - private async doSync(liveSyncInfo: ILiveSyncResultInfo, { doRefresh = false }: { doRefresh?: boolean } = {}): Promise { + private async doSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise { const operationId = this.livesyncTool.generateOperationIdentifier(); let result = { operationId, didRefresh: true }; if (liveSyncInfo.modifiedFilesData.length) { - - const doSyncPromise = this.livesyncTool.sendDoSyncOperation(doRefresh, null, operationId); + const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); + const doSyncPromise = this.livesyncTool.sendDoSyncOperation(canExecuteFastSync, null, operationId); const syncInterval: NodeJS.Timer = setInterval(() => { if (this.livesyncTool.isOperationInProgress(operationId)) { @@ -64,30 +69,29 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa }; this.$processService.attachToProcessExitSignals(this, actionOnEnd); - doSyncPromise.then(actionOnEnd, actionOnEnd); + // We need to clear resources when the action fails + // But we also need the real result of the action. + await doSyncPromise.then(actionOnEnd.bind(this), actionOnEnd.bind(this)); result = await doSyncPromise; + } else { + await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(liveSyncInfo.deviceAppData.appIdentifier), liveSyncInfo.deviceAppData.appIdentifier); } - await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(liveSyncInfo.deviceAppData.appIdentifier), liveSyncInfo.deviceAppData.appIdentifier); - return result; } - public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo) { + public async refreshApplication(projectData: IProjectData, liveSyncInfo: IAndroidLiveSyncResultInfo) { const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); - - const syncOperationResult = await this.doSync(liveSyncInfo, { doRefresh: canExecuteFastSync }); - - this.livesyncTool.end(); - - if (!canExecuteFastSync || !syncOperationResult.didRefresh) { + if (!canExecuteFastSync || !liveSyncInfo.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 { await this.livesyncTool.removeFiles(_.map(localToDevicePaths, (element: any) => element.filePath)); + + await this.getDeviceHashService(deviceAppData.appIdentifier).removeHashes(localToDevicePaths); } public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise { @@ -96,15 +100,21 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa if (isFullSync) { transferredFiles = await this._transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath); } else { - transferredFiles = await this._transferFiles(localToDevicePaths); + transferredFiles = await this._transferFiles(deviceAppData, localToDevicePaths); } return transferredFiles; } - private async _transferFiles(localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { + private async _transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise { await this.livesyncTool.sendFiles(localToDevicePaths.map(localToDevicePathData => localToDevicePathData.getLocalPath())); + // Update hashes + const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier); + if (! await deviceHashService.updateHashes(localToDevicePaths)) { + this.$logger.trace("Unable to find hash file on device. The next livesync command will create it."); + } + return localToDevicePaths; } diff --git a/lib/services/livesync/android-livesync-service.ts b/lib/services/livesync/android-livesync-service.ts index 481b2af57e..9821e7e23e 100644 --- a/lib/services/livesync/android-livesync-service.ts +++ b/lib/services/livesync/android-livesync-service.ts @@ -23,6 +23,25 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen return this.$injector.resolve(AndroidDeviceLiveSyncService, { device, data }); } + public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise { + const liveSyncResult = await super.liveSyncWatchAction(device, liveSyncInfo); + const result = await this.finalizeSync(device, liveSyncInfo.projectData, liveSyncResult); + return result; + } + + public async fullSync(syncInfo: IFullSyncInfo): Promise { + const liveSyncResult = await super.fullSync(syncInfo); + const result = await this.finalizeSync(syncInfo.device, syncInfo.projectData, liveSyncResult); + return result; + } + public async prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir): Promise { /* */ } + + private async finalizeSync(device: Mobile.IDevice, projectData: IProjectData, liveSyncResult: ILiveSyncResultInfo): Promise { + const liveSyncService = this.getDeviceLiveSyncService(device, projectData); + const finalizeResult = await liveSyncService.finalizeSync(liveSyncResult, projectData); + const result = _.extend(liveSyncResult, finalizeResult); + return result; + } } $injector.register("androidLiveSyncService", AndroidLiveSyncService); diff --git a/lib/services/livesync/android-livesync-tool.ts b/lib/services/livesync/android-livesync-tool.ts index 23275c87d3..0f319faacb 100644 --- a/lib/services/livesync/android-livesync-tool.ts +++ b/lib/services/livesync/android-livesync-tool.ts @@ -22,13 +22,13 @@ const DEFAULT_LOCAL_HOST_ADDRESS = "127.0.0.1"; export class AndroidLivesyncTool implements IAndroidLivesyncTool { private operationPromises: IDictionary; private socketError: string | Error; - private socketConnection: IDuplexSocket; + private socketConnection: INetSocket; private configuration: IAndroidLivesyncToolConfiguration; private pendingConnectionData: { connectionTimer?: NodeJS.Timer, socketTimer?: NodeJS.Timer, rejectHandler?: Function, - socket?: IDuplexSocket + socket?: INetSocket } = null; constructor(private $androidProcessService: Mobile.IAndroidProcessService, @@ -173,6 +173,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { this.cleanState(socketUid); //call end of the connection (close and error callbacks won't be called - listeners removed) socket.end(); + socket.destroy(); //reject all pending sync requests and clear timeouts this.rejectPendingSyncOperations(socketUid, error); } @@ -254,7 +255,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { }); } - private createSocket(port: number): IDuplexSocket { + private createSocket(port: number): INetSocket { const socket = new net.Socket(); socket.connect(port, this.configuration.localHostAddress); return socket; @@ -280,7 +281,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { } } - private handleConnection({ socket, data }: { socket: IDuplexSocket, data: NodeBuffer | string }) { + private handleConnection({ socket, data }: { socket: INetSocket, data: NodeBuffer | string }) { this.socketConnection = socket; this.socketConnection.uid = this.generateOperationIdentifier(); @@ -304,7 +305,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { }); } - private connectEventuallyUntilTimeout(factory: () => IDuplexSocket, timeout: number): Promise<{socket: IDuplexSocket, data: NodeBuffer | string}> { + private connectEventuallyUntilTimeout(factory: () => INetSocket, timeout: number): Promise<{socket: INetSocket, data: NodeBuffer | string}> { return new Promise((resolve, reject) => { let lastKnownError: Error | string, isConnected = false; @@ -312,7 +313,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { const connectionTimer = setTimeout(() => { if (!isConnected) { isConnected = true; - reject(lastKnownError || { message: "Socket connection timeouted." }); + reject(lastKnownError || new Error("Socket connection timeouted.")); this.pendingConnectionData = null; } }, timeout); diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts index 6bf76cbac1..d2e447cecc 100644 --- a/lib/services/livesync/device-livesync-service-base.ts +++ b/lib/services/livesync/device-livesync-service-base.ts @@ -39,7 +39,11 @@ export abstract class DeviceLiveSyncServiceBase { return transferredFiles; } - public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo): Promise { + public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise { //implement in case a sync point for all remove/create operation is needed + return { + didRefresh:true, + operationId: "" + }; } } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index ad17982098..5a6de53cd0 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -164,9 +164,6 @@ 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