Skip to content

Commit e34d148

Browse files
author
Fatme
authored
Merge pull request #3692 from NativeScript/fatme/faster-android-livesync
Speed up android livesync
2 parents 3a783e1 + 54003cc commit e34d148

17 files changed

+597
-91
lines changed

lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const CONFIG_NS_APP_RESOURCES_ENTRY = "appResourcesPath";
3939
export const CONFIG_NS_APP_ENTRY = "appPath";
4040
export const DEPENDENCIES_JSON_NAME = "dependencies.json";
4141
export const APK_EXTENSION_NAME = ".apk";
42+
export const HASHES_FILE_NAME = ".nshashes";
4243

4344
export class PackageVersion {
4445
static NEXT = "next";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
interface IFilesHashService {
22
generateHashes(files: string[]): Promise<IStringDictionary>;
3+
/**
4+
* Generate hashes for all prepared files (all files from app folder under platforms folder).
5+
* @param platformData - Current platform's data
6+
* @returns {Promise<IStringDictionary>}
7+
* A map with key file's path and value - file's hash
8+
*/
9+
generateHashesForProject(platformData: IPlatformData): Promise<IStringDictionary>;
10+
/**
11+
* Generates hashes for all prepared files (all files from app folder under platforms folder)
12+
* and saves them in .nshashes file under `hashFileDirectory` directory.
13+
* @param platformData - Current platform's data
14+
* @param hashesFileDirectory - Path to directory containing the hash file.
15+
* @returns {Promise<void>}
16+
*/
17+
saveHashesForProject(platformData: IPlatformData, hashesFileDirectory: string): Promise<IStringDictionary>;
18+
saveHashes(hashes: IStringDictionary, hashesFileDirectory: string): void;
319
getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary>;
420
hasChangesInShasums(oldHashes: IStringDictionary, newHashes: IStringDictionary): boolean;
521
}

lib/definitions/livesync.d.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOp
154154
*/
155155
clean?: boolean;
156156

157+
/**
158+
* Defines if initial sync will be forced.
159+
* In case it is true, transfers all project's directory on device
160+
* In case it is false, transfers only changed files.
161+
*/
162+
force?: boolean;
163+
157164
/**
158165
* Defines the timeout in seconds {N} CLI will wait to find the inspector socket port from device's logs.
159166
* If not provided, defaults to 10seconds.
@@ -330,6 +337,8 @@ interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleRe
330337
filesToSync: string[];
331338
isReinstalled: boolean;
332339
syncAllFiles: boolean;
340+
liveSyncDeviceInfo: ILiveSyncDeviceInfo;
341+
force?: boolean;
333342
}
334343

335344
interface ILiveSyncResultInfo extends IHasUseHotModuleReloadOption {
@@ -344,6 +353,13 @@ interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadO
344353
device: Mobile.IDevice;
345354
watch: boolean;
346355
syncAllFiles: boolean;
356+
liveSyncDeviceInfo: ILiveSyncDeviceInfo;
357+
force?: boolean;
358+
}
359+
360+
interface ITransferFilesOptions {
361+
isFullSync: boolean;
362+
force?: boolean;
347363
}
348364

349365
interface IPlatformLiveSyncService {
@@ -383,7 +399,7 @@ interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase
383399
* @param {boolean} isFullSync Indicates if the operation is part of a fullSync
384400
* @return {Promise<Mobile.ILocalToDevicePathData[]>} Returns the ILocalToDevicePathData of all transfered files
385401
*/
386-
transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise<Mobile.ILocalToDevicePathData[]>;
402+
transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]>;
387403
}
388404

389405
interface IAndroidNativeScriptDeviceLiveSyncService extends INativeScriptDeviceLiveSyncService {

lib/helpers/livesync-command-helper.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper {
115115
release: this.$options.release,
116116
env: this.$options.env,
117117
timeout: this.$options.timeout,
118-
useHotModuleReload: this.$options.hmr
118+
useHotModuleReload: this.$options.hmr,
119+
force: this.$options.force
119120
};
120121

121122
await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo);

lib/services/android-project-service.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
3030
private $npm: INodePackageManager,
3131
private $androidPluginBuildService: IAndroidPluginBuildService,
3232
private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements,
33-
private $androidResourcesMigrationService: IAndroidResourcesMigrationService) {
33+
private $androidResourcesMigrationService: IAndroidResourcesMigrationService,
34+
private $filesHashService: IFilesHashService) {
3435
super($fs, $projectDataService);
3536
this.isAndroidStudioTemplate = false;
3637
}
@@ -340,6 +341,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
340341
message: "Gradle build..."
341342
})
342343
);
344+
345+
await this.$filesHashService.saveHashesForProject(this._platformData, this._platformData.deviceBuildOutputPath);
343346
}
344347

