Skip to content

Commit 2aeef9e

Browse files
authored
Merge pull request #3789 from NativeScript/fatme/fix-livesync-sockets
Fix android livesync with sockets
2 parents a10c2c5 + 9726009 commit 2aeef9e

7 files changed

+70
-37
lines changed

lib/definitions/livesync-global.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import * as stream from "stream";
1+
import { Socket } from "net";
22

33
declare global {
4-
interface IDuplexSocket extends stream.Duplex {
4+
interface INetSocket extends Socket {
55
uid?: string;
66
}
77
}

lib/definitions/livesync.d.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ interface ILiveSyncResultInfo {
332332
useLiveEdit?: boolean;
333333
}
334334

335+
interface IAndroidLiveSyncResultInfo extends ILiveSyncResultInfo, IAndroidLivesyncSyncOperationResult { }
336+
335337
interface IFullSyncInfo extends IProjectDataComposition {
336338
device: Mobile.IDevice;
337339
watch: boolean;
@@ -377,22 +379,22 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase
377379
* @return {Promise<Mobile.ILocalToDevicePathData[]>} Returns the ILocalToDevicePathData of all transfered files
378380
*/
379381
transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise<Mobile.ILocalToDevicePathData[]>;
380-
381-
/**
382-
* Guarantees all remove/update operations have finished
383-
* @param {ILiveSyncResultInfo} liveSyncInfo Describes the LiveSync operation - for which project directory is the operation and other settings.
384-
* @return {Promise<void>}
385-
*/
386-
finalizeSync(liveSyncInfo: ILiveSyncResultInfo): Promise<void>;
387382
}
388383

389-
interface IAndroidNativeScriptDeviceLiveSyncService {
384+
interface IAndroidNativeScriptDeviceLiveSyncService extends INativeScriptDeviceLiveSyncService {
390385
/**
391386
* Retrieves the android device's hash service.
392387
* @param {string} appIdentifier Application identifier.
393388
* @return {Promise<Mobile.IAndroidDeviceHashService>} The hash service
394389
*/
395390
getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService;
391+
392+
/**
393+
* Guarantees all remove/update operations have finished
394+
* @param {ILiveSyncResultInfo} liveSyncInfo Describes the LiveSync operation - for which project directory is the operation and other settings.
395+
* @return {Promise<IAndroidLiveSyncResultInfo>}
396+
*/
397+
finalizeSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise<IAndroidLivesyncSyncOperationResult>;
396398
}
397399

398400
interface IAndroidLivesyncTool {

lib/services/livesync/android-device-livesync-sockets-service.ts

+27-17
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,23 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa
3939
return `${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appIdentifier}-livesync-in-progress`;
4040
}
4141

42-
public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo) {
43-
await this.doSync(liveSyncInfo);
42+
public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise<IAndroidLivesyncSyncOperationResult> {
43+
try {
44+
const result = await this.doSync(liveSyncInfo, projectData);
45+
return result;
46+
} finally {
47+
this.livesyncTool.end();
48+
}
4449
}
4550

46-
private async doSync(liveSyncInfo: ILiveSyncResultInfo, { doRefresh = false }: { doRefresh?: boolean } = {}): Promise<IAndroidLivesyncSyncOperationResult> {
51+
private async doSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise<IAndroidLivesyncSyncOperationResult> {
4752
const operationId = this.livesyncTool.generateOperationIdentifier();
4853

4954
let result = { operationId, didRefresh: true };
5055

5156
if (liveSyncInfo.modifiedFilesData.length) {
52-
53-
const doSyncPromise = this.livesyncTool.sendDoSyncOperation(doRefresh, null, operationId);
57+
const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform);
58+
const doSyncPromise = this.livesyncTool.sendDoSyncOperation(canExecuteFastSync, null, operationId);
5459

5560
const syncInterval: NodeJS.Timer = setInterval(() => {
5661
if (this.livesyncTool.isOperationInProgress(operationId)) {
@@ -64,30 +69,29 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa
6469
};
6570

6671
this.$processService.attachToProcessExitSignals(this, actionOnEnd);
67-
doSyncPromise.then(actionOnEnd, actionOnEnd);
72+
// We need to clear resources when the action fails
73+
// But we also need the real result of the action.
74+
await doSyncPromise.then(actionOnEnd.bind(this), actionOnEnd.bind(this));
6875

6976
result = await doSyncPromise;
77+
} else {
78+
await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(liveSyncInfo.deviceAppData.appIdentifier), liveSyncInfo.deviceAppData.appIdentifier);
7079
}
7180

72-
await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(liveSyncInfo.deviceAppData.appIdentifier), liveSyncInfo.deviceAppData.appIdentifier);
73-
7481
return result;
7582
}
7683

77-
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo) {
84+
public async refreshApplication(projectData: IProjectData, liveSyncInfo: IAndroidLiveSyncResultInfo) {
7885
const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform);
79-
80-
const syncOperationResult = await this.doSync(liveSyncInfo, { doRefresh: canExecuteFastSync });
81-
82-
this.livesyncTool.end();
83-
84-
if (!canExecuteFastSync || !syncOperationResult.didRefresh) {
86+
if (!canExecuteFastSync || !liveSyncInfo.didRefresh) {
8587
await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName });
8688
}
8789
}
8890

