mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 09:00:26 +00:00
File transfer fixes
This commit is contained in:
parent
d41b3017f6
commit
a7d825be67
11 changed files with 237 additions and 155 deletions
|
@ -0,0 +1,83 @@
|
||||||
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.util.BooleanScope;
|
||||||
|
import io.xpipe.app.util.FileBridge;
|
||||||
|
import io.xpipe.app.util.FileOpener;
|
||||||
|
import io.xpipe.core.store.FileNames;
|
||||||
|
import io.xpipe.core.store.FileSystem;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class BrowserFileOpener {
|
||||||
|
|
||||||
|
public static void openWithAnyApplication(OpenFileSystemModel model, FileSystem.FileEntry entry) {
|
||||||
|
var file = entry.getPath();
|
||||||
|
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
||||||
|
FileBridge.get()
|
||||||
|
.openIO(
|
||||||
|
FileNames.getFileName(file),
|
||||||
|
key,
|
||||||
|
new BooleanScope(model.getBusy()).exclusive(),
|
||||||
|
() -> {
|
||||||
|
return entry.getFileSystem().openInput(file);
|
||||||
|
},
|
||||||
|
(size) -> {
|
||||||
|
if (model.isClosed()) {
|
||||||
|
return OutputStream.nullOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.getFileSystem().openOutput(file, size);
|
||||||
|
},
|
||||||
|
s -> FileOpener.openWithAnyApplication(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openInDefaultApplication(OpenFileSystemModel model, FileSystem.FileEntry entry) {
|
||||||
|
var file = entry.getPath();
|
||||||
|
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
||||||
|
FileBridge.get()
|
||||||
|
.openIO(
|
||||||
|
FileNames.getFileName(file),
|
||||||
|
key,
|
||||||
|
new BooleanScope(model.getBusy()).exclusive(),
|
||||||
|
() -> {
|
||||||
|
return entry.getFileSystem().openInput(file);
|
||||||
|
},
|
||||||
|
(size) -> {
|
||||||
|
if (model.isClosed()) {
|
||||||
|
return OutputStream.nullOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.getFileSystem().openOutput(file, size);
|
||||||
|
},
|
||||||
|
s -> FileOpener.openInDefaultApplication(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openInTextEditor(OpenFileSystemModel model, FileSystem.FileEntry entry) {
|
||||||
|
var editor = AppPrefs.get().externalEditor().getValue();
|
||||||
|
if (editor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = entry.getPath();
|
||||||
|
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
||||||
|
FileBridge.get()
|
||||||
|
.openIO(
|
||||||
|
FileNames.getFileName(file),
|
||||||
|
key,
|
||||||
|
new BooleanScope(model.getBusy()).exclusive(),
|
||||||
|
() -> {
|
||||||
|
return entry.getFileSystem().openInput(file);
|
||||||
|
|
||||||
|
},
|
||||||
|
(size) -> {
|
||||||
|
if (model.isClosed()) {
|
||||||
|
return OutputStream.nullOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.getFileSystem().openOutput(file, size);
|
||||||
|
},
|
||||||
|
FileOpener::openInTextEditor);
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ public interface LeafAction extends BrowserAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.execute(model.getBusy(), () -> {
|
BooleanScope.executeExclusive(model.getBusy(), () -> {
|
||||||
// Start shell in case we exited
|
// Start shell in case we exited
|
||||||
model.getFileSystem().getShell().orElseThrow().start();
|
model.getFileSystem().getShell().orElseThrow().start();
|
||||||
execute(model, selected);
|
execute(model, selected);
|
||||||
|
@ -77,7 +77,7 @@ public interface LeafAction extends BrowserAction {
|
||||||
}));
|
}));
|
||||||
mi.setOnAction(event -> {
|
mi.setOnAction(event -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.execute(model.getBusy(), () -> {
|
BooleanScope.executeExclusive(model.getBusy(), () -> {
|
||||||
// Start shell in case we exited
|
// Start shell in case we exited
|
||||||
model.getFileSystem().getShell().orElseThrow().start();
|
model.getFileSystem().getShell().orElseThrow().start();
|
||||||
execute(model, selected);
|
execute(model, selected);
|
||||||
|
|
|
@ -79,7 +79,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
var fs = entry.getStore().createFileSystem();
|
var fs = entry.getStore().createFileSystem();
|
||||||
if (fs.getShell().isPresent()) {
|
if (fs.getShell().isPresent()) {
|
||||||
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
|
ProcessControlProvider.get().withDefaultScripts(fs.getShell().get());
|
||||||
|
@ -100,7 +100,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (entry.getStore() instanceof ShellStore s) {
|
if (entry.getStore() instanceof ShellStore s) {
|
||||||
c.accept(fileSystem.getShell().orElseThrow());
|
c.accept(fileSystem.getShell().orElseThrow());
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
|
@ -153,7 +153,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
cdSyncWithoutCheck(currentPath.get());
|
cdSyncWithoutCheck(currentPath.get());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -339,7 +339,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
|
|
||||||
public void dropLocalFilesIntoAsync(FileSystem.FileEntry entry, List<Path> files) {
|
public void dropLocalFilesIntoAsync(FileSystem.FileEntry entry, List<Path> files) {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -361,7 +361,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -384,7 +384,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -408,7 +408,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -431,7 +431,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -466,7 +466,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem.getShell().isPresent()) {
|
if (fileSystem.getShell().isPresent()) {
|
||||||
var connection = fileSystem.getShell().get();
|
var connection = fileSystem.getShell().get();
|
||||||
var name = (directory != null ? directory + " - " : "")
|
var name = (directory != null ? directory + " - " : "")
|
||||||
|
|
|
@ -1,27 +1,17 @@
|
||||||
package io.xpipe.app.util;
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
|
||||||
import io.xpipe.core.util.FailableRunnable;
|
import io.xpipe.core.util.FailableRunnable;
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
|
||||||
public class BooleanScope implements AutoCloseable {
|
public class BooleanScope implements AutoCloseable {
|
||||||
|
|
||||||
private final BooleanProperty prop;
|
private final BooleanProperty prop;
|
||||||
private boolean invert;
|
|
||||||
private boolean forcePlatform;
|
|
||||||
private boolean wait;
|
private boolean wait;
|
||||||
|
|
||||||
public BooleanScope(BooleanProperty prop) {
|
public BooleanScope(BooleanProperty prop) {
|
||||||
this.prop = prop;
|
this.prop = prop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <E extends Throwable> void execute(BooleanProperty prop, FailableRunnable<E> r) throws E {
|
|
||||||
try (var ignored = new BooleanScope(prop).start()) {
|
|
||||||
r.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <E extends Throwable> void executeExclusive(BooleanProperty prop, FailableRunnable<E> r) throws E {
|
public static <E extends Throwable> void executeExclusive(BooleanProperty prop, FailableRunnable<E> r) throws E {
|
||||||
try (var ignored = new BooleanScope(prop).exclusive().start()) {
|
try (var ignored = new BooleanScope(prop).exclusive().start()) {
|
||||||
r.run();
|
r.run();
|
||||||
|
@ -33,37 +23,19 @@ public class BooleanScope implements AutoCloseable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanScope invert() {
|
public synchronized BooleanScope start() {
|
||||||
this.invert = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanScope forcePlatform() {
|
|
||||||
this.forcePlatform = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanScope start() {
|
|
||||||
if (wait) {
|
if (wait) {
|
||||||
while (!invert == prop.get()) {
|
while (prop.get()) {
|
||||||
ThreadHelper.sleep(50);
|
ThreadHelper.sleep(50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (forcePlatform) {
|
prop.setValue(true);
|
||||||
PlatformThread.runLaterIfNeeded(() -> prop.setValue(!invert));
|
|
||||||
} else {
|
|
||||||
prop.setValue(!invert);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public synchronized void close() {
|
||||||
if (forcePlatform) {
|
prop.setValue(false);
|
||||||
PlatformThread.runLaterIfNeeded(() -> prop.setValue(invert));
|
|
||||||
} else {
|
|
||||||
prop.setValue(invert);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,18 @@ import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.util.FailableFunction;
|
import io.xpipe.core.util.FailableFunction;
|
||||||
import io.xpipe.core.util.FailableSupplier;
|
import io.xpipe.core.util.FailableSupplier;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedInputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardWatchEventKinds;
|
import java.nio.file.StandardWatchEventKinds;
|
||||||
import java.nio.file.WatchEvent;
|
import java.nio.file.WatchEvent;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
@ -24,6 +26,37 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
public class FileBridge {
|
public class FileBridge {
|
||||||
|
|
||||||
|
private static class FixedSizeInputStream extends SimpleFilterInputStream {
|
||||||
|
|
||||||
|
private long count;
|
||||||
|
private final long size;
|
||||||
|
|
||||||
|
protected FixedSizeInputStream(InputStream in, long size) {
|
||||||
|
super(in);
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (count >= size) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var read = in.read();
|
||||||
|
count++;
|
||||||
|
if (read == -1) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return (int) (size - count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final Path TEMP = ShellTemp.getLocalTempDataDirectory("bridge");
|
private static final Path TEMP = ShellTemp.getLocalTempDataDirectory("bridge");
|
||||||
private static FileBridge INSTANCE;
|
private static FileBridge INSTANCE;
|
||||||
private final Set<Entry> openEntries = new HashSet<>();
|
private final Set<Entry> openEntries = new HashSet<>();
|
||||||
|
@ -95,16 +128,17 @@ public class FileBridge {
|
||||||
if (e.hasChanged()) {
|
if (e.hasChanged()) {
|
||||||
event("Registering change for file " + TEMP.relativize(e.file) + " for editor entry " + e.getName());
|
event("Registering change for file " + TEMP.relativize(e.file) + " for editor entry " + e.getName());
|
||||||
e.registerChange();
|
e.registerChange();
|
||||||
var expectedSize = Files.size(e.file);
|
|
||||||
try (var in = Files.newInputStream(e.file)) {
|
try (var in = Files.newInputStream(e.file)) {
|
||||||
var actualSize = (long) in.available();
|
var actualSize = (long) in.available();
|
||||||
if (expectedSize != actualSize) {
|
var started = Instant.now();
|
||||||
event("Expected file size " + expectedSize + " but got size " + actualSize + ". Ignoring change ...");
|
try (var fixedIn = new FixedSizeInputStream(new BufferedInputStream(in), actualSize)) {
|
||||||
return;
|
e.writer.accept(fixedIn, actualSize);
|
||||||
}
|
}
|
||||||
|
var taken = Duration.between(started, Instant.now());
|
||||||
e.writer.accept(in, actualSize);
|
event("Wrote " + HumanReadableFormat.byteCount(actualSize) + " in " + taken.toMillis() + "ms");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
event("File doesn't seem to be changed");
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||||
|
@ -134,45 +168,10 @@ public class FileBridge {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openReadOnlyString(String input, Consumer<String> fileConsumer) {
|
|
||||||
if (input == null) {
|
|
||||||
input = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = UUID.randomUUID();
|
|
||||||
String s = input;
|
|
||||||
openIO(
|
|
||||||
id.toString(),
|
|
||||||
id,
|
|
||||||
() -> new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)),
|
|
||||||
null,
|
|
||||||
fileConsumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openString(
|
|
||||||
String keyName, Object key, String input, Consumer<String> output, Consumer<String> fileConsumer) {
|
|
||||||
if (input == null) {
|
|
||||||
input = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
String s = input;
|
|
||||||
openIO(
|
|
||||||
keyName,
|
|
||||||
key,
|
|
||||||
() -> new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)),
|
|
||||||
(size) -> new ByteArrayOutputStream(s.length()) {
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
super.close();
|
|
||||||
output.accept(new String(toByteArray(), StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fileConsumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void openIO(
|
public synchronized void openIO(
|
||||||
String keyName,
|
String keyName,
|
||||||
Object key,
|
Object key,
|
||||||
|
BooleanScope scope,
|
||||||
FailableSupplier<InputStream> input,
|
FailableSupplier<InputStream> input,
|
||||||
FailableFunction<Long, OutputStream, Exception> output,
|
FailableFunction<Long, OutputStream, Exception> output,
|
||||||
Consumer<String> consumer) {
|
Consumer<String> consumer) {
|
||||||
|
@ -206,12 +205,22 @@ public class FileBridge {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry = new Entry(file, key, keyName, (in, size) -> {
|
var entry = new Entry(file, key, keyName, scope, (in, size) -> {
|
||||||
if (output != null) {
|
if (output != null) {
|
||||||
try (var out = output.apply(size)) {
|
if (scope != null) {
|
||||||
in.transferTo(out);
|
try (var ignored = scope.start()) {
|
||||||
} catch (Exception ex) {
|
try (var out = output.apply(size)) {
|
||||||
ErrorEvent.fromThrowable(ex).handle();
|
in.transferTo(out);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try (var out = output.apply(size)) {
|
||||||
|
in.transferTo(out);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ErrorEvent.fromThrowable(ex).handle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -227,13 +236,15 @@ public class FileBridge {
|
||||||
private final Path file;
|
private final Path file;
|
||||||
private final Object key;
|
private final Object key;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
private final BooleanScope scope;
|
||||||
private final BiConsumer<InputStream, Long> writer;
|
private final BiConsumer<InputStream, Long> writer;
|
||||||
private Instant lastModified;
|
private Instant lastModified;
|
||||||
|
|
||||||
public Entry(Path file, Object key, String name, BiConsumer<InputStream, Long> writer) {
|
public Entry(Path file, Object key, String name, BooleanScope scope, BiConsumer<InputStream, Long> writer) {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.scope = scope;
|
||||||
this.writer = writer;
|
this.writer = writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,64 +5,20 @@ import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.core.process.CommandBuilder;
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
import io.xpipe.core.process.CommandControl;
|
import io.xpipe.core.process.CommandControl;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.store.FileNames;
|
|
||||||
import io.xpipe.core.store.FileSystem;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class FileOpener {
|
public class FileOpener {
|
||||||
|
|
||||||
public static void openWithAnyApplication(FileSystem.FileEntry entry) {
|
|
||||||
var file = entry.getPath();
|
|
||||||
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
|
||||||
FileBridge.get()
|
|
||||||
.openIO(
|
|
||||||
FileNames.getFileName(file),
|
|
||||||
key,
|
|
||||||
() -> {
|
|
||||||
return entry.getFileSystem().openInput(file);
|
|
||||||
},
|
|
||||||
(size) -> entry.getFileSystem().openOutput(file, size),
|
|
||||||
s -> openWithAnyApplication(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void openInDefaultApplication(FileSystem.FileEntry entry) {
|
|
||||||
var file = entry.getPath();
|
|
||||||
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
|
||||||
FileBridge.get()
|
|
||||||
.openIO(
|
|
||||||
FileNames.getFileName(file),
|
|
||||||
key,
|
|
||||||
() -> {
|
|
||||||
return entry.getFileSystem().openInput(file);
|
|
||||||
},
|
|
||||||
(size) -> entry.getFileSystem().openOutput(file, size),
|
|
||||||
s -> openInDefaultApplication(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void openInTextEditor(FileSystem.FileEntry entry) {
|
|
||||||
var editor = AppPrefs.get().externalEditor().getValue();
|
|
||||||
if (editor == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = entry.getPath();
|
|
||||||
var key = entry.getPath().hashCode() + entry.getFileSystem().hashCode();
|
|
||||||
FileBridge.get()
|
|
||||||
.openIO(
|
|
||||||
FileNames.getFileName(file),
|
|
||||||
key,
|
|
||||||
() -> {
|
|
||||||
return entry.getFileSystem().openInput(file);
|
|
||||||
},
|
|
||||||
(size) -> entry.getFileSystem().openOutput(file, size),
|
|
||||||
FileOpener::openInTextEditor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void openInTextEditor(String localFile) {
|
public static void openInTextEditor(String localFile) {
|
||||||
var editor = AppPrefs.get().externalEditor().getValue();
|
var editor = AppPrefs.get().externalEditor().getValue();
|
||||||
if (editor == null) {
|
if (editor == null) {
|
||||||
|
@ -119,11 +75,40 @@ public class FileOpener {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openReadOnlyString(String input) {
|
public static void openReadOnlyString(String input) {
|
||||||
FileBridge.get().openReadOnlyString(input, s -> openInTextEditor(s));
|
if (input == null) {
|
||||||
|
input = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = UUID.randomUUID();
|
||||||
|
String s = input;
|
||||||
|
FileBridge.get().openIO(
|
||||||
|
id.toString(),
|
||||||
|
id,
|
||||||
|
null,
|
||||||
|
() -> new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
null,
|
||||||
|
v -> openInTextEditor(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openString(String keyName, Object key, String input, Consumer<String> output) {
|
public static void openString(String keyName, Object key, String input, Consumer<String> output) {
|
||||||
FileBridge.get().openString(keyName, key, input, output, file -> openInTextEditor(file));
|
if (input == null) {
|
||||||
|
input = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String s = input;
|
||||||
|
FileBridge.get().openIO(
|
||||||
|
keyName,
|
||||||
|
key,
|
||||||
|
null,
|
||||||
|
() -> new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
(size) -> new ByteArrayOutputStream(s.length()) {
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
output.accept(new String(toByteArray(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
file -> openInTextEditor(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openCommandOutput(String keyName, Object key, CommandControl cc) {
|
public static void openCommandOutput(String keyName, Object key, CommandControl cc) {
|
||||||
|
@ -131,6 +116,7 @@ public class FileOpener {
|
||||||
.openIO(
|
.openIO(
|
||||||
keyName,
|
keyName,
|
||||||
key,
|
key,
|
||||||
|
null,
|
||||||
() -> new FilterInputStream(cc.getStdout()) {
|
() -> new FilterInputStream(cc.getStdout()) {
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
|
|
|
@ -110,7 +110,7 @@ public class ScanAlert {
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
entry.get().get().setExpanded(true);
|
entry.get().get().setExpanded(true);
|
||||||
var copy = new ArrayList<>(selected);
|
var copy = new ArrayList<>(selected);
|
||||||
for (var a : copy) {
|
for (var a : copy) {
|
||||||
|
@ -177,7 +177,7 @@ public class ScanAlert {
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
BooleanScope.execute(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (shellControl != null) {
|
if (shellControl != null) {
|
||||||
shellControl.close();
|
shellControl.close();
|
||||||
shellControl = null;
|
shellControl = null;
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public abstract class SimpleFilterInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
protected SimpleFilterInputStream(InputStream in) {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract int read() throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte @NonNull [] b, int off, int len) throws IOException {
|
||||||
|
for (int i = off; i < off + len; i++) {
|
||||||
|
var r = (byte) read();
|
||||||
|
if (r == -1) {
|
||||||
|
return i - off == 0 ? -1 : i - off;
|
||||||
|
}
|
||||||
|
|
||||||
|
b[i] = r;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
package io.xpipe.ext.base.browser;
|
package io.xpipe.ext.base.browser;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.BrowserFileOpener;
|
||||||
import io.xpipe.app.browser.action.LeafAction;
|
import io.xpipe.app.browser.action.LeafAction;
|
||||||
import io.xpipe.app.browser.file.BrowserEntry;
|
import io.xpipe.app.browser.file.BrowserEntry;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.util.FileOpener;
|
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -28,7 +28,7 @@ public class EditFileAction implements LeafAction {
|
||||||
@Override
|
@Override
|
||||||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||||
for (BrowserEntry entry : entries) {
|
for (BrowserEntry entry : entries) {
|
||||||
FileOpener.openInTextEditor(entry.getRawFileEntry());
|
BrowserFileOpener.openInTextEditor(model, entry.getRawFileEntry());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.ext.base.browser;
|
package io.xpipe.ext.base.browser;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.BrowserFileOpener;
|
||||||
import io.xpipe.app.browser.action.LeafAction;
|
import io.xpipe.app.browser.action.LeafAction;
|
||||||
import io.xpipe.app.browser.file.BrowserEntry;
|
import io.xpipe.app.browser.file.BrowserEntry;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.util.FileOpener;
|
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -22,7 +22,7 @@ public class OpenFileDefaultAction implements LeafAction {
|
||||||
@Override
|
@Override
|
||||||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||||
for (var entry : entries) {
|
for (var entry : entries) {
|
||||||
FileOpener.openInDefaultApplication(entry.getRawFileEntry());
|
BrowserFileOpener.openInDefaultApplication(model, entry.getRawFileEntry());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.ext.base.browser;
|
package io.xpipe.ext.base.browser;
|
||||||
|
|
||||||
|
import io.xpipe.app.browser.BrowserFileOpener;
|
||||||
import io.xpipe.app.browser.action.LeafAction;
|
import io.xpipe.app.browser.action.LeafAction;
|
||||||
import io.xpipe.app.browser.file.BrowserEntry;
|
import io.xpipe.app.browser.file.BrowserEntry;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.util.FileOpener;
|
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.store.FileKind;
|
import io.xpipe.core.store.FileKind;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ public class OpenFileWithAction implements LeafAction {
|
||||||
@Override
|
@Override
|
||||||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||||
var e = entries.getFirst();
|
var e = entries.getFirst();
|
||||||
FileOpener.openWithAnyApplication(e.getRawFileEntry());
|
BrowserFileOpener.openWithAnyApplication(model, e.getRawFileEntry());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue