2017-12-20 23:49:53 +08:00
|
|
|
package io.neoterm.setup;
|
2017-12-16 16:59:59 +08:00
|
|
|
|
|
|
|
import android.app.ProgressDialog;
|
2021-04-20 23:21:51 +03:00
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
2017-12-16 16:59:59 +08:00
|
|
|
import android.system.Os;
|
|
|
|
import android.util.Pair;
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileOutputStream;
|
2018-07-31 22:31:43 +08:00
|
|
|
import java.io.IOException;
|
2017-12-16 16:59:59 +08:00
|
|
|
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;
|
2021-04-20 23:21:51 +03:00
|
|
|
private final AppCompatActivity activity;
|
2017-12-16 16:59:59 +08:00
|
|
|
private final ResultListener resultListener;
|
|
|
|
private final ProgressDialog progressDialog;
|
|
|
|
|
2021-04-20 23:21:51 +03:00
|
|
|
public SetupThread(AppCompatActivity activity, SourceConnection sourceConnection,
|
2017-12-16 16:59:59 +08:00
|
|
|
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 totalReadBytes = 0;
|
|
|
|
final byte[] buffer = new byte[8096];
|
|
|
|
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
|
|
|
|
|
|
|
|
|
|
|
try (ZipInputStream zipInput = new ZipInputStream(sourceConnection.getInputStream())) {
|
|
|
|
ZipEntry zipEntry;
|
|
|
|
|
2018-09-10 22:38:57 +08:00
|
|
|
int totalBytes = sourceConnection.getSize();
|
|
|
|
|
2017-12-16 16:59:59 +08:00
|
|
|
while ((zipEntry = zipInput.getNextEntry()) != null) {
|
|
|
|
totalReadBytes += zipEntry.getCompressedSize();
|
|
|
|
|
|
|
|
final int totalReadBytesFinal = totalReadBytes;
|
|
|
|
final int totalBytesFinal = totalBytes;
|
|
|
|
|
2018-03-25 00:39:46 +08:00
|
|
|
activity.runOnUiThread(() -> {
|
|
|
|
try {
|
|
|
|
double progressFloat = ((double) totalReadBytesFinal) / ((double) totalBytesFinal) * 100.0;
|
|
|
|
progressDialog.setProgress((int) progressFloat);
|
|
|
|
} catch (RuntimeException ignore) {
|
|
|
|
// activity dismissed
|
2017-12-16 16:59:59 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2018-07-31 22:31:43 +08:00
|
|
|
activity.runOnUiThread(() -> resultListener.onResult(null));
|
2017-12-16 16:59:59 +08:00
|
|
|
} catch (final Exception e) {
|
|
|
|
NLog.INSTANCE.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
|
2018-07-31 22:31:43 +08:00
|
|
|
activity.runOnUiThread(() -> {
|
|
|
|
try {
|
|
|
|
resultListener.onResult(e);
|
|
|
|
} catch (RuntimeException e1) {
|
|
|
|
// Activity already dismissed - ignore.
|
2017-12-16 16:59:59 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} finally {
|
2018-07-31 22:31:43 +08:00
|
|
|
activity.runOnUiThread(() -> {
|
|
|
|
try {
|
|
|
|
progressDialog.dismiss();
|
|
|
|
} catch (RuntimeException e) {
|
|
|
|
// Activity already dismissed - ignore.
|
2017-12-16 16:59:59 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-31 22:31:43 +08:00
|
|
|
private static void deleteFolder(File fileOrDirectory) throws IOException {
|
|
|
|
if (fileOrDirectory.getCanonicalPath().equals(fileOrDirectory.getAbsolutePath()) && fileOrDirectory.isDirectory()) {
|
|
|
|
File[] children = fileOrDirectory.listFiles();
|
|
|
|
|
|
|
|
if (children != null) {
|
|
|
|
for (File child : children) {
|
|
|
|
deleteFolder(child);
|
|
|
|
}
|
2017-12-16 16:59:59 +08:00
|
|
|
}
|
|
|
|
}
|
2018-07-31 22:31:43 +08:00
|
|
|
|
2017-12-16 16:59:59 +08:00
|
|
|
if (!fileOrDirectory.delete()) {
|
2018-07-31 22:31:43 +08:00
|
|
|
throw new RuntimeException("Unable to delete "
|
|
|
|
+ (fileOrDirectory.isDirectory() ? "directory " : "file ")
|
|
|
|
+ fileOrDirectory.getAbsolutePath());
|
2017-12-16 16:59:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|