8991
public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<void> {
9092
await this.livesyncTool.removeFiles(_.map(localToDevicePaths, (element: any) => element.filePath));
93+
94+
await this.getDeviceHashService(deviceAppData.appIdentifier).removeHashes(localToDevicePaths);
9195
}
9296

9397
public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise<Mobile.ILocalToDevicePathData[]> {
@@ -96,15 +100,21 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa
96100
if (isFullSync) {
97101
transferredFiles = await this._transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath);
98102
} else {
99-
transferredFiles = await this._transferFiles(localToDevicePaths);
103+
transferredFiles = await this._transferFiles(deviceAppData, localToDevicePaths);
100104
}
101105

102106
return transferredFiles;
103107
}
104108

105-
private async _transferFiles(localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<Mobile.ILocalToDevicePathData[]> {
109+
private async _transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<Mobile.ILocalToDevicePathData[]> {
106110
await this.livesyncTool.sendFiles(localToDevicePaths.map(localToDevicePathData => localToDevicePathData.getLocalPath()));
107111

112+
// Update hashes
113+
const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier);
114+
if (! await deviceHashService.updateHashes(localToDevicePaths)) {
115+
this.$logger.trace("Unable to find hash file on device. The next livesync command will create it.");
116+
}
117+
108118
return localToDevicePaths;
109119
}
110120

lib/services/livesync/android-livesync-service.ts

+19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen
2323
return this.$injector.resolve<INativeScriptDeviceLiveSyncService>(AndroidDeviceLiveSyncService, { device, data });
2424
}
2525

26+
public async liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise<IAndroidLiveSyncResultInfo> {
27+
const liveSyncResult = await super.liveSyncWatchAction(device, liveSyncInfo);
28+
const result = await this.finalizeSync(device, liveSyncInfo.projectData, liveSyncResult);
29+
return result;
30+
}
31+
32+
public async fullSync(syncInfo: IFullSyncInfo): Promise<IAndroidLiveSyncResultInfo> {
33+
const liveSyncResult = await super.fullSync(syncInfo);
34+
const result = await this.finalizeSync(syncInfo.device, syncInfo.projectData, liveSyncResult);
35+
return result;
36+
}
37+
2638
public async prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir): Promise<void> { /* */ }
39+
40+
private async finalizeSync(device: Mobile.IDevice, projectData: IProjectData, liveSyncResult: ILiveSyncResultInfo): Promise<IAndroidLiveSyncResultInfo> {
41+
const liveSyncService = <IAndroidNativeScriptDeviceLiveSyncService>this.getDeviceLiveSyncService(device, projectData);
42+
const finalizeResult = await liveSyncService.finalizeSync(liveSyncResult, projectData);
43+
const result = _.extend(liveSyncResult, finalizeResult);
44+
return result;
45+
}
2746
}
2847
$injector.register("androidLiveSyncService", AndroidLiveSyncService);

lib/services/livesync/android-livesync-tool.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ const DEFAULT_LOCAL_HOST_ADDRESS = "127.0.0.1";
2222
export class AndroidLivesyncTool implements IAndroidLivesyncTool {
2323
private operationPromises: IDictionary<any>;
2424
private socketError: string | Error;
25-
private socketConnection: IDuplexSocket;
25+
private socketConnection: INetSocket;
2626
private configuration: IAndroidLivesyncToolConfiguration;
2727
private pendingConnectionData: {
2828
connectionTimer?: NodeJS.Timer,
2929
socketTimer?: NodeJS.Timer,
3030
rejectHandler?: Function,
31-
socket?: IDuplexSocket
31+
socket?: INetSocket
3232
} = null;
3333

3434
constructor(private $androidProcessService: Mobile.IAndroidProcessService,
@@ -173,6 +173,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
173173
this.cleanState(socketUid);
174174
//call end of the connection (close and error callbacks won't be called - listeners removed)
175175
socket.end();
176+
socket.destroy();
176177
//reject all pending sync requests and clear timeouts
177178
this.rejectPendingSyncOperations(socketUid, error);
178179
}
@@ -254,7 +255,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
254255
});
255256
}
256257

257-
private createSocket(port: number): IDuplexSocket {
258+
private createSocket(port: number): INetSocket {
258259
const socket = new net.Socket();
259260
socket.connect(port, this.configuration.localHostAddress);
260261
return socket;
@@ -280,7 +281,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
280281
}
281282
}
282283

283-
private handleConnection({ socket, data }: { socket: IDuplexSocket, data: NodeBuffer | string }) {
284+
private handleConnection({ socket, data }: { socket: INetSocket, data: NodeBuffer | string }) {
284285
this.socketConnection = socket;
285286
this.socketConnection.uid = this.generateOperationIdentifier();
286287

@@ -304,15 +305,15 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
304305
});
305306
}
306307

307-
private connectEventuallyUntilTimeout(factory: () => IDuplexSocket, timeout: number): Promise<{socket: IDuplexSocket, data: NodeBuffer | string}> {
308+
private connectEventuallyUntilTimeout(factory: () => INetSocket, timeout: number): Promise<{socket: INetSocket, data: NodeBuffer | string}> {
308309
return new Promise((resolve, reject) => {
309310
let lastKnownError: Error | string,
310311
isConnected = false;
311312

312313
const connectionTimer = setTimeout(() => {
313314
if (!isConnected) {
314315
isConnected = true;
315-
reject(lastKnownError || { message: "Socket connection timeouted." });
316+
reject(lastKnownError || new Error("Socket connection timeouted."));
316317
this.pendingConnectionData = null;
317318
}
318319
}, timeout);

lib/services/livesync/device-livesync-service-base.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ export abstract class DeviceLiveSyncServiceBase {
3939
return transferredFiles;
4040
}
4141

42-
public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo): Promise<void> {
42+
public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo, projectData: IProjectData): Promise<IAndroidLivesyncSyncOperationResult> {
4343
//implement in case a sync point for all remove/create operation is needed
44+
return {
45+
didRefresh:true,
46+
operationId: ""
47+
};
4448
}
4549
}

lib/services/livesync/livesync-service.ts

-3
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,6 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
164164
};
165165

166166
try {
167-
const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform);
168-
const deviceLivesyncService = platformLiveSyncService.getDeviceLiveSyncService(deviceAppData.device, projectData);
169-
await deviceLivesyncService.finalizeSync(liveSyncResultInfo);
170167
await deviceAppData.device.applicationManager.stopApplication({ appId: applicationId, projectName: projectData.projectName });
171168
// Now that we've stopped the application we know it isn't started, so set debugOptions.start to false
172169
// so that it doesn't default to true in attachDebugger method

0 commit comments

Comments
 (0)