Skip to content

Commit 82364e4

Browse files
committed
Start livesync watcher before preparing the project
Implemented #3404
1 parent acb7030 commit 82364e4

8 files changed

+78
-22
lines changed

lib/bootstrap.ts

+2
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,5 @@ $injector.require("nativeScriptCloudExtensionService", "./services/nativescript-
165165
$injector.requireCommand("resources|generate|icons", "./commands/generate-assets");
166166
$injector.requireCommand("resources|generate|splashes", "./commands/generate-assets");
167167
$injector.requirePublic("assetsGenerationService", "./services/assets-generation/assets-generation-service");
168+
169+
$injector.require("filesHashService", "./services/files-hash-service");
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface IFilesHashService {
2+
generateHashes(files: string[]): Promise<IStringDictionary>;
3+
getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary>;
4+
}

lib/definitions/platform.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter {
8888
* @param {IShouldPrepareInfo} shouldPrepareInfo Options needed to decide whether to prepare.
8989
* @returns {Promise<boolean>} true indicates that the project should be prepared.
9090
*/
91-
shouldPrepare(shouldPrepareInfo: IShouldPrepareInfo): Promise<boolean>
91+
shouldPrepare(shouldPrepareInfo: IShouldPrepareInfo): Promise<boolean>;
9292

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

219219
interface IPlatformOptions extends IPlatformSpecificData, ICreateProjectOptions { }

lib/definitions/project-changes.d.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
interface IPrepareInfo extends IAddedNativePlatform {
1+
interface IAppFilesHashes {
2+
appFilesHashes: IStringDictionary;
3+
}
4+
5+
interface IPrepareInfo extends IAddedNativePlatform, IAppFilesHashes {
26
time: string;
37
bundle: boolean;
48
release: boolean;

lib/services/files-hash-service.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { executeActionByChunks } from "../common/helpers";
2+
import { DEFAULT_CHUNK_SIZE } from "../common/constants";
3+
4+
export class FilesHashService implements IFilesHashService {
5+
constructor(private $fs: IFileSystem) { }
6+
7+
public async generateHashes(files: string[]): Promise<IStringDictionary> {
8+
const result: IStringDictionary = {};
9+
10+
const action = async (file: string) => {
11+
if (this.$fs.getFsStats(file).isFile()) {
12+
result[file] = await this.$fs.getFileShasum(file);
13+
}
14+
};
15+
16+
await executeActionByChunks(files, DEFAULT_CHUNK_SIZE, action);
17+
18+
return result;
19+
}
20+
21+
public async getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary> {
22+
const newHashes = await this.generateHashes(files);
23+
return _.omitBy(newHashes, (hash: string, pathToFile: string) => !!_.find(oldHashes, (oldHash: string, oldPath: string) => pathToFile === oldPath && hash === oldHash));
24+
}
25+
}
26+
$injector.register("filesHashService", FilesHashService);

lib/services/livesync/livesync-service.ts

+17-14
Original file line numberDiff line numberDiff line change
@@ -318,26 +318,16 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
318318
}
319319

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

326-
// 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.
327-
const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir);
328-
const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors;
329325
this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors);
330326

331-
await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData);
332-
333327
if (!liveSyncData.skipWatcher && this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length) {
334328
// Should be set after prepare
335329
this.$usbLiveSyncService.isInitialized = true;
336-
337-
const devicesIds = deviceDescriptors.map(dd => dd.identifier);
338-
const devices = _.filter(this.$devicesService.getDeviceInstances(), device => _.includes(devicesIds, device.deviceInfo.identifier));
339-
const platforms = _(devices).map(device => device.deviceInfo.platform).uniq().value();
340-
await this.startWatcher(projectData, liveSyncData, platforms);
330+
await this.startWatcher(projectData, liveSyncData, deviceDescriptors, { isAlreadyLiveSyncing });
341331
}
342332
}
343333

@@ -351,6 +341,13 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
351341
this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey);
352342
}
353343

344+
private async initialSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[], options: { isAlreadyLiveSyncing: boolean }): Promise<void> {
345+
// 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.
346+
const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir);
347+
const deviceDescriptorsForInitialSync = options.isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors;
348+
await this.initialSyncCore(projectData, deviceDescriptorsForInitialSync, liveSyncData);
349+
}
350+
354351
private getLiveSyncService(platform: string): IPlatformLiveSyncService {
355352
if (this.$mobileHelper.isiOSPlatform(platform)) {
356353
return this.$injector.resolve("iOSLiveSyncService");
@@ -452,7 +449,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
452449
return null;
453450
}
454451

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

@@ -483,6 +480,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
483480
useLiveEdit: liveSyncData.useLiveEdit,
484481
watch: !liveSyncData.skipWatcher
485482
});
483+
486484
await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version });
487485
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
488486

