Skip to content

Commit

Permalink
Start livesync watcher before preparing the project
Browse files Browse the repository at this point in the history
Implemented #3404
  • Loading branch information
Fatme committed May 15, 2018
1 parent acb7030 commit 897cf32
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 26 deletions.
2 changes: 2 additions & 0 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,5 @@ $injector.require("nativeScriptCloudExtensionService", "./services/nativescript-
$injector.requireCommand("resources|generate|icons", "./commands/generate-assets");
$injector.requireCommand("resources|generate|splashes", "./commands/generate-assets");
$injector.requirePublic("assetsGenerationService", "./services/assets-generation/assets-generation-service");

$injector.require("filesHashService", "./services/files-hash-service");
4 changes: 4 additions & 0 deletions lib/definitions/files-hash-service.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface IFilesHashService {
generateHashes(files: string[]): Promise<IStringDictionary>;
getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary>;
}
4 changes: 2 additions & 2 deletions lib/definitions/platform.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter {
* @param {IShouldPrepareInfo} shouldPrepareInfo Options needed to decide whether to prepare.
* @returns {Promise<boolean>} true indicates that the project should be prepared.
*/
shouldPrepare(shouldPrepareInfo: IShouldPrepareInfo): Promise<boolean>
shouldPrepare(shouldPrepareInfo: IShouldPrepareInfo): Promise<boolean>;

/**
* Installs the application on specified device.
Expand Down Expand Up @@ -213,7 +213,7 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter {
* @param {string} buildInfoFileDirname The directory where the build file should be written to.
* @returns {void}
*/
saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void
saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void;
}

interface IPlatformOptions extends IPlatformSpecificData, ICreateProjectOptions { }
Expand Down
6 changes: 5 additions & 1 deletion lib/definitions/project-changes.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
interface IPrepareInfo extends IAddedNativePlatform {
interface IAppFilesHashes {
appFilesHashes: IStringDictionary;
}

interface IPrepareInfo extends IAddedNativePlatform, IAppFilesHashes {
time: string;
bundle: boolean;
release: boolean;
Expand Down
26 changes: 26 additions & 0 deletions lib/services/files-hash-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { executeActionByChunks } from "../common/helpers";
import { DEFAULT_CHUNK_SIZE } from "../common/constants";

export class FilesHashService implements IFilesHashService {
constructor(private $fs: IFileSystem) { }

public async generateHashes(files: string[]): Promise<IStringDictionary> {
const result: IStringDictionary = {};

const action = async (file: string) => {
if (this.$fs.getFsStats(file).isFile()) {
result[file] = await this.$fs.getFileShasum(file);
}
};

await executeActionByChunks(files, DEFAULT_CHUNK_SIZE, action);

return result;
}

public async getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary> {
const newHashes = await this.generateHashes(files);
return _.omitBy(newHashes, (hash: string, pathToFile: string) => !!_.find(oldHashes, (oldHash: string, oldPath: string) => pathToFile === oldPath && hash === oldHash));
}
}
$injector.register("filesHashService", FilesHashService);
34 changes: 18 additions & 16 deletions lib/services/livesync/livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
super();
}

public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[],
liveSyncData: ILiveSyncInfo): Promise<void> {
public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise<void> {
const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir);
await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
await this.liveSyncOperation(deviceDescriptors, liveSyncData, projectData);
Expand Down Expand Up @@ -318,27 +317,19 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
}

@hook("liveSync")
private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[],
liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise<void> {
private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise<void> {
// In case liveSync is called for a second time for the same projectDir.
const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped;

// Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D.
const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir);
const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors;
this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors);

await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData);

if (!liveSyncData.skipWatcher && this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length) {
// Should be set after prepare
this.$usbLiveSyncService.isInitialized = true;

const devicesIds = deviceDescriptors.map(dd => dd.identifier);
const devices = _.filter(this.$devicesService.getDeviceInstances(), device => _.includes(devicesIds, device.deviceInfo.identifier));
const platforms = _(devices).map(device => device.deviceInfo.platform).uniq().value();
await this.startWatcher(projectData, liveSyncData, platforms);
await this.startWatcher(projectData, liveSyncData, deviceDescriptors, { isAlreadyLiveSyncing });
}

