Skip to content

Commit c263ab3

Browse files
committed
Split livesync-service-base so that we could have different implementations for AppBuilder and NativeScript
1 parent 9d95734 commit c263ab3

File tree

6 files changed

+330
-21
lines changed

6 files changed

+330
-21
lines changed

lib/bootstrap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ $injector.require("androidProjectService", "./services/android-project-service")
1212
$injector.require("iOSProjectService", "./services/ios-project-service");
1313

1414
$injector.require("cocoapodsService", "./services/cocoapods-service");
15+
$injector.require("liveSyncServiceBase", "./services/livesync-service-base");
1516

1617
$injector.require("projectTemplatesService", "./services/project-templates-service");
1718
$injector.require("projectNameService", "./services/project-name-service");

lib/services/livesync/android-livesync-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import {DeviceAndroidDebugBridge} from "../../common/mobile/android/device-andro
22
import {AndroidDeviceHashService} from "../../common/mobile/android/android-device-hash-service";
33
import Future = require("fibers/future");
44
import * as helpers from "../../common/helpers";
5-
import liveSyncServiceBaseLib = require("./livesync-service-base");
5+
import liveSyncServiceBaseLib = require("./platform-livesync-service-base");
66
import * as path from "path";
77
import * as net from "net";
88

9-
class AndroidLiveSyncService extends liveSyncServiceBaseLib.LiveSyncServiceBase<Mobile.IAndroidDevice> implements IPlatformLiveSyncService {
9+
class AndroidLiveSyncService extends liveSyncServiceBaseLib.PlatformLiveSyncServiceBase<Mobile.IAndroidDevice> implements IPlatformLiveSyncService {
1010
private static BACKEND_PORT = 18182;
1111

1212
constructor(_device: Mobile.IDevice,

lib/services/livesync/ios-livesync-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import liveSyncServiceBaseLib = require("./livesync-service-base");
1+
import liveSyncServiceBaseLib = require("./platform-livesync-service-base");
22
import * as helpers from "../../common/helpers";
33
import * as net from "net";
44

55
let currentPageReloadId = 0;
66

7-
class IOSLiveSyncService extends liveSyncServiceBaseLib.LiveSyncServiceBase<Mobile.IiOSDevice> implements IPlatformLiveSyncService {
7+
class IOSLiveSyncService extends liveSyncServiceBaseLib.PlatformLiveSyncServiceBase<Mobile.IiOSDevice> implements IPlatformLiveSyncService {
88
private static BACKEND_PORT = 18181;
99
private socket: net.Socket;
1010

Lines changed: 298 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,308 @@
1-
export abstract class LiveSyncServiceBase<T extends Mobile.IDevice> {
2-
protected get device(): T {
3-
return <T>(this._device);
1+
import * as fiberBootstrap from "../../common/fiber-bootstrap";
2+
import syncBatchLib = require("../../common/services/livesync/sync-batch");
3+
import * as shell from "shelljs";
4+
import * as path from "path";
5+
import * as temp from "temp";
6+
import * as minimatch from "minimatch";
7+
import * as constants from "../../common/constants";
8+
import * as util from "util";
9+
10+
let gaze = require("gaze");
11+
12+
class LiveSyncServiceBase implements ILiveSyncServiceBase {
13+
private showFullLiveSyncInformation: boolean = false;
14+
private fileHashes: IDictionary<string>;
15+
16+
constructor(protected $devicesService: Mobile.IDevicesService,
17+
protected $mobileHelper: Mobile.IMobileHelper,
18+
protected $logger: ILogger,
19+
protected $options: ICommonOptions,
20+
protected $deviceAppDataFactory: Mobile.IDeviceAppDataFactory,
21+
protected $fs: IFileSystem,
22+
protected $injector: IInjector,
23+
protected $hooksService: IHooksService,
24+
private $projectFilesManager: IProjectFilesManager,
25+
private $projectFilesProvider: IProjectFilesProvider,
26+
private $liveSyncProvider: ILiveSyncProvider,
27+
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
28+
private $hostInfo: IHostInfo,
29+
private $dispatcher: IFutureDispatcher) {
30+
this.fileHashes = Object.create(null);
431
}
532

6-
constructor(private _device: Mobile.IDevice,
7-
private $liveSyncProvider: ILiveSyncProvider) { }
33+
public getPlatform(platform?: string): IFuture<string> { // gets the platform and ensures that the devicesService is initialized
34+
return (() => {
35+
this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait();
36+
return platform || this.$devicesService.platform;
37+
}).future<string>()();
38+
}
39+
40+
public sync(data: ILiveSyncData[], filePaths?: string[]): IFuture<void> {
41+
return (() => {
42+
this.syncCore(data, filePaths).wait();
43+
if (this.$options.watch) {
44+
this.$hooksService.executeBeforeHooks('watch').wait();
45+
this.partialSync(data, data[0].syncWorkingDirectory);
46+
}
47+
}).future<void>()();
48+
}
849

9-
public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture<void> {
10-
let canExecuteFastSync = !forceExecuteFullSync && localToDevicePaths !== undefined;
11-
for (let localToDevicePath of localToDevicePaths) {
12-
if (!this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform)) {
13-
canExecuteFastSync = false;
14-
break;
50+
private isFileExcluded(filePath: string, excludedPatterns: string[]): boolean {
51+
let isFileExcluded = false;
52+
_.each(excludedPatterns, pattern => {
53+
if (minimatch(filePath, pattern, { nocase: true })) {
54+
isFileExcluded = true;
55+
return false;
1556
}
57+
});
58+
59+
return isFileExcluded;
60+
}
61+
62+
private partialSync(data: ILiveSyncData[], syncWorkingDirectory: string): void {
63+
let that = this;
64+
this.showFullLiveSyncInformation = true;
65+
gaze("**/*", { cwd: syncWorkingDirectory }, function (err: any, watcher: any) {
66+
this.on('all', (event: string, filePath: string) => {
67+
fiberBootstrap.run(() => {
68+
that.$dispatcher.dispatch(() => (() => {
69+
try {
70+
71+
if (filePath.indexOf(constants.APP_RESOURCES_FOLDER_NAME) !== -1) {
72+
that.$logger.warn(`Skipping livesync for changed file ${filePath}. This change requires a full build to update your application. `.yellow.bold);
73+
return;
74+
}
75+
76+
let fileHash = that.$fs.exists(filePath).wait() && that.$fs.getFsStats(filePath).wait().isFile() ? that.$fs.getFileShasum(filePath).wait() : "";
77+
if (fileHash === that.fileHashes[filePath]) {
78+
that.$logger.trace(`Skipping livesync for ${filePath} file with ${fileHash} hash.`);
79+
return;
80+
}
81+
82+
that.$logger.trace(`Adding ${filePath} file with ${fileHash} hash.`);
83+
that.fileHashes[filePath] = fileHash;
84+
85+
for (let dataItem of data) {
86+
if (that.isFileExcluded(filePath, dataItem.excludedProjectDirsAndFiles)) {
87+
that.$logger.trace(`Skipping livesync for changed file ${filePath} as it is excluded in the patterns: ${dataItem.excludedProjectDirsAndFiles.join(", ")}`);
88+
continue;
89+
}
90+
let mappedFilePath = that.$projectFilesProvider.mapFilePath(filePath, dataItem.platform);
91+
that.$logger.trace(`Syncing filePath ${filePath}, mappedFilePath is ${mappedFilePath}`);
92+
if (!mappedFilePath) {
93+
that.$logger.warn(`Unable to sync ${filePath}.`);
94+
continue;
95+
}
96+
97+
if (event === "added" || event === "changed" || event === "renamed") {
98+
that.batchSync(dataItem, mappedFilePath);
99+
} else if (event === "deleted") {
100+
that.fileHashes = <any>(_.omit(that.fileHashes, filePath));
101+
that.syncRemovedFile(dataItem, mappedFilePath).wait();
102+
}
103+
}
104+
} catch (err) {
105+
that.$logger.info(`Unable to sync file ${filePath}. Error is:${err.message}`.red.bold);
106+
that.$logger.info("Try saving it again or restart the livesync operation.");
107+
}
108+
}).future<void>()());
109+
});
110+
});
111+
});
112+
113+
this.$dispatcher.run();
114+
}
115+
116+
private batch: IDictionary<ISyncBatch> = Object.create(null);
117+
private livesyncData: IDictionary<ILiveSyncData> = Object.create(null);
118+
119+
private batchSync(data: ILiveSyncData, filePath: string): void {
120+
let platformBatch: ISyncBatch = this.batch[data.platform];
121+
if (!platformBatch || !platformBatch.syncPending) {
122+
let done = () => {
123+
return (() => {
124+
setTimeout(() => {
125+
fiberBootstrap.run(() => {
126+
this.$dispatcher.dispatch(() => (() => {
127+
try {
128+
for (let platformName in this.batch) {
129+
let batch = this.batch[platformName];
130+
let livesyncData = this.livesyncData[platformName];
131+
batch.syncFiles(((filesToSync:string[]) => {
132+
this.$liveSyncProvider.preparePlatformForSync(platformName).wait();
133+
this.syncCore([livesyncData], filesToSync);
134+
}).future<void>()).wait();
135+
}
136+
} catch (err) {
137+
this.$logger.warn(`Unable to sync files. Error is:`, err.message);
138+
}
139+
}).future<void>()());
140+
141+
});
142+
}, syncBatchLib.SYNC_WAIT_THRESHOLD);
143+
}).future<void>()();
144+
};
145+
this.batch[data.platform] = this.$injector.resolve(syncBatchLib.SyncBatch, { done: done });
146+
this.livesyncData[data.platform] = data;
16147
}
17-
if (canExecuteFastSync) {
18-
return this.reloadPage(deviceAppData);
148+
149+
this.batch[data.platform].addFile(filePath);
150+
}
151+
152+
private syncRemovedFile(data: ILiveSyncData, filePath: string): IFuture<void> {
153+
return (() => {
154+
let filePathArray = [filePath],
155+
deviceFilesAction = this.getSyncRemovedFilesAction(data);
156+
157+
this.syncCore([data], filePathArray, deviceFilesAction).wait();
158+
}).future<void>()();
159+
}
160+
161+
public getSyncRemovedFilesAction(data: ILiveSyncData): (device: Mobile.IDevice, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => IFuture<void> {
162+
return (device: Mobile.IDevice, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => {
163+
let platformLiveSyncService = this.resolvePlatformLiveSyncService(data.platform, device);
164+
return platformLiveSyncService.removeFiles(data.appIdentifier, localToDevicePaths);
165+
};
166+
}
167+
168+
public getSyncAction(data: ILiveSyncData, filesToSync: string[], deviceFilesAction: (device: Mobile.IDevice, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => IFuture<void>, liveSyncOptions: ILiveSyncOptions): (device: Mobile.IDevice) => IFuture<void> {
169+
let appIdentifier = data.appIdentifier;
170+
let platform = data.platform;
171+
let projectFilesPath = data.projectFilesPath;
172+
173+
let packageFilePath: string = null;
174+
175+
let action = (device: Mobile.IDevice): IFuture<void> => {
176+
return (() => {
177+
let shouldRefreshApplication = true;
178+
let deviceAppData = this.$deviceAppDataFactory.create(appIdentifier, this.$mobileHelper.normalizePlatformName(platform), device, liveSyncOptions);
179+
if (deviceAppData.isLiveSyncSupported().wait()) {
180+
let platformLiveSyncService = this.resolvePlatformLiveSyncService(platform, device);
181+
182+
if (platformLiveSyncService.beforeLiveSyncAction) {
183+
platformLiveSyncService.beforeLiveSyncAction(deviceAppData).wait();
184+
}
185+
186+
// Not installed application
187+
device.applicationManager.checkForApplicationUpdates().wait();
188+
189+
let wasInstalled = true;
190+
if (!device.applicationManager.isApplicationInstalled(appIdentifier).wait() && !this.$options.companion) {
191+
this.$logger.warn(`The application with id "${appIdentifier}" is not installed on device with identifier ${device.deviceInfo.identifier}.`);
192+
if (!packageFilePath) {
193+
packageFilePath = this.$liveSyncProvider.buildForDevice(device).wait();
194+
}
195+
device.applicationManager.installApplication(packageFilePath).wait();
196+
197+
if (platformLiveSyncService.afterInstallApplicationAction) {
198+
let localToDevicePaths = this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, filesToSync, data.excludedProjectDirsAndFiles, liveSyncOptions);
199+
shouldRefreshApplication = platformLiveSyncService.afterInstallApplicationAction(deviceAppData, localToDevicePaths).wait();
200+
} else {
201+
shouldRefreshApplication = false;
202+
}
203+
204+
if (device.applicationManager.canStartApplication() && !shouldRefreshApplication) {
205+
device.applicationManager.startApplication(appIdentifier).wait();
206+
}
207+
wasInstalled = false;
208+
}
209+
210+
// Restart application or reload page
211+
if (shouldRefreshApplication) {
212+
// Transfer or remove files on device
213+
let localToDevicePaths = this.$projectFilesManager.createLocalToDevicePaths(deviceAppData, projectFilesPath, filesToSync, data.excludedProjectDirsAndFiles, liveSyncOptions);
214+
if (deviceFilesAction) {
215+
deviceFilesAction(device, localToDevicePaths).wait();
216+
} else {
217+
this.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, !filesToSync).wait();
218+
}
219+
220+
this.$logger.info("Applying changes...");
221+
platformLiveSyncService.refreshApplication(deviceAppData, localToDevicePaths, data.forceExecuteFullSync || !wasInstalled).wait();
222+
this.$logger.info(`Successfully synced application ${data.appIdentifier} on device ${device.deviceInfo.identifier}.`);
223+
}
224+
} else {
225+
this.$logger.warn(`LiveSync is not supported for application: ${deviceAppData.appIdentifier} on device with identifier ${device.deviceInfo.identifier}.`);
226+
}
227+
}).future<void>()();
228+
};
229+
230+
return action;
231+
}
232+
233+
private syncCore(data: ILiveSyncData[], filesToSync: string[], deviceFilesAction?: (device: Mobile.IDevice, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => IFuture<void>): IFuture<void> {
234+
return (() => {
235+
for (let dataItem of data) {
236+
let appIdentifier = dataItem.appIdentifier;
237+
let platform = dataItem.platform;
238+
let canExecute = this.getCanExecuteAction(platform, appIdentifier, dataItem.canExecute);
239+
let action = this.getSyncAction(dataItem, filesToSync, deviceFilesAction, { isForCompanionApp: this.$options.companion, additionalConfigurations: dataItem.additionalConfigurations, configuration: dataItem.configuration, isForDeletedFiles: false });
240+
this.$devicesService.execute(action, canExecute).wait();
241+
}
242+
}).future<void>()();
243+
}
244+
245+
private transferFiles(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string, isFullSync: boolean): IFuture<void> {
246+
return (() => {
247+
this.$logger.info("Transferring project files...");
248+
this.logFilesSyncInformation(localToDevicePaths, "Transferring %s.", this.$logger.trace);
249+
250+
let canTransferDirectory = isFullSync && (this.$devicesService.isAndroidDevice(deviceAppData.device) || this.$devicesService.isiOSSimulator(deviceAppData.device));
251+
if (canTransferDirectory) {
252+
let tempDir = temp.mkdirSync("tempDir");
253+
shell.cp("-Rf", path.join(projectFilesPath, "*"), tempDir);
254+
this.$projectFilesManager.processPlatformSpecificFiles(tempDir, deviceAppData.platform).wait();
255+
deviceAppData.device.fileSystem.transferDirectory(deviceAppData, localToDevicePaths, tempDir).wait();
256+
} else {
257+
this.$liveSyncProvider.transferFiles(deviceAppData, localToDevicePaths, projectFilesPath, isFullSync).wait();
258+
}
259+
260+
this.logFilesSyncInformation(localToDevicePaths, "Successfully transferred %s.", this.$logger.info);
261+
}).future<void>()();
262+
}
263+
264+
private logFilesSyncInformation(localToDevicePaths: Mobile.ILocalToDevicePathData[], message: string, action: Function): void {
265+
if (this.showFullLiveSyncInformation) {
266+
_.each(localToDevicePaths, (file: Mobile.ILocalToDevicePathData) => {
267+
action.call(this.$logger, util.format(message, path.basename(file.getLocalPath()).yellow));
268+
});
269+
} else {
270+
action.call(this.$logger, util.format(message, "all files"));
19271
}
272+
}
20273

21-
return this.restartApplication(deviceAppData);
274+
private resolvePlatformLiveSyncService(platform: string, device: Mobile.IDevice): IPlatformLiveSyncService {
275+
return this.$injector.resolve(this.$liveSyncProvider.platformSpecificLiveSyncServices[platform.toLowerCase()], { _device: device });
22276
}
23277

24-
protected abstract restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void>;
25-
protected abstract reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture<void>;
278+
public getCanExecuteAction(platform: string, appIdentifier: string, canExecute: (dev: Mobile.IDevice) => boolean): (dev: Mobile.IDevice) => boolean {
279+
canExecute = canExecute || ((dev: Mobile.IDevice) => dev.deviceInfo.platform.toLowerCase() === platform.toLowerCase());
280+
let finalCanExecute = canExecute;
281+
if (this.$options.device) {
282+
return (device: Mobile.IDevice): boolean => canExecute(device) && device.deviceInfo.identifier === this.$devicesService.getDeviceByDeviceOption().deviceInfo.identifier;
283+
}
284+
285+
if (this.$mobileHelper.isiOSPlatform(platform)) {
286+
if (this.$options.emulator) {
287+
finalCanExecute = (device: Mobile.IDevice): boolean => canExecute(device) && this.$devicesService.isiOSSimulator(device);
288+
} else {
289+
let devices = this.$devicesService.getDevicesForPlatform(platform);
290+
let simulator = _.find(devices, d => this.$devicesService.isiOSSimulator(d));
291+
if (simulator) {
292+
let iOSDevices = _.filter(devices, d => d.deviceInfo.identifier !== simulator.deviceInfo.identifier);
293+
if (iOSDevices && iOSDevices.length) {
294+
let isApplicationInstalledOnSimulator = simulator.applicationManager.isApplicationInstalled(appIdentifier).wait();
295+
let isApplicationInstalledOnAllDevices = _.intersection.apply(null, iOSDevices.map(device => device.applicationManager.isApplicationInstalled(appIdentifier).wait()));
296+
// In case the application is not installed on both device and simulator, syncs only on device.
297+
if (!isApplicationInstalledOnSimulator && !isApplicationInstalledOnAllDevices) {
298+
finalCanExecute = (device: Mobile.IDevice): boolean => canExecute(device) && this.$devicesService.isiOSDevice(device);
299+
}
300+
}
301+
}
302+
}
303+
}
304+
305+
return finalCanExecute;
306+
}
26307
}
308+
$injector.register('liveSyncServiceBase', LiveSyncServiceBase);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export abstract class PlatformLiveSyncServiceBase<T extends Mobile.IDevice> {
2+
protected get device(): T {
3+
return <T>(this._device);
4+
}
5+
6+
constructor(private _device: Mobile.IDevice,
7+
private $liveSyncProvider: ILiveSyncProvider) { }
8+
9+
public refreshApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], forceExecuteFullSync: boolean): IFuture<void> {
10+
let canExecuteFastSync = !forceExecuteFullSync && localToDevicePaths !== undefined;
11+
for (let localToDevicePath of localToDevicePaths) {
12+
if (!this.$liveSyncProvider.canExecuteFastSync(localToDevicePath.getLocalPath(), deviceAppData.platform)) {
13+
canExecuteFastSync = false;
14+
break;
15+
}
16+
}
17+
if (canExecuteFastSync) {
18+
return this.reloadPage(deviceAppData);
19+
}
20+
21+
return this.restartApplication(deviceAppData);
22+
}
23+
24+
protected abstract restartApplication(deviceAppData: Mobile.IDeviceAppData): IFuture<void>;
25+
protected abstract reloadPage(deviceAppData: Mobile.IDeviceAppData): IFuture<void>;
26+
}

0 commit comments

Comments
 (0)