Skip to content

Speed up android livesync #3692

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const CONFIG_NS_APP_RESOURCES_ENTRY = "appResourcesPath";
export const CONFIG_NS_APP_ENTRY = "appPath";
export const DEPENDENCIES_JSON_NAME = "dependencies.json";
export const APK_EXTENSION_NAME = ".apk";
export const HASHES_FILE_NAME = ".nshashes";

export class PackageVersion {
static NEXT = "next";
Expand Down
16 changes: 16 additions & 0 deletions lib/definitions/files-hash-service.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
interface IFilesHashService {
generateHashes(files: string[]): Promise<IStringDictionary>;
/**
* Generate hashes for all prepared files (all files from app folder under platforms folder).
* @param platformData - Current platform's data
* @returns {Promise<IStringDictionary>}
* A map with key file's path and value - file's hash
*/
generateHashesForProject(platformData: IPlatformData): Promise<IStringDictionary>;
/**
* Generates hashes for all prepared files (all files from app folder under platforms folder)
* and saves them in .nshashes file under `hashFileDirectory` directory.
* @param platformData - Current platform's data
* @param hashesFileDirectory - Path to directory containing the hash file.
* @returns {Promise<void>}
*/
saveHashesForProject(platformData: IPlatformData, hashesFileDirectory: string): Promise<IStringDictionary>;
saveHashes(hashes: IStringDictionary, hashesFileDirectory: string): void;
getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary>;
hasChangesInShasums(oldHashes: IStringDictionary, newHashes: IStringDictionary): boolean;
}
18 changes: 17 additions & 1 deletion lib/definitions/livesync.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOp
*/
clean?: boolean;

/**
* Defines if initial sync will be forced.
* In case it is true, transfers all project's directory on device
* In case it is false, transfers only changed files.
*/
force?: boolean;

/**
* Defines the timeout in seconds {N} CLI will wait to find the inspector socket port from device's logs.
* If not provided, defaults to 10seconds.
Expand Down Expand Up @@ -330,6 +337,8 @@ interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleRe
filesToSync: string[];
isReinstalled: boolean;
syncAllFiles: boolean;
liveSyncDeviceInfo: ILiveSyncDeviceInfo;
force?: boolean;
}

interface ILiveSyncResultInfo extends IHasUseHotModuleReloadOption {
Expand All @@ -344,6 +353,13 @@ interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadO
device: Mobile.IDevice;
watch: boolean;
syncAllFiles: boolean;
liveSyncDeviceInfo: ILiveSyncDeviceInfo;
force?: boolean;
}

interface ITransferFilesOptions {
isFullSync: boolean;
force?: boolean;
}

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

interface IAndroidNativeScriptDeviceLiveSyncService extends INativeScriptDeviceLiveSyncService {
Expand Down
3 changes: 2 additions & 1 deletion lib/helpers/livesync-command-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper {
release: this.$options.release,
env: this.$options.env,
timeout: this.$options.timeout,
useHotModuleReload: this.$options.hmr
useHotModuleReload: this.$options.hmr,
force: this.$options.force
};

await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo);
Expand Down
5 changes: 4 additions & 1 deletion lib/services/android-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
private $npm: INodePackageManager,
private $androidPluginBuildService: IAndroidPluginBuildService,
private $platformEnvironmentRequirements: IPlatformEnvironmentRequirements,
private $androidResourcesMigrationService: IAndroidResourcesMigrationService) {
private $androidResourcesMigrationService: IAndroidResourcesMigrationService,
private $filesHashService: IFilesHashService) {
super($fs, $projectDataService);
this.isAndroidStudioTemplate = false;
}
Expand Down Expand Up @@ -340,6 +341,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
message: "Gradle build..."
})
);

await this.$filesHashService.saveHashesForProject(this._platformData, this._platformData.deviceBuildOutputPath);
}

private getGradleBuildOptions(settings: IAndroidBuildOptionsSettings, projectData: IProjectData): Array<string> {
Expand Down
20 changes: 20 additions & 0 deletions lib/services/files-hash-service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { executeActionByChunks } from "../common/helpers";
import { DEFAULT_CHUNK_SIZE } from "../common/constants";
import { APP_FOLDER_NAME, HASHES_FILE_NAME } from "../constants";
import * as path from "path";

export class FilesHashService implements IFilesHashService {
constructor(private $fs: IFileSystem,
Expand All @@ -24,6 +26,19 @@ export class FilesHashService implements IFilesHashService {
return result;
}

public async generateHashesForProject(platformData: IPlatformData): Promise<IStringDictionary> {
const appFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME);
const files = this.$fs.enumerateFilesInDirectorySync(appFilesPath);
const hashes = await this.generateHashes(files);
return hashes;
}

public async saveHashesForProject(platformData: IPlatformData, hashesFileDirectory: string): Promise<IStringDictionary> {
const hashes = await this.generateHashesForProject(platformData);
this.saveHashes(hashes, hashesFileDirectory);
return hashes;
}

public async getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary> {
const newHashes = await this.generateHashes(files);
return this.getChangesInShasums(oldHashes, newHashes);
Expand All @@ -33,6 +48,11 @@ export class FilesHashService implements IFilesHashService {
return !!_.keys(this.getChangesInShasums(oldHashes, newHashes)).length;
}

public saveHashes(hashes: IStringDictionary, hashesFileDirectory: string): void {
const hashesFilePath = path.join(hashesFileDirectory, HASHES_FILE_NAME);
this.$fs.writeJson(hashesFilePath, hashes);
}

private getChangesInShasums(oldHashes: IStringDictionary, newHashes: IStringDictionary): IStringDictionary {
return _.omitBy(newHashes, (hash: string, pathToFile: string) => !!_.find(oldHashes, (oldHash: string, oldPath: string) => pathToFile === oldPath && hash === oldHash));
}
Expand Down
85 changes: 85 additions & 0 deletions lib/services/livesync/android-device-livesync-service-base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { DeviceLiveSyncServiceBase } from './device-livesync-service-base';
import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service";

export abstract class AndroidDeviceLiveSyncServiceBase extends DeviceLiveSyncServiceBase {
private deviceHashServices: IDictionary<Mobile.IAndroidDeviceHashService>;

constructor(protected $injector: IInjector,
protected $platformsData: IPlatformsData,
protected $filesHashService: IFilesHashService,
protected $logger: ILogger,
protected device: Mobile.IAndroidDevice) {
super($platformsData, device);
this.deviceHashServices = {};
}

public abstract async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<void>;
public abstract async transferDirectoryOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<void>;

public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService {
const key = `${this.device.deviceInfo.identifier}${appIdentifier}`;
if (!this.deviceHashServices[key]) {
const deviceHashService = this.$injector.resolve(AndroidDeviceHashService, { adb: this.device.adb, appIdentifier });
this.deviceHashServices[key] = deviceHashService;
}

return this.deviceHashServices[key];
}

public async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo, options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]> {
const transferredFiles = await this.transferFilesCore(deviceAppData, localToDevicePaths, projectFilesPath, options);
await this.updateHashes(deviceAppData, localToDevicePaths, projectData, liveSyncDeviceInfo);
return transferredFiles;
}

private async transferFilesCore(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]> {
if (options.force && options.isFullSync) {
const hashFileDevicePath = this.getDeviceHashService(deviceAppData.appIdentifier).hashFileDevicePath;
await this.device.fileSystem.deleteFile(hashFileDevicePath, deviceAppData.appIdentifier);
this.$logger.trace("Before transfer directory on device ", localToDevicePaths);
await this.transferDirectoryOnDevice(deviceAppData, localToDevicePaths, projectFilesPath);
return localToDevicePaths;
}

const localToDevicePathsToTransfer = await this.getLocalToDevicePathsToTransfer(deviceAppData, localToDevicePaths, options);
this.$logger.trace("Files to transfer: ", localToDevicePathsToTransfer);
await this.transferFilesOnDevice(deviceAppData, localToDevicePathsToTransfer);
return localToDevicePathsToTransfer;
}

private async getLocalToDevicePathsToTransfer(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], options: ITransferFilesOptions): Promise<Mobile.ILocalToDevicePathData[]> {
if (options.force || !options.isFullSync) {
return localToDevicePaths;
}

const changedLocalToDevicePaths = await this.getChangedLocalToDevicePaths(deviceAppData.appIdentifier, localToDevicePaths);
return changedLocalToDevicePaths;
}

private async getChangedLocalToDevicePaths(appIdentifier: string, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<Mobile.ILocalToDevicePathData[]> {
const deviceHashService = this.getDeviceHashService(appIdentifier);
const currentHashes = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths);
const oldHashes = (await deviceHashService.getShasumsFromDevice()) || {};
const changedHashes = deviceHashService.getChangedShasums(oldHashes, currentHashes);
const changedFiles = _.keys(changedHashes);
const changedLocalToDevicePaths = localToDevicePaths.filter(localToDevicePathData => changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0);
return changedLocalToDevicePaths;
}

private async updateHashes(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo): Promise<void> {
const hashes = await this.updateHashesOnDevice(deviceAppData, localToDevicePaths, projectData, liveSyncDeviceInfo);
this.updateLocalHashes(hashes, deviceAppData, projectData, liveSyncDeviceInfo);
}

private async updateHashesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo): Promise<IStringDictionary> {
const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier);
const currentHashes = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths);
await deviceHashService.uploadHashFileToDevice(currentHashes);
return currentHashes;
}

private updateLocalHashes(hashes: IStringDictionary, deviceAppData: Mobile.IDeviceAppData, projectData: IProjectData, liveSyncDeviceInfo: ILiveSyncDeviceInfo): void {
const hashFilePath = liveSyncDeviceInfo.outputPath || this.$platformsData.getPlatformData(deviceAppData.platform, projectData).deviceBuildOutputPath;
this.$filesHashService.saveHashes(hashes, hashFilePath);
}
}
32 changes: 16 additions & 16 deletions lib/services/livesync/android-device-livesync-service.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
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 { AndroidDeviceLiveSyncServiceBase } from "./android-device-livesync-service-base";
import * as helpers from "../../common/helpers";
import { LiveSyncPaths } from "../../common/constants";
import { cache } from "../../common/decorators";
import * as path from "path";
import * as net from "net";

export class AndroidDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService {
export class AndroidDeviceLiveSyncService extends AndroidDeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService {
private port: number;

constructor(
private $mobileHelper: Mobile.IMobileHelper,
constructor(private $mobileHelper: Mobile.IMobileHelper,
private $devicePathProvider: IDevicePathProvider,
private $injector: IInjector,
$injector: IInjector,
private $androidProcessService: Mobile.IAndroidProcessService,
protected $platformsData: IPlatformsData,
protected device: Mobile.IAndroidDevice) {
super($platformsData, device);
protected device: Mobile.IAndroidDevice,
$filesHashService: IFilesHashService,
$logger: ILogger) {
super($injector, $platformsData, $filesHashService, $logger, device);
}

public async transferFilesOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]): Promise<void> {
await this.device.fileSystem.transferFiles(deviceAppData, localToDevicePaths);
}

public async transferDirectoryOnDevice(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<void> {
await this.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, projectFilesPath);
}

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

@cache()
public getDeviceHashService(appIdentifier: string): Mobile.IAndroidDeviceHashService {
const adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: this.device.deviceInfo.identifier });
return this.$injector.resolve(AndroidDeviceHashService, { adb, appIdentifier });
}

private async awaitRuntimeReloadSuccessMessage(): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
let isResolved = false;
Expand Down
Loading