From 9b3b3a69bd0fd0e26c1ee9471f72202c332ddb53 Mon Sep 17 00:00:00 2001 From: Kristina Koeva Date: Wed, 3 Aug 2016 11:14:03 +0300 Subject: [PATCH] Send special message if we live sync javascript file and the liveEdit option is on --- lib/common | 2 +- lib/declarations.ts | 1 + lib/options.ts | 3 +- .../android-device-livesync-service.ts | 22 ++- .../livesync/device-livesync-service-base.ts | 21 -- .../livesync/ios-device-livesync-service.ts | 187 ++++++++++++------ test/ios-project-service.ts | 1 + 7 files changed, 152 insertions(+), 85 deletions(-) delete mode 100644 lib/services/livesync/device-livesync-service-base.ts diff --git a/lib/common b/lib/common index d4ff454c14..3bf09c997e 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit d4ff454c14df7fcc17f4f3855915372dd0970143 +Subproject commit 3bf09c997e11a7e71c89a5e57dbbb49e70554b8f diff --git a/lib/declarations.ts b/lib/declarations.ts index b4b559f608..92980772f7 100644 --- a/lib/declarations.ts +++ b/lib/declarations.ts @@ -100,6 +100,7 @@ interface IOptions extends ICommonOptions { teamId: string; rebuild: boolean; syncAllFiles: boolean; + liveEdit: boolean; } interface IInitService { diff --git a/lib/options.ts b/lib/options.ts index e45a771dd8..517251de54 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -38,7 +38,8 @@ export class Options extends commonOptionsLibPath.OptionsBase { all: {type: OptionType.Boolean }, teamId: { type: OptionType.String }, rebuild: { type: OptionType.Boolean, default: true }, - syncAllFiles: { type: OptionType.Boolean } + syncAllFiles: { type: OptionType.Boolean }, + liveEdit: { type: OptionType.Boolean } }, path.join($hostInfo.isWindows ? process.env.AppData : path.join(osenv.home(), ".local/share"), ".nativescript-cli"), $errors, $staticConfig); diff --git a/lib/services/livesync/android-device-livesync-service.ts b/lib/services/livesync/android-device-livesync-service.ts index 425c612427..9689f9b3c2 100644 --- a/lib/services/livesync/android-device-livesync-service.ts +++ b/lib/services/livesync/android-device-livesync-service.ts @@ -1,13 +1,13 @@ 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 Future = require("fibers/future"); import * as helpers from "../../common/helpers"; import * as path from "path"; import * as net from "net"; -class AndroidLiveSyncService extends DeviceLiveSyncServiceBase implements IDeviceLiveSyncService { +class AndroidLiveSyncService implements IDeviceLiveSyncService { private static BACKEND_PORT = 18182; + private device: Mobile.IAndroidDevice; constructor(_device: Mobile.IDevice, private $fs: IFileSystem, @@ -16,15 +16,25 @@ class AndroidLiveSyncService extends DeviceLiveSyncServiceBase(_device); } public get debugService(): IDebugService { return this.$androidDebugService; } - public restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture { + public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture { + let canExecuteFastSync = !forceExecuteFullSync && !_.some(localToDevicePaths, (localToDevicePath:any) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform)); + + if (canExecuteFastSync) { + return this.reloadPage(deviceAppData, localToDevicePaths); + } + + return this.restartApplication(deviceAppData); + } + + private restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture { return (() => { this.device.adb.executeShellCommand(["chmod", "777", deviceAppData.deviceProjectRootPath, `/data/local/tmp/${deviceAppData.appIdentifier}`]).wait(); @@ -56,7 +66,7 @@ class AndroidLiveSyncService extends DeviceLiveSyncServiceBase()(); } - public reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture { + private reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture { return (() => { this.device.adb.executeCommand(["forward", `tcp:${AndroidLiveSyncService.BACKEND_PORT.toString()}`, `localabstract:${deviceAppData.appIdentifier}-livesync`]).wait(); this.sendPageReloadMessage().wait(); diff --git a/lib/services/livesync/device-livesync-service-base.ts b/lib/services/livesync/device-livesync-service-base.ts deleted file mode 100644 index e5188f8da2..0000000000 --- a/lib/services/livesync/device-livesync-service-base.ts +++ /dev/null @@ -1,21 +0,0 @@ -export abstract class DeviceLiveSyncServiceBase { - protected get device(): T { - return (this._device); - } - - constructor(private _device: Mobile.IDevice, - private $liveSyncProvider: ILiveSyncProvider) { } - - public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture { - let canExecuteFastSync = !forceExecuteFullSync && !_.some(localToDevicePaths, (localToDevicePath:any) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform)); - - if (canExecuteFastSync) { - return this.reloadPage(deviceAppData); - } - - return this.restartApplication(deviceAppData); - } - - protected abstract restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture; - protected abstract reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture; -} diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 329e9e9322..66a14356e0 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -1,12 +1,13 @@ -import {DeviceLiveSyncServiceBase} from "./device-livesync-service-base"; import * as helpers from "../../common/helpers"; import * as net from "net"; +import Future = require("fibers/future"); let currentPageReloadId = 0; -class IOSLiveSyncService extends DeviceLiveSyncServiceBase implements IDeviceLiveSyncService { +class IOSLiveSyncService implements IDeviceLiveSyncService { private static BACKEND_PORT = 18181; private socket: net.Socket; + private device: Mobile.IiOSDevice; constructor(_device: Mobile.IDevice, private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, @@ -17,8 +18,9 @@ class IOSLiveSyncService extends DeviceLiveSyncServiceBase im private $options: IOptions, private $iOSDebugService: IDebugService, private $childProcess: IChildProcess, - $liveSyncProvider: ILiveSyncProvider) { - super(_device, $liveSyncProvider); + private $fs: IFileSystem, + private $liveSyncProvider: ILiveSyncProvider) { + this.device = (_device); } public get debugService(): IDebugService { @@ -31,76 +33,155 @@ class IOSLiveSyncService extends DeviceLiveSyncServiceBase im }).future()(); } + private setupSocketIfNeeded(): IFuture { + return (() => { + if (this.socket) { + return true; + } + + let enableDebuggerMessage = `{ "method":"Debugger.enable","id":${++currentPageReloadId} }`; + if (this.device.isEmulator) { + this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.attachRequest).wait(); + try { + this.socket = helpers.connectEventuallyUntilTimeout(() => net.connect(IOSLiveSyncService.BACKEND_PORT), 5000).wait(); + } catch (e) { + this.$logger.warn(e); + + return false; + } + } else { + let timeout = 9000; + this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, timeout).wait(); + this.socket = this.device.connectToPort(IOSLiveSyncService.BACKEND_PORT); + } + + this.attachEventHandlers(); + this.sendMessage(enableDebuggerMessage).wait(); + + return true; + }).future()(); + } + public removeFiles(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture { return (() => { _.each(localToDevicePaths, localToDevicePathData => this.device.fileSystem.deleteFile(localToDevicePathData.getDevicePath(), appIdentifier)); }).future()(); } - protected restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture { + public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture { + return (() => { + if (forceExecuteFullSync) { + this.restartApplication(deviceAppData).wait(); + return; + } + let scriptFiles = _.filter(localToDevicePaths, localToDevicePath => _.endsWith(localToDevicePath.getDevicePath(), ".js")); + let otherFiles = _.difference(localToDevicePaths, scriptFiles); + let shouldRestart = _.some(otherFiles, (localToDevicePath: Mobile.ILocalToDevicePathData) => !this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform)); + + if (shouldRestart) { + this.restartApplication(deviceAppData).wait(); + + return; + } + + if (!this.$options.liveEdit && scriptFiles.length) { + this.restartApplication(deviceAppData).wait(); + + return; + } + + if (this.setupSocketIfNeeded().wait()) { + this.liveEdit(scriptFiles); + this.reloadPage(deviceAppData, otherFiles).wait(); + } + }).future()(); + } + + private restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture { let projectData: IProjectData = this.$injector.resolve("projectData"); return this.device.applicationManager.restartApplication(deviceAppData.appIdentifier, projectData.projectName); } - protected reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture { + private reloadPage(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): IFuture { return (() => { - let timeout = 9000; - if (this.device.isEmulator) { - if (!this.socket) { - helpers.connectEventually(() => net.connect(IOSLiveSyncService.BACKEND_PORT), (socket: net.Socket) => { - this.socket = socket; - this.attachEventHandlersIfNecessary(); - this.sendPageReloadMessage(); - }); - } else { - this.sendPageReloadMessage(); - } - this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.attachRequest).wait(); - } else { - if(!this.socket) { - this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, timeout).wait(); - this.socket = this.device.connectToPort(IOSLiveSyncService.BACKEND_PORT); - this.attachEventHandlersIfNecessary(); - } - this.sendPageReloadMessage(); + if (localToDevicePaths.length) { + let message = JSON.stringify({ + method: "Page.reload", + params: { + ignoreCache: false + }, + id: ++currentPageReloadId + }); + + this.sendMessage(message).wait(); } }).future()(); } - private attachEventHandlersIfNecessary(): void { - if(this.$options.watch) { - this.attachProcessExitHandlers(); - this.attachSocketCloseEvent(); - } + private liveEdit(localToDevicePaths: Mobile.ILocalToDevicePathData[]) { + return (() => { + _.each(localToDevicePaths, localToDevicePath => { + let content = this.$fs.readText(localToDevicePath.getLocalPath()).wait(); + let message = JSON.stringify({ + method: "Debugger.setScriptSource", + params: { + scriptUrl: localToDevicePath.getDevicePath(), + scriptSource: content + }, + id: ++currentPageReloadId + }); + + this.sendMessage(message).wait(); + }); + }).future()(); } - private attachSocketCloseEvent(): void { + private attachEventHandlers(): void { + this.attachProcessExitHandlers(); + this.socket.on("close", (hadError: boolean) => { this.$logger.trace(`Socket closed, hadError is ${hadError}.`); this.socket = null; }); - } - private sendPageReloadMessage(): void { - try { - this.sendPageReloadMessageCore(); - this.socket.on("data", (data: NodeBuffer|string) => { - this.$logger.trace(`Socket sent data: ${data.toString()}`); - this.destroySocketIfNecessary(); - }); - } catch(err) { - this.$logger.trace("Error while sending page reload:", err); - this.destroySocketIfNecessary(); - } + this.socket.on("error", (error: any) => { + this.$logger.trace(`Socket error received: ${error}`); + }); + + this.socket.on("data", (data: NodeBuffer|string) => { + this.$logger.trace(`Socket sent data: ${data.toString()}`); + }); } - private sendPageReloadMessageCore(): void { - let message = `{ "method":"Page.reload","params":{"ignoreCache":false},"id":${++currentPageReloadId} }`; - let length = Buffer.byteLength(message, "utf16le"); - let payload = new Buffer(length + 4); - payload.writeInt32BE(length, 0); - payload.write(message, 4, length, "utf16le"); - this.socket.write(payload); + private sendMessage(message: string): IFuture { + return (() => { + let socketWriteFuture = new Future(); + try { + let length = Buffer.byteLength(message, "utf16le"); + let payload = new Buffer(length + 4); + payload.writeInt32BE(length, 0); + payload.write(message, 4, length, "utf16le"); + + this.socket.once("error", (error: Error) => { + if (!socketWriteFuture.isResolved()) { + socketWriteFuture.throw(error); + } + }); + + this.socket.write(payload, "utf16le", () => { + this.socket.removeAllListeners("error"); + + if (!socketWriteFuture.isResolved()) { + socketWriteFuture.return(); + } + }); + + socketWriteFuture.wait(); + } catch(error) { + this.$logger.trace("Error while sending message:", error); + this.destroySocket(); + } + }).future()(); } private attachProcessExitHandlers(): void { @@ -118,12 +199,6 @@ class IOSLiveSyncService extends DeviceLiveSyncServiceBase im }); } - private destroySocketIfNecessary(): void { - if(!this.$options.watch) { - this.destroySocket(); - } - } - private destroySocket(): void { if(this.socket) { this.socket.destroy(); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index cfcc59d621..1b509d4be4 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -82,6 +82,7 @@ function createTestInjector(projectPath: string, projectName: string): IInjector testInjector.register("pluginVariablesService", PluginVariablesService); testInjector.register("pluginVariablesHelper", PluginVariablesHelper); testInjector.register("androidProcessService", {}); + testInjector.register("processService", {}); return testInjector; }