Skip to content

Commit 40201cb

Browse files
authored
Merge pull request #3746 from NativeScript/kddimitrov/android-livesync-sockets
Kddimitrov/android livesync sockets
2 parents d8e1dfb + 9273366 commit 40201cb

15 files changed

+954
-35
lines changed

lib/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ $injector.require("deployCommandHelper", "./helpers/deploy-command-helper");
122122

123123
$injector.requirePublicClass("localBuildService", "./services/local-build-service");
124124
$injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service");
125+
$injector.requirePublicClass("androidLivesyncTool", "./services/livesync/android-livesync-tool");
125126
$injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service");
126127
$injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service");
127128
$injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript

lib/definitions/livesync-global.d.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as stream from "stream";
2+
3+
declare global {
4+
interface IDuplexSocket extends stream.Duplex {
5+
uid?: string;
6+
}
7+
}
8+

lib/definitions/livesync.d.ts

+109-1
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ interface IPlatformLiveSyncService {
344344
liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise<ILiveSyncResultInfo>;
345345
refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void>;
346346
prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir, liveSyncInfo: ILiveSyncInfo, debugOptions: IDebugOptions): Promise<void>;
347+
getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService;
347348
}
348349

349350
interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase {
@@ -362,9 +363,27 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase
362363
* Removes specified files from a connected device
363364
* @param {Mobile.IDeviceAppData} deviceAppData Data about device and app.
364365
* @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device.
366+
* @param {string} projectFilesPath The Path to the app folder inside platforms folder
365367
* @return {Promise<void>}
366368
*/
367-
removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<void>;
369+
removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath?: string): Promise<void>;
370+
371+
/**
372+
* Transfers specified files to a connected device
373+
* @param {Mobile.IDeviceAppData} deviceAppData Data about device and app.
374+
* @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device.
375+
* @param {string} projectFilesPath The Path to the app folder inside platforms folder
376+
* @param {boolean} isFullSync Indicates if the operation is part of a fullSync
377+
* @return {Promise<Mobile.ILocalToDevicePathData[]>} Returns the ILocalToDevicePathData of all transfered files
378+
*/
379+
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>;
368387
}
369388

370389
interface IAndroidNativeScriptDeviceLiveSyncService {
@@ -376,6 +395,95 @@ interface IAndroidNativeScriptDeviceLiveSyncService {
376395
getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService;
377396
}
378397

398+
interface IAndroidLivesyncTool {
399+
/**
400+
* Creates new socket connection.
401+
* @param configuration - The configuration to the socket connection.
402+
* @returns {Promise<void>}
403+
*/
404+
connect(configuration: IAndroidLivesyncToolConfiguration): Promise<void>;
405+
/**
406+
* Sends a file through the socket.
407+
* @param filePath - The full path to the file.
408+
* @returns {Promise<void>}
409+
*/
410+
sendFile(filePath: string): Promise<void>;
411+
/**
412+
* Sends files through the socket.
413+
* @param filePaths - Array of files that will be send by the socket.
414+
* @returns {Promise<void>}
415+
*/
416+
sendFiles(filePaths: string[]): Promise<void>;
417+
/**
418+
* Sends all files from directory by the socket.
419+
* @param directoryPath - The path to the directory which files will be send by the socket.
420+
* @returns {Promise<void>}
421+
*/
422+
sendDirectory(directoryPath: string): Promise<void>;
423+
/**
424+
* Removes file
425+
* @param filePath - The full path to the file.
426+
* @returns {Promise<boolean>}
427+
*/
428+
removeFile(filePath: string): Promise<boolean>;
429+
/**
430+
* Removes files
431+
* @param filePaths - Array of files that will be removed.
432+
* @returns {Promise<boolean[]>}
433+
*/
434+
removeFiles(filePaths: string[]): Promise<boolean[]>;
435+
/**
436+
* Sends doSyncOperation that will be handled by the runtime.
437+
* @param doRefresh - Indicates if the application should be restarted. Defaults to true.
438+
* @param operationId - The identifier of the operation
439+
* @param timeout - The timeout in milliseconds
440+
* @returns {Promise<void>}
441+
*/
442+
sendDoSyncOperation(doRefresh: boolean, timeout?: number, operationId?: string): Promise<IAndroidLivesyncSyncOperationResult>;
443+
/**
444+
* Generates new operation identifier.
445+
*/
446+
generateOperationIdentifier(): string;
447+
/**
448+
* Checks if the current operation is in progress.
449+
* @param operationId - The identifier of the operation.
450+
*/
451+
isOperationInProgress(operationId: string): boolean;
452+
453+
/**
454+
* Closes the current socket connection.
455+
*/
456+
end(): void;
457+
}
458+
459+
interface IAndroidLivesyncToolConfiguration {
460+
/**
461+
* The application identifier.
462+
*/
463+
appIdentifier: string;
464+
/**
465+
* The device identifier.
466+
*/
467+
deviceIdentifier: string;
468+
/**
469+
* The path to app folder inside platforms folder: platforms/android/app/src/main/assets/app/
470+
*/
471+
appPlatformsPath: string;
472+
/**
473+
* If not provided, defaults to 127.0.0.1
474+
*/
475+
localHostAddress?: string;
476+
/**
477+
* If provider will call it when an error occurs.
478+
*/
479+
errorHandler?: any;
480+
}
481+
482+
interface IAndroidLivesyncSyncOperationResult {
483+
operationId: string,
484+
didRefresh: boolean
485+
}
486+
379487
interface IDeviceProjectRootOptions {
380488
appIdentifier: string;
381489
getDirname?: boolean;

lib/device-path-provider.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class DevicePathProvider implements IDevicePathProvider {
2525
} else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) {
2626
projectRoot = `/data/local/tmp/${options.appIdentifier}`;
2727
if (!options.getDirname) {
28-
const deviceLiveSyncService = this.$injector.resolve<AndroidDeviceLiveSyncService>(AndroidDeviceLiveSyncService, { _device: device });
28+
const deviceLiveSyncService = this.$injector.resolve<AndroidDeviceLiveSyncService>(AndroidDeviceLiveSyncService, { device });
2929
const hashService = deviceLiveSyncService.getDeviceHashService(options.appIdentifier);
3030
const hashFile = options.syncAllFiles ? null : await hashService.doesShasumFileExistsOnDevice();
3131
const syncFolderName = options.watch || hashFile ? LiveSyncPaths.SYNC_DIR_NAME : LiveSyncPaths.FULLSYNC_DIR_NAME;

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

+6-7
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@ import * as path from "path";
88
import * as net from "net";
99

1010
export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService {
11-
private device: Mobile.IAndroidDevice;
1211
private port: number;
1312

14-
constructor(_device: Mobile.IDevice,
13+
constructor(
1514
private $mobileHelper: Mobile.IMobileHelper,
1615
private $devicePathProvider: IDevicePathProvider,
1716
private $injector: IInjector,
1817
private $androidProcessService: Mobile.IAndroidProcessService,
19-
protected $platformsData: IPlatformsData) {
20-
super($platformsData);
21-
this.device = <Mobile.IAndroidDevice>(_device);
18+
protected $platformsData: IPlatformsData,
19+
protected device: Mobile.IAndroidDevice) {
20+
super($platformsData, device);
2221
}
2322

2423
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void> {
@@ -53,9 +52,9 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl
5352
getDirname: true
5453
});
5554

56-
await this.device.adb.executeShellCommand(["rm", "-rf", await this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.FULLSYNC_DIR_NAME),
55+
await this.device.adb.executeShellCommand(["rm", "-rf", this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.FULLSYNC_DIR_NAME),
5756
this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.SYNC_DIR_NAME),
58-
await this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.REMOVEDSYNC_DIR_NAME)]);
57+
this.$mobileHelper.buildDevicePath(deviceRootPath, LiveSyncPaths.REMOVEDSYNC_DIR_NAME)]);
5958
}
6059