345348
private getGradleBuildOptions(settings: IAndroidBuildOptionsSettings, projectData: IProjectData): Array<string> {

lib/services/files-hash-service.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { executeActionByChunks } from "../common/helpers";
22
import { DEFAULT_CHUNK_SIZE } from "../common/constants";
3+
import { APP_FOLDER_NAME, HASHES_FILE_NAME } from "../constants";
4+
import * as path from "path";
35

46
export class FilesHashService implements IFilesHashService {
57
constructor(private $fs: IFileSystem,
@@ -24,6 +26,19 @@ export class FilesHashService implements IFilesHashService {
2426
return result;
2527
}
2628

29+
public async generateHashesForProject(platformData: IPlatformData): Promise<IStringDictionary> {
30+
const appFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME);
31+
const files = this.$fs.enumerateFilesInDirectorySync(appFilesPath);
32+
const hashes = await this.generateHashes(files);
33+
return hashes;
34+
}
35+
36+
public async saveHashesForProject(platformData: IPlatformData, hashesFileDirectory: string): Promise<IStringDictionary> {
37+
const hashes = await this.generateHashesForProject(platformData);
38+
this.saveHashes(hashes, hashesFileDirectory);
39+
return hashes;
40+
}
41+
2742
public async getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary> {
2843
const newHashes = await this.generateHashes(files);
2944
return this.getChangesInShasums(oldHashes, newHashes);
@@ -33,6 +48,11 @@ export class FilesHashService implements IFilesHashService {
3348
return !!_.keys(this.getChangesInShasums(oldHashes, newHashes)).length;
3449
}
3550

51+
public saveHashes(hashes: IStringDictionary, hashesFileDirectory: string): void {
52+
const hashesFilePath = path.join(hashesFileDirectory, HASHES_FILE_NAME);
53+
this.$fs.writeJson(hashesFilePath, hashes);
54+
}
55+
3656
private getChangesInShasums(oldHashes: IStringDictionary, newHashes: IStringDictionary): IStringDictionary {
3757
return _.omitBy(newHashes, (hash: string, pathToFile: string) => !!_.find(oldHashes, (oldHash: string, oldPath: string) => pathToFile === oldPath && hash === oldHash));
3858
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { DeviceLiveSyncServiceBase } from './device-livesync-service-base';
2+
import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service";
3+
4+
export abstract class AndroidDeviceLiveSyncServiceBase extends DeviceLiveSyncServiceBase {
5+
private deviceHashServices: IDictionary<Mobile.IAndroidDeviceHashService>;
6+
7+
constructor(protected $injector: IInjector,
8+
protected $platformsData: IPlatformsData,
9+
protected $filesHashService: IFilesHashService,
10+
protected $logger: ILogger,
11+
protected device: Mobile.IAndroidDevice) {
12+
super($platformsData, device);
13+
this.deviceHashServices = {};
14+
}
15+
16+
public abstract async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<void>;
17+
public abstract async transferDirectoryOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<void>;
18+
19+
public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService {
20+
const key = `${this.device.deviceInfo.identifier}${appIdentifier}`;
21+
if (!this.deviceHashServices[key]) {
22+
const deviceHashService = this.$injector.resolve(AndroidDeviceHashService, { adb: this.device.adb, appIdentifier });
23+
this.deviceHashServices[key] = deviceHashService;
24+
}
25+
26+
return this.deviceHashServices[key];
27+
}
28+
29+
public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]> {
30+
const transferredFiles = await this.transferFilesCore(deviceAppData, localToDevicePaths, projectFilesPath, options);
31+
await this.updateHashes(deviceAppData, localToDevicePaths, projectData, liveSyncDeviceInfo);
32+
return transferredFiles;
33+
}
34+
35+
private async transferFilesCore(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]> {
36+
if (options.force && options.isFullSync) {
37+
const hashFileDevicePath = this.getDeviceHashService(deviceAppData.appIdentifier).hashFileDevicePath;
38+
await this.device.fileSystem.deleteFile(hashFileDevicePath, deviceAppData.appIdentifier);
39+
this.$logger.trace("Before transfer directory on device ", localToDevicePaths);
40+
await this.transferDirectoryOnDevice(deviceAppData, localToDevicePaths, projectFilesPath);
41+
return localToDevicePaths;
42+
}
43+
44+
const localToDevicePathsToTransfer = await this.getLocalToDevicePathsToTransfer(deviceAppData, localToDevicePaths, options);
45+
this.$logger.trace("Files to transfer: ", localToDevicePathsToTransfer);
46+
await this.transferFilesOnDevice(deviceAppData, localToDevicePathsToTransfer);
47+
return localToDevicePathsToTransfer;
48+
}
49+
50+
private async getLocalToDevicePathsToTransfer(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]> {
51+
if (options.force || !options.isFullSync) {
52+
return localToDevicePaths;
53+
}
54+
55+
const changedLocalToDevicePaths = await this.getChangedLocalToDevicePaths(deviceAppData.appIdentifier, localToDevicePaths);
56+
return changedLocalToDevicePaths;
57+
}
58+
59+
private async getChangedLocalToDevicePaths(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<Mobile.ILocalToDevicePathData[]> {
60+
const deviceHashService = this.getDeviceHashService(appIdentifier);
61+
const currentHashes = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths);
62+
const oldHashes = (await deviceHashService.getShasumsFromDevice()) || {};
63+
const changedHashes = deviceHashService.getChangedShasums(oldHashes, currentHashes);
64+
const changedFiles = _.keys(changedHashes);
65+
const changedLocalToDevicePaths = localToDevicePaths.filter(localToDevicePathData => changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0);
66+
return changedLocalToDevicePaths;
67+
}
68+
69+
private async updateHashes(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo): Promise<void> {
70+
const hashes = await this.updateHashesOnDevice(deviceAppData, localToDevicePaths, projectData, liveSyncDeviceInfo);
71+
this.updateLocalHashes(hashes, deviceAppData, projectData, liveSyncDeviceInfo);
72+
}
73+
74+
private async updateHashesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo): Promise<IStringDictionary> {
75+
const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier);
76+
const currentHashes = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths);
77+
await deviceHashService.uploadHashFileToDevice(currentHashes);
78+
return currentHashes;
79+
}
80+
81+
private updateLocalHashes(hashes: IStringDictionary, deviceAppData: Mobile.IDeviceAppData, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo): void {
82+
const hashFilePath = liveSyncDeviceInfo.outputPath || this.$platformsData.getPlatformData(deviceAppData.platform, projectData).deviceBuildOutputPath;
83+
this.$filesHashService.saveHashes(hashes, hashFilePath);
84+
}
85+
}

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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
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";
1+
import { AndroidDeviceLiveSyncServiceBase } from "./android-device-livesync-service-base";
42
import * as helpers from "../../common/helpers";
53
import { LiveSyncPaths } from "../../common/constants";
6-
import { cache } from "../../common/decorators";
74
import * as path from "path";
85
import * as net from "net";
96

