Skip to content

Commit 90b9aaa

Browse files
Fatme HavaluovaFatme
authored andcommitted
LiveSync support
1 parent c0acb61 commit 90b9aaa

File tree

11 files changed

+411
-13
lines changed

11 files changed

+411
-13
lines changed

.settings/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
"configurations": [
66
{
77
// Name of configuration; appears in the launch configuration drop down menu.
8-
"name": "build",
8+
"name": "livesync",
99
// Type of configuration. Possible values: "node", "mono".
1010
"type": "node",
1111
// Workspace relative or absolute path to the program.
1212
"program": "bin/nativescript.js",
1313
// Automatically stop program after launch.
1414
"stopOnEntry": true,
1515
// Command line arguments passed to the program.
16-
"args": [],
16+
"args": ["livesync", "--path", "scratch/syncProject", "--justlaunch"],
1717
// Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace.
1818
"cwd": ".",
1919
"isShellCommand": true,

lib/bootstrap.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ $injector.require("analyticsSettingsService", "./services/analytics-settings-ser
2323

2424
$injector.require("emulatorSettingsService", "./services/emulator-settings-service");
2525

26+
$injector.require("usbLiveSyncService", "./services/usb-livesync-service");
27+
2628
$injector.require("platformCommandParameter", "./platform-command-param");
2729
$injector.requireCommand("create", "./commands/create-project");
2830
$injector.requireCommand("platform|*list", "./commands/list-platforms");
@@ -49,6 +51,7 @@ $injector.require("lockfile", "./lockfile");
4951
$injector.require("dynamicHelpProvider", "./dynamic-help-provider");
5052
$injector.require("mobilePlatformsCapabilities", "./mobile-platforms-capabilities");
5153
$injector.require("commandsServiceProvider", "./providers/commands-service-provider");
54+
$injector.require("deviceAppDataProvider", "./providers/device-app-data-provider");
5255

5356
$injector.require("logcatPrinter", "./providers/logcat-printer");
5457

@@ -67,3 +70,4 @@ $injector.require("initService", "./services/init-service");
6770
$injector.requireCommand("init", "./commands/init");
6871

6972
$injector.require("projectFilesManager", "./services/project-files-manager");
73+
$injector.requireCommand("livesync", "./commands/livesync");

lib/commands/livesync.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
///<reference path="../.d.ts"/>
2+
"use strict";
3+
4+
export class LivesyncCommand implements ICommand {
5+
constructor(private $logger: ILogger,
6+
private $usbLiveSyncService: IUsbLiveSyncService,
7+
private $mobileHelper: Mobile.IMobileHelper) { }
8+
9+
public execute(args: string[]): IFuture<void> {
10+
return this.$usbLiveSyncService.liveSync(args[0]);
11+
}
12+
13+
public canExecute(args: string[]): IFuture<boolean> {
14+
return (() => {
15+
let platform = args[0];
16+
if(platform) {
17+
return _.contains(this.$mobileHelper.platformNames, this.$mobileHelper.normalizePlatformName(platform));
18+
}
19+
20+
return true;
21+
}).future<boolean>()();
22+
}
23+
24+
allowedParameters: ICommandParameter[] = [];
25+
}
26+
$injector.registerCommand("livesync", LivesyncCommand);

lib/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ export class StaticConfig extends staticConfigBaseLibPath.StaticConfigBase imple
3030
public TRACK_FEATURE_USAGE_SETTING_NAME = "TrackFeatureUsage";
3131
public ANALYTICS_INSTALLATION_ID_SETTING_NAME = "AnalyticsInstallationID";
3232
public START_PACKAGE_ACTIVITY_NAME = "com.tns.NativeScriptActivity";
33+
34+
constructor($injector: IInjector) {
35+
super($injector);
36+
}
37+
3338
public get SYS_REQUIREMENTS_LINK(): string {
3439
var linkToSysRequirements: string;
3540
switch(process.platform) {

lib/declarations.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ interface IOpener {
5050
open(target: string, appname: string): void;
5151
}
5252

53+
interface IUsbLiveSyncService {
54+
liveSync(platform: string): IFuture<void>;
55+
}
56+
57+
interface IPlatformSpecificUsbLiveSyncService {
58+
restartApplication(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths?: Mobile.ILocalToDevicePathData[]): IFuture<void>;
59+
}
60+
5361
interface IOptions extends ICommonOptions {
5462
frameworkPath: string;
5563
frameworkName: string;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
///<reference path="../.d.ts"/>
2+
"use strict";
3+
import deviceAppDataBaseLib = require("../common/mobile/device-app-data/device-app-data-base");
4+
import constantsLib = require("../common/mobile/constants");
5+
import Future = require("fibers/future");
6+
7+
export class IOSAppIdentifier extends deviceAppDataBaseLib.DeviceAppDataBase implements Mobile.IDeviceAppData {
8+
private static DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync";
9+
10+
constructor(_appIdentifier: string) {
11+
super(_appIdentifier);
12+
}
13+
14+
public get deviceProjectRootPath(): string {
15+
return this.getDeviceProjectRootPath(IOSAppIdentifier.DEVICE_PROJECT_ROOT_PATH);
16+
}
17+
18+
public isLiveSyncSupported(device: Mobile.IDevice): IFuture<boolean> {
19+
return Future.fromResult(true);
20+
}
21+
}
22+
23+
export class AndroidAppIdentifier extends deviceAppDataBaseLib.DeviceAppDataBase implements Mobile.IDeviceAppData {
24+
constructor(_appIdentifier: string) {
25+
super(_appIdentifier);
26+
}
27+
28+
public get deviceProjectRootPath(): string {
29+
return `/data/local/tmp/12590FAA-5EDD-4B12-856D-F52A0A1599F2/${this.appIdentifier}`;
30+
}
31+
32+
public isLiveSyncSupported(device: Mobile.IDevice): IFuture<boolean> {
33+
return Future.fromResult(true);
34+
}
35+
}
36+
37+
export class AndroidCompanionAppIdentifier extends deviceAppDataBaseLib.CompanionDeviceAppDataBase implements Mobile.IDeviceAppData {
38+
private static APP_IDENTIFIER = "com.telerik.NativeScript";
39+
40+
constructor() {
41+
super(AndroidCompanionAppIdentifier.APP_IDENTIFIER);
42+
}
43+
44+
public get deviceProjectRootPath(): string {
45+
return `/mnt/sdcard/Android/data/${this.appIdentifier}/files/12590FAA-5EDD-4B12-856D-F52A0A1599F2`;
46+
}
47+
}
48+
49+
export class DeviceAppDataProvider implements Mobile.IDeviceAppDataProvider {
50+
public createFactoryRules(): IDictionary<Mobile.IDeviceAppDataFactoryRule> {
51+
return {
52+
iOS: {
53+
vanilla: IOSAppIdentifier
54+
},
55+
Android: {
56+
vanilla: AndroidAppIdentifier,
57+
companion: AndroidCompanionAppIdentifier
58+
}
59+
}
60+
}
61+
}
62+
$injector.register("deviceAppDataProvider", DeviceAppDataProvider);

lib/services/android-debug-service.ts

Lines changed: 187 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
11
import iOSProxyServices = require("./../common/mobile/ios/ios-proxy-services");
22
import iOSDevice = require("./../common/mobile/ios/ios-device");
3+
import helpers = require("../common/helpers");
34
import net = require("net");
5+
import path = require("path");
6+
import util = require("util");
47

58
class AndroidDebugService implements IDebugService {
9+
private static ENV_DEBUG_IN_FILENAME = "envDebug.in";
10+
private static ENV_DEBUG_OUT_FILENAME = "envDebug.out";
11+
private static DEFAULT_NODE_INSPECTOR_URL = "http://127.0.0.1:8080/debug";
12+
private static PACKAGE_EXTERNAL_DIR_TEMPLATE = "/sdcard/Android/data/%s/files/";
13+
14+
private _device: Mobile.IAndroidDevice = null;
15+
616
constructor(private $devicesServices: Mobile.IDevicesServices,
717
private $platformService: IPlatformService,
818
private $platformsData: IPlatformsData,
919
private $projectData: IProjectData,
1020
private $logger: ILogger,
11-
private $options: IOptions) { }
21+
private $options: IOptions,
22+
private $childProcess: IChildProcess,
23+
private $mobileHelper: Mobile.IMobileHelper,
24+
private $hostInfo: IHostInfo,
25+
private $errors: IErrors,
26+
private $opener: IOpener,
27+
private $staticConfig: IStaticConfig) { }
1228

1329
private get platform() { return "android"; }
14-
30+
31+
private get device(): Mobile.IAndroidDevice {
32+
return this._device;
33+
}
34+
35+
private set device(newDevice) {
36+
this._device = newDevice;
37+
}
38+
1539
public debug(): IFuture<void> {
1640
return this.$options.emulator
1741
? this.debugOnEmulator()
@@ -27,27 +51,183 @@ class AndroidDebugService implements IDebugService {
2751

2852
public debugOnDevice(): IFuture<void> {
2953
return (() => {
30-
var platformData = this.$platformsData.getPlatformData(this.platform);
31-
var packageFile = "";
32-
var platformData = this.$platformsData.getPlatformData(this.platform);
54+
let packageFile = "";
3355

3456
if (this.$options.debugBrk) {
3557
this.$platformService.preparePlatform(this.platform).wait();
3658

37-
var cachedDeviceOption = this.$options.forDevice;
59+
let cachedDeviceOption = this.$options.forDevice;
3860
this.$options.forDevice = true;
3961
this.$platformService.buildPlatform(this.platform).wait();
4062
this.$options.forDevice = !!cachedDeviceOption;
4163

64+
let platformData = this.$platformsData.getPlatformData(this.platform);
4265
packageFile = this.$platformService.getLatestApplicationPackageForDevice(platformData).wait().packageName;
4366
this.$logger.out("Using ", packageFile);
4467
}
4568

4669
this.$devicesServices.initialize({ platform: this.platform, deviceId: this.$options.device}).wait();
47-
var action = (device: Mobile.IAndroidDevice): IFuture<void> => { return device.debug(packageFile, this.$projectData.projectId) };
70+
let action = (device: Mobile.IAndroidDevice): IFuture<void> => { return this.debugCore(device, packageFile, this.$projectData.projectId) };
4871
this.$devicesServices.execute(action).wait();
4972

5073
}).future<void>()();
5174
}
75+
76+
private debugCore(device: Mobile.IAndroidDevice, packageFile: string, packageName: string): IFuture<void> {
77+
return (() => {
78+
this.device = device;
79+
80+
if (this.$options.getPort) {
81+
this.printDebugPort(packageName).wait();
82+
} else if (this.$options.start) {
83+
this.attachDebugger(packageName);
84+
} else if (this.$options.stop) {
85+
this.detachDebugger(packageName).wait();
86+
} else if (this.$options.debugBrk) {
87+
this.startAppWithDebugger(packageFile, packageName);
88+
} else {
89+
this.$logger.info("Should specify at least one option: debug-brk, start, stop, get-port.");
90+
}
91+
}).future<void>()();
92+
}
93+
94+
private printDebugPort(packageName: string): IFuture<void> {
95+
return (() => {
96+
let res = this.$childProcess.spawnFromEvent(this.$staticConfig.getAdbFilePath().wait(), ["shell", "am", "broadcast", "-a", packageName + "-GetDgbPort"], "exit").wait();
97+
this.$logger.info(res.stdout);
98+
}).future<void>()();
99+
}
100+
101+
private attachDebugger(packageName: string): void {
102+
let startDebuggerCommand = `am broadcast -a \"${packageName}-Debug\" --ez enable true`;
103+
let port = this.$options.debugPort;
104+
105+
if (port > 0) {
106+
startDebuggerCommand += " --ei debuggerPort " + port;
107+
this.device.adb.executeShellCommand(startDebuggerCommand).wait();
108+
} else {
109+
let res = this.$childProcess.spawnFromEvent(this.$staticConfig.getAdbFilePath().wait(), ["shell", "am", "broadcast", "-a", packageName + "-Debug", "--ez", "enable", "true"], "exit").wait();
110+
let match = res.stdout.match(/result=(\d)+/);
111+
if (match) {
112+
port = match[0].substring(7);
113+
} else {
114+
port = 0;
115+
}
116+
}
117+
if ((0 < port) && (port < 65536)) {
118+
this.tcpForward(port, port);
119+
this.startDebuggerClient(port).wait();
120+
this.openDebuggerClient(AndroidDebugService.DEFAULT_NODE_INSPECTOR_URL + "?port=" + port);
121+
} else {
122+
this.$logger.info("Cannot detect debug port.");
123+
}
124+
}
125+
126+
private detachDebugger(packageName: string): IFuture<void> {
127+
return this.device.adb.executeShellCommand(this.device.deviceInfo.identifier, `shell am broadcast -a \"${packageName}-Debug\" --ez enable false`);
128+
}
129+
130+
private startAppWithDebugger(packageFile: string, packageName: string): IFuture<void> {
131+
return (() => {
132+
this.device.applicationManager.uninstallApplication(packageName).wait();
133+
this.device.applicationManager.installApplication(packageFile).wait();
134+
135+
let port = this.$options.debugPort;
136+
137+
let packageDir = util.format(AndroidDebugService.PACKAGE_EXTERNAL_DIR_TEMPLATE, packageName);
138+
let envDebugOutFullpath = this.$mobileHelper.buildDevicePath(packageDir, AndroidDebugService.ENV_DEBUG_OUT_FILENAME);
139+
140+
this.device.adb.executeShellCommand(`rm "${envDebugOutFullpath}"`).wait();
141+
this.device.adb.executeShellCommand(`mkdir -p "${packageDir}"`).wait();
142+
143+
let debugBreakPath = this.$mobileHelper.buildDevicePath(packageDir, "debugbreak");
144+
this.device.adb.executeShellCommand(`"cat /dev/null > ${debugBreakPath}"`).wait();
145+
146+
this.device.applicationManager.startApplication(packageName).wait();
147+
148+
let dbgPort = this.startAndGetPort(packageName).wait();
149+
if (dbgPort > 0) {
150+
this.tcpForward(dbgPort, dbgPort);
151+
this.startDebuggerClient(dbgPort).wait();
152+
this.openDebuggerClient(AndroidDebugService.DEFAULT_NODE_INSPECTOR_URL + "?port=" + dbgPort);
153+
}
154+
}).future<void>()();
155+
}
156+
157+
private tcpForward(src: Number, dest: Number): IFuture<void> {
158+
return this.device.adb.executeCommand(`forward tcp:${src.toString()} tcp:${dest.toString()}`);
159+
}
160+
161+
private startDebuggerClient(port: Number): IFuture<void> {
162+
return (() => {
163+
let nodeInspectorModuleFilePath = require.resolve("node-inspector");
164+
let nodeInspectorModuleDir = path.dirname(nodeInspectorModuleFilePath);
165+
let nodeInspectorFullPath = path.join(nodeInspectorModuleDir, "bin", "inspector");
166+
this.$childProcess.spawn(process.argv[0], [nodeInspectorFullPath, "--debug-port", port.toString()], { stdio: "ignore", detached: true });
167+
}).future<void>()();
168+
}
169+
170+
private openDebuggerClient(url: string): void {
171+
let chrome = this.$hostInfo.isDarwin ? "Google\ Chrome" : "chrome";
172+
let child = this.$opener.open(url, chrome);
173+
if(!child) {
174+
this.$errors.fail(`Unable to open ${chrome}.`);
175+
}
176+
return child;
177+
}
178+
179+
private checkIfRunning(packageName: string): boolean {
180+
let packageDir = util.format(AndroidDebugService.PACKAGE_EXTERNAL_DIR_TEMPLATE, packageName);
181+
let envDebugOutFullpath = packageDir + AndroidDebugService.ENV_DEBUG_OUT_FILENAME;
182+
let isRunning = this.checkIfFileExists(envDebugOutFullpath).wait();
183+
return isRunning;
184+
}
185+
186+
private checkIfFileExists(filename: string): IFuture<boolean> {
187+
return (() => {
188+
let args = ["shell", "test", "-f", filename, "&&", "echo 'yes'", "||", "echo 'no'"];
189+
let res = this.$childProcess.spawnFromEvent(this.$staticConfig.getAdbFilePath().wait(), args, "exit").wait();
190+
let exists = res.stdout.indexOf('yes') > -1;
191+
return exists;
192+
}).future<boolean>()();
193+
}
194+
195+
private startAndGetPort(packageName: string): IFuture<number> {
196+
return (() => {
197+
let port = -1;
198+
let timeout = 60;
199+
200+
let packageDir = util.format(AndroidDebugService.PACKAGE_EXTERNAL_DIR_TEMPLATE, packageName);
201+
let envDebugInFullpath = packageDir + AndroidDebugService.ENV_DEBUG_IN_FILENAME;
202+
this.device.adb.executeShellCommand(`rm "${envDebugInFullpath}"`).wait();
203+
204+
let isRunning = false;
205+
for (let i = 0; i < timeout; i++) {
206+
helpers.sleep(1000 /* ms */);
207+
isRunning = this.checkIfRunning(packageName);
208+
if (isRunning)
209+
break;
210+
}
211+
212+
if (isRunning) {
213+
this.device.adb.executeShellCommand(`"cat /dev/null > ${envDebugInFullpath}"`).wait();
214+
215+
for (let i = 0; i < timeout; i++) {
216+
helpers.sleep(1000 /* ms */);
217+
let envDebugOutFullpath = packageDir + AndroidDebugService.ENV_DEBUG_OUT_FILENAME;
218+
let exists = this.checkIfFileExists(envDebugOutFullpath).wait();
219+
if (exists) {
220+
let res = this.$childProcess.spawnFromEvent(this.$staticConfig.getAdbFilePath().wait(), ["shell", "cat", envDebugOutFullpath], "exit").wait();
221+
let match = res.stdout.match(/PORT=(\d)+/);
222+
if (match) {
223+
port = parseInt(match[0].substring(5), 10);
224+
break;
225+
}
226+
}
227+
}
228+
}
229+
return port;
230+
}).future<number>()();
231+
}
52232
}
53233
$injector.register("androidDebugService", AndroidDebugService);

0 commit comments

Comments
 (0)