Skip to content

Commit 932fd75

Browse files
author
Fatme
authored
Merge pull request #3576 from NativeScript/fatme/watch
Start livesync watcher before preparing the project
2 parents 9bbb2e1 + 3b68ab2 commit 932fd75

11 files changed

+105
-25
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

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
private $logger: ILogger) { }
7+
8+
public async generateHashes(files: string[]): Promise<IStringDictionary> {
9+
const result: IStringDictionary = {};
10+
11+
const action = async (file: string) => {
12+
try {
13+
const isFile = this.$fs.getFsStats(file).isFile();
14+
if (isFile) {
15+
result[file] = await this.$fs.getFileShasum(file);
16+
}
17+
} catch (err) {
18+
this.$logger.trace(`Unable to generate hash for file ${file}. Error is: ${err}`);
19+
}
20+
};
21+
22+
await executeActionByChunks(files, DEFAULT_CHUNK_SIZE, action);
23+
24+
return result;
25+
}
26+
27+
public async getChanges(files: string[], oldHashes: IStringDictionary): Promise<IStringDictionary> {
28+
const newHashes = await this.generateHashes(files);
29+
return _.omitBy(newHashes, (hash: string, pathToFile: string) => !!_.find(oldHashes, (oldHash: string, oldPath: string) => pathToFile === oldPath && hash === oldHash));
30+
}
31+
}
32+
$injector.register("filesHashService", FilesHashService);

lib/services/livesync/livesync-service.ts

+18-16
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
4141
super();
4242
}
4343

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

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

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;
329324
this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors);
330325

331-
await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData);
332-
333326
if (!liveSyncData.skipWatcher && this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length) {
334327
// Should be set after prepare
335328
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);
329+
await this.startWatcher(projectData, liveSyncData, deviceDescriptors, { isAlreadyLiveSyncing });
341330
}
331+
332+
await this.initialSync(projectData, liveSyncData, deviceDescriptors, { isAlreadyLiveSyncing });
342333
}
343334

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

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

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

@@ -483,6 +481,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
483481
useLiveEdit: liveSyncData.useLiveEdit,
484482
watch: !liveSyncData.skipWatcher
485483
});
484+
486485
await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version });
487486
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
488487

@@ -525,7 +524,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
525524
};
526525
}
527526

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

531533
if (liveSyncData.watchAllFiles) {

lib/services/project-changes-service.ts

+25-5
Original file line numberDiff line numberDiff line change
@@ -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 {
@@ -58,9 +59,12 @@ export class ProjectChangesService implements IProjectChangesService {
5859
public async checkForChanges(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): Promise<IProjectChangesInfo> {
5960
const platformData = this.$platformsData.getPlatformData(platform, projectData);
6061
this._changesInfo = new ProjectChangesInfo();
61-
if (!this.ensurePrepareInfo(platform, projectData, projectChangesOptions)) {
62+
const isNewPrepareInfo = await this.ensurePrepareInfo(platform, projectData, projectChangesOptions);
63+
if (!isNewPrepareInfo) {
6264
this._newFiles = 0;
63-
this._changesInfo.appFilesChanged = this.containsNewerFiles(projectData.appDirectoryPath, projectData.appResourcesDirectoryPath, projectData);
65+
66+
this._changesInfo.appFilesChanged = await this.hasChangedAppFiles(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*/
@@ -152,7 +156,7 @@ export class ProjectChangesService implements IProjectChangesService {
152156
}
153157
}
154158

155-
private ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): boolean {
159+
private async ensurePrepareInfo(platform: string, projectData: IProjectData, projectChangesOptions: IProjectChangesOptions): Promise<boolean> {
156160
this._prepareInfo = this.getPrepareInfo(platform, projectData);
157161
if (this._prepareInfo) {
158162
this._prepareInfo.nativePlatformStatus = this._prepareInfo.nativePlatformStatus && this._prepareInfo.nativePlatformStatus < projectChangesOptions.nativePlatformStatus ?
@@ -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: await this.$filesHashService.generateHashes(this.getAppFiles(projectData))
177182
};
178183

179184
this._outputProjectMtime = 0;
@@ -300,5 +305,20 @@ export class ProjectChangesService implements IProjectChangesService {
300305
}
301306
return false;
302307
}
308+
309+
private getAppFiles(projectData: IProjectData): string[] {
310+
return this.$fs.enumerateFilesInDirectorySync(projectData.appDirectoryPath, (filePath: string, stat: IFsStats) => filePath !== projectData.appResourcesDirectoryPath);
311+
}
312+
313+
private async hasChangedAppFiles(projectData: IProjectData): Promise<boolean> {
314+
const files = this.getAppFiles(projectData);
315+
const changedFiles = await this.$filesHashService.getChanges(files, this._prepareInfo.appFilesHashes || {});
316+
const hasChanges = changedFiles && _.keys(changedFiles).length > 0;
317+
if (hasChanges) {
318+
this._prepareInfo.appFilesHashes = await this.$filesHashService.generateHashes(files);
319+
}
320+
321+
return hasChanges;
322+
}
303323
}
304324
$injector.register("projectChangesService", ProjectChangesService);

test/npm-support.ts

+4
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ function createTestInjector(): IInjector {
9898
});
9999
testInjector.register("httpClient", {});
100100
testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub);
101+
testInjector.register("filesHashService", {
102+
getChanges: () => Promise.resolve({}),
103+
generateHashes: () => Promise.resolve()
104+
});
101105

102106
return testInjector;
103107
}

test/platform-commands.ts

+1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ function createTestInjector() {
164164
testInjector.register("analyticsSettingsService", {
165165
getPlaygroundInfo: () => Promise.resolve(null)
166166
});
167+
testInjector.register("filesHashService", {});
167168

168169
return testInjector;
169170
}

test/platform-service.ts

+4
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ function createTestInjector() {
106106
})
107107
});
108108
testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub);
109+
testInjector.register("filesHashService", {
110+
generateHashes: () => Promise.resolve(),
111+
getChanges: () => Promise.resolve({test: "testHash"})
112+
});
109113

110114
return testInjector;
111115
}

test/project-changes-service.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ 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", {
34+
generateHashes: () => Promise.resolve({})
35+
});
36+
this.injector.register("logger", {
37+
warn: () => ({})
38+
});
3339

3440
const fs = this.injector.resolve<IFileSystem>("fs");
3541
fs.writeJson(path.join(this.projectDir, Constants.PACKAGE_JSON_FILE_NAME), {
@@ -127,7 +133,8 @@ describe("Project Changes Service Tests", () => {
127133
changesRequireBuildTime: new Date().toString(),
128134
iOSProvisioningProfileUUID: "provisioning_profile_test",
129135
projectFileHash: "",
130-
nativePlatformStatus: Constants.NativePlatformStatus.requiresPlatformAdd
136+
nativePlatformStatus: Constants.NativePlatformStatus.requiresPlatformAdd,
137+
appFilesHashes: {}
131138
};
132139
fs.writeJson(prepareInfoPath, expectedPrepareInfo);
133140

0 commit comments

Comments
 (0)