Skip to content

Commit 5bb46d1

Browse files
author
Dimitar Tachev
authored
Merge pull request #4312 from NativeScript/tachev/fix-debugger-detached-event
fix: emit the DEBUGGER_DETACHED event properly
2 parents a42ea42 + 6aed7a1 commit 5bb46d1

8 files changed

+128
-366
lines changed

lib/common/declarations.d.ts

-2
Original file line numberDiff line numberDiff line change
@@ -1137,8 +1137,6 @@ interface IDeviceLiveSyncServiceBase {
11371137
* Specifies some action that will be executed before every sync operation
11381138
*/
11391139
beforeLiveSyncAction?(deviceAppData: Mobile.IDeviceAppData): Promise<void>;
1140-
1141-
debugService?: any;
11421140
}
11431141

11441142
interface IDeviceLiveSyncService extends IDeviceLiveSyncServiceBase {
+2-287
Original file line numberDiff line numberDiff line change
@@ -1,289 +1,4 @@
1-
import syncBatchLib = require("./livesync/sync-batch");
2-
import * as shell from "shelljs";
3-
import * as path from "path";
4-
import * as temp from "temp";
5-
import * as minimatch from "minimatch";
6-
import * as constants from "../constants";
7-
import * as util from "util";
8-
9-
const gaze = require("gaze");
10-
11-
class LiveSyncServiceBase implements ILiveSyncServiceBase {
12-
private showFullLiveSyncInformation: boolean = false;
13-
private fileHashes: IDictionary<string>;
14-
15-
constructor(protected $devicesService: Mobile.IDevicesService,
16-
protected $mobileHelper: Mobile.IMobileHelper,
17-
protected $logger: ILogger,
18-
protected $options: IOptions,
19-
protected $deviceAppDataFactory: Mobile.IDeviceAppDataFactory,
20-
protected $fs: IFileSystem,
21-
protected $injector: IInjector,
22-
protected $hooksService: IHooksService,
23-
private $projectFilesManager: IProjectFilesManager,
24-
private $projectFilesProvider: IProjectFilesProvider,
25-
private $liveSyncProvider: ILiveSyncProvider,
26-
private $dispatcher: IFutureDispatcher,
27-
private $processService: IProcessService) {
28-
this.fileHashes = Object.create(null);
29-
}
30-
31-
public async sync(data: ILiveSyncData[], projectId: string, projectFilesConfig: IProjectFilesConfig, filePaths?: string[]): Promise<void> {
32-
await this.syncCore(data, filePaths);
33-
if (this.$options.watch) {
34-
await this.$hooksService.executeBeforeHooks('watch');
35-
this.partialSync(data, data[0].syncWorkingDirectory, projectId, projectFilesConfig);
36-
}
37-
}
38-
39-
private isFileExcluded(filePath: string, excludedPatterns: string[]): boolean {
40-
let isFileExcluded = false;
41-
_.each(excludedPatterns, pattern => {
42-
if (minimatch(filePath, pattern, { nocase: true })) {
43-
isFileExcluded = true;
44-
return false;
45-
}
46-
});
47-
48-
return isFileExcluded;
49-
}
50-
51-
private partialSync(data: ILiveSyncData[], syncWorkingDirectory: string, projectId: string, projectFilesConfig: IProjectFilesConfig): void {
52-
const that = this;
53-
this.showFullLiveSyncInformation = true;
54-
const gazeInstance = gaze(["**/*", "!node_modules/**/*", "!platforms/**/*"], { cwd: syncWorkingDirectory }, function (err: any, watcher: any) {
55-
this.on('all', (event: string, filePath: string) => {
56-
that.$logger.trace(`Received event ${event} for filePath: ${filePath}. Add it to queue.`);
57-
58-
that.$dispatcher.dispatch(async () => {
59-
try {
60-
if (filePath.indexOf(constants.APP_RESOURCES_FOLDER_NAME) !== -1) {
61-
that.$logger.warn(`Skipping livesync for changed file ${filePath}. This change requires a full build to update your application. `.yellow.bold);
62-
return;
63-
}
64-
65-
const fileHash = that.$fs.exists(filePath) && that.$fs.getFsStats(filePath).isFile() ? await that.$fs.getFileShasum(filePath) : "";
66-
if (fileHash === that.fileHashes[filePath]) {
67-
that.$logger.trace(`Skipping livesync for ${filePath} file with ${fileHash} hash.`);
68-
return;
69-
}
70-
71-
that.$logger.trace(`Adding ${filePath} file with ${fileHash} hash.`);
72-
that.fileHashes[filePath] = <string>fileHash;
73-
74-
for (const dataItem of data) {
75-
if (that.isFileExcluded(filePath, dataItem.excludedProjectDirsAndFiles)) {
76-
that.$logger.trace(`Skipping livesync for changed file ${filePath} as it is excluded in the patterns: ${dataItem.excludedProjectDirsAndFiles.join(", ")}`);
77-
continue;
78-
}
79-
const mappedFilePath = that.$projectFilesProvider.mapFilePath(filePath, dataItem.platform, projectId, projectFilesConfig);
80-
that.$logger.trace(`Syncing filePath ${filePath}, mappedFilePath is ${mappedFilePath}`);
81-
if (!mappedFilePath) {
82-
that.$logger.warn(`Unable to sync ${filePath}.`);
83-
continue;
84-
}
85-
86-
if (event === "added" || event === "changed" || event === "renamed") {
87-
that.batchSync(dataItem, mappedFilePath, projectId);
88-
} else if (event === "deleted") {
89-
that.fileHashes = <any>(_.omit(that.fileHashes, filePath));
90-
await that.syncRemovedFile(dataItem, mappedFilePath);
91-
}
92-
}
93-
} catch (err) {
94-
that.$logger.info(`Unable to sync file ${filePath}. Error is:${err.message}`.red.bold);
95-
that.$logger.info("Try saving it again or restart the livesync operation.");
96-
}
97-
});
98-
});
99-
});
100-
101-
this.$processService.attachToProcessExitSignals(this, () => gazeInstance.close());
102-
this.$dispatcher.run();
103-
}
104-
105-
private batch: IDictionary<ISyncBatch> = Object.create(null);
106-
private livesyncData: IDictionary<ILiveSyncData> = Object.create(null);
107-
108-
private batchSync(data: ILiveSyncData, filePath: string, projectId: string): void {
109-
const platformBatch: ISyncBatch = this.batch[data.platform];
110-
if (!platformBatch || !platformBatch.syncPending) {
111-
const done = () => {
112-
setTimeout(() => {
113-
this.$dispatcher.dispatch(async () => {
114-
try {
115-
for (const platformName in this.batch) {
116-
const batch = this.batch[platformName];
117-
const livesyncData = this.livesyncData[platformName];
118-
await batch.syncFiles(async (filesToSync: string[]) => {
119-
await this.$liveSyncProvider.preparePlatformForSync(platformName, projectId);
120-
await this.syncCore([livesyncData], filesToSync);
121-
});
122-
}
123-
} catch (err) {
124-
this.$logger.warn(`Unable to sync files. Error is:`, err.message);
125-
}
126-
});
127-
128-
}, syncBatchLib.SYNC_WAIT_THRESHOLD);
129-
};
130-
this.batch[data.platform] = this.$injector.resolve(syncBatchLib.SyncBatch, { done: done });
131-
this.livesyncData[data.platform] = data;
132-
}
133-
134-
this.batch[data.platform].addFile(filePath);
135-
}
136-
137-
private async syncRemovedFile(data: ILiveSyncData, filePath: string): Promise<void> {
138-
const filePathArray = [filePath],
139-
deviceFilesAction = this.getSyncRemovedFilesAction(data);
140-
141-
await this.syncCore([data], filePathArray, deviceFilesAction);
142-
}
143-
144-
public getSyncRemovedFilesAction(data: ILiveSyncData): (deviceAppData: Mobile.IDeviceAppData, device: Mobile.IDevice, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise<void> {
145-
return (deviceAppData: Mobile.IDeviceAppData, device: Mobile.IDevice, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => {
146-
const platformLiveSyncService = this.resolveDeviceLiveSyncService(data.platform, device);
147-
return platformLiveSyncService.removeFiles(deviceAppData.appIdentifier, localToDevicePaths);
148-
};
149-
}
150-
151-
public getSyncAction(data: ILiveSyncData, filesToSync: string[], deviceFilesAction: (deviceAppData: Mobile.IDeviceAppData, device: Mobile.IDevice, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise<void>, liveSyncOptions: ILiveSyncOptions): (device: Mobile.IDevice) => Promise<void> {
152-
const appIdentifier = data.appIdentifier;
153-
const platform = data.platform;
154-
const projectFilesPath = data.projectFilesPath;
155-
156-
let packageFilePath: string = null;
157-
158-
return async (device: Mobile.IDevice): Promise<void> => {
159-
let shouldRefreshApplication = true;
160-
const deviceAppData = this.$deviceAppDataFactory.create(appIdentifier, this.$mobileHelper.normalizePlatformName(platform), device, liveSyncOptions);
161-
if (await deviceAppData.isLiveSyncSupported()) {
162-
const platformLiveSyncService = this.resolveDeviceLiveSyncService(platform, device);
163-
164-
if (platformLiveSyncService.beforeLiveSyncAction) {
165-
await platformLiveSyncService.beforeLiveSyncAction(deviceAppData);
166-
}
167-
168-
// Not installed application
169-
await device.applicationManager.checkForApplicationUpdates();
170-
171-
let wasInstalled = true;
172-
if (! await device.applicationManager.isApplicationInstalled(appIdentifier) && !this.$options.companion) {
173-
this.$logger.warn(`The application with id "${appIdentifier}" is not installed on device with identifier ${device.deviceInfo.identifier}.`);
174-
if (!packageFilePath) {
175-
packageFilePath = await this.$liveSyncProvider.buildForDevice(device);
176-
}
177-
await device.applicationManager.installApplication(packageFilePath);
178-
179-
if (platformLiveSyncService.afterInstallApplicationAction) {
180-
const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, filesToSync, data.excludedProjectDirsAndFiles, liveSyncOptions);
181-
shouldRefreshApplication = await platformLiveSyncService.afterInstallApplicationAction(deviceAppData, localToDevicePaths);
182-
} else {
183-
shouldRefreshApplication = false;
184-
}
185-
186-
if (!shouldRefreshApplication) {
187-
await device.applicationManager.startApplication({ appId: appIdentifier, projectName: "" });
188-
}
189-
wasInstalled = false;
190-
}
191-
192-
// Restart application or reload page
193-
if (shouldRefreshApplication) {
194-
// Transfer or remove files on device
195-
const localToDevicePaths = await this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, filesToSync, data.excludedProjectDirsAndFiles, liveSyncOptions);
196-
if (deviceFilesAction) {
197-
await deviceFilesAction(deviceAppData, device, localToDevicePaths);
198-
} else {
199-
await this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, !filesToSync);
200-
}
201-
202-
this.$logger.info("Applying changes...");
203-
await platformLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, data.forceExecuteFullSync || !wasInstalled);
204-
this.$logger.info(`Successfully synced application ${data.appIdentifier} on device ${device.deviceInfo.identifier}.`);
205-
}
206-
} else {
207-
this.$logger.warn(`LiveSync is not supported for application: ${deviceAppData.appIdentifier} on device with identifier ${device.deviceInfo.identifier}.`);
208-
}
209-
};
210-
}
211-
212-
private async syncCore(data: ILiveSyncData[], filesToSync: string[], deviceFilesAction?: (deviceAppData: Mobile.IDeviceAppData, device: Mobile.IDevice, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => Promise<void>): Promise<void> {
213-
for (const dataItem of data) {
214-
const appIdentifier = dataItem.appIdentifier;
215-
const platform = dataItem.platform;
216-
const canExecute = await this.getCanExecuteAction(platform, appIdentifier, dataItem.canExecute);
217-
const action = this.getSyncAction(dataItem, filesToSync, deviceFilesAction, { isForCompanionApp: this.$options.companion, additionalConfigurations: dataItem.additionalConfigurations, configuration: dataItem.configuration, isForDeletedFiles: false });
218-
await this.$devicesService.execute(action, canExecute);
219-
}
220-
}
221-
222-
private async transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): Promise<void> {
223-
this.$logger.info("Transferring project files...");
224-
this.logFilesSyncInformation(localToDevicePaths, "Transferring %s on device %s.", this.$logger.trace, deviceAppData.device.deviceInfo.identifier);
225-
226-
const canTransferDirectory = isFullSync && (this.$devicesService.isAndroidDevice(deviceAppData.device) || this.$devicesService.isiOSSimulator(deviceAppData.device));
227-
if (canTransferDirectory) {
228-
const tempDir = temp.mkdirSync("tempDir");
229-
_.each(localToDevicePaths, localToDevicePath => {
230-
const fileDirname = path.join(tempDir, path.dirname(localToDevicePath.getRelativeToProjectBasePath()));
231-
shell.mkdir("-p", fileDirname);
232-
if (!this.$fs.getFsStats(localToDevicePath.getLocalPath()).isDirectory()) {
233-
shell.cp("-f", localToDevicePath.getLocalPath(), path.join(fileDirname, path.basename(localToDevicePath.getDevicePath())));
234-
}
235-
});
236-
await deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, tempDir);
237-
} else {
238-
await this.$liveSyncProvider.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, isFullSync);
239-
}
240-
241-
this.logFilesSyncInformation(localToDevicePaths, "Successfully transferred %s on device %s.", this.$logger.info, deviceAppData.device.deviceInfo.identifier);
242-
}
243-
244-
private logFilesSyncInformation(localToDevicePaths: Mobile.ILocalToDevicePathData[], message: string, action: Function, deviceIdentifier: string): void {
245-
if (this.showFullLiveSyncInformation) {
246-
_.each(localToDevicePaths, (file: Mobile.ILocalToDevicePathData) => {
247-
action.call(this.$logger, util.format(message, path.basename(file.getLocalPath()).yellow), deviceIdentifier);
248-
});
249-
} else {
250-
action.call(this.$logger, util.format(message, "all files", deviceIdentifier));
251-
}
252-
}
253-
254-
private resolveDeviceLiveSyncService(platform: string, device: Mobile.IDevice): IDeviceLiveSyncService {
255-
return this.$injector.resolve(this.$liveSyncProvider.deviceSpecificLiveSyncServices[platform.toLowerCase()], { _device: device });
256-
}
257-
258-
public async getCanExecuteAction(platform: string, appIdentifier: string, canExecute: (dev: Mobile.IDevice) => boolean): Promise<(dev: Mobile.IDevice) => boolean> {
259-
canExecute = canExecute || ((dev: Mobile.IDevice) => dev.deviceInfo.platform.toLowerCase() === platform.toLowerCase());
260-
let finalCanExecute = canExecute;
261-
if (this.$options.device) {
262-
return (device: Mobile.IDevice): boolean => canExecute(device) && device.deviceInfo.identifier === this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier;
263-
}
264-
265-
if (this.$mobileHelper.isiOSPlatform(platform)) {
266-
if (this.$options.emulator) {
267-
finalCanExecute = (device: Mobile.IDevice): boolean => canExecute(device) && this.$devicesService.isiOSSimulator(device);
268-
} else {
269-
const devices = this.$devicesService.getDevicesForPlatform(platform);
270-
const simulator = _.find(devices, d => this.$devicesService.isiOSSimulator(d));
271-
if (simulator) {
272-
const iOSDevices = _.filter(devices, d => d.deviceInfo.identifier !== simulator.deviceInfo.identifier);
273-
if (iOSDevices && iOSDevices.length) {
274-
const isApplicationInstalledOnSimulator = await simulator.applicationManager.isApplicationInstalled(appIdentifier);
275-
const isInstalledPromises = await Promise.all(iOSDevices.map(device => device.applicationManager.isApplicationInstalled(appIdentifier)));
276-
const isApplicationInstalledOnAllDevices = _.intersection.apply(null, isInstalledPromises);
277-
// In case the application is not installed on both device and simulator, syncs only on device.
278-
if (!isApplicationInstalledOnSimulator && !isApplicationInstalledOnAllDevices) {
279-
finalCanExecute = (device: Mobile.IDevice): boolean => canExecute(device) && this.$devicesService.isiOSDevice(device);
280-
}
281-
}
282-
}
283-
}
284-
}
285-
286-
return finalCanExecute;
287-
}
1+
class LiveSyncServiceBase {
2+
// TODO: delete the file when cleaning all AppBuild resources
2883
}
2894
$injector.register('liveSyncServiceBase', LiveSyncServiceBase);

lib/definitions/livesync.d.ts

+20-15
Original file line numberDiff line numberDiff line change
@@ -396,28 +396,33 @@ interface ITransferFilesOptions {
396396
interface IPlatformLiveSyncService {
397397
fullSync(syncInfo: IFullSyncInfo): Promise<ILiveSyncResultInfo>;
398398
liveSyncWatchAction(device: Mobile.IDevice, liveSyncInfo: ILiveSyncWatchInfo): Promise<ILiveSyncResultInfo>;
399-
refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo>;
399+
tryRefreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<boolean>;
400+
restartApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<void>;
401+
shouldRestart(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo): Promise<boolean>;
400402
getDeviceLiveSyncService(device: Mobile.IDevice, projectData: IProjectData): INativeScriptDeviceLiveSyncService;
401403
}
402-
403-
interface IHasDidRestart {
404-
didRestart: boolean;
405-
}
406-
407-
interface IRefreshApplicationInfo extends IHasDidRestart {
404+
interface IRefreshApplicationInfo {
405+
didRefresh: boolean;
408406
}
409407

410408
interface INativeScriptDeviceLiveSyncService extends IDeviceLiveSyncServiceBase {
411409
/**
412-
* Refreshes the application's content on a device
413-
* @param {Mobile.IDeviceAppData} deviceAppData Information about the application and the device.
414-
* @param {Mobile.ILocalToDevicePathData[]} localToDevicePaths Object containing a mapping of file paths from the system to the device.
415-
* @param {boolean} forceExecuteFullSync If this is passed a full LiveSync is performed instead of an incremental one.
416-
* @param {IProjectData} projectData Project data.
417-
* @return {Promise<void>}
410+
* Tries to refresh the application's content on the device
411+
*/
412+
tryRefreshApplication(projectData: IProjectData,
413+
liveSyncInfo: ILiveSyncResultInfo): Promise<boolean>;
414+
415+
/**
416+
* Restarts the specified application
417+
*/
418+
restartApplication(projectData: IProjectData,
419+
liveSyncInfo: ILiveSyncResultInfo): Promise<void>;
420+
421+
/**
422+
* Returns if the application have to be restarted in order to apply the specified livesync changes
418423
*/
419-
refreshApplication(projectData: IProjectData,
420-
liveSyncInfo: ILiveSyncResultInfo): Promise<IRefreshApplicationInfo>;
424+
shouldRestart(projectData: IProjectData,
425+
liveSyncInfo: ILiveSyncResultInfo): Promise<boolean>;
421426

422427
/**
423428
* Removes specified files from a connected device

0 commit comments

Comments
 (0)