From 93dda69990bf6c5535eabefcfb5755948f1fda9e Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 8 Feb 2017 00:58:42 +0200 Subject: [PATCH 1/2] Fix livesync when directories are modified In case you try adding/removing directories in your `app` dir, `chokidar` (the file system watcher that we are using now instead of `gaze`) raises `addDir`/`unlinkDir` events. However we do not have handles for these events, so we do not execute anything. After that, when we add a file in the newly created dir, `chokidar` sends `add` event, we handle it and try to process the file. This works fine for iOS and Android devices, but it does not work at all for iOS Simulator, as we have not transferred the new directory to `platforms` dir. Add required handles for `addDir` and `unlinkDir` methods. Also currently there's a problem when already existing directory is renamed. In this case its modified time (`mtime`) is not changed, so the projectChangesService disregards the change and doesn't transfer it to `platforms` dir. After that, in case you modify any file inside the renamed dir, you'll see ENOENT error. In order to fix this, check the time of last status change (`ctime`) of the directory/file. --- lib/services/livesync/livesync-service.ts | 1 + lib/services/livesync/platform-livesync-service.ts | 5 +++-- lib/services/platform-service.ts | 3 +++ lib/services/project-changes-service.ts | 9 +++++++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 1651c35afb..dc14e6a040 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -138,6 +138,7 @@ class LiveSyncService implements ILiveSyncService { fiberBootstrap.run(() => { that.$dispatcher.dispatch(() => (() => { try { + that.$logger.trace(`Event '${event}' triggered for path: '${filePath}'`); filePath = path.join(syncWorkingDirectory, filePath); for (let i = 0; i < onChangedActions.length; i++) { onChangedActions[i](event, filePath, that.$dispatcher); diff --git a/lib/services/livesync/platform-livesync-service.ts b/lib/services/livesync/platform-livesync-service.ts index 012b5f7ca0..acee0c3c65 100644 --- a/lib/services/livesync/platform-livesync-service.ts +++ b/lib/services/livesync/platform-livesync-service.ts @@ -65,9 +65,9 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe return; } - if (event === "add" || event === "change") { + if (event === "add" || event === "addDir"|| event === "change") { this.batchSync(filePath, dispatcher, afterFileSyncAction); - } else if (event === "unlink") { + } else if (event === "unlink" || event === "unlinkDir") { this.syncRemovedFile(filePath, afterFileSyncAction).wait(); } } @@ -134,6 +134,7 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe for (let platform in this.batch) { let batch = this.batch[platform]; batch.syncFiles(((filesToSync:string[]) => { + console.log("syncing ", filesToSync); this.$platformService.preparePlatform(this.liveSyncData.platform).wait(); let canExecute = this.getCanExecuteAction(this.liveSyncData.platform, this.liveSyncData.appIdentifier); let deviceFileAction = (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => this.transferFiles(deviceAppData, localToDevicePaths, this.liveSyncData.projectFilesPath, !filePath); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index b7e7073f02..bceec10857 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -220,6 +220,9 @@ export class PlatformService implements IPlatformService { this.ensurePlatformInstalled(platform).wait(); let changesInfo = this.$projectChangesService.checkForChanges(platform); + + this.$logger.trace("Changes info in prepare platform:", changesInfo); + if (changesInfo.hasChanges) { // android build artifacts need to be cleaned up when switching from release to debug builds if (platform.toLowerCase() === "android") { diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index fdc4120102..7a7c3afe13 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -176,12 +176,16 @@ export class ProjectChangesService implements IProjectChangesService { if (filePath === skipDir) { continue; } + let fileStats = this.$fs.getFsStats(filePath); - let changed = fileStats.mtime.getTime() > this._outputProjectMtime; + + let changed = fileStats.mtime.getTime() > this._outputProjectMtime || fileStats.ctime.getTime() > this._outputProjectMtime + if (!changed) { let lFileStats = this.$fs.getLsStats(filePath); - changed = lFileStats.mtime.getTime() > this._outputProjectMtime; + changed = lFileStats.mtime.getTime() > this._outputProjectMtime || lFileStats.ctime.getTime() > this._outputProjectMtime; } + if (changed) { if (processFunc) { this._newFiles ++; @@ -193,6 +197,7 @@ export class ProjectChangesService implements IProjectChangesService { return true; } } + if (fileStats.isDirectory()) { if (this.containsNewerFiles(filePath, skipDir, processFunc)) { return true; From 14e1a7faf788bfe807811dfe59cfb5c213d82041 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 8 Feb 2017 13:32:16 +0200 Subject: [PATCH 2/2] LiveSync only existing files during `--watch` During `--watch`, when a change is detected, the project is prepared and after that we are trying to move the prepared file (from `platforms//...` dir) to the device. However some plugins modify the content of the `platforms` directory on afterPrepare. For example `nativescript-dev-sass` allows you to use `.scss` files, on `beforePrepare` they are "transpiled" to `.css` files. After that, on `afterPrepare` the plugin itself removes the file from `platforms//...` dir. CLI detects the change in `.scss` file, prepares the project (which moves the `.scss` file to `platforms` dir) and after that tries to move it from `platforms` to the device. During the last step, the `.scss` file is already removed from `platforms` directory, so our code fails. Meanwhile, the `beforePrepare` hook of the plugin has created/modified the `.css` file inside `app` dir. This will trigger new iteration, where the file will be sent to device. In order to fix the error when the `.scss` file is modified, we'll execute livesync only if the modified files exist in `platforms` dir. --- .../livesync/platform-livesync-service.ts | 37 +++++++++++++------ lib/services/project-changes-service.ts | 2 +- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/services/livesync/platform-livesync-service.ts b/lib/services/livesync/platform-livesync-service.ts index acee0c3c65..4eddb60789 100644 --- a/lib/services/livesync/platform-livesync-service.ts +++ b/lib/services/livesync/platform-livesync-service.ts @@ -47,8 +47,8 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe } if (postAction) { - this.finishLivesync(deviceAppData).wait(); - return postAction(deviceAppData).wait(); + this.finishLivesync(deviceAppData).wait(); + return postAction(deviceAppData).wait(); } this.refreshApplication(deviceAppData, localToDevicePaths, true).wait(); @@ -65,7 +65,7 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe return; } - if (event === "add" || event === "addDir"|| event === "change") { + if (event === "add" || event === "addDir" || event === "change") { this.batchSync(filePath, dispatcher, afterFileSyncAction); } else if (event === "unlink" || event === "unlinkDir") { this.syncRemovedFile(filePath, afterFileSyncAction).wait(); @@ -133,8 +133,7 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe try { for (let platform in this.batch) { let batch = this.batch[platform]; - batch.syncFiles(((filesToSync:string[]) => { - console.log("syncing ", filesToSync); + batch.syncFiles(((filesToSync: string[]) => { this.$platformService.preparePlatform(this.liveSyncData.platform).wait(); let canExecute = this.getCanExecuteAction(this.liveSyncData.platform, this.liveSyncData.appIdentifier); let deviceFileAction = (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => this.transferFiles(deviceAppData, localToDevicePaths, this.liveSyncData.projectFilesPath, !filePath); @@ -143,7 +142,7 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe }).future()).wait(); } } catch (err) { - this.$logger.warn(`Unable to sync files. Error is:`, err.message); + this.$logger.warn(`Unable to sync files. Error is:`, err.message); } }).future()()); }).future()(); @@ -174,8 +173,8 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe afterFileSyncAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => IFuture): (device: Mobile.IDevice) => IFuture { let action = (device: Mobile.IDevice): IFuture => { return (() => { - let deviceAppData:Mobile.IDeviceAppData = null; - let localToDevicePaths:Mobile.ILocalToDevicePathData[] = null; + let deviceAppData: Mobile.IDeviceAppData = null; + let localToDevicePaths: Mobile.ILocalToDevicePathData[] = null; let isFullSync = false; if (this.$options.clean || this.$projectChangesService.currentChanges.changesRequireBuild) { let buildConfig: IBuildConfig = { buildForDevice: !device.isEmulator }; @@ -188,13 +187,29 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe isFullSync = true; } else { deviceAppData = this.$deviceAppDataFactory.create(this.liveSyncData.appIdentifier, this.$mobileHelper.normalizePlatformName(this.liveSyncData.platform), device); - let mappedFiles = filesToSync.map((file: string) => this.$projectFilesProvider.mapFilePath(file, device.deviceInfo.platform)); - localToDevicePaths = this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, this.liveSyncData.projectFilesPath, mappedFiles, this.liveSyncData.excludedProjectDirsAndFiles); + + const mappedFiles = filesToSync.map((file: string) => this.$projectFilesProvider.mapFilePath(file, device.deviceInfo.platform)); + + // Some plugins modify platforms dir on afterPrepare (check nativescript-dev-sass) - we want to sync only existing file. + const existingFiles = mappedFiles.filter(m => this.$fs.exists(m)); + + this.$logger.trace("Will execute livesync for files: ", existingFiles); + + const skippedFiles = _.difference(mappedFiles, existingFiles); + + if (skippedFiles.length) { + this.$logger.trace("The following files will not be synced as they do not exist:", skippedFiles); + } + + localToDevicePaths = this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, this.liveSyncData.projectFilesPath, existingFiles, this.liveSyncData.excludedProjectDirsAndFiles); + fileSyncAction(deviceAppData, localToDevicePaths).wait(); } + if (!afterFileSyncAction) { this.refreshApplication(deviceAppData, localToDevicePaths, isFullSync).wait(); } + device.fileSystem.putFile(this.$projectChangesService.getPrepareInfoFilePath(device.deviceInfo.platform), this.getLiveSyncInfoFilePath(deviceAppData), this.liveSyncData.appIdentifier).wait(); this.finishLivesync(deviceAppData).wait(); if (afterFileSyncAction) { @@ -214,7 +229,7 @@ export abstract class PlatformLiveSyncServiceBase implements IPlatformLiveSyncSe let remoteLivesyncInfo: IPrepareInfo = JSON.parse(fileText); let localPrepareInfo = this.$projectChangesService.getPrepareInfo(platform); return remoteLivesyncInfo.time !== localPrepareInfo.time; - } catch(e) { + } catch (e) { return true; } } diff --git a/lib/services/project-changes-service.ts b/lib/services/project-changes-service.ts index 7a7c3afe13..5f3ecba9db 100644 --- a/lib/services/project-changes-service.ts +++ b/lib/services/project-changes-service.ts @@ -179,7 +179,7 @@ export class ProjectChangesService implements IProjectChangesService { let fileStats = this.$fs.getFsStats(filePath); - let changed = fileStats.mtime.getTime() > this._outputProjectMtime || fileStats.ctime.getTime() > this._outputProjectMtime + let changed = fileStats.mtime.getTime() > this._outputProjectMtime || fileStats.ctime.getTime() > this._outputProjectMtime; if (!changed) { let lFileStats = this.$fs.getLsStats(filePath);