10-
export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService {
7+
export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService {
118
private port: number;
129

13-
constructor(
14-
private $mobileHelper: Mobile.IMobileHelper,
10+
constructor(private $mobileHelper: Mobile.IMobileHelper,
1511
private $devicePathProvider: IDevicePathProvider,
16-
private $injector: IInjector,
12+
$injector: IInjector,
1713
private $androidProcessService: Mobile.IAndroidProcessService,
1814
protected $platformsData: IPlatformsData,
19-
protected device: Mobile.IAndroidDevice) {
20-
super($platformsData, device);
15+
protected device: Mobile.IAndroidDevice,
16+
$filesHashService: IFilesHashService,
17+
$logger: ILogger) {
18+
super($injector, $platformsData, $filesHashService, $logger, device);
19+
}
20+
21+
public async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<void> {
22+
await this.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths);
23+
}
24+
25+
public async transferDirectoryOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<void> {
26+
await this.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath);
2127
}
2228

2329
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void> {
@@ -116,12 +122,6 @@ export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase impl
116122
await this.getDeviceHashService(deviceAppData.appIdentifier).removeHashes(localToDevicePaths);
117123
}
118124

119-
@cache()
120-
public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService {
121-
const adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier });
122-
return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier });
123-
}
124-
125125
private async awaitRuntimeReloadSuccessMessage(): Promise<boolean> {
126126
return new Promise<boolean>((resolve, reject) => {
127127
let isResolved = false;

0 commit comments

Comments
 (0)