await this.initialSync(projectData, liveSyncData, deviceDescriptors, { isAlreadyLiveSyncing });
}

private setLiveSyncProcessInfo(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]): void {
Expand All @@ -351,6 +342,13 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey);
}

private async initialSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[], options: { isAlreadyLiveSyncing: boolean }): Promise<void> {
// Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D.
const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir);
const deviceDescriptorsForInitialSync = options.isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors;
await this.initialSyncCore(projectData, deviceDescriptorsForInitialSync, liveSyncData);
}

private getLiveSyncService(platform: string): IPlatformLiveSyncService {
if (this.$mobileHelper.isiOSPlatform(platform)) {
return this.$injector.resolve("iOSLiveSyncService");
Expand Down Expand Up @@ -452,7 +450,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
return null;
}

private async initialSync(projectData: IProjectData, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise<void> {
private async initialSyncCore(projectData: IProjectData, deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise<void> {
const preparedPlatforms: string[] = [];
const rebuiltInformation: ILiveSyncBuildInfo[] = [];

Expand Down Expand Up @@ -483,6 +481,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
useLiveEdit: liveSyncData.useLiveEdit,
watch: !liveSyncData.skipWatcher
});

await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version });
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);

Expand Down Expand Up @@ -525,7 +524,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
};
}

private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo, platforms: string[]): Promise<void> {
private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[], options: { isAlreadyLiveSyncing: boolean }): Promise<void> {
const devicesIds = deviceDescriptors.map(dd => dd.identifier);
const devices = _.filter(this.$devicesService.getDeviceInstances(), device => _.includes(devicesIds, device.deviceInfo.identifier));
const platforms = _(devices).map(device => device.deviceInfo.platform).uniq().value();
const patterns = await this.getWatcherPatterns(liveSyncData, projectData, platforms);

if (liveSyncData.watchAllFiles) {
Expand Down
32 changes: 26 additions & 6 deletions lib/services/project-changes-service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as path from "path";
import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME } from "../constants";
import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME, APP_RESOURCES_FOLDER_NAME } from "../constants";
import { getHash } from "../common/helpers";