6160
private async restartApplication(deviceAppData: Mobile.IDeviceAppData, projectName: string): Promise<void> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-android-debug-bridge";
2+
import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service";
3+
import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base";
4+
import { APP_FOLDER_NAME } from "../../constants";
5+
import { AndroidLivesyncTool } from "./android-livesync-tool";
6+
import * as path from "path";
7+
8+
export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService {
9+
private livesyncTool: IAndroidLivesyncTool;
10+
private static STATUS_UPDATE_INTERVAL = 10000;
11+
12+
constructor(
13+
private data: IProjectData,
14+
private $injector: IInjector,
15+
protected $platformsData: IPlatformsData,
16+
protected $staticConfig: Config.IStaticConfig,
17+
private $logger: ILogger,
18+
protected device: Mobile.IAndroidDevice,
19+
private $options: ICommonOptions,
20+
private $processService: IProcessService) {
21+
super($platformsData, device);
22+
this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool);
23+
}
24+
25+
public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise<void> {
26+
const platformData = this.$platformsData.getPlatformData(deviceAppData.platform, this.data);
27+
const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME);
28+
await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName });
29+
await this.connectLivesyncTool(projectFilesPath, this.data.projectId);
30+
}
31+
32+
public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo) {
33+
await this.doSync(liveSyncInfo);
34+
}
35+
36+
private async doSync(liveSyncInfo: ILiveSyncResultInfo, {doRefresh = false}: {doRefresh?: boolean} = {}): Promise<IAndroidLivesyncSyncOperationResult> {
37+
const operationId = this.livesyncTool.generateOperationIdentifier();
38+
39+
let result = {operationId, didRefresh: true };
40+
41+
if (liveSyncInfo.modifiedFilesData.length) {
42+
43+
const doSyncPromise = this.livesyncTool.sendDoSyncOperation(doRefresh, null, operationId);
44+
45+
const syncInterval : NodeJS.Timer = setInterval(() => {
46+
if (this.livesyncTool.isOperationInProgress(operationId)) {
47+
this.$logger.info("Sync operation in progress...");
48+
}
49+
}, AndroidDeviceSocketsLiveSyncService.STATUS_UPDATE_INTERVAL);
50+
51+
const clearSyncInterval = () => {
52+
clearInterval(syncInterval);
53+
};
54+
55+
this.$processService.attachToProcessExitSignals(this, clearSyncInterval);
56+
doSyncPromise.then(clearSyncInterval, clearSyncInterval);
57+
58+
result = await doSyncPromise;
59+
}
60+
61+
return result;
62+
}
63+
64+
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo) {
65+
const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform);
66+
67+
const syncOperationResult = await this.doSync(liveSyncInfo, {doRefresh: canExecuteFastSync});
68+
69+
this.livesyncTool.end();
70+
71+
if (!canExecuteFastSync || !syncOperationResult.didRefresh) {
72+
await this.device.applicationManager.restartApplication({ appId: liveSyncInfo.deviceAppData.appIdentifier, projectName: projectData.projectName });
73+
}
74+
}
75+
76+
public async removeFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<void> {
77+
await this.livesyncTool.removeFiles(_.map(localToDevicePaths, (element: any) => element.filePath));
78+
}
79+
80+
public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise<Mobile.ILocalToDevicePathData[]> {
81+
let transferredFiles;
82+
83+
if (isFullSync) {
84+
transferredFiles = await this._transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath);
85+
} else {
86+
transferredFiles = await this._transferFiles(localToDevicePaths);
87+
}
88+
89+
return transferredFiles;
90+
}
91+
92+
private async _transferFiles(localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<Mobile.ILocalToDevicePathData[]> {
93+
await this.livesyncTool.sendFiles(localToDevicePaths.map(localToDevicePathData => localToDevicePathData.getLocalPath()));
94+
95+
return localToDevicePaths;
96+
}
97+
98+
private async _transferDirectory(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<Mobile.ILocalToDevicePathData[]> {
99+
let transferredLocalToDevicePaths : Mobile.ILocalToDevicePathData[];
100+
const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier);
101+
const currentShasums: IStringDictionary = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths);
102+
const oldShasums = await deviceHashService.getShasumsFromDevice();
103+
104+
if (this.$options.force || !oldShasums) {
105+
await this.livesyncTool.sendDirectory(projectFilesPath);
106+
await deviceHashService.uploadHashFileToDevice(currentShasums);
107+
transferredLocalToDevicePaths = localToDevicePaths;
108+
} else {
109+
const changedShasums = deviceHashService.getChangedShasums(oldShasums, currentShasums);
110+
const changedFiles = _.keys(changedShasums);
111+
if (changedFiles.length) {
112+
await this.livesyncTool.sendFiles(changedFiles);
113+
await deviceHashService.uploadHashFileToDevice(currentShasums);
114+
transferredLocalToDevicePaths = localToDevicePaths.filter(localToDevicePathData => changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0);
115+
} else {
116+
transferredLocalToDevicePaths = [];
117+
}
118+
}
119+
120+
return transferredLocalToDevicePaths ;
121+
}
122+
123+
private async connectLivesyncTool(projectFilesPath: string, appIdentifier: string) {
124+
await this.livesyncTool.connect({
125+
appIdentifier,
126+
deviceIdentifier: this.device.deviceInfo.identifier,
127+
appPlatformsPath: projectFilesPath
128+
});
129+
}
130+
131+
public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService {
132+
const adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier });
133+
return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier });
134+
}
135+
}

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

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { AndroidDeviceLiveSyncService } from "./android-device-livesync-service";
2+
import { AndroidDeviceSocketsLiveSyncService } from "./android-device-livesync-sockets-service";
23
import { PlatformLiveSyncServiceBase } from "./platform-livesync-service-base";
4+
import * as semver from "semver";
35