@@ -525,7 +523,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
525523
};
526524
}
527525

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

531532
if (liveSyncData.watchAllFiles) {
@@ -683,6 +684,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
683684
this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, patterns };
684685
this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer;
685686

687+
await this.initialSync(projectData, liveSyncData, deviceDescriptors, options);
688+
686689
this.$processService.attachToProcessExitSignals(this, () => {
687690
_.keys(this.liveSyncProcessesInfo).forEach(projectDir => {
688691
// Do not await here, we are in process exit's handler.

lib/services/project-changes-service.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as path from "path";
2-
import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME } from "../constants";
2+
import { NODE_MODULES_FOLDER_NAME, NativePlatformStatus, PACKAGE_JSON_FILE_NAME, APP_GRADLE_FILE_NAME, BUILD_XCCONFIG_FILE_NAME, APP_RESOURCES_FOLDER_NAME } from "../constants";
33
import { getHash } from "../common/helpers";
44

55
const prepareInfoFileName = ".nsprepareinfo";
@@ -48,7 +48,8 @@ export class ProjectChangesService implements IProjectChangesService {
4848
constructor(
4949
private $platformsData: IPlatformsData,
5050
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
51-
private $fs: IFileSystem) {
51+
private $fs: IFileSystem,
52+
private $filesHashService: IFilesHashService) {
5253
}
5354

5455
public get currentChanges(): IProjectChangesInfo {
@@ -60,7 +61,10 @@ export class ProjectChangesService implements IProjectChangesService {
6061
this._changesInfo = new ProjectChangesInfo();
6162
if (!this.ensurePrepareInfo(platform, projectData, projectChangesOptions)) {
6263
this._newFiles = 0;
63-
this._changesInfo.appFilesChanged = this.containsNewerFiles(projectData.appDirectoryPath, projectData.appResourcesDirectoryPath, projectData);
64+
65+
const appFiles = this.$fs.enumerateFilesInDirectorySync(projectData.appDirectoryPath, (filePath: string, stat: IFsStats) => filePath.indexOf(APP_RESOURCES_FOLDER_NAME) === -1);
66+
this._changesInfo.appFilesChanged = await this.hasChangedFiles(appFiles, projectData);
67+
6468
this._changesInfo.packageChanged = this.isProjectFileChanged(projectData, platform);
6569
this._changesInfo.appResourcesChanged = this.containsNewerFiles(projectData.appResourcesDirectoryPath, null, projectData);
6670
/*done because currently all node_modules are traversed, a possible improvement could be traversing only the production dependencies*/
@@ -173,7 +177,8 @@ export class ProjectChangesService implements IProjectChangesService {
173177
release: projectChangesOptions.release,
174178
changesRequireBuild: true,
175179
projectFileHash: this.getProjectFileStrippedHash(projectData, platform),
176-
changesRequireBuildTime: null
180+
changesRequireBuildTime: null,
181+
appFilesHashes: {}
177182
};
178183

179184
this._outputProjectMtime = 0;
@@ -300,5 +305,15 @@ export class ProjectChangesService implements IProjectChangesService {
300305
}
301306
return false;
302307
}
308+
309+
private async hasChangedFiles(files: string[], projectData: IProjectData): Promise<boolean> {
310+
const changedFiles = await this.$filesHashService.getChanges(files, this._prepareInfo.appFilesHashes || {});
311+
const hasChanges = changedFiles && _.keys(changedFiles).length > 0;
312+
if (hasChanges) {
313+
this._prepareInfo.appFilesHashes = await this.$filesHashService.generateHashes(files);
314+
}
315+
316+
return hasChanges;
317+
}
303318
}
304319
$injector.register("projectChangesService", ProjectChangesService);

test/project-changes-service.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class ProjectChangesServiceTest extends BaseServiceTest {
3030
this.injector.register("devicePlatformsConstants", {});
3131
this.injector.register("devicePlatformsConstants", {});
3232
this.injector.register("projectChangesService", ProjectChangesService);
33+
this.injector.register("filesHashService", {});
3334

3435
const fs = this.injector.resolve<IFileSystem>("fs");
3536
fs.writeJson(path.join(this.projectDir, Constants.PACKAGE_JSON_FILE_NAME), {
@@ -127,7 +128,8 @@ describe("Project Changes Service Tests", () => {
127128
changesRequireBuildTime: new Date().toString(),
128129
iOSProvisioningProfileUUID: "provisioning_profile_test",
129130
projectFileHash: "",
130-
nativePlatformStatus: Constants.NativePlatformStatus.requiresPlatformAdd
131+
nativePlatformStatus: Constants.NativePlatformStatus.requiresPlatformAdd,
132+
appFilesHashes: {}
131133
};
132134
fs.writeJson(prepareInfoPath, expectedPrepareInfo);
133135

0 commit comments

Comments
 (0)