const prepareInfoFileName = ".nsprepareinfo";
Expand Down Expand Up @@ -48,7 +48,8 @@ export class ProjectChangesService implements IProjectChangesService {
constructor(
private $platformsData: IPlatformsData,
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
private $fs: IFileSystem) {
private $fs: IFileSystem,
private $filesHashService: IFilesHashService) {
}

public get currentChanges(): IProjectChangesInfo {
Expand All @@ -58,9 +59,12 @@ export class ProjectChangesService implements IProjectChangesService {
public async checkForChanges(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): Promise<IProjectChangesInfo> {
const platformData = this.$platformsData.getPlatformData(platform, projectData);
this._changesInfo = new ProjectChangesInfo();
if (!this.ensurePrepareInfo(platform, projectData, projectChangesOptions)) {
const isPrepareInfoEnsured = await this.ensurePrepareInfo(platform, projectData, projectChangesOptions);
if (!isPrepareInfoEnsured) {
this._newFiles = 0;
this._changesInfo.appFilesChanged = this.containsNewerFiles(projectData.appDirectoryPath, projectData.appResourcesDirectoryPath, projectData);

this._changesInfo.appFilesChanged = await this.hasChangedAppFiles(projectData);

this._changesInfo.packageChanged = this.isProjectFileChanged(projectData, platform);
this._changesInfo.appResourcesChanged = this.containsNewerFiles(projectData.appResourcesDirectoryPath, null, projectData);
/*done because currently all node_modules are traversed, a possible improvement could be traversing only the production dependencies*/
Expand Down Expand Up @@ -152,7 +156,7 @@ export class ProjectChangesService implements IProjectChangesService {
}
}

private ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean {
private async ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): Promise<boolean> {
this._prepareInfo = this.getPrepareInfo(platform, projectData);
if (this._prepareInfo) {
this._prepareInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus && this._prepareInfo.nativePlatformStatus < projectChangesOptions.nativePlatformStatus ?
Expand All @@ -173,7 +177,8 @@ export class ProjectChangesService implements IProjectChangesService {
release: projectChangesOptions.release,
changesRequireBuild: true,
projectFileHash: this.getProjectFileStrippedHash(projectData, platform),
changesRequireBuildTime: null
changesRequireBuildTime: null,
appFilesHashes: await this.$filesHashService.generateHashes(this.getAppFiles(projectData.appDirectoryPath))
};

this._outputProjectMtime = 0;
Expand Down Expand Up @@ -300,5 +305,20 @@ export class ProjectChangesService implements IProjectChangesService {
}
return false;
}

private getAppFiles(appDirectoryPath: string): string[] {
return this.$fs.enumerateFilesInDirectorySync(appDirectoryPath, (filePath: string, stat: IFsStats) => filePath.indexOf(APP_RESOURCES_FOLDER_NAME) === -1);
}

private async hasChangedAppFiles(projectData: IProjectData): Promise<boolean> {
const files = this.getAppFiles(projectData.appDirectoryPath);
const changedFiles = await this.$filesHashService.getChanges(files, this._prepareInfo.appFilesHashes || {});
const hasChanges = changedFiles && _.keys(changedFiles).length > 0;
if (hasChanges) {
this._prepareInfo.appFilesHashes = await this.$filesHashService.generateHashes(files);
}

return hasChanges;
}
}
$injector.register("projectChangesService", ProjectChangesService);
4 changes: 4 additions & 0 deletions test/npm-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ function createTestInjector(): IInjector {
});
testInjector.register("httpClient", {});
testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub);
testInjector.register("filesHashService", {
getChanges: () => Promise.resolve({}),
generateHashes: () => Promise.resolve()
});

return testInjector;
}
Expand Down
1 change: 1 addition & 0 deletions test/platform-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ function createTestInjector() {
testInjector.register("analyticsSettingsService", {
getPlaygroundInfo: () => Promise.resolve(null)
});
testInjector.register("filesHashService", {});

return testInjector;
}
Expand Down
4 changes: 4 additions & 0 deletions test/platform-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ function createTestInjector() {
})
});
testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub);
testInjector.register("filesHashService", {
generateHashes: () => Promise.resolve(),
getChanges: () => Promise.resolve({test: "testHash"})
});

return testInjector;
}
Expand Down
9 changes: 8 additions & 1 deletion test/project-changes-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ class ProjectChangesServiceTest extends BaseServiceTest {
this.injector.register("devicePlatformsConstants", {});
this.injector.register("devicePlatformsConstants", {});
this.injector.register("projectChangesService", ProjectChangesService);
this.injector.register("filesHashService", {
generateHashes: () => Promise.resolve({})
});
this.injector.register("logger", {
warn: () => ({})
});

const fs = this.injector.resolve<IFileSystem>("fs");
fs.writeJson(path.join(this.projectDir, Constants.PACKAGE_JSON_FILE_NAME), {
Expand Down Expand Up @@ -127,7 +133,8 @@ describe("Project Changes Service Tests", () => {
changesRequireBuildTime: new Date().toString(),
iOSProvisioningProfileUUID: "provisioning_profile_test",
projectFileHash: "",
nativePlatformStatus: Constants.NativePlatformStatus.requiresPlatformAdd
nativePlatformStatus: Constants.NativePlatformStatus.requiresPlatformAdd,
appFilesHashes: {}
};
fs.writeJson(prepareInfoPath, expectedPrepareInfo);

Expand Down

0 comments on commit 897cf32

Please sign in to comment.