diff --git a/cli.js b/cli.js index ec7f07cd..b6002d04 100755 --- a/cli.js +++ b/cli.js @@ -16,7 +16,7 @@ const utils = require("./src/utils"); const systemImage = require("./src/system-image"); const package = require("./package.json") -const defaultChannel = "ubuntu-touch/stable"; +const defaultChannel = "ubports-touch/legacy"; process.env.NO_GUI = 1; @@ -111,7 +111,7 @@ var bootstrap = (device) => { cli .version(package.version) .option('-d, --device ', 'Specify device') - .option('-c, --channel ', 'Specify channel (default: ubuntu-touch/stable)') + .option('-c, --channel ', 'Specify channel (default: ubports-touch/legacy)') .option('-v, --verbose', "Verbose output") .option('-b, --bootstrap', "Flash boot and recovery from bootloader") .parse(process.argv); diff --git a/package.json b/package.json index 3cdf2076..a5aa696b 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "src/main.js", "bin": "./src/main.js", "scripts": { + "test-unit": "NODE_ENV=test mocha 'tests/unit-tests/*.js'", "start": "electron .", "install": "node ./build.js -d", "dist:unix": "node ./build.js -lm", @@ -16,26 +17,35 @@ "author": "The Ubports developers ", "license": "GPL-3.0", "devDependencies": { + "chai": "^3.5.0", + "chai-as-promised": "^6.0.0", "electron": "^1.4.1", "electron-builder": "^15.2.0", "electron-packager": "^8.5.2", + "istanbul": "^0.4.5", + "mocha": "^3.2.0", + "sinon": "^1.17.7", + "spectron": "^3.6.0", "unzip": "^0.1.11" }, "dependencies": { "bootstrap": "^3.3.7", "checksum": "^0.1.1", "commander": "^2.9.0", + "decompress-tarxz": "^2.1.0", "electron-open-link-in-browser": "^1.0.2", "electron-sudo": "^3.0.13", "executable": "^4.1.0", "forward-emitter": "^0.1.1", "fs-extra": "^2.0.0", + "ini": "^1.3.4", "jquery": "^3.1.1", "mkdirp": "^0.5.1", "openurl": "^1.1.1", "request": "^2.79.0", "request-progress": "^3.0.0", "tmp": "0.0.31", + "wildcard": "^1.1.2", "winston": "^2.3.1" } } diff --git a/src/adb.js b/src/adb.js index 35a2a5dc..926b5465 100644 --- a/src/adb.js +++ b/src/adb.js @@ -15,24 +15,60 @@ const fs = require("fs"); const events = require("events") const fEvent = require('forward-emitter'); const utils = require("./utils"); +const ini = require('ini'); const adb = utils.getPlatformTools().adb class event extends events {} +var getDeviceNameFromPropFile = (callback) => { + shell("cat default.prop", (output) => { + output=output.split("\n"); + var ret; + output.forEach((prop) => { + if (prop.includes("ro.product.device") && prop !== undefined && !ret){ + ret = prop.split("=")[1]; + } + }) + callback(ret.replace(/\W/g, "")); + }) +} + var getDeviceName = (callback, method) => { if (!method) method = "device"; cp.execFile(adb, ["shell", "getprop ro.product."+method], (err, stdout, stderr) => { - if (err !== null) callback(false); - else callback(stdout.replace(/\W/g, "")); + if (stdout.includes("getprop: not found")){ + getDeviceNameFromPropFile(callback); + return; + } + if (err !== null) { + callback(false); + return; + } + callback(stdout.replace(/\W/g, "")); }); } +var isUbuntuDevice = () => { + shell("cat /etc/system-image/channel.ini", (output) => { + return output ? true : false; + }) +} + +var readUbuntuChannelINI = () => { + shell("cat /etc/system-image/channel.ini", (output) => { + return ini.parse(output); + }) +} + var push = (file, dest, pushEvent) => { var done; var fileSize = fs.statSync(file)["size"]; cp.execFile(adb, ["push", file, dest], {maxBuffer: 2000*1024}, (err, stdout, stderr) => { done=true; - if (err !== null) pushEvent.emit("adbpush:error", err+" stdout: " + stdout.length > 50*1024 ? "overflow" : stdout + " stderr: " + stderr.length > 50*1024 ? "overflow" : stderr) + if (err !== null) { + pushEvent.emit("adbpush:error", err+" stdout: " + stdout.length > 50*1024 ? "overflow" : stdout + " stderr: " + stderr.length > 50*1024 ? "overflow" : stderr) + console.log(stdout.length > 50*1024 ? "overflow" : stdout + " stderr: " + stderr.length > 50*1024 ? "overflow" : stderr) + } else pushEvent.emit("adbpush:end") }); var progress = () => { @@ -115,5 +151,8 @@ module.exports = { push: push, pushMany: pushMany, hasAdbAccess: hasAdbAccess, - reboot: reboot + reboot: reboot, + getDeviceNameFromPropFile: getDeviceNameFromPropFile, + isUbuntuDevice: isUbuntuDevice, + readUbuntuChannelINI: readUbuntuChannelINI } diff --git a/src/devices.js b/src/devices.js index 1daba694..928d94c2 100644 --- a/src/devices.js +++ b/src/devices.js @@ -17,6 +17,7 @@ const os = require("os"); const path = require("path"); const events = require("events") const fEvent = require('forward-emitter'); +//const wildcard = require("wildcard"); class event extends events {} @@ -71,6 +72,7 @@ var getInstallInstructs = (device, callback) => { } var getNotWorking = (ww) => { + if (!ww) return false; var notWorking = []; var whatsWorking = JSON.parse(ww); for (var i in whatsWorking) { @@ -83,6 +85,7 @@ var getNotWorking = (ww) => { } var formatNotWorking = (nw) => { + if (!nw) return false; return nw.join(", ").replace("/\,(?=[^,]*$)", " and"); } @@ -100,9 +103,9 @@ var instructReboot = (state, button, rebootEvent, callback) => { } if (state === "bootloader") { requestPassword(rebootEvent, (pass) => { - fastboot.waitForDevice(pass, (err) => { + fastboot.waitForDevice(pass, (err, errM) => { if (err){ - rebootEvent.emit("Error", err); + rebootEvent.emit("Error", errM); return; } rebootEvent.emit("reboot:done"); @@ -143,31 +146,49 @@ var requestPassword = (bootstrapEvent, callback) => { var instructBootstrap = (fastbootboot, images, bootstrapEvent) => { //TODO check bootloader name/version/device //TODO OEM unlock - var flash = (p) => { - fastboot.flash(images, (err) => { + fastboot.flash(images, (err, errM) => { if(err) if(err.password) bootstrapEvent.emit("user:password:wrong"); else - bootstrapEvent.emit("error", err) - else - bootstrapEvent.emit("bootstrap:done") + bootstrapEvent.emit("error", errM) + else { + if (fastbootboot) { + utils.log.info("Booting into recovery image..."); + // find recovery image + var recoveryImg; + images.forEach((image) => { + if (image.type === "recovery") + recoveryImg = image; + }); + // If we can't find it, report error! + if (!recoveryImg){ + bootstrapEvent.emit("error", "Cant find recoveryImg to boot: "+images); + }else { + fastboot.boot(recoveryImg, p, (err, errM) => { + if (err) { + if(err.password) + bootstrapEvent.emit("user:password:wrong"); + else + bootstrapEvent.emit("error", errM) + }else + bootstrapEvent.emit("bootstrap:done", fastbootboot); + }) + } + } else + bootstrapEvent.emit("bootstrap:done", fastbootboot) + } }, p) } - - if (fastbootboot) { - bootstrapEvent.emit("user:write:status", "Booting into recovery image...") - } else { - bootstrapEvent.emit("bootstrap:flashing") - bootstrapEvent.emit("user:write:status", "Flashing images") - if (!utils.needRoot()) { - flash(false); - }else { - requestPassword(bootstrapEvent, (p) => { - flash(p); - }); - } + bootstrapEvent.emit("bootstrap:flashing") + bootstrapEvent.emit("user:write:status", "Flashing images") + if (!utils.needRoot()) { + flash(false); + }else { + requestPassword(bootstrapEvent, (p) => { + flash(p); + }); } } @@ -194,6 +215,7 @@ var setEvents = (downloadEvent) => { utils.log.error("Devices: Download error "+r); }); downloadEvent.on("error", (r) => { + downloadEvent.emit("user:error", r); utils.log.error("Devices: Error: "+r); }); downloadEvent.on("download:checking", () => { @@ -260,14 +282,21 @@ var install = (device, channel, noUserEvents, noSystemImage) => { postSuccess({ device: device, channel: channel - }) + }, () => {}); }); }) - installEvent.on("bootstrap:done", () => { + installEvent.on("bootstrap:done", (fastbootboot) => { utils.log.info("bootstrap done"); - instructReboot("recovery", instructs.buttons, installEvent, () => { - installEvent.emit("system-image:start") - }); + if (!fastbootboot){ + instructReboot("recovery", instructs.buttons, installEvent, () => { + installEvent.emit("system-image:start") + }); + } else { + installEvent.emit("user:write:status", "Waiting for device to enter recovery mode"); + adb.waitForDevice(() => { + installEvent.emit("system-image:start"); + }) + } }) if (getInstallSettings(instructs, "bootstrap")) { // We need to be in bootloader @@ -296,6 +325,7 @@ var getChannelSelects = (device, callback) => { setTimeout(function () { getInstallInstructs(device, (ret) => { systemImage.getDeviceChannes(device, channels).forEach((channel) => { + var _channel = channel.replace("ubports-touch/", ""); var _channel = channel.replace("ubuntu-touch/", ""); // Ignore blacklisted channels if (ret["system_server"]["blacklist"].indexOf(channel) > -1) @@ -317,6 +347,10 @@ module.exports = { var waitEvent = adb.waitForDevice(() => { adb.getDeviceName((name) => { getDevice(name, (ret) => { + if (!ret){ + callback(false, name); + return; + } getChannelSelects(ret.device.device, (channels) => { callback(ret, device, channels); }) diff --git a/src/fastboot.js b/src/fastboot.js index 465d3d22..29534f16 100644 --- a/src/fastboot.js +++ b/src/fastboot.js @@ -29,8 +29,8 @@ var waitForDevice = (password, callback) => { asarExec.done(); }else { // Unknown error; - utils.log.error("Fastboot: Unknown error: 001"); - callback(true); + utils.log.error("Fastboot: Unknown error: " + r.replace(password, "***") + " " + e.replace(password, "***")); + callback(true, "Fastboot: Unknown error: " + r.replace(password, "***") + " " + e.replace(password, "***")); } return; } else { @@ -57,7 +57,7 @@ var flash = (images, callback, password) => { images.forEach((image, l) => { if (utils.needRoot()) cmd += "echo " + password + " | sudo -S " - cmd += fastboot + " flash " + image.type + " \"" + image.path + "\"/" + path.basename(image.url); + cmd += fastboot + " flash " + image.type + " \"" + path.join(image.path, path.basename(image.url)) + "\""; if (l !== images.length - 1) cmd += " && " }); @@ -68,6 +68,7 @@ var flash = (images, callback, password) => { callback({ password: true }); + else callback(true, "Fastboot: Unknown error: " + r.replace(password, "***") + " " + e.replace(password, "***")); } else { callback(c, r, e) } @@ -76,7 +77,29 @@ var flash = (images, callback, password) => { }); } +var boot = (image, password, callback) => { + var cmd=""; + if (utils.needRoot()) + cmd += "echo " + password + " | sudo -S " + cmd += fastboot + " boot \"" + path.join(image.path, path.basename(image.url)) + "\""; + utils.asarExec(fastboot, (asarExec) => { + asarExec.exec(cmd, (c, r, e) => { + if (c) { + if (c.message.includes("incorrect password")) + callback({ + password: true + }); + else callback(true, "Fastboot: Unknown error: " + r.replace(password, "***") + " " + e.replace(password, "***")); + } else { + callback(c, r, e) + } + asarExec.done(); + }) + }); +} + module.exports = { flash: flash, - waitForDevice: waitForDevice + waitForDevice: waitForDevice, + boot: boot } diff --git a/src/html/index.html b/src/html/index.html index 65715d44..c6e60ee2 100644 --- a/src/html/index.html +++ b/src/html/index.html @@ -34,6 +34,9 @@

Please connect your device

+

+ If you want to switch channel or switch from Ubuntu to Ubports on an existing device please enable developet mode. +

@@ -102,7 +105,7 @@

Please reboot to bootload

Ready to install!

Your device:

-

Please note that this device is still under development and that is currently not working!

+

Please note that this device is still under development and that is currently not working!

Read before installing: this will factory reset your device and install Ubuntu touch, this means it will remove all of your data

Make sure to backup all data you want to keep!

@@ -151,6 +154,29 @@
+ +