mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-09-27 10:55:52 +00:00
Add ability to read files via sudo
This commit is contained in:
@@ -2,6 +2,7 @@ package io.xpipe.app.browser.action.impl;
|
||||
|
||||
import io.xpipe.app.action.AbstractAction;
|
||||
import io.xpipe.app.action.ActionProvider;
|
||||
import io.xpipe.app.browser.file.BrowserFileInput;
|
||||
import io.xpipe.app.browser.file.BrowserFileOutput;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
|
||||
@@ -28,7 +29,7 @@ public class ApplyFileEditActionProvider implements ActionProvider {
|
||||
String target;
|
||||
|
||||
@NonNull
|
||||
InputStream input;
|
||||
BrowserFileInput input;
|
||||
|
||||
@NonNull
|
||||
BrowserFileOutput output;
|
||||
@@ -37,9 +38,13 @@ public class ApplyFileEditActionProvider implements ActionProvider {
|
||||
public void executeImpl() throws Exception {
|
||||
output.beforeTransfer();
|
||||
try (var out = output.open()) {
|
||||
input.transferTo(out);
|
||||
input.open().transferTo(out);
|
||||
}
|
||||
try {
|
||||
output.onFinish();
|
||||
} finally {
|
||||
input.onFinish();
|
||||
}
|
||||
output.onFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.ext.ConnectionFileSystem;
|
||||
import io.xpipe.app.ext.FileEntry;
|
||||
import io.xpipe.app.ext.FileInfo;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.process.CommandBuilder;
|
||||
import io.xpipe.app.process.ElevationFunction;
|
||||
import io.xpipe.app.process.ProcessOutputException;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.FilePath;
|
||||
import io.xpipe.core.OsType;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BrowserFileInput {
|
||||
|
||||
static BrowserFileInput openFileInput(BrowserFileSystemTabModel model, FileEntry file)
|
||||
throws Exception {
|
||||
if (model.isClosed()) {
|
||||
return BrowserFileInput.none();
|
||||
}
|
||||
|
||||
var defOutput = createFileInputImpl(model, file, false);
|
||||
if (model.getFileSystem().getShell().isEmpty()) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var sc = model.getFileSystem().getShell().orElseThrow();
|
||||
var requiresSudo =
|
||||
sc.getOsType() != OsType.WINDOWS && requiresSudo(model, (FileInfo.Unix) file.getInfo(), file.getPath());
|
||||
|
||||
if (!requiresSudo) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var elevate = AppDialog.confirm("fileReadSudo");
|
||||
if (!elevate) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var rootOutput = createFileInputImpl(model, file, true);
|
||||
return rootOutput;
|
||||
}
|
||||
|
||||
private static boolean requiresSudo(BrowserFileSystemTabModel model, FileInfo.Unix info, FilePath filePath)
|
||||
throws Exception {
|
||||
if (model.getFileSystem().getShell().isEmpty() || model.getCache() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.getCache().isRoot()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info != null) {
|
||||
var otherWrite = info.getPermissions().charAt(6) == 'r';
|
||||
if (otherWrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var userOwned = info.getUid() != null
|
||||
&& model.getCache().getUidForUser(model.getCache().getUsername()) == info.getUid()
|
||||
|| info.getUser() != null && model.getCache().getUsername().equals(info.getUser());
|
||||
var userWrite = info.getPermissions().charAt(0) == 'r';
|
||||
if (userOwned && userWrite) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var test = model.getFileSystem()
|
||||
.getShell()
|
||||
.orElseThrow()
|
||||
.command(CommandBuilder.of().add("test", "-r").addFile(filePath))
|
||||
.executeAndCheck();
|
||||
return !test;
|
||||
}
|
||||
|
||||
private static BrowserFileInput createFileInputImpl(
|
||||
BrowserFileSystemTabModel model, FileEntry file, boolean elevate) throws Exception {
|
||||
var shell = model.getFileSystem().getShell();
|
||||
var sc = shell.isEmpty()
|
||||
? null
|
||||
: elevate
|
||||
? shell.orElseThrow()
|
||||
.identicalDialectSubShell()
|
||||
.elevated(ElevationFunction.elevated(null))
|
||||
.start()
|
||||
: model.getFileSystem().getShell().orElseThrow().start();
|
||||
var fs = elevate ? new ConnectionFileSystem(sc) : model.getFileSystem();
|
||||
var output = new BrowserFileInput() {
|
||||
|
||||
@Override
|
||||
public InputStream open() throws Exception {
|
||||
try {
|
||||
return fs.openInput(file.getPath());
|
||||
} catch (Exception ex) {
|
||||
if (elevate) {
|
||||
fs.close();
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() throws Exception {
|
||||
if (elevate) {
|
||||
fs.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
return output;
|
||||
}
|
||||
|
||||
static BrowserFileInput none() {
|
||||
return new BrowserFileInput() {
|
||||
|
||||
@Override
|
||||
public InputStream open() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() throws Exception {}
|
||||
};
|
||||
}
|
||||
|
||||
static BrowserFileInput of(InputStream in) {
|
||||
return new BrowserFileInput() {
|
||||
@Override
|
||||
public InputStream open() throws Exception {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() throws Exception {}
|
||||
};
|
||||
}
|
||||
|
||||
InputStream open() throws Exception;
|
||||
|
||||
void onFinish() throws Exception;
|
||||
}
|
||||
@@ -26,148 +26,6 @@ import java.util.Optional;
|
||||
|
||||
public class BrowserFileOpener {
|
||||
|
||||
private static BrowserFileOutput openFileOutput(BrowserFileSystemTabModel model, FileEntry file, long totalBytes)
|
||||
throws Exception {
|
||||
if (model.isClosed()) {
|
||||
return BrowserFileOutput.none();
|
||||
}
|
||||
|
||||
if (totalBytes == 0) {
|
||||
var existingSize = model.getFileSystem().getFileSize(file.getPath());
|
||||
if (existingSize != 0) {
|
||||
var blank = AppDialog.confirm(
|
||||
"fileWriteBlankTitle", AppI18n.observable("fileWriteBlankContent", file.getPath()));
|
||||
if (!blank) {
|
||||
return BrowserFileOutput.none();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var defOutput = createFileOutput(model, file, totalBytes, false);
|
||||
if (model.getFileSystem().getShell().isEmpty()) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var sc = model.getFileSystem().getShell().orElseThrow();
|
||||
var requiresSudo =
|
||||
sc.getOsType() != OsType.WINDOWS && requiresSudo(model, (FileInfo.Unix) file.getInfo(), file.getPath());
|
||||
|
||||
if (!requiresSudo) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var elevate = AppDialog.confirm("fileWriteSudo");
|
||||
if (!elevate) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var rootOutput = createFileOutput(model, file, totalBytes, true);
|
||||
return rootOutput;
|
||||
}
|
||||
|
||||
private static boolean requiresSudo(BrowserFileSystemTabModel model, FileInfo.Unix info, FilePath filePath)
|
||||
throws Exception {
|
||||
if (model.getFileSystem().getShell().isEmpty() || model.getCache() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.getCache().isRoot()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info != null) {
|
||||
var otherWrite = info.getPermissions().charAt(7) == 'w';
|
||||
if (otherWrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var userOwned = info.getUid() != null
|
||||
&& model.getCache().getUidForUser(model.getCache().getUsername()) == info.getUid()
|
||||
|| info.getUser() != null && model.getCache().getUsername().equals(info.getUser());
|
||||
var userWrite = info.getPermissions().charAt(1) == 'w';
|
||||
if (userOwned && userWrite) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var test = model.getFileSystem()
|
||||
.getShell()
|
||||
.orElseThrow()
|
||||
.command(CommandBuilder.of().add("test", "-w").addFile(filePath))
|
||||
.executeAndCheck();
|
||||
return !test;
|
||||
}
|
||||
|
||||
private static BrowserFileOutput createFileOutput(
|
||||
BrowserFileSystemTabModel model, FileEntry file, long totalBytes, boolean elevate) throws Exception {
|
||||
var shell = model.getFileSystem().getShell();
|
||||
var sc = shell.isEmpty()
|
||||
? null
|
||||
: elevate
|
||||
? shell.orElseThrow()
|
||||
.identicalDialectSubShell()
|
||||
.elevated(ElevationFunction.elevated(null))
|
||||
.start()
|
||||
: model.getFileSystem().getShell().orElseThrow().start();
|
||||
var fs = elevate ? new ConnectionFileSystem(sc) : model.getFileSystem();
|
||||
var checkSudoersFile = shell.isPresent() && file.getPath().startsWith("/etc/sudo");
|
||||
var output = new BrowserFileOutput() {
|
||||
|
||||
@Override
|
||||
public Optional<DataStoreEntry> target() {
|
||||
return Optional.of(model.getEntry().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream open() throws Exception {
|
||||
try {
|
||||
return fs.openOutput(file.getPath(), totalBytes);
|
||||
} catch (Exception ex) {
|
||||
if (elevate) {
|
||||
fs.close();
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTransfer() throws Exception {
|
||||
if (checkSudoersFile) {
|
||||
fs.copy(file.getPath(), sc.getSystemTemporaryDirectory().join(file.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() throws Exception {
|
||||
if (checkSudoersFile) {
|
||||
if (sc.view().findProgram("visudo").isPresent()) {
|
||||
try {
|
||||
sc.command(CommandBuilder.of()
|
||||
.add("visudo", "-c", "-f")
|
||||
.addFile(file.getPath()))
|
||||
.execute();
|
||||
} catch (ProcessOutputException ex) {
|
||||
ErrorEventFactory.fromThrowable(ex).expected().handle();
|
||||
fs.copy(sc.getSystemTemporaryDirectory().join(file.getName()), file.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (elevate) {
|
||||
fs.close();
|
||||
}
|
||||
|
||||
model.refreshFileEntriesSync(List.of(file));
|
||||
}
|
||||
};
|
||||
return output;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static int calculateKey(BrowserFileSystemTabModel model, FileEntry entry) {
|
||||
// Use different key for empty / non-empty files to prevent any issues from blanked files when transfer fails
|
||||
@@ -189,39 +47,8 @@ public class BrowserFileOpener {
|
||||
file.getFileName(),
|
||||
key,
|
||||
new BooleanScope(model.getBusy()).exclusive(),
|
||||
() -> {
|
||||
return entry.getFileSystem().openInput(file);
|
||||
},
|
||||
(size) -> {
|
||||
if (model.isClosed()) {
|
||||
return BrowserFileOutput.none();
|
||||
}
|
||||
|
||||
return new BrowserFileOutput() {
|
||||
@Override
|
||||
public Optional<DataStoreEntry> target() {
|
||||
return Optional.of(model.getEntry().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream open() throws Exception {
|
||||
return entry.getFileSystem().openOutput(file, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTransfer() {}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
model.refreshFileEntriesSync(List.of(entry));
|
||||
}
|
||||
};
|
||||
},
|
||||
() -> BrowserFileInput.openFileInput(model, entry),
|
||||
(size) -> BrowserFileOutput.openFileOutput(model, entry, size),
|
||||
s -> FileOpener.openWithAnyApplication(s));
|
||||
}
|
||||
|
||||
@@ -239,39 +66,8 @@ public class BrowserFileOpener {
|
||||
file.getFileName(),
|
||||
key,
|
||||
new BooleanScope(model.getBusy()).exclusive(),
|
||||
() -> {
|
||||
return entry.getFileSystem().openInput(file);
|
||||
},
|
||||
(size) -> {
|
||||
if (model.isClosed()) {
|
||||
return BrowserFileOutput.none();
|
||||
}
|
||||
|
||||
return new BrowserFileOutput() {
|
||||
@Override
|
||||
public Optional<DataStoreEntry> target() {
|
||||
return Optional.of(model.getEntry().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream open() throws Exception {
|
||||
return entry.getFileSystem().openOutput(file, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTransfer() {}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
model.refreshFileEntriesSync(List.of(entry));
|
||||
}
|
||||
};
|
||||
},
|
||||
() -> BrowserFileInput.openFileInput(model, entry),
|
||||
(size) -> BrowserFileOutput.openFileOutput(model, entry, size),
|
||||
s -> FileOpener.openInDefaultApplication(s));
|
||||
}
|
||||
|
||||
@@ -294,11 +90,9 @@ public class BrowserFileOpener {
|
||||
file.getFileName(),
|
||||
key,
|
||||
new BooleanScope(model.getBusy()).exclusive(),
|
||||
() -> {
|
||||
return entry.getFileSystem().openInput(file);
|
||||
},
|
||||
() -> BrowserFileInput.openFileInput(model, entry),
|
||||
(size) -> {
|
||||
return openFileOutput(model, entry, size);
|
||||
return BrowserFileOutput.openFileOutput(model, entry, size);
|
||||
},
|
||||
FileOpener::openInTextEditor);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,166 @@
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.ext.ConnectionFileSystem;
|
||||
import io.xpipe.app.ext.FileEntry;
|
||||
import io.xpipe.app.ext.FileInfo;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.process.CommandBuilder;
|
||||
import io.xpipe.app.process.ElevationFunction;
|
||||
import io.xpipe.app.process.ProcessOutputException;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.FilePath;
|
||||
import io.xpipe.core.OsType;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BrowserFileOutput {
|
||||
|
||||
static BrowserFileOutput openFileOutput(BrowserFileSystemTabModel model, FileEntry file, long totalBytes)
|
||||
throws Exception {
|
||||
if (model.isClosed()) {
|
||||
return BrowserFileOutput.none();
|
||||
}
|
||||
|
||||
if (totalBytes == 0) {
|
||||
var existingSize = model.getFileSystem().getFileSize(file.getPath());
|
||||
if (existingSize != 0) {
|
||||
var blank = AppDialog.confirm(
|
||||
"fileWriteBlankTitle", AppI18n.observable("fileWriteBlankContent", file.getPath()));
|
||||
if (!blank) {
|
||||
return BrowserFileOutput.none();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var defOutput = createFileOutputImpl(model, file, totalBytes, false);
|
||||
if (model.getFileSystem().getShell().isEmpty()) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var sc = model.getFileSystem().getShell().orElseThrow();
|
||||
var requiresSudo =
|
||||
sc.getOsType() != OsType.WINDOWS && requiresSudo(model, (FileInfo.Unix) file.getInfo(), file.getPath());
|
||||
|
||||
if (!requiresSudo) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var elevate = AppDialog.confirm("fileWriteSudo");
|
||||
if (!elevate) {
|
||||
return defOutput;
|
||||
}
|
||||
|
||||
var rootOutput = createFileOutputImpl(model, file, totalBytes, true);
|
||||
return rootOutput;
|
||||
}
|
||||
|
||||
private static boolean requiresSudo(BrowserFileSystemTabModel model, FileInfo.Unix info, FilePath filePath)
|
||||
throws Exception {
|
||||
if (model.getFileSystem().getShell().isEmpty() || model.getCache() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.getCache().isRoot()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info != null) {
|
||||
var otherWrite = info.getPermissions().charAt(7) == 'w';
|
||||
if (otherWrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var userOwned = info.getUid() != null
|
||||
&& model.getCache().getUidForUser(model.getCache().getUsername()) == info.getUid()
|
||||
|| info.getUser() != null && model.getCache().getUsername().equals(info.getUser());
|
||||
var userWrite = info.getPermissions().charAt(1) == 'w';
|
||||
if (userOwned && userWrite) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var test = model.getFileSystem()
|
||||
.getShell()
|
||||
.orElseThrow()
|
||||
.command(CommandBuilder.of().add("test", "-w").addFile(filePath))
|
||||
.executeAndCheck();
|
||||
return !test;
|
||||
}
|
||||
|
||||
private static BrowserFileOutput createFileOutputImpl(
|
||||
BrowserFileSystemTabModel model, FileEntry file, long totalBytes, boolean elevate) throws Exception {
|
||||
var shell = model.getFileSystem().getShell();
|
||||
var sc = shell.isEmpty()
|
||||
? null
|
||||
: elevate
|
||||
? shell.orElseThrow()
|
||||
.identicalDialectSubShell()
|
||||
.elevated(ElevationFunction.elevated(null))
|
||||
.start()
|
||||
: model.getFileSystem().getShell().orElseThrow().start();
|
||||
var fs = elevate ? new ConnectionFileSystem(sc) : model.getFileSystem();
|
||||
var checkSudoersFile = shell.isPresent() && file.getPath().startsWith("/etc/sudo");
|
||||
var output = new BrowserFileOutput() {
|
||||
|
||||
@Override
|
||||
public Optional<DataStoreEntry> target() {
|
||||
return Optional.of(model.getEntry().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOutput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream open() throws Exception {
|
||||
try {
|
||||
return fs.openOutput(file.getPath(), totalBytes);
|
||||
} catch (Exception ex) {
|
||||
if (elevate) {
|
||||
fs.close();
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTransfer() throws Exception {
|
||||
if (checkSudoersFile) {
|
||||
fs.copy(file.getPath(), sc.getSystemTemporaryDirectory().join(file.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() throws Exception {
|
||||
if (checkSudoersFile) {
|
||||
if (sc.view().findProgram("visudo").isPresent()) {
|
||||
try {
|
||||
sc.command(CommandBuilder.of()
|
||||
.add("visudo", "-c", "-f")
|
||||
.addFile(file.getPath()))
|
||||
.execute();
|
||||
} catch (ProcessOutputException ex) {
|
||||
ErrorEventFactory.fromThrowable(ex).expected().handle();
|
||||
fs.copy(sc.getSystemTemporaryDirectory().join(file.getName()), file.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (elevate) {
|
||||
fs.close();
|
||||
}
|
||||
|
||||
model.refreshFileEntriesSync(List.of(file));
|
||||
}
|
||||
};
|
||||
return output;
|
||||
}
|
||||
|
||||
static BrowserFileOutput none() {
|
||||
return new BrowserFileOutput() {
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.browser.action.impl.ApplyFileEditActionProvider;
|
||||
import io.xpipe.app.browser.file.BrowserFileInput;
|
||||
import io.xpipe.app.browser.file.BrowserFileOutput;
|
||||
import io.xpipe.app.core.AppFileWatcher;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
@@ -145,16 +146,18 @@ public class FileBridge {
|
||||
String keyName,
|
||||
Object key,
|
||||
BooleanScope scope,
|
||||
FailableSupplier<InputStream> input,
|
||||
FailableFunction<Long, BrowserFileOutput, Exception> output,
|
||||
FailableSupplier<BrowserFileInput> inputSupplier,
|
||||
FailableFunction<Long, BrowserFileOutput, Exception> outputSupplier,
|
||||
Consumer<String> consumer) {
|
||||
var ext = getForKey(key);
|
||||
if (ext.isPresent()) {
|
||||
var existingFile = ext.get().file;
|
||||
try {
|
||||
try (var out = Files.newOutputStream(existingFile);
|
||||
var in = input.get()) {
|
||||
var input = inputSupplier.get();
|
||||
try (var out = Files.newOutputStream(existingFile); var in = input.open()) {
|
||||
in.transferTo(out);
|
||||
} finally {
|
||||
input.onFinish();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEventFactory.fromThrowable(ex).handle();
|
||||
@@ -169,9 +172,11 @@ public class FileBridge {
|
||||
.resolve(OsFileSystem.ofLocal().makeFileSystemCompatible(keyName));
|
||||
try {
|
||||
FileUtils.forceMkdirParent(file.toFile());
|
||||
try (var out = Files.newOutputStream(file);
|
||||
var in = input.get()) {
|
||||
var input = inputSupplier.get();
|
||||
try (var out = Files.newOutputStream(file); var in = input.open()) {
|
||||
in.transferTo(out);
|
||||
} finally {
|
||||
input.onFinish();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEventFactory.fromThrowable(ex).handle();
|
||||
@@ -179,16 +184,16 @@ public class FileBridge {
|
||||
}
|
||||
|
||||
var entry = new Entry(file, key, keyName, scope, (in, size) -> {
|
||||
if (output != null) {
|
||||
if (outputSupplier != null) {
|
||||
var effectiveScope = scope != null ? scope : BooleanScope.noop();
|
||||
try (var ignored = effectiveScope.start()) {
|
||||
var outSupplier = output.apply(size);
|
||||
var outSupplier = outputSupplier.apply(size);
|
||||
if (!outSupplier.hasOutput()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var action = ApplyFileEditActionProvider.Action.builder()
|
||||
.input(in)
|
||||
.input(BrowserFileInput.of(in))
|
||||
.output(outSupplier)
|
||||
.target(file.getFileName().toString())
|
||||
.build();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserFileInput;
|
||||
import io.xpipe.app.browser.file.BrowserFileOutput;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
@@ -105,7 +106,7 @@ public class FileOpener {
|
||||
id.toString(),
|
||||
id,
|
||||
null,
|
||||
() -> new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)),
|
||||
() -> BrowserFileInput.of(new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8))),
|
||||
null,
|
||||
v -> openInTextEditor(v));
|
||||
}
|
||||
@@ -121,7 +122,7 @@ public class FileOpener {
|
||||
keyName,
|
||||
key,
|
||||
null,
|
||||
() -> new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)),
|
||||
() -> BrowserFileInput.of(new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8))),
|
||||
(size) -> {
|
||||
return new BrowserFileOutput() {
|
||||
@Override
|
||||
|
||||
Generated
+2
@@ -1635,3 +1635,5 @@ rdpSmartSizing=Enable smart sizing
|
||||
rdpSmartSizingDescription=When enabled, mstsc will scale down the desktop size if the window is too small to display it in its full resolution. The aspect ratio of the desktop is preserved when scaled down.
|
||||
disableStartOnInit=Disable automatic startup
|
||||
enableStartOnInit=Enable automatic startup
|
||||
fileReadSudoTitle=Sudo file read
|
||||
fileReadSudoContent=The file you are trying to read requires root privileges. Do you want to read this file with sudo?
|
||||
|
||||
Reference in New Issue
Block a user