Setup: Support multiple setup source
This commit is contained in:
parent
d18f0bf763
commit
c354ee703e
@ -86,7 +86,7 @@ open class NeoColorScheme : CodeGenObject {
|
|||||||
COLOR_FOREGROUND -> foregroundColor
|
COLOR_FOREGROUND -> foregroundColor
|
||||||
COLOR_CURSOR -> cursorColor
|
COLOR_CURSOR -> cursorColor
|
||||||
else -> {
|
else -> {
|
||||||
if (type in (0..color.size - 1)) {
|
if (type in (0 until color.size)) {
|
||||||
color[type]
|
color[type]
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package io.neoterm.component.setup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public interface ResultListener {
|
||||||
|
void onResult(Exception error);
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package io.neoterm.component.setup;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.system.Os;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import io.neoterm.R;
|
||||||
|
import io.neoterm.backend.EmulatorDebug;
|
||||||
|
import io.neoterm.frontend.logging.NLog;
|
||||||
|
import io.neoterm.frontend.config.NeoPreference;
|
||||||
|
import io.neoterm.frontend.config.NeoTermPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public final class SetupHelper {
|
||||||
|
public static boolean needSetup() {
|
||||||
|
final File PREFIX_FILE = new File(NeoTermPath.USR_PATH);
|
||||||
|
return !PREFIX_FILE.isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setup(final Activity activity, final SourceConnection connection,
|
||||||
|
final ResultListener resultListener) {
|
||||||
|
if (!needSetup()) {
|
||||||
|
resultListener.onResult(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File prefixFile = new File(NeoTermPath.USR_PATH);
|
||||||
|
|
||||||
|
final ProgressDialog progress = makeProgressDialog(activity);
|
||||||
|
progress.setMax(100);
|
||||||
|
progress.show();
|
||||||
|
|
||||||
|
new SetupThread(activity, connection, prefixFile, resultListener, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProgressDialog makeProgressDialog(Context context) {
|
||||||
|
ProgressDialog dialog = new ProgressDialog(context);
|
||||||
|
dialog.setMessage(context.getString(R.string.installer_message));
|
||||||
|
dialog.setIndeterminate(false);
|
||||||
|
dialog.setCancelable(false);
|
||||||
|
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String determineArchName() {
|
||||||
|
for (String androidArch : Build.SUPPORTED_ABIS) {
|
||||||
|
switch (androidArch) {
|
||||||
|
case "arm64-v8a":
|
||||||
|
return "aarch64";
|
||||||
|
case "armeabi-v7a":
|
||||||
|
return "arm";
|
||||||
|
case "x86_64":
|
||||||
|
return "x86_64";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
|
||||||
|
Arrays.toString(Build.SUPPORTED_ABIS));
|
||||||
|
}
|
||||||
|
}
|
172
app/src/main/java/io/neoterm/component/setup/SetupThread.java
Normal file
172
app/src/main/java/io/neoterm/component/setup/SetupThread.java
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package io.neoterm.component.setup;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.system.Os;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import io.neoterm.backend.EmulatorDebug;
|
||||||
|
import io.neoterm.frontend.config.NeoTermPath;
|
||||||
|
import io.neoterm.frontend.logging.NLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class SetupThread extends Thread {
|
||||||
|
private final SourceConnection sourceConnection;
|
||||||
|
private final File prefixPath;
|
||||||
|
private final Activity activity;
|
||||||
|
private final ResultListener resultListener;
|
||||||
|
private final ProgressDialog progressDialog;
|
||||||
|
|
||||||
|
public SetupThread(Activity activity, SourceConnection sourceConnection,
|
||||||
|
File prefixPath, ResultListener resultListener,
|
||||||
|
ProgressDialog progressDialog) {
|
||||||
|
this.activity = activity;
|
||||||
|
this.sourceConnection = sourceConnection;
|
||||||
|
this.prefixPath = prefixPath;
|
||||||
|
this.resultListener = resultListener;
|
||||||
|
this.progressDialog = progressDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final String stagingPrefixPath = NeoTermPath.ROOT_PATH + "/usr-staging";
|
||||||
|
final File stagingPrefixFile = new File(stagingPrefixPath);
|
||||||
|
|
||||||
|
if (stagingPrefixFile.exists()) {
|
||||||
|
deleteFolder(stagingPrefixFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalBytes = 0;
|
||||||
|
int totalReadBytes = 0;
|
||||||
|
final byte[] buffer = new byte[8096];
|
||||||
|
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
||||||
|
|
||||||
|
totalBytes = sourceConnection.getSize();
|
||||||
|
|
||||||
|
try (ZipInputStream zipInput = new ZipInputStream(sourceConnection.getInputStream())) {
|
||||||
|
ZipEntry zipEntry;
|
||||||
|
|
||||||
|
while ((zipEntry = zipInput.getNextEntry()) != null) {
|
||||||
|
totalReadBytes += zipEntry.getCompressedSize();
|
||||||
|
|
||||||
|
final int totalReadBytesFinal = totalReadBytes;
|
||||||
|
final int totalBytesFinal = totalBytes;
|
||||||
|
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
double progressFloat = ((double) totalReadBytesFinal) / ((double) totalBytesFinal) * 100.0;
|
||||||
|
progressDialog.setProgress((int) progressFloat);
|
||||||
|
} catch (RuntimeException ignore) {
|
||||||
|
// activity dismissed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (zipEntry.getName().contains("SYMLINKS.txt")) {
|
||||||
|
BufferedReader symlinksReader = new BufferedReader(new InputStreamReader(zipInput));
|
||||||
|
String line;
|
||||||
|
while ((line = symlinksReader.readLine()) != null) {
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] parts = line.split("←");
|
||||||
|
if (parts.length != 2)
|
||||||
|
throw new RuntimeException("Malformed symlink line: " + line);
|
||||||
|
String oldPath = parts[0];
|
||||||
|
String newPath = stagingPrefixPath + "/" + parts[1];
|
||||||
|
symlinks.add(Pair.create(oldPath, newPath));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String zipEntryName = zipEntry.getName();
|
||||||
|
File targetFile = new File(stagingPrefixPath, zipEntryName);
|
||||||
|
if (zipEntry.isDirectory()) {
|
||||||
|
if (!targetFile.mkdirs())
|
||||||
|
throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
|
||||||
|
} else {
|
||||||
|
try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
|
||||||
|
int readBytes;
|
||||||
|
while ((readBytes = zipInput.read(buffer)) != -1) {
|
||||||
|
outStream.write(buffer, 0, readBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || zipEntryName.startsWith("lib/apt/methods")) {
|
||||||
|
//noinspection OctalInteger
|
||||||
|
Os.chmod(targetFile.getAbsolutePath(), 0700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceConnection.close();
|
||||||
|
|
||||||
|
if (symlinks.isEmpty())
|
||||||
|
throw new RuntimeException("No SYMLINKS.txt encountered");
|
||||||
|
for (Pair<String, String> symlink : symlinks) {
|
||||||
|
NLog.INSTANCE.e("Setup", "Linking " + symlink.first + " to " + symlink.second);
|
||||||
|
Os.symlink(symlink.first, symlink.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stagingPrefixFile.renameTo(prefixPath)) {
|
||||||
|
throw new RuntimeException("Unable to rename staging folder");
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
resultListener.onResult(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (final Exception e) {
|
||||||
|
NLog.INSTANCE.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
resultListener.onResult(e);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Activity already dismissed - ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
progressDialog.dismiss();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Activity already dismissed - ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteFolder(File fileOrDirectory) {
|
||||||
|
File[] children = fileOrDirectory.listFiles();
|
||||||
|
if (children != null) {
|
||||||
|
for (File child : children) {
|
||||||
|
deleteFolder(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fileOrDirectory.delete()) {
|
||||||
|
throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package io.neoterm.component.setup;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface SourceConnection {
|
||||||
|
InputStream getInputStream() throws IOException;
|
||||||
|
|
||||||
|
int getSize();
|
||||||
|
|
||||||
|
void close();
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package io.neoterm.component.setup.connection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import io.neoterm.R;
|
||||||
|
import io.neoterm.component.setup.SetupHelper;
|
||||||
|
import io.neoterm.component.setup.SourceConnection;
|
||||||
|
import io.neoterm.frontend.config.NeoPreference;
|
||||||
|
import io.neoterm.frontend.config.NeoTermPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class NetworkConnection implements SourceConnection {
|
||||||
|
private HttpURLConnection connection = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
if (connection == null) {
|
||||||
|
connection = openBaseFileConnection();
|
||||||
|
connection.setConnectTimeout(8000);
|
||||||
|
connection.setReadTimeout(8000);
|
||||||
|
}
|
||||||
|
return connection.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
if (connection != null) {
|
||||||
|
return connection.getContentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpURLConnection openBaseFileConnection() throws IOException {
|
||||||
|
String arch = SetupHelper.determineArchName();
|
||||||
|
String baseUrl = NeoTermPath.INSTANCE.getSERVER_BASE_URL();
|
||||||
|
|
||||||
|
// Use the same source
|
||||||
|
NeoPreference.INSTANCE.store(R.string.key_package_source, baseUrl);
|
||||||
|
|
||||||
|
return (HttpURLConnection) new URL(baseUrl + "/boot/" + arch + ".zip").openConnection();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package io.neoterm.component.setup.connection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import io.neoterm.App;
|
||||||
|
import io.neoterm.component.setup.SetupHelper;
|
||||||
|
import io.neoterm.component.setup.SourceConnection;
|
||||||
|
import io.neoterm.utils.AssetsUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class OfflineConnection implements SourceConnection {
|
||||||
|
private InputStream inputStream;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
if (inputStream == null) {
|
||||||
|
String arch = SetupHelper.determineArchName();
|
||||||
|
String fileName = "offline_setup/" + arch + ".zip";
|
||||||
|
inputStream = AssetsUtils.INSTANCE.openAssetsFile(App.Companion.get(), fileName);
|
||||||
|
}
|
||||||
|
return inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
return inputStream.available();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (inputStream != null) {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException ignore) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,8 @@
|
|||||||
package io.neoterm.ui.setup
|
package io.neoterm.ui.setup
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import android.support.v7.app.AppCompatActivity
|
import android.support.v7.app.AppCompatActivity
|
||||||
import io.neoterm.App
|
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.component.setup.BaseFileInstaller
|
|
||||||
import io.neoterm.utils.PackageUtils
|
import io.neoterm.utils.PackageUtils
|
||||||
|
|
||||||
|
|
||||||
@ -19,35 +15,36 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.ui_setup)
|
setContentView(R.layout.ui_setup)
|
||||||
installBaseFiles()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun installBaseFiles() {
|
private fun setup() {
|
||||||
var resultListener: BaseFileInstaller.ResultListener? = null
|
// TODO: Refactor
|
||||||
resultListener = BaseFileInstaller.ResultListener { error ->
|
// var resultListener: SetupHelper.ResultListener? = null
|
||||||
if (error == null) {
|
// resultListener = SetupHelper.ResultListener { error ->
|
||||||
setResult(Activity.RESULT_OK)
|
// if (error == null) {
|
||||||
PackageUtils.syncSource()
|
// setResult(Activity.RESULT_OK)
|
||||||
executeAptUpdate()
|
// PackageUtils.syncSource()
|
||||||
} else {
|
// executeAptUpdate()
|
||||||
AlertDialog.Builder(this@SetupActivity)
|
// } else {
|
||||||
.setTitle(R.string.error)
|
// AlertDialog.Builder(this@SetupActivity)
|
||||||
.setMessage(error.toString())
|
// .setTitle(R.string.error)
|
||||||
.setNegativeButton(R.string.use_system_shell, { _, _ ->
|
// .setMessage(error.toString())
|
||||||
setResult(Activity.RESULT_CANCELED)
|
// .setNegativeButton(R.string.use_system_shell, { _, _ ->
|
||||||
finish()
|
// setResult(Activity.RESULT_CANCELED)
|
||||||
})
|
// finish()
|
||||||
.setPositiveButton(R.string.retry, { dialog, _ ->
|
// })
|
||||||
dialog.dismiss()
|
// .setPositiveButton(R.string.retry, { dialog, _ ->
|
||||||
BaseFileInstaller.installBaseFiles(this@SetupActivity, resultListener)
|
// dialog.dismiss()
|
||||||
})
|
// SetupHelper.setup(this@SetupActivity, resultListener)
|
||||||
.setNeutralButton(R.string.show_help, { _, _ ->
|
// })
|
||||||
App.get().openHelpLink()
|
// .setNeutralButton(R.string.show_help, { _, _ ->
|
||||||
})
|
// App.get().openHelpLink()
|
||||||
.show()
|
// })
|
||||||
}
|
// .show()
|
||||||
}
|
// }
|
||||||
BaseFileInstaller.installBaseFiles(this, resultListener)
|
// }
|
||||||
|
// SetupHelper.setup(this, resultListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun executeAptUpdate() {
|
private fun executeAptUpdate() {
|
||||||
|
@ -20,7 +20,7 @@ import de.mrapp.android.tabswitcher.*
|
|||||||
import io.neoterm.App
|
import io.neoterm.App
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
import io.neoterm.component.setup.BaseFileInstaller
|
import io.neoterm.component.setup.SetupHelper
|
||||||
import io.neoterm.frontend.session.shell.client.TermSessionCallback
|
import io.neoterm.frontend.session.shell.client.TermSessionCallback
|
||||||
import io.neoterm.frontend.session.shell.client.TermViewClient
|
import io.neoterm.frontend.session.shell.client.TermViewClient
|
||||||
import io.neoterm.frontend.session.shell.client.event.*
|
import io.neoterm.frontend.session.shell.client.event.*
|
||||||
@ -319,7 +319,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isRecreating()) {
|
if (!isRecreating()) {
|
||||||
if (BaseFileInstaller.needSetup()) {
|
if (SetupHelper.needSetup()) {
|
||||||
val intent = Intent(this, SetupActivity::class.java)
|
val intent = Intent(this, SetupActivity::class.java)
|
||||||
startActivityForResult(intent, REQUEST_SETUP)
|
startActivityForResult(intent, REQUEST_SETUP)
|
||||||
return
|
return
|
||||||
|
@ -2,6 +2,7 @@ package io.neoterm.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kiva
|
* @author kiva
|
||||||
@ -18,4 +19,9 @@ object AssetsUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openAssetsFile(context: Context, fileName: String) : InputStream {
|
||||||
|
val assets = context.assets
|
||||||
|
return assets.open(fileName)
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user