Пофиксил получение имени файла с unicode

This commit is contained in:
kirill.labutin 2025-02-07 01:02:55 +03:00
parent ea937b12f3
commit 54c6b9a28e
4 changed files with 93 additions and 25 deletions

View File

@ -16,10 +16,18 @@ public class App {
private static final String LOG_CONTEXT = App.class.getSimpleName();
private List<ContainerMetadata> containers;
private final Console console;
private DeviceContext deviceContext;
private Device device;
private final static String BACKUP_FOLDER = "backups";
private File backupFolder;
public App() {
backupFolder = new File(BACKUP_FOLDER);
if(!backupFolder.exists()){
backupFolder.mkdir();
}
console = new Console();
try {
@ -94,7 +102,7 @@ public class App {
backupName = suggestion;
}
var outputFile = new File(backupName + ".tar.gz");
var outputFile = new File(backupFolder,backupName + ".tar.gz");
List<FileMetadata> files;
@ -104,7 +112,9 @@ public class App {
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);
SystemLogger.message("Building image layer from remote files", LOG_CONTEXT);
createSystemLayer(container, files, layerFile);
@ -120,14 +130,16 @@ public class App {
layerFile.delete();
}
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) {
SystemLogger.message("Stopping container " + container, LOG_CONTEXT);
try {
deviceContext.getApiConnection().execute("/container/stop .id=" + container.getId());
device.getApiConnection().execute("/container/stop .id=" + container.getId());
Thread.sleep(10000L);
} catch (MikrotikApiException | InterruptedException e) {
throw new RuntimeException("Error stopping container", e);
@ -140,8 +152,18 @@ public class App {
if (!container.isRunning()) {
SystemLogger.warning("The selected container is not running. There is a limitation that requires the container to be running.", LOG_CONTEXT);
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);
deviceContext.getApiConnection().execute("/container/start .id=" + container.getId());
device.getApiConnection().execute("/container/start .id=" + container.getId());
Thread.sleep(10000L);
updateContainers();
var id = container.getId();
@ -164,12 +186,12 @@ public class App {
}
private void updateContainers() throws MikrotikApiException {
containers = deviceContext.getContainers();
containers = device.getContainers();
}
private void createSystemLayer(ContainerMetadata container, List<FileMetadata> files, File outputFile) {
try {
var sftp = deviceContext.getSftpClient();
var sftp = device.getSftpClient();
try (var outputStream = new FileOutputStream(outputFile)) {
try (var tar = new TarArchiveOutputStream(outputStream)) {
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
@ -223,7 +245,7 @@ public class App {
SystemLogger.message("Reading container filesystem metadata...", LOG_CONTEXT);
var systemDirs = List.of("/sys/", "/proc/", "/dev/", "/mnt/", "/run/", "/tmp/");
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);
return files;
}
@ -238,16 +260,16 @@ public class App {
console.clear();
deviceContext = new DeviceContext(host, username, password);
deviceContext.getApiConnection();
device = new Device(host, username, password);
device.getApiConnection();
} catch (Exception e) {
SystemLogger.error("Unable to connect", LOG_CONTEXT, e);
if (deviceContext != null) {
deviceContext.close();
if (device != null) {
device.close();
}
deviceContext = null;
device = null;
}
} while (deviceContext == null);
} while (device == null);
SystemLogger.message("Connected", LOG_CONTEXT);
}

View File

@ -69,13 +69,14 @@ public class Console implements Closeable {
if (values.size() > 1) {
do {
clear();
System.out.println(caption + ":");
System.out.println(caption);
var i = 0;
for (var row : values) {
System.out.println(String.valueOf(++i) + ' ' + row);
}
index = -1;
System.out.print("Select item: ");
try {
index = Integer.parseInt(read()) - 1;
} catch (NumberFormatException ignored) {

View File

@ -11,12 +11,16 @@ import ru.kirillius.utils.logging.SystemLogger;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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 username;
private final String password;
@ -24,10 +28,12 @@ public class DeviceContext implements Closeable {
private SSHClient sshClient;
private ApiConnection apiConnection;
public DeviceContext(String host, String username, String password) {
public Device(String host, String username, String password) {
this.host = host;
this.username = username;
this.password = password;
}
public SFTPClient getSftpClient() throws IOException {
@ -42,6 +48,14 @@ public class DeviceContext implements Closeable {
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) {
var escaped = false;
var doubleQuotes = false;
@ -55,6 +69,35 @@ public class DeviceContext implements Closeable {
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);
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 {
var files = new ArrayList<FileMetadata>();
@ -102,6 +140,10 @@ public class DeviceContext implements Closeable {
if (line.endsWith(END_PATTERN)) {
break;
}
if (line.isBlank()) {
continue;
}
if ((line.startsWith("/") || line.startsWith("'/")) && line.endsWith(":")) {
directory = line.substring(0, line.length() - 1);
if (directory.indexOf('\'') == 0) {

View File

@ -8,6 +8,7 @@ public class DownloadStatus {
private long startTime;
private String label;
private long lastUpdate = 0;
private long lastBytes = 0;
public DownloadStatus(String label, long total) {
this.label = label;
@ -56,7 +57,8 @@ public class DownloadStatus {
return;
}
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");
System.out.print(buffer);
lastUpdate = System.currentTimeMillis();
@ -72,6 +74,7 @@ public class DownloadStatus {
public void finish() {
lastUpdate = 0;
update();
System.out.println();
}
}