Skip to content

Commit faaf337

Browse files
committed
refactor: refactor refreshApplication service
1 parent eb52dd9 commit faaf337

File tree

5 files changed

+282
-107
lines changed

5 files changed

+282
-107
lines changed

lib/bootstrap.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ $injector.require("platformCommandsService", "./services/platform/platform-comma
4242
$injector.require("platformWatcherService", "./services/platform/platform-watcher-service");
4343

4444
$injector.require("deviceInstallationService", "./services/device/device-installation-service");
45-
$injector.require("deviceRestartApplicationService", "./services/device/device-restart-application-service");
45+
$injector.require("deviceRefreshApplicationService", "./services/device/device-refresh-application-service");
4646

4747
$injector.require("workflowDataService", "./services/workflow/workflow-data-service");
4848
$injector.require("bundleWorkflowService", "./services/bundle-workflow-service");

lib/services/bundle-workflow-service.ts

Lines changed: 94 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
1-
import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME } from "../constants";
1+
import { INITIAL_SYNC_EVENT_NAME, FILES_CHANGE_EVENT_NAME, LiveSyncEvents } from "../constants";
22
import { WorkflowDataService } from "./workflow/workflow-data-service";
33
import { AddPlatformService } from "./platform/add-platform-service";
44
import { BuildPlatformService } from "./platform/build-platform-service";
55
import { PreparePlatformService } from "./platform/prepare-platform-service";
6+
import { EventEmitter } from "events";
7+
import { DeviceRefreshApplicationService } from "./device/device-refresh-application-service";
68

79
const deviceDescriptorPrimaryKey = "identifier";
810

9-
export class BundleWorkflowService implements IBundleWorkflowService {
10-
private liveSyncProcessesInfo: IDictionary<any> = {};
11+
export class BundleWorkflowService extends EventEmitter implements IBundleWorkflowService {
12+
private liveSyncProcessesInfo: IDictionary<ILiveSyncProcessInfo> = {};
1113

1214
constructor(
15+
private $addPlatformService: AddPlatformService,
16+
private $buildPlatformService: BuildPlatformService,
1317
private $deviceInstallationService: IDeviceInstallationService,
14-
private $deviceRestartApplicationService: IDeviceRestartApplicationService,
18+
private $deviceRefreshApplicationService: DeviceRefreshApplicationService,
1519
private $devicesService: Mobile.IDevicesService,
1620
private $errors: IErrors,
21+
private $hooksService: IHooksService,
1722
private $injector: IInjector,
18-
private $mobileHelper: Mobile.IMobileHelper,
1923
private $logger: ILogger,
20-
private $preparePlatformService: PreparePlatformService,
21-
private $addPlatformService: AddPlatformService,
22-
private $buildPlatformService: BuildPlatformService,
24+
private $mobileHelper: Mobile.IMobileHelper,
2325
private $platformWatcherService: IPlatformWatcherService,
2426
private $pluginsService: IPluginsService,
27+
private $preparePlatformService: PreparePlatformService,
2528
private $projectDataService: IProjectDataService,
2629
private $workflowDataService: WorkflowDataService
27-
) { }
30+
) { super(); }
2831

2932
public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise<void> {
3033
const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options);
@@ -53,11 +56,6 @@ export class BundleWorkflowService implements IBundleWorkflowService {
5356
const { nativePlatformData, projectData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectDir, liveSyncInfo);
5457
await this.$buildPlatformService.buildPlatformIfNeeded(nativePlatformData, projectData, buildPlatformData);
5558
await this.$deviceInstallationService.installOnDeviceIfNeeded(device, nativePlatformData, projectData, buildPlatformData);
56-
await device.applicationManager.startApplication({
57-
appId: projectData.projectIdentifiers[device.deviceInfo.platform.toLowerCase()],
58-
projectName: projectData.projectName
59-
});
60-
this.$logger.out(`Successfully started on device with identifier '${device.deviceInfo.identifier}'.`);
6159
};
6260

6361
await this.$devicesService.execute(executeAction, (device: Mobile.IDevice) => true);
@@ -108,17 +106,89 @@ export class BundleWorkflowService implements IBundleWorkflowService {
108106
private async syncInitialDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise<void> {
109107
const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo);
110108

111-
const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData);
112-
const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath);
109+
try {
110+
const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData);
111+
const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath);
113112

114-
await this.$deviceInstallationService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath);
113+
await this.$deviceInstallationService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath);
115114

116-
// TODO: Consider to improve this
117-
const platformLiveSyncService = this.getLiveSyncService(platformData.platformNameLowerCase);
118-
const { force, useHotModuleReload, skipWatcher } = liveSyncInfo;
119-
const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor });
115+
// TODO: Consider to improve this
116+
const platformLiveSyncService = this.getLiveSyncService(platformData.platformNameLowerCase);
117+
const { force, useHotModuleReload, skipWatcher } = liveSyncInfo;
118+
const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor });
120119

121-
await this.$deviceRestartApplicationService.restartOnDevice(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService);
120+
await this.$deviceRefreshApplicationService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this);
121+
} catch (err) {
122+
this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`);
123+
124+
this.emitLivesyncEvent(LiveSyncEvents.liveSyncError, {
125+
error: err,
126+
deviceIdentifier: device.deviceInfo.identifier,
127+
projectDir: projectData.projectDir,
128+
applicationIdentifier: projectData.projectIdentifiers[platformData.platformNameLowerCase]
129+
});
130+
131+
await this.stopLiveSync(projectData.projectDir, [device.deviceInfo.identifier], { shouldAwaitAllActions: false });
132+
}
133+
}
134+
135+
public async stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise<void> {
136+
const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectDir];
137+
if (liveSyncProcessInfo && !liveSyncProcessInfo.isStopped) {
138+
// In case we are coming from error during livesync, the current action is the one that erred (but we are still executing it),
139+
// so we cannot await it as this will cause infinite loop.
140+
const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions;
141+
142+
const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier);
143+
144+
const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier))
145+
.map(descriptor => descriptor.identifier);
146+
147+
// In case deviceIdentifiers are not passed, we should stop the whole LiveSync.
148+
if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) {
149+
if (liveSyncProcessInfo.timer) {
150+
clearTimeout(liveSyncProcessInfo.timer);
151+
}
152+
153+
if (liveSyncProcessInfo.watcherInfo && liveSyncProcessInfo.watcherInfo.watcher) {
154+
liveSyncProcessInfo.watcherInfo.watcher.close();
155+
}
156+
157+
liveSyncProcessInfo.watcherInfo = null;
158+
liveSyncProcessInfo.isStopped = true;
159+
160+
if (liveSyncProcessInfo.actionsChain && shouldAwaitPendingOperation) {
161+
await liveSyncProcessInfo.actionsChain;
162+
}
163+
164+
liveSyncProcessInfo.deviceDescriptors = [];
165+
166+
if (liveSyncProcessInfo.syncToPreviewApp) {
167+
// await this.$previewAppLiveSyncService.stopLiveSync();
168+
// this.$previewAppLiveSyncService.removeAllListeners();
169+
}
170+
171+
// Kill typescript watcher
172+
const projectData = this.$projectDataService.getProjectData(projectDir);
173+
await this.$hooksService.executeAfterHooks('watch', {
174+
hookArgs: {
175+
projectData
176+
}
177+
});
178+
} else if (liveSyncProcessInfo.currentSyncAction && shouldAwaitPendingOperation) {
179+
await liveSyncProcessInfo.currentSyncAction;
180+
}
181+
182+
// Emit LiveSync stopped when we've really stopped.
183+
_.each(removedDeviceIdentifiers, deviceIdentifier => {
184+
this.emitLivesyncEvent(LiveSyncEvents.liveSyncStopped, { projectDir, deviceIdentifier });
185+
});
186+
}
187+
}
188+
189+
public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean {
190+
this.$logger.trace(`Will emit event ${event} with data`, livesyncData);
191+
return this.emit(event, livesyncData);
122192
}
123193

124194
private async syncChangedDataOnDevice(device: Mobile.IDevice, deviceDescriptor: ILiveSyncDeviceInfo, data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo): Promise<void> {
@@ -142,7 +212,8 @@ export class BundleWorkflowService implements IBundleWorkflowService {
142212
force: liveSyncInfo.force,
143213
connectTimeout: 1000
144214
});
145-
await this.$deviceRestartApplicationService.restartOnDevice(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService);
215+
216+
await this.$deviceRefreshApplicationService.refreshApplication(deviceDescriptor, projectData, liveSyncResultInfo, platformLiveSyncService, this);
146217
}
147218

148219
public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] {
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { performanceLog } from "../../common/decorators";
2+
import { EventEmitter } from "events";
3+
import { DEBUGGER_DETACHED_EVENT_NAME, USER_INTERACTION_NEEDED_EVENT_NAME, LiveSyncEvents, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants";
4+
import { EOL } from "os";
5+
6+
export class DeviceRefreshApplicationService {
7+
8+
constructor(
9+
// private $buildArtefactsService: IBuildArtefactsService,
10+
private $debugDataService: IDebugDataService,
11+
private $debugService: IDebugService,
12+
private $devicesService: Mobile.IDevicesService,
13+
private $errors: IErrors,
14+
private $logger: ILogger,
15+
// private $platformsData: IPlatformsData,
16+
private $projectDataService: IProjectDataService
17+
) { }
18+
19+
@performanceLog()
20+
public async refreshApplication(deviceDescriptor: ILiveSyncDeviceInfo, projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter): Promise<IRestartApplicationInfo | IDebugInformation> {
21+
return liveSyncResultInfo && deviceDescriptor.debugggingEnabled ?
22+
this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter) :
23+
this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter);
24+
}
25+
26+
@performanceLog()
27+
public async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter): Promise<IDebugInformation> {
28+
const { debugOptions } = deviceDescriptor;
29+
if (debugOptions.debugBrk) {
30+
liveSyncResultInfo.waitForDebugger = true;
31+
}
32+
33+
const refreshInfo = await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor, platformLiveSyncService, eventEmitter, { shouldSkipEmitLiveSyncNotification: true, shouldCheckDeveloperDiscImage: true });
34+
35+
// we do not stop the application when debugBrk is false, so we need to attach, instead of launch
36+
// if we try to send the launch request, the debugger port will not be printed and the command will timeout
37+
debugOptions.start = !debugOptions.debugBrk;
38+
39+
debugOptions.forceDebuggerAttachedEvent = refreshInfo.didRestart;
40+
const deviceOption = {
41+
deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
42+
debugOptions: debugOptions,
43+
};
44+
45+
return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, deviceDescriptor, { projectDir: projectData.projectDir });
46+
}
47+
48+
public async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo, platformLiveSyncService: IPlatformLiveSyncService, eventEmitter: EventEmitter, settings?: IRefreshApplicationSettings): Promise<IRestartApplicationInfo> {
49+
const result = { didRestart: false };
50+
const platform = liveSyncResultInfo.deviceAppData.platform;
51+
const applicationIdentifier = projectData.projectIdentifiers[platform.toLowerCase()];
52+
try {
53+
let shouldRestart = await platformLiveSyncService.shouldRestart(projectData, liveSyncResultInfo);
54+
if (!shouldRestart) {
55+
shouldRestart = !await platformLiveSyncService.tryRefreshApplication(projectData, liveSyncResultInfo);
56+
}
57+
58+
if (shouldRestart) {
59+
const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier;
60+
eventEmitter.emit(DEBUGGER_DETACHED_EVENT_NAME, { deviceIdentifier });
61+
await platformLiveSyncService.restartApplication(projectData, liveSyncResultInfo);
62+
result.didRestart = true;
63+
}
64+
} catch (err) {
65+
this.$logger.info(`Error while trying to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`);
66+
const msg = `Unable to start application ${applicationIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`;
67+
this.$logger.warn(msg);
68+
if (!settings || !settings.shouldSkipEmitLiveSyncNotification) {
69+
eventEmitter.emit(LiveSyncEvents.liveSyncNotification, {
70+
projectDir: projectData.projectDir,
71+
applicationIdentifier,
72+
deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
73+
notification: msg
74+
});
75+
}
76+
77+
if (settings && settings.shouldCheckDeveloperDiscImage) {
78+
this.handleDeveloperDiskImageError(err, liveSyncResultInfo, projectData, deviceDescriptor, eventEmitter);
79+
}
80+
}
81+
82+
eventEmitter.emit(LiveSyncEvents.liveSyncExecuted, {
83+
projectDir: projectData.projectDir,
84+
applicationIdentifier,
85+
syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()),
86+
deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier,
87+
isFullSync: liveSyncResultInfo.isFullSync
88+
});
89+
90+
return result;
91+
}
92+
93+
// TODO: This should be into separate class
94+
public async attachDebugger(settings: IAttachDebuggerOptions): Promise<IDebugInformation> {
95+
// Default values
96+
if (settings.debugOptions) {
97+
settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome;
98+
settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start;
99+
} else {
100+
settings.debugOptions = {
101+
chrome: true,
102+
start: true
103+
};
104+
}
105+
106+
const projectData = this.$projectDataService.getProjectData(settings.projectDir);
107+
const debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier });
108+
// const platformData = this.$platformsData.getPlatformData(settings.platform, projectData);
109+
110+
// Of the properties below only `buildForDevice` and `release` are currently used.
111+
// Leaving the others with placeholder values so that they may not be forgotten in future implementations.
112+
// debugData.pathToAppPackage = this.$buildArtefactsService.getLastBuiltPackagePath(platformData, buildConfig, settings.outputPath);
113+
const debugInfo = await this.$debugService.debug(debugData, settings.debugOptions);
114+
const result = this.printDebugInformation(debugInfo, null, settings.debugOptions.forceDebuggerAttachedEvent);
115+
return result;
116+
}
117+
118+
public printDebugInformation(debugInformation: IDebugInformation, eventEmitter: EventEmitter, fireDebuggerAttachedEvent: boolean = true): IDebugInformation {
119+
if (!!debugInformation.url) {
120+
if (fireDebuggerAttachedEvent) {
121+
eventEmitter.emit(DEBUGGER_ATTACHED_EVENT_NAME, debugInformation);
122+
}
123+
124+
this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${debugInformation.url}${EOL}`.cyan);
125+
}
126+
127+
return debugInformation;
128+
}
129+
130+
@performanceLog()
131+
private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, deviceDescriptor: ILiveSyncDeviceInfo, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise<IDebugInformation> {
132+
if (!deviceDescriptor) {
133+
this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`);
134+
}
135+
136+
deviceDescriptor.debugggingEnabled = true;
137+
deviceDescriptor.debugOptions = deviceOption.debugOptions;
138+
const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier);
139+
const attachDebuggerOptions: IAttachDebuggerOptions = {
140+
deviceIdentifier: deviceOption.deviceIdentifier,
141+
isEmulator: currentDeviceInstance.isEmulator,
142+
outputPath: deviceDescriptor.outputPath,
143+
platform: currentDeviceInstance.deviceInfo.platform,
144+
projectDir: debuggingAdditionalOptions.projectDir,
145+
debugOptions: deviceOption.debugOptions
146+
};
147+
148+
let debugInformation: IDebugInformation;
149+
try {
150+
debugInformation = await this.attachDebugger(attachDebuggerOptions);
151+
} catch (err) {
152+
this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err);
153+
attachDebuggerOptions.debugOptions.start = false;
154+
try {
155+
debugInformation = await this.attachDebugger(attachDebuggerOptions);
156+
} catch (innerErr) {
157+
this.$logger.trace("Couldn't attach debugger with modified options.", innerErr);
158+
throw err;
159+
}
160+
}
161+
162+
return debugInformation;
163+
}
164+
165+
private handleDeveloperDiskImageError(err: any, liveSyncResultInfo: ILiveSyncResultInfo, projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, eventEmitter: EventEmitter) {
166+
if ((err.message || err) === "Could not find developer disk image") {
167+
const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier;
168+
const attachDebuggerOptions: IAttachDebuggerOptions = {
169+
platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform,
170+
isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator,
171+
projectDir: projectData.projectDir,
172+
deviceIdentifier,
173+
debugOptions: deviceDescriptor.debugOptions,
174+
outputPath: deviceDescriptor.outputPath
175+
};
176+
eventEmitter.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions);
177+
}
178+
}
179+
}
180+
$injector.register("deviceRefreshApplicationService", DeviceRefreshApplicationService);

0 commit comments

Comments
 (0)