46
export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implements IPlatformLiveSyncService {
7+
private static MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION = "4.2.0-2018-07-20-02";
58
constructor(protected $platformsData: IPlatformsData,
69
protected $projectFilesManager: IProjectFilesManager,
710
private $injector: IInjector,
@@ -12,9 +15,12 @@ export class AndroidLiveSyncService extends PlatformLiveSyncServiceBase implemen
1215
super($fs, $logger, $platformsData, $projectFilesManager, $devicePathProvider, $projectFilesProvider);
1316
}
1417

15-
protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir): INativeScriptDeviceLiveSyncService {
16-
const service = this.$injector.resolve<INativeScriptDeviceLiveSyncService>(AndroidDeviceLiveSyncService, { _device: device, data });
17-
return service;
18+
protected _getDeviceLiveSyncService(device: Mobile.IDevice, data: IProjectDir, frameworkVersion: string): INativeScriptDeviceLiveSyncService {
19+
if (semver.gt(frameworkVersion, AndroidLiveSyncService.MIN_SOCKETS_LIVESYNC_RUNTIME_VERSION)) {
20+
return this.$injector.resolve<INativeScriptDeviceLiveSyncService>(AndroidDeviceSocketsLiveSyncService, { device, data });
21+
}
22+
23+
return this.$injector.resolve<INativeScriptDeviceLiveSyncService>(AndroidDeviceLiveSyncService, { device, data });
1824
}
1925

2026
public async prepareForLiveSync(device: Mobile.IDevice, data: IProjectDir): Promise<void> { /* */ }

0 commit comments

Comments
 (0)