Пофиксил получение имени файла с unicode
This commit is contained in:
parent
ea937b12f3
commit
54c6b9a28e
|
|
@ -16,10 +16,18 @@ public class App {
|
||||||
private static final String LOG_CONTEXT = App.class.getSimpleName();
|
private static final String LOG_CONTEXT = App.class.getSimpleName();
|
||||||
private List<ContainerMetadata> containers;
|
private List<ContainerMetadata> containers;
|
||||||
private final Console console;
|
private final Console console;
|
||||||
private DeviceContext deviceContext;
|
private Device device;
|
||||||
|
private final static String BACKUP_FOLDER = "backups";
|
||||||
|
|
||||||
|
private File backupFolder;
|
||||||
|
|
||||||
public App() {
|
public App() {
|
||||||
|
|
||||||
|
backupFolder = new File(BACKUP_FOLDER);
|
||||||
|
|
||||||
|
if(!backupFolder.exists()){
|
||||||
|
backupFolder.mkdir();
|
||||||
|
}
|
||||||
|
|
||||||
console = new Console();
|
console = new Console();
|
||||||
try {
|
try {
|
||||||
|
|
@ -94,7 +102,7 @@ public class App {
|
||||||
backupName = suggestion;
|
backupName = suggestion;
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputFile = new File(backupName + ".tar.gz");
|
var outputFile = new File(backupFolder,backupName + ".tar.gz");
|
||||||
|
|
||||||
|
|
||||||
List<FileMetadata> files;
|
List<FileMetadata> files;
|
||||||
|
|
@ -104,7 +112,9 @@ public class App {
|
||||||
throw new RuntimeException("Error loading container file tree", e);
|
throw new RuntimeException("Error loading container file tree", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopContainer(container);
|
if(console.confirm("Container can be stopped now. Stop it?")) {
|
||||||
|
stopContainer(container);
|
||||||
|
}
|
||||||
container = validate(container);
|
container = validate(container);
|
||||||
SystemLogger.message("Building image layer from remote files", LOG_CONTEXT);
|
SystemLogger.message("Building image layer from remote files", LOG_CONTEXT);
|
||||||
createSystemLayer(container, files, layerFile);
|
createSystemLayer(container, files, layerFile);
|
||||||
|
|
@ -120,14 +130,16 @@ public class App {
|
||||||
layerFile.delete();
|
layerFile.delete();
|
||||||
}
|
}
|
||||||
SystemLogger.message("Backup is done ", LOG_CONTEXT);
|
SystemLogger.message("Backup is done ", LOG_CONTEXT);
|
||||||
console.pause();
|
if (!container.isRunning() && console.confirm("Do you want to start container?")) {
|
||||||
|
startContainer(container);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopContainer(ContainerMetadata container) {
|
private void stopContainer(ContainerMetadata container) {
|
||||||
SystemLogger.message("Stopping container " + container, LOG_CONTEXT);
|
SystemLogger.message("Stopping container " + container, LOG_CONTEXT);
|
||||||
try {
|
try {
|
||||||
deviceContext.getApiConnection().execute("/container/stop .id=" + container.getId());
|
device.getApiConnection().execute("/container/stop .id=" + container.getId());
|
||||||
Thread.sleep(10000L);
|
Thread.sleep(10000L);
|
||||||
} catch (MikrotikApiException | InterruptedException e) {
|
} catch (MikrotikApiException | InterruptedException e) {
|
||||||
throw new RuntimeException("Error stopping container", e);
|
throw new RuntimeException("Error stopping container", e);
|
||||||
|
|
@ -140,8 +152,18 @@ public class App {
|
||||||
if (!container.isRunning()) {
|
if (!container.isRunning()) {
|
||||||
SystemLogger.warning("The selected container is not running. There is a limitation that requires the container to be running.", LOG_CONTEXT);
|
SystemLogger.warning("The selected container is not running. There is a limitation that requires the container to be running.", LOG_CONTEXT);
|
||||||
console.pause();
|
console.pause();
|
||||||
|
startContainer(container);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startContainer(ContainerMetadata container) {
|
||||||
|
try {
|
||||||
|
if (!container.isRunning()) {
|
||||||
SystemLogger.message("Starting container " + container, LOG_CONTEXT);
|
SystemLogger.message("Starting container " + container, LOG_CONTEXT);
|
||||||
deviceContext.getApiConnection().execute("/container/start .id=" + container.getId());
|
device.getApiConnection().execute("/container/start .id=" + container.getId());
|
||||||
Thread.sleep(10000L);
|
Thread.sleep(10000L);
|
||||||
updateContainers();
|
updateContainers();
|
||||||
var id = container.getId();
|
var id = container.getId();
|
||||||
|
|
@ -164,12 +186,12 @@ public class App {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateContainers() throws MikrotikApiException {
|
private void updateContainers() throws MikrotikApiException {
|
||||||
containers = deviceContext.getContainers();
|
containers = device.getContainers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createSystemLayer(ContainerMetadata container, List<FileMetadata> files, File outputFile) {
|
private void createSystemLayer(ContainerMetadata container, List<FileMetadata> files, File outputFile) {
|
||||||
try {
|
try {
|
||||||
var sftp = deviceContext.getSftpClient();
|
var sftp = device.getSftpClient();
|
||||||
try (var outputStream = new FileOutputStream(outputFile)) {
|
try (var outputStream = new FileOutputStream(outputFile)) {
|
||||||
try (var tar = new TarArchiveOutputStream(outputStream)) {
|
try (var tar = new TarArchiveOutputStream(outputStream)) {
|
||||||
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
|
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
|
||||||
|
|
@ -223,7 +245,7 @@ public class App {
|
||||||
SystemLogger.message("Reading container filesystem metadata...", LOG_CONTEXT);
|
SystemLogger.message("Reading container filesystem metadata...", LOG_CONTEXT);
|
||||||
var systemDirs = List.of("/sys/", "/proc/", "/dev/", "/mnt/", "/run/", "/tmp/");
|
var systemDirs = List.of("/sys/", "/proc/", "/dev/", "/mnt/", "/run/", "/tmp/");
|
||||||
var filterFiles = List.of("/.type");
|
var filterFiles = List.of("/.type");
|
||||||
var files = deviceContext.listFilesystem(container).stream().filter(fileMetadata -> !filterFiles.contains(fileMetadata.getPath())).filter(f -> systemDirs.contains(f.getPath()) || systemDirs.stream().noneMatch(s -> f.getPath().startsWith(s))).toList();
|
var files = device.listFilesystem(container).stream().filter(fileMetadata -> !filterFiles.contains(fileMetadata.getPath())).filter(f -> systemDirs.contains(f.getPath()) || systemDirs.stream().noneMatch(s -> f.getPath().startsWith(s))).toList();
|
||||||
SystemLogger.message("Found " + files.size() + " files", LOG_CONTEXT);
|
SystemLogger.message("Found " + files.size() + " files", LOG_CONTEXT);
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
@ -238,16 +260,16 @@ public class App {
|
||||||
|
|
||||||
console.clear();
|
console.clear();
|
||||||
|
|
||||||
deviceContext = new DeviceContext(host, username, password);
|
device = new Device(host, username, password);
|
||||||
deviceContext.getApiConnection();
|
device.getApiConnection();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
SystemLogger.error("Unable to connect", LOG_CONTEXT, e);
|
SystemLogger.error("Unable to connect", LOG_CONTEXT, e);
|
||||||
if (deviceContext != null) {
|
if (device != null) {
|
||||||
deviceContext.close();
|
device.close();
|
||||||
}
|
}
|
||||||
deviceContext = null;
|
device = null;
|
||||||
}
|
}
|
||||||
} while (deviceContext == null);
|
} while (device == null);
|
||||||
|
|
||||||
SystemLogger.message("Connected", LOG_CONTEXT);
|
SystemLogger.message("Connected", LOG_CONTEXT);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,14 @@ public class Console implements Closeable {
|
||||||
if (values.size() > 1) {
|
if (values.size() > 1) {
|
||||||
do {
|
do {
|
||||||
clear();
|
clear();
|
||||||
System.out.println(caption + ":");
|
System.out.println(caption);
|
||||||
var i = 0;
|
var i = 0;
|
||||||
for (var row : values) {
|
for (var row : values) {
|
||||||
System.out.println(String.valueOf(++i) + ' ' + row);
|
System.out.println(String.valueOf(++i) + ' ' + row);
|
||||||
}
|
}
|
||||||
index = -1;
|
index = -1;
|
||||||
|
|
||||||
|
System.out.print("Select item: ");
|
||||||
try {
|
try {
|
||||||
index = Integer.parseInt(read()) - 1;
|
index = Integer.parseInt(read()) - 1;
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,16 @@ import ru.kirillius.utils.logging.SystemLogger;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class DeviceContext implements Closeable {
|
public class Device implements Closeable {
|
||||||
|
|
||||||
private final static String LOG_CONTEXT = DeviceContext.class.getSimpleName();
|
private final static String LOG_CONTEXT = Device.class.getSimpleName();
|
||||||
|
private final static String LIST_COMMAND = "ls --color=never -lAnpR --full-time /";
|
||||||
|
private final static String BEGIN_PATTERN = "///BEGIN-OF-STREAM///";
|
||||||
|
private final static String END_PATTERN = "///END-OF-STREAM///";
|
||||||
private final String host;
|
private final String host;
|
||||||
private final String username;
|
private final String username;
|
||||||
private final String password;
|
private final String password;
|
||||||
|
|
@ -24,10 +28,12 @@ public class DeviceContext implements Closeable {
|
||||||
private SSHClient sshClient;
|
private SSHClient sshClient;
|
||||||
private ApiConnection apiConnection;
|
private ApiConnection apiConnection;
|
||||||
|
|
||||||
public DeviceContext(String host, String username, String password) {
|
public Device(String host, String username, String password) {
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SFTPClient getSftpClient() throws IOException {
|
public SFTPClient getSftpClient() throws IOException {
|
||||||
|
|
@ -42,6 +48,14 @@ public class DeviceContext implements Closeable {
|
||||||
return getApiConnection().execute("/container/print").stream().map(ContainerMetadata::new).toList();
|
return getApiConnection().execute("/container/print").stream().map(ContainerMetadata::new).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String bytesToString(List<Byte> list) {
|
||||||
|
var bytes = new byte[list.size()];
|
||||||
|
for (var i = 0; i < list.size(); i++) {
|
||||||
|
bytes[i] = list.get(i);
|
||||||
|
}
|
||||||
|
return new String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
private String stripBashSlashes(String path) {
|
private String stripBashSlashes(String path) {
|
||||||
var escaped = false;
|
var escaped = false;
|
||||||
var doubleQuotes = false;
|
var doubleQuotes = false;
|
||||||
|
|
@ -55,6 +69,35 @@ public class DeviceContext implements Closeable {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path.contains("'$'")) {
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
var first = true;
|
||||||
|
for (var part : path.split(Pattern.quote("'$'"))) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
builder.append(part);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var group = part.split(Pattern.quote("''"));
|
||||||
|
if (group.length != 2) {
|
||||||
|
SystemLogger.warning("Unable to parse file path: " + path, LOG_CONTEXT);
|
||||||
|
builder.append(part);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
|
||||||
|
var encoded = group[0];
|
||||||
|
var bytes = new ArrayList<Byte>();
|
||||||
|
for (var code : Arrays.stream(encoded.split(Pattern.quote("\\"))).skip(1).toArray(String[]::new)) {
|
||||||
|
bytes.add((byte) Integer.parseInt(code, 8));
|
||||||
|
}
|
||||||
|
builder.append(bytesToString(bytes));
|
||||||
|
builder.append(group[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path = builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
path = path.substring(1, path.length() - 1);
|
path = path.substring(1, path.length() - 1);
|
||||||
|
|
||||||
if (doubleQuotes) {
|
if (doubleQuotes) {
|
||||||
|
|
@ -69,11 +112,6 @@ public class DeviceContext implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static String LIST_COMMAND = "ls --color=never -lAnpR --full-time /";
|
|
||||||
private final static String BEGIN_PATTERN = ":::BEGIN-OF-STREAM:::";
|
|
||||||
private final static String END_PATTERN = ":::END-OF-STREAM:::";
|
|
||||||
|
|
||||||
|
|
||||||
public List<FileMetadata> listFilesystem(ContainerMetadata container) throws IOException, InterruptedException {
|
public List<FileMetadata> listFilesystem(ContainerMetadata container) throws IOException, InterruptedException {
|
||||||
var files = new ArrayList<FileMetadata>();
|
var files = new ArrayList<FileMetadata>();
|
||||||
|
|
||||||
|
|
@ -102,6 +140,10 @@ public class DeviceContext implements Closeable {
|
||||||
if (line.endsWith(END_PATTERN)) {
|
if (line.endsWith(END_PATTERN)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (line.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if ((line.startsWith("/") || line.startsWith("'/")) && line.endsWith(":")) {
|
if ((line.startsWith("/") || line.startsWith("'/")) && line.endsWith(":")) {
|
||||||
directory = line.substring(0, line.length() - 1);
|
directory = line.substring(0, line.length() - 1);
|
||||||
if (directory.indexOf('\'') == 0) {
|
if (directory.indexOf('\'') == 0) {
|
||||||
|
|
@ -8,6 +8,7 @@ public class DownloadStatus {
|
||||||
private long startTime;
|
private long startTime;
|
||||||
private String label;
|
private String label;
|
||||||
private long lastUpdate = 0;
|
private long lastUpdate = 0;
|
||||||
|
private long lastBytes = 0;
|
||||||
|
|
||||||
public DownloadStatus(String label, long total) {
|
public DownloadStatus(String label, long total) {
|
||||||
this.label = label;
|
this.label = label;
|
||||||
|
|
@ -56,7 +57,8 @@ public class DownloadStatus {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
clear();
|
clear();
|
||||||
var speed = bytes * 1000 / (System.currentTimeMillis() - startTime);
|
var speed = (bytes - lastBytes) * 1000 / (System.currentTimeMillis() - lastUpdate);
|
||||||
|
lastBytes = bytes;
|
||||||
buffer = ("[" + spinner() + "] " + label + " " + Math.floor((float) copied / total * 1000f) / 10 + "% " + copied + "/" + total + " (" + formatBytes(bytes) + ") " + formatBytes(speed) + "/s");
|
buffer = ("[" + spinner() + "] " + label + " " + Math.floor((float) copied / total * 1000f) / 10 + "% " + copied + "/" + total + " (" + formatBytes(bytes) + ") " + formatBytes(speed) + "/s");
|
||||||
System.out.print(buffer);
|
System.out.print(buffer);
|
||||||
lastUpdate = System.currentTimeMillis();
|
lastUpdate = System.currentTimeMillis();
|
||||||
|
|
@ -72,6 +74,7 @@ public class DownloadStatus {
|
||||||
public void finish() {
|
public void finish() {
|
||||||
lastUpdate = 0;
|
lastUpdate = 0;
|
||||||
update();
|
update();
|
||||||
|
System